前言
元类属于python面向对象编程的深层次的魔法,非常重要,它使我们可以更好的掌控类从创建到消亡的整个生命周期过程。很多框架的源码中都使用到了元类。例如 Django Framework 中的 ORM engine.
白类 === 普通的自定义类
什么是元类
面向对象编程最重要的一句话:一切皆对象 过去我们都 是这样创建类的:
class Panda(object): hobby= "study python" def __init__(self, name, age): # initialize self.name = name self.age = age def __str__(self): # format return "My name=%s, age=%s"%(self.name, self.age)
然后再实例化获得 对象
suosuo = Panda("suosuo", 120) print(suosuo) # "My name=suosuo, age=120" print(type(suosuo)) # <class '__main__.Panda'>
都说了一切皆对象,那请问 类:<class '__main__.Panda'> 也该是一个对象吧!
答:没错,创建这个 Panda 类 的 类 我们称之为 元类, 也就是 这个 Panda 类是元类实例化得到的结果对象。
print(type(Panda)) # <class 'type'> , 即默认的唯一一个内置元类 叫 type
class 关键字创建 类 的流程分析
以前我们只知道如何使用类, 现在有必要分析底层原理。手动创建一个 类
class 关键字创建类时,必然代替我们调用了元类 Panda = type(.....), 调用时传入的参数是啥呢?
一个类有三个关键组成部分,分别是:
- 类名 class_name = "Panda"
- 基类们 class_bases = (object, )
- 类的名称空间 class_dic,---> 是由执行类体代码决定的
自定义元类 手动创建白类 Panda :
一个类没有声明自己的元类,语言底层自动指定默认元类就是 type,
当然我们可以自定义元类------》通过继承 type 元类的方式。然后使用 metaclass 关键字为一个 白类 指定元类
class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 pass class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......}) hobby= "study python" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "My name=%s, age=%s"%(self.name, self.age)
自定义元类可以控制白类的产生过程,白类的产生过程其实就是元类调用自身 __new__(...) 方法的过程:
class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 def __new__(cls, class_name, class_bases, class_dic): # def __new__(cls, *args, **kwargs): print(Mymeta) # <class '__main__.Mymeta'> print(cls) # <class '__main__.Mymeta'> print(class_name) # Panda print(class_bases) # (<class 'object'>,) print(class_dic) # {'__module__': '__main__', '__qualname__': 'Panda', 'hobby': 'study python', '__init__': <function Panda.__init__ at 0x02AB3780>, return super(Mymeta, cls).__new__(cls, class_name, class_bases, class_dic) # 重用父类的功能 # return super(Mymeta, cls).__new__(cls, *args, **kwargs) def __init__(self, class_name, class_bases, class_dic): # 必须为 4 个参数,做自定制的 print(self) # <class '__main__.Panda'> 注意这里,注意这里, 注意这里 super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父类的功能 if class_name.islower(): raise TypeError('类名%s请修改为驼峰体' %class_name) if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0: raise TypeError('类中必须有文档注释,并且文档注释不能为空') class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......}) """ 类 Panda 的文档注释 """ hobby= "study python" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "My name=%s, age=%s"%(self.name, self.age) suosuo = Panda("suosuo", 120)
需注意以下几点:
三个参数: (类名, 基类们,类的名称空间) 都自动传入自定义元类的 __new__ 和 __init__ 方法。
__new__ 方法的 第一个参数 cls = 自定义元类, 而 __init__ 方法 的第一个参数 self = 白类, 也就是此自定义元类
__new__ 方法 的执行结果。
__new__ 方法 必须要有 return 语句, 调用基类的 __new__ 方法, 而 __init__ 可以没有 return 语句 (初始化)
手动控制 白类 实例化 的过程
储备知识: __call__
class Foo: def __call__(self, *args, **kwargs): print(self) print(args) print(kwargs) obj=Foo() # 1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发 # 2、调用obj的返回值就是__call__方法的返回值 res=obj(1,2,3,x=1,y=2)
由上例得知, 调用一个对象 ----》(对象 + 括号), 就是触发此对象所在类中的 __call__ 方法的执行。又因为白类 是
元类实例化的对象,故 白类 加 括号 运行 -----》白类 实例化,也必然 触发 元类中的 __call__ 方法
# 测试 ,查看一下关键的几个参数的值 class Mymeta(type): def __call__(self, *args, **kwargs): print(self) # <class '__main__.Panda'> print(args) # ('suosuo',) print(kwargs) # {'age': 120} class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......}) hobby= "study python" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "My name=%s, age=%s"%(self.name, self.age) suosuo = Panda("suosuo", age=120) print(suosuo) # 结果:None , 原因为 元类 __call__ 方法无返回值
注意以下几点:
- 调用 白类 则自动触发 元类中的 __call__ 方法
- 元类 __call__ 方法 的 self 为白类,*args 为白类实例化溢出的位置参数,**kwargs 为白类实例化溢出的关键字参数
- 白类实例化的执行结果 就是 元类 __call__ 方法的返回结果 ,
默认 白类 实例化 Panda("suosuo", age=120) -----》 元类的 __call__ 方法 会做三件事:
- 产生一个 空 对象 obj
- 调用 __init__ 方法 初始化 对象 obj
- 返回 初始化好的 obj
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 def __call__(self, *args, **kwargs): # 1、调用__new__产生一个空对象obj obj = self.__new__(self) # 此处必须传参, self = 白类,代表创建一个白类对象 # 2、调用__init__初始化空对象obj# self.__init__(obj, *args, **kwargs) # 3、返回初始化好的对象obj return obj class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......}) hobby= "study python" def __init__(self, name, age): print(self) # <__main__.Panda object at 0x0106E790> self.name = name self.age = age suosuo = Panda("suosuo", age=120) print(suosuo) # <__main__.Panda object at 0x0220E790>
上例 元类的 __call__ 方法 就相当于一个 白类实例化 创建对象的 模板 , 我们 可以在该基础上改写 __call__ 方法的 逻辑
从而 控制 白类 实例化 Panda("suosuo", age=120) 的过程。
比如将 Panda 的 对象的所有属性都变为私有的
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 def __call__(self, *args, **kwargs): # 1、调用__new__产生一个空对象obj obj = self.__new__(self) # 此处必须传参, self = 白类,代表创建一个白类对象 # 2、调用__init__初始化空对象obj# self.__init__(obj, *args, **kwargs) # 在初始化之后,obj.__dict__里就有值了 obj.__dict__ = {'_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items()} # 3、返回初始化好的对象obj return obj class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......}) hobby= "study python" def __init__(self, name, age): print(self) self.name = name self.age = age suosuo = Panda("suosuo", age=120) print(suosuo.__dict__) # {'_Panda__name': 'suosuo', '_Panda__age': 120}
结合 元类 属性的查找顺序
在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,
可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,
将下述继承应该说成是:对象 Panda 继承对象 Footer,对象 Footer 继承对象Skin,对象 Skin 继承对象object
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 n = 444 def __call__(self, *args, **kwargs): obj = self.__new__(self) self.__init__(obj, *args, **kwargs) return obj # 必须 有 返回值 class Skin(object): n = 333 class Footer(Skin): n = 222 class Panda(Footer,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......}) n = 111 def __init__(self, name, age): self.name = name self.age = age suosuo = Panda("suosuo", age=120) print(Panda.n) # 注意是 类 属性
于是属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找
白类类名 查找顺序:
- 先白类层 Panda--> Footer -->Skin--> object
- 再元类层 Mymeta--> type
依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__ 方法的查找
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 n = 444 def __call__(self, *args, **kwargs): obj = self.__new__(self) print(self.__new__ is object.__new__) #True self.__init__(obj, *args, **kwargs) return obj class Skin(object): n = 333 class Footer(Skin): n = 222 class Panda(Footer,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......}) n = 111 def __init__(self, name, age): self.name = name self.age = age suosuo = Panda("suosuo", age=120) print(Panda.n) # 注意是 类 属性
总结,Mymeta下的__call__里的self.__new__在 Panda、Footer、Skin 里都没有找到__new__的情况下,
会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,
也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__
我们在元类的__call__中也可以用object.__new__(self) 去造空对象
但还是推荐在__call__中使用self.__new__(self)去创造空对象,因为这种方式会检索三个类Panda--->Footer--->Skin,而object.__new__则是直接跨过了他们三个
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 n = 444 def __new__(self, *args, **kwargs): obj=type.__new__(self, *args,**kwargs) # 必须按照这种传值方式 # return obj # 只有在返回值是type的对象时,才会触发下面的__init__ return 123 def __init__(self,class_name,class_bases,class_dic): print('run。。。') class Panda(object,metaclass=Mymeta): # Panda = Mytype("Panda", (object,),{......}) n = 111 def __new__(self, *args, **kwargs): # return super().__new__(self) # 只有返回是 object的对象时,才会触发下方的__init__方法 return 456 def __init__(self, name, age): self.name = name self.age = age suosuo = Panda("suosuo", age=120) print(suosuo) # 'int' object is not callable
注意:
- 只有 元类的 __new__ 方法只有返回 type 的对象时, 才会触发 元类的 __init__ 方法, 同理。
- 只有 白类的 __new__ 方法只有返回 object 的对象时, 才会触发 白类的 __init__ 方法
- 凡是调用 __new__ 方法, 只需 传一个 self 参数,为class , 表示 创建一个 元类 obj 或 白类 obj
练习
练习一:在元类中控制把自定义类的数据属性都变成大写
class Mymetaclass(type): def __new__(cls,name,bases,attrs): update_attrs={} for k,v in attrs.items(): if not callable(v) and not k.startswith('__'): update_attrs[k.upper()]=v else: update_attrs[k]=v return type.__new__(cls,name,bases,update_attrs) class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龙的传人 def walk(self): print('%s is walking' %self.name) print(Chinese.__dict__) ''' {'__module__': '__main__', 'COUNTRY': 'China', 'TAG': 'Legend of the Dragon', 'walk': <function Chinese.walk at 0x0000000001E7B950>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None} '''
**练习二:在元类中控制自定义的类无需__init__方法**
- 元类帮其完成创建对象,以及初始化操作;
- 要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
- key作为用户自定义类产生对象的属性,且所有属性变成大写
class Mymetaclass(type): # def __new__(cls,name,bases,attrs): # update_attrs={} # for k,v in attrs.items(): # if not callable(v) and not k.startswith('__'): # update_attrs[k.upper()]=v # else: # update_attrs[k]=v # return type.__new__(cls,name,bases,update_attrs) def __call__(self, *args, **kwargs): if args: raise TypeError('must use keyword argument for key function') obj = object.__new__(self) #创建对象,self为类Foo for k,v in kwargs.items(): obj.__dict__[k.upper()]=v return obj class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龙的传人 def walk(self): print('%s is walking' %self.name) p=Chinese(name='egon',age=18,sex='male') print(p.__dict__)
练习三:在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性
class Mymeta(type): def __init__(self,class_name,class_bases,class_dic): #控制类Foo的创建 super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #控制Foo的调用过程,即Foo对象的产生过程 obj = self.__new__(self) self.__init__(obj, *args, **kwargs) obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()} return obj class Foo(object,metaclass=Mymeta): # Foo=Mymeta(...) def __init__(self, name, age,sex): self.name=name self.age=age self.sex=sex obj=Foo('egon',18,'male') print(obj.__dict__)
练习四: 基于元类实现单例模式
#步骤五:基于元类实现单例模式 # 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间 # 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了 #settings.py文件内容如下 HOST='1.1.1.1' PORT=3306 #方式一:定义一个类方法实现单例模式 import settings class Mysql: __instance=None def __init__(self,host,port): self.host=host self.port=port @classmethod def singleton(cls): if not cls.__instance: cls.__instance=cls(settings.HOST,settings.PORT) return cls.__instance obj1=Mysql('1.1.1.2',3306) obj2=Mysql('1.1.1.3',3307) print(obj1 is obj2) #False obj3=Mysql.singleton() obj4=Mysql.singleton() print(obj3 is obj4) #True #方式二:定制元类实现单例模式 import settings class Mymeta(type): def __init__(self,name,bases,dic): #定义类Mysql时就触发 # 事先先从配置文件中取配置来造一个Mysql的实例出来 self.__instance = object.__new__(self) # 产生对象 self.__init__(self.__instance, settings.HOST, settings.PORT) # 初始化对象 # 上述两步可以合成下面一步 # self.__instance=super().__call__(*args,**kwargs) super().__init__(name,bases,dic) def __call__(self, *args, **kwargs): #Mysql(...)时触发 if args or kwargs: # args或kwargs内有值 obj=object.__new__(self) self.__init__(obj,*args,**kwargs) return obj return self.__instance class Mysql(metaclass=Mymeta): def __init__(self,host,port): self.host=host self.port=port obj1=Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址 obj2=Mysql() obj3=Mysql() print(obj1 is obj2 is obj3) obj4=Mysql('1.1.1.4',3307) #方式三:定义一个装饰器实现单例模式 import settings def singleton(cls): #cls=Mysql _instance=cls(settings.HOST,settings.PORT) def wrapper(*args,**kwargs): if args or kwargs: obj=cls(*args,**kwargs) return obj return _instance return wrapper @singleton # Mysql=singleton(Mysql) class Mysql: def __init__(self,host,port): self.host=host self.port=port obj1=Mysql() obj2=Mysql() obj3=Mysql() print(obj1 is obj2 is obj3) #True obj4=Mysql('1.1.1.3',3307) obj5=Mysql('1.1.1.4',3308) print(obj3 is obj4) #False