反射的实际应用、面向对象中的各种双下方法、元类
今日学习内容总结
通过上周的学习,我们对面向编程的学习也已经走向正轨,对面向对象的三大特性的学习,只要做好复习。再配合上一些小练习对所学的知识进行一个灵活运用。就能从中体验编程思想。而今日就是对面向编程学习内容的收尾了。
反射的实际应用
反射的实际应用:
# 利用面向对象编写系统终端功能
class WinCmd(object):
def ls(self):
print('windows系统正在执行ls命令')
def dir(self):
print('windows系统正在执行dir命令')
def cd(self):
print('windows系统正在执行cd命令')
class LinuxCmd(object):
def ls(self):
print('Linux系统正在执行ls命令')
def dir(self):
print('Linux系统正在执行dir命令')
def cd(self):
print('Linux系统正在执行cd命令')
# 创建对象
obj1 = WinCmd()
obj2 = LinuxCmd()
# 反射的使用
def run(obj):
while True:
cmd = input('请输入您的指令>>>:')
if hasattr(obj, cmd):
func_name = getattr(obj, cmd)
func_name()
else:
print('cmd command not found')
run(obj1)
run(obj2)
# 反射提供了一种不需要考虑代码的前提下操作数据和功能。
实际运行后会发现,在run(obj1)中,我们确实能够根据输入的指令来执行WinCmd类中的效果。并且判断当WinCmd类中不存在您输入的指令的时候返回一个 cmd command not found 。在run(obj2)中同样如此,执行了 LinuxCmd 类中的方法。这也是反射的强大之处
面向对象中的各种双下方法
面向对象中有很多双下方法,并且这些方法被一些人称之为魔法方法。而某些双下方法不需要可以调用,而是达到某个条件会自动触发。比如:init 在对象实例化的时候会自动触发。而现在我们就对这些双下方法进行学习与总结。
1__str__
对象被执行打印(print、前端展示)操作的时候自动触发,该方法必须返回字符串类型的数据,很多时候用来更加精准的描述对象。案例:
class A:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return f'姓名:{self.name} 年龄:{self.age}'
a = A('海狗',18)
print(a) # 姓名:海狗 年龄:18 打印对象触发__str__方法
2__del__
对象被执行(被动、主动)删除操作之后自动执行。案例:
class Foo:
def __del__(self):
print('执行我啦')
f1=Foo()
del f1 # 执行我啦
3__getattr__
对象查找不存在名字的时候自动触发。
4__setattr__
对象在执行添加属性操作的时候自动触发。比如:obj.变量名=变量值
5__call__
对象被加括号调用的时候自动触发。案例:
class A:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('鸡哥好帅')
obj = A()
obj() # 鸡哥好帅 对象() 自动触发对象从属于类(父类)的__call__方法
6__enter__ 和 exit
enter 是在对象被执行with上下文管理语法开始自动触发,该方法返回什么as后面的变量名就会得到什么。exit 在对象被执行with上下文管理语法结束之后自动触发。并且这两个双下方法是一组的,作用为上下文管理。案例:
class A:
def __init__(self,name):
self.name = name
def __enter__(self):
print(111)
return self # 必须返回self
def __exit__(self, exc_type, exc_val, exc_tb):
print(333)
with A('海狗') as obj:
print(obj.name)
# 打印结果为: 111 海狗 333
7__getattribute__
只要对象查找名字无论名字是否存在都会执行该方法,如果类中有__getattribute__方法那么就不会去执行__getattr__方法。
8__new__
对象是object类的__new__方法 产生了一个对象。实例化对象时,先触发,object 的__new__方法,此方法在内存中开辟一个对象空间。再执行__init__方法,给对象封装属性。案例:
class A:
__instance = None
def __init__(self,name):
self.name = name
def __new__(cls, *args, **kwargs):
if not cls.__instance:
cls.__instance = object.__new__(cls)
return cls.__instance
obj = A('liky')
让字典具备句点符查找值的功能
# 定义一个类继承字典
class MyDict(dict):
def __getattr__(self, item):
return self.get(item)
def __setattr__(self, key, value):
self[key] = value
# 创建一个obj对象
obj = MyDict({'name': 'jason', 'age': 18})
# 具备句点符取v
print(obj.name) # jason
print(obj.age) # 18
# 具备句点符添加k:v
obj['gender'] = 'male'
obj.pwd = 123 # 给字典名称空间添加名字 不是数据k:v
print(obj) # {'name': 'jason', 'age': 18, 'gender': 'male', 'pwd': 123}
元类
元类简介
所有的对象都是实例化或者说是通过调用类而得到的,python中一切皆对象,通过class关键字定义的类本质也是对象,对象又是通过调用类得到的,因此通过class关键字定义的类肯定也是调用了一个类得到的,这个类就是元类。
元类就是用来实例化产生类的类,因此我们可以得到如下的关系:

查看元类的方式
class MyClass(object):
pass
obj = MyClass()
print(type(obj)) # <class '__main__.MyClass'>
print(type(MyClass)) # <class 'type'> 查看MyClass类的类型
产生类的两种表现形式
产生类的两种表现形式是class关键字和type元类实现。但其本质其实是一种。因为class关键创建类时,一定调用了元类。而这两种产生类的表现形式的写法:
# class关键字
class C1(object):
pass
print(C1) # <class '__main__.C1'>
# type元类
type(类名,父类,类的名称空间)
res = type('C1', (), {})
print(res) # <class '__main__.C1'>
由上述代码我们发现,在调用type时会依次传入三个参数。这三个参数分别是:
参数一:包含一系列符合python语法代码的字符串;
参数二:字典形式的全局名称空间中的名字及所对应的值;
参数三:字典形式的局部名称空间中的名字及所对应的值;
学习元类的目的:
元类能够控制类的创建,也就意味着我们可以高度定制类的行为。比如:掌握了物品的生产过程,就可以在过程中做任何的额外操作。而元类可以高度制定类的行为,比如:要求类的名字必须首字母大写。思考在哪里编写定制化代码等等。
元类的基本使用
元类是不能通过继承的方式直接指定的,需要通过关键字参数的形式修改。案例:
class MyTypeClass(type):
def __init__(cls, cls_name, cls_bases, cls_dict):
# print(cls, cls_name, cls_bases, cls_dict)
if not cls_name.istitle():
raise Exception("类名的首字母必须大写")
super().__init__(cls_name, cls_bases, cls_dict)
class C1(metaclass=MyTypeClass):
school = '清华大学'
# 创建成功,符合条件
class a(metaclass=MyTypeClass):
school = '清华大学'
# 报错,Exception: 类名的首字母必须大写
元类的进阶操作
在之前的 call 方法中,对象加括号会自动执行产生该对象的类里面的__call__,并且该方法返回什么对象加括号就会得到什么。由此可以推导出类加括号会执行元类的里面的__call__该方法返回什么其实类加括号就会得到什么。
那么为什么类加括号就可以被调用呢?类调用之后又是如何保证先运行类中的__new__方法再运行类中的__init__方法的?其实答案就在__call__方法中。如果想让一个对象变成一个可调用对象(加括号可以调用),需要在该对象的类中定义__call__方法,调用可调用对象的返回值就是__call__方法的返回值。案例:
class Test():
def __init__(self):
self.name = 'python'
def __call__(self, *args, **kwargs): # self是Test类的对象
print(self) # <__main__.Test object at 0x000001C78CE78FD0>
print(self.name)
t = Test()
t() # <__main__.Test object at 0x0000027912B19278> python
因此我们可以得到以下结论:
对象加括号调用会调用该对象的类中定义的__call__方法;
类加括号调用会调用内置元类或自定义元类中的__call__方法,取决于类的元类是什么;
自定义元类加括号调用会内置元类中的__call__方法。
我们可以通过一个案例验证一下上述结论:
class MyMeta(type):
def __call__(self, *args, **kwargs):
print(self)
print(args)
print(kwargs)
return 'test'
class Test(metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
# 调用Test就调用了
t = Test()
print(t)
'''
产生结果:
<class '__main__.Test'>
('haha', '123')
{}
test
'''
通过上述案例我们可以推断出调用Test时,会调用自定义元类中的__call__方法,并将返回值赋值给调用类产生的对象。
我们可以通过__call__方法自定义元类来控制类的调用,也就是产生对象。案例:
class Mymeta(type):
def __init__(self, class_name, class_bases, class_dict):
# 实现类名首字母必须大写,否则抛出异常
if not class_name.istitle():
raise NameError('类名的首字母必须大写')
# 实现创建的类必须有文档注释,否则抛出异常
if '__doc__' not in class_dict or len(class_dict['__doc__'].strip())==0:
raise TypeError('必须有文档注释')
def __new__(cls, *args, **kwargs):
return type.__new__(cls, *args, **kwargs)
def __call__(self, *args, **kwargs):
people_obj = self.__new__(self)
self.__init__(people_obj,*args, **kwargs)
return people_obj
Test = MyMeta(class_name, class_bases, class_dic) # 调用的是内置元类type的__call__方法
class Test(metaclass=Mymeta):
def __new__(cls, *args, **kwargs):
# 产生空对象--真正的对象,真正造对象的是object
return object.__new__(cls) # 这里使用type也没有问题
def __init__(self,name):
self.name = name
t = Test() # 调用的是自定义元类中的__call__方法
通过自定义元类来创建类的时候,会调用type的__call__方法,该方法内部会做三件事情:
1、先调用自定义元类中的__new__方法,产生一个空对象
2、在调用自定义元类中的__init__方法,为空对象添加独有属性
3、返回一个初始化好的自定义元类的对象,就是上述的Test类
调用Test类时则会调用自定义元类MyMeta.__call__方法,同样也会做三件事:
1、先调用 Test类中的__new__方法产生一个空对象
2、再调用Test 类中的__init__方法为空对象添加独有属性
3、返回一个初始化好的Test类的对象赋值给t
所以,我们可以得到这个道理:如果你想高度定制类的产生过程,那么编写元类里面的__init__方法。如果你想高度定制对象的产生过程,那么编写元类里面的__call__方法。
__new__方法
创建类时先执行type的__init__方法,当一个类实例化时(创建一个对象)执行type的__call__方法,__call__方法的返回值就是实例化的对象。实例化对象是谁取决于__new__方法,__new__返回什么就是什么。而 new 方法的特性:
__new__方法是在类准备将自身实例化时调用。
__new__方法始终都是类的静态方法,即使没有被加上静态方法装饰器。
__new__方法的作用主要有两个:
1.为对象分配内存空间
2.返回对象的引用
返回对象的引用的作用是:
我们学习过python中__init__方法,__init__方法中的第一个参数self就是实例对象的引用,也就是说,哪个对象调用该方法,self就指向哪个对象。self中对象的引用就是从__new__方法中返回的,换句话说,__new__方法返回对象的引用,__init__方法用self参数接收了。因此我们可以知道,pyhon在创建对象时,首先调用了内置__new__方法,其次再调用__init__方法。
其实我们在代码中写了__new__方法相当于是对该__new__方法进行了一次重写,重写 __new__方法一定要 return super().new(cls),否则python解释器得不到分配的内存空间的对象引用,就不会调用对象的初始化方法,不会吧对象的引用传递到__init__方法中的self中。
同时,不是所有的地方都可以直接调用__new__ ,如果是在元类的__new__里面,可以直接调用。案例:
class Meta(type):
def __new__(cls, *args, **kwargs):
obj = type.__new__(cls,*args,**kwargs)
return obj
如果是在元类的__call__里面,需要间接调用:
class Mate(type):
def __call__(self, *args, **kwargs):
obj = object.__new__(self) # 创建一个空对象
self.__init__(obj,*args,**kwargs) # 让对象去初始化
return obj

学习内容总结
浙公网安备 33010602011771号