【python基础】第36回 魔法方法 元类
1. 反射
1.1 定义
反射:通过字符串来操作对象的数据或方法
1.2 反射主要方法
- hasattr(): 判断对象是否含有某个字符串对应的属性
- getattr(): 获取对象字符串对应的属性
- setattr(): 根据字符串给对象设置属性
- delattr(): 根据字符串给对象删除属性
1.3 什么情况下用反射
只要在需求中看到了关键字(...对象...字符串)那么肯定需要使用反射
1.4 反射实战案列
1.4.1 加载配置文件纯大写的配置
# 配置文件加载:获取配置文件中所有大写的配置,小写的直接忽略,组织成字典
import settings
new_dict = {}
# print(dir(settings)) # dir获取括号中对象可以调用的名字
# ['AGE', 'INFO', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'desc', 'name']
for i in dir(settings):
if i.isupper(): # 如果名字是纯大写 那么获取该大写名字对应的值 'AGE' 'INFO'
v = getattr(settings, i)
new_dict[i] = v
print(new_dict)
1.4.2 模拟操作系统cmd终端执行用户命令
class WinCmd(object):
def dir(self):
print('dir获取当前目录下所有的文件名称')
def ls(self):
print('ls获取当前路径下所有的文件名称')
def ipconfig(self):
print('ipconfig获取当前计算机的网卡信息')
obj = WinCmd()
while True:
cmd = input('请输入您的命令>>>:')
if hasattr(obj, cmd):
cmd_name = getattr(obj, cmd)
cmd_name()
else:
print('%s 不是内部或外部命令,也不是可运行的程序或批处理文件' % cmd)
2. 面向对象魔法方法
2.1 定义
魔法方法其实就是类中定义的双下方法,之所以会叫魔法方法原因是这些方法都是到达某个条件自动触发无需调用
eg: __init__方法在给对象设置独有数据的时候自动触发(实例化)
2.2 魔法方法触发的条件
2.2.1 实例化对象的时候自动触发
# 1.实例化对象的时候自动触发
class MyClass(object):
def __init__(self, name):
print('__init__方法')
self.name = name
2.2.2 对象被执行打印操作的时候会自动触发
该方法必须返回一个字符串,返回什么字符串打印对象之后就展示什么
# 2.对象被执行打印操作的时候会自动触发
# 该方法必须返回一个字符串,返回什么字符串打印对象之后就展示什么
def __str__(self):
print('__str__方法')
print('这是类:%s 产生的一个对象')
return '对象:%s' %self
2.2.3 对象加括号调用,自动触发该方法
# 3.对象加括号调用,自动触发该方法
def __call__(self, *args, **kwargs):
print('__call__方法')
print(args)
print(kwargs)
2.2.4 当对象获取一个不存在的属性名,自动触发
该方法返回什么 对象获取不存在的属性名就会得到什么,形参item就是对象想要获取的不存在的属性名
# 4.当对象获取一个不存在的属性名,自动触发
# 该方法返回什么 对象获取不存在的属性名就会得到什么,形参item就是对象想要获取的不存在的属性
def __getattr__(self, item):
print('__getattr', item)
return '您想要获取的属性名:%s不存在'%item
2.2.5 对象操作属性值的1时候自动触发: 对象.属性名= 属性值
# 5.对象操作属性值的时候自动触发: 对象.属性名= 属性值
def __setattr__(self, key, value):
print("__setattr__")
print(key)
print(value)
super().__setattr__(key, value)
2.2.6 对象在被删除(主动 被动)的时候自动触发
# 6.对象在被删除(主动 被动)的时候自动触发
def __del__(self):
print('__del__')
2.2.7 对象获取属性的时候自动触发
# 7.对象获取属性的时候自动触发
# 无论这个属性存不存在, 当类中既有__getattr__又有__getattribute__的时候 只会走后者
def __getattribute__(self, item):
print('__getattribute__')
# return super(MyClass, self).__getattribute__(item) # 复杂写法
return super().__getattribute__(item) # 简便写法
2.2.8 对象被with语法执行的时候自动触发
# 8.对象被with语法执行的时候自动触发
# 该方法返回什么 as关键字后面的变量名就能得到什么
def __enter__(self):
print('__enter__')
2.2.9 对象被with语法执行并运行完with子代码之后 自动触发
# 9.对象被with语法执行并运行完with子代码之后 自动触发
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
3. 魔法方法笔试题
# 补全以下代码 执行之后不报错
class Context:
pass
with Context() as f:
f.dp_something()
class Context:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def do_something(self):
pass
with Context() as f:
f.do_something()
4. 元类简介
4.1 基础阶段使用type来查找数据的数据类型。
s1 = '元类简介'
l2 = [1, 2, 3]
a = {'name': 'jason', 'age': 18}
print(type(s1)) # <class 'str'>
print(type(l2)) # <class 'list'>
print(type(a)) # <class 'dict'>
4.2 type用于查看产生当前对象的类是谁
但学了面向对象之后,发现产看的不是数据类型,而是数据所属的类,我们定义的数据类型 其实本质还是通过各个类产生了对象
class MyClass:
pass
obj = MyClass()
print(type(obj)) # 查看产生对象obj的类:<class '__main__.MyClass'>
print(type(MyClass)) # 查看产生对象MyClass的类:<class 'type'>
4.3 结论
自定义的类都是由type类产生的,我们将产生类的类称之为 '元类'
5. 产生类的两种方式
5.1 第一种方式
1.class关键字
class MyClass:
pass
5.2 第二种方式
2.利用元类type
type(类名,类的父类,类的名称空间)
5.3 总结
学习元类其实就是掌握了类的产生过程,我们就可以在类的产生过程中高度定制化类的行为
eg: 类名必须首字母大写
6. 元类基本使用
6.1 只有继承了type的类才可以称之为是元类
如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass声明
class MyMetaClass(type):
pass
class MyClass(metaclass=MyMetaClass):
pass
6.2 思考
# 思考
类中的__init__用于实例化对象
元类中__init__用于实例化类
class MyMetaClass(type):
def __init__(self,what, bases=None, dict=None):
# print('别晕')
# print('what', what) 类名
# print('bases', bases) 类的父类
# print('dict', dict) 类的名称空间
if not what.istitle():
# print('首字母必须大写 你会不会写python 面向对象学过吗 lowB')
raise Exception('首字母必须大写 你会不会写python 面向对象学过吗 lowB')
super().__init__(what, bases, dict)
7. 元类进阶
7.1 控制对象
- 元类不单单可以控制类的产生过程 其实也可以控制对象的
- 对象加括号执行产生该对象类里面的双下call
- 类加括号执行产生该类的元类里面的双下call
class MyMetaClass(type):
def __call__(self, *args, **kwargs):
print('__call__')
if args:
raise Exception('必须用关键字参数传参')
super().__call__(*args, **kwargs)
class MyClass(metaclass=MyMetaClass):
def __init__(self, name, age):
self.name = name
self.age = age
print('__init__')
7.2 总结
- 如果我们想高度定制对象的产生过程,可以操作元类里面的__call__
- 如果我们想高度定制类的产生过程,可以操作元类里面的__init__
8. 双下new方法
8.1 类产生对象的步骤
- 产生一个空对象
- 自动触发__init__方法实例化对象
- 返回实例化好的对象
8.2 双下new
- __new__方法专用于产生空对象(骨架)
- __init__方法专门用于给对象添加属性(血肉)