Python 元类详解 __new__、__init__、__call__[收官之作]
最近一直在寻找上下班能够学习的视频,尝试过看英语学习的,看计算机网络的,看C语言教学的,但个人水平有限,
早晚的时间在车上可能又时候也比较困,很多视频虽然刷完了,但也没记住多少,很多没坚持下去。最近在看老男孩的Python开发教学视频
链接在此:https://www.bilibili.com/video/BV1Sp4y1U7Jr?from=search&seid=14441071008385845222
确实讲的不错,看了里面一些元类的知识,结合我自己的理解,再次提笔写下此博客。
切入正文:所有的元类都会谈到__new__,__init__,__call__
其实,如果没有很好的理解class关键字后面在做什么,以及Python的基础类实例化过程,去理解元类是非常累的。
这里我将先从普通类开始讲,此博文的长度可能会比较长!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Demo: def __init__( self , name, age): self .name = name self .age = age if __name__ = = '__main__' : obj = Demo( 'sidian' , 18 ) print ( "以下是实例的私有属性" ) print ( vars (obj)) print ( "以下是实例的所有可用属性" ) print ( dir (obj)) |
输出
1 2 3 4 | 以下是实例的私有属性 { 'name' : 'sidian' , 'age' : 18 } 以下是实例的所有可用属性 [ '__class__' , '__delattr__' , '__dict__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__gt__' , '__hash__' , '__init__' , '__init_subclass__' , '__le__' , '__lt__' , '__module__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '__weakref__' , 'age' , 'name' ] |
从输出中可以看到__init__给实例赋值了属性name与age,而且__init__是在实例化过程中会自动调用的。
但以前我就想过另外的这些双下划线的魔法指令是哪里来的,很多都会说继承了父类object。
既然继承了这么多属性,所以它在实例化的时候,可以使用很多object的方法,其中就包括很重要的__new__与__init__
一次简单的实例化过程,我的理解,从分开的步骤分析,首先调用类名,然后产生一个属于该类的实例[__new__实现],最后给实例赋值属性[__init__实现]。
逐个分析,有意思的画面出现了。首相是调用类名,都知道,一个对象可以被调用需要内部实现__call__的方法,但经过测试object没有__call__的方法。所有的类都是object的子类,所以剩下的子类,默认情况下都没有__call__的方法,但奇怪的是,所有的类又都能被正常调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | In [ 34 ]: class A: ...: ... ...: In [ 35 ]: callable (A) Out[ 35 ]: True In [ 37 ]: hasattr (A, '__call__' ) Out[ 37 ]: True In [ 38 ]: '__call__' in dir (A) Out[ 38 ]: False In [ 39 ]: '__call__' in dir ( object ) Out[ 39 ]: False |
这是ipython的测试输出。很明显测试的结果是可以调用的,但自身没有__call__的方法,从我们Python的理解来看。一个对象自身没有的方法找父类,很明显我们的继承的祖宗类对象也没有__call__方法。
这个时候,我就就要查找造这个对象的类是否又该方法,那类的类是谁呢,大家都知道那是type,这也给后面使用type的__call__留下了伏笔.
1 2 3 4 5 6 7 8 | In [ 41 ]: type ( object ) Out[ 41 ]: type In [ 42 ]: '__call__' in dir ( type ) Out[ 42 ]: True In [ 43 ]: callable ( type ) Out[ 43 ]: True |
这里其实就显式了Python底层对于元类的特殊性了,在Python的类创建实例的过程中,类的所有属性,实例都能通过dir显式并使用,假如实例的私有属性没有覆盖的情况下。
但元类并不这样,至少元类这个__call__元类并没有显式的继承给自己的实例[也就是类]。这样可以避免元类创造的类的部分方法传导给元类创造的类的实例使用。
1 2 3 4 5 6 7 8 9 10 | In [ 45 ]: set ( dir ( object )).issubset( set ( dir ( type ))) Out[ 45 ]: True In [ 46 ]: len ( dir ( object )) Out[ 46 ]: 23 In [ 47 ]: len ( dir ( type )) Out[ 47 ]: 43 In [ 48 ]: |
上面也证实了我的想法,object作为元类创造的原始类[个人认为],其实很多元类的方法都没有显式的继承给自己的实例,但实例其实都可以使用type的方法,后面还会看到,元类的自定义属性,创建的类能够读取,但在dir()里面也是不显示的,这应该是一种保护机制,避免元类的很多属性影响实例。我个人的理解,是为了切断元类跟自己创建类的实例的关系。
上面已经展开样式的讲了很多为什么没有类没有__call__属性,但能够被调用,而且后面马上能看到,在类中定义的__call__是给实例使用的,这样后续反推元类的__call__是给类使用的,所以这个__call__是隔代使用,故不能继承。
一个普通的类的调用会用到元类的__call__,类自身的__new__,还有__init__,我心中马上又有一万个为什么了,这三个方法的执行顺序到底是如何执行的?
网上很多都说什么先执行__new__再执行__init__,其实这里面还少了一些。
打停住,我陷入了错误的方向,前面我在考虑普通的类实例化过程中__call__与类中定义的__init__与__new__的先后关系,很明显我陷入了错误的方向。
在Python中万物皆对象,这里我先打住好好的介绍以下__call__,然后就会了解到类中__init__与__new__为什么会执行,而且为什么是先执行__new__而后执行__init__
后面我的理解不知道正确与否,但我肯定感觉是正确的。
从万物皆对象的角度看问题,当我们调用一个方法的时候,用的是()这个符号,这其实是Python的一个语法糖,作为万物皆对象的oop编程,你用了()这个符号,底层肯定要调用一个方法,没错那就是__call__。
下面我写个代码解释一下
class Simpler:
def __init__(self, name, age):
self.name = name
self.age = age
# 在一个类中定义一个__call__方法
def __call__(self):
return f'My name is {self.name}, age is {self.age}'
if __name__ == '__main__':
s = Simpler('sidian', 18)
print(Simpler.__call__(s))
print('=' * 50)
print(s())
print('=' * 50)
print(s.__call__())
输出
My name is sidian, age is 18
==================================================
My name is sidian, age is 18
==================================================
My name is sidian, age is 18
我分别用了三种方式来调用一个类中的定义的__call__方法,出来的都是同样的效果。很明显通过类本身调用方法,需要传入self参数,通过self调用的话不需要传入self,可以直接通过()这样的语法糖实现,还有就是显式的调用__call__方法。
这里涉及到为什么同样都是对象的属性,实例调用自己的方法【方法也是一种属性】,不用传入参数。这个后续我有能力单独写一篇博客,因为__get__描述符的作用,Python的def 制作一个函数,包括class创建一个类都是一些语法糖。
既然讲到这里又继续扯开了。
1 2 3 4 5 6 7 8 9 10 11 | In [ 56 ]: def run(): ...: ... ...: In [ 57 ]: type (run) Out[ 57 ]: function In [ 58 ]: type (run).__class__ Out[ 58 ]: type In [ 59 ]: |
我们定义函数def是一个什么行为,这只不过是一个类的实例化的过程,我们的函数名是一个标准的实例对象。我们在函数中写的任何东西,只不过是function类的__init__方法的一些参数。最后函数对象调用自身的__call__方法
现在我们可以记住一点,通过类传送过来的属性对象,如果该属性对象具有__call__与__get__的属性,那实例如果调用该属性对象,可以少传第一个参数self[也就是自身]。
这是因为__get__的功劳,后续有机会再好好的研究一下描述符。
记住了这些,我们可以知道我们可以创建一个可调用对象,只要再类中定义了__call__方法。
这里我可能需要穿插的介绍元类的__call__以及类本身的__new__和__init__,这个理解了,后面的元类也就很容易理解了。
当我们实例化对象,通过()调用类的时候,其实很明显可以看到,我们没有传入对象本身,这个说明我们这个类实例化的过程,对类来说,是属于绑定它的方法。
而且有意思的是,你再类本身找不到__call__的属性,前面已经说明了。那更加明显它只能通过从创造自己的类[与就是元类上面调用__call__]。
那我们在实例化过程中的__new__与__init__方法能够自动执行,肯定实在元类的__call__中执行,因为我们在通过()实例化的时候,只调用了绑定在类对象的__call__方法,也就是元类的定义的__call__的的属性。
这里唯一需要理解的是,虽然类没有__call__方法,但内部其实它有一个绑定了__call__的方法,且来至与元类,这个跟类的实例调用__call__方法,来至与类一样。对象调用__call__,这个__call__是它的创造者定义的。
下面写一个简单的示例代码.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Goodmeta( type ): # 修改type的__call__方法 def __call__( self , * args, * * kwargs): return 'ok' # 创建对象时候,元类继承与哪里 class Simpler(metaclass = Goodmeta): def __init__( self , name, age): print ( 'obj__init__' ) self .name = name self .age = age def __new__( cls , * args, * * kwargs): print ( 'obj__new__' ) return object .__new__( cls ) # 在一个类中定义一个__call__方法 def __call__( self ): return f 'My name is {self.name}, age is {self.age}' if __name__ = = '__main__' : s = Simpler( 'sidian' , 18 ) print (s) |
从代码中可以看到,当我们的Simpler实例话的时候,调用了元类Goodmeta的__call__方法,因为我们重写了该方法,直接返回了'ok',所有Simpler的__init__与__new__都没有执行。
那我们先让这个类能够正常的实例化,再来分析其中的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class Goodmeta( type ): # 修改type的__call__方法 def __call__( self , * args, * * kwargs): # self是什么,是Goodmeta创建的对象,谁调用了他,创建了类,那self就是那个被创建的类 obj = self .__new__( self ) # 下面有两种写法,1 为通过类调用自身的函数对象属性 self .__init__(obj, * args, * * kwargs) # 第二个是对象直接调用__init__方法执行 # obj.__init__(*args, **kwargs) return obj # 创建对象时候,元类继承与哪里 class Simpler(metaclass = Goodmeta): def __init__( self , name, age): print ( 'obj__init__' ) self .name = name self .age = age def __new__( cls , * args, * * kwargs): print ( 'obj__new__' ) return object .__new__( cls ) # 在一个类中定义一个__call__方法 def __call__( self ): return f 'My name is {self.name}, age is {self.age}' if __name__ = = '__main__' : s = Simpler( 'sidian' , 18 ) print (s) |
输出
1 2 3 | obj__new__ obj__init__ <__main__.Simpler object at 0x7faab82ac130 > |
很明显,我们在元类中通过调用元类中的self对象[被创建的类对象],去调用该对象的一些属性__new__以及__init__来执行一些操作,完成并返回seif[元类创造的类]的实例。
这个应该也是标准的实例化的过程,也解释了为什么在类在实例化的过程中会自动调用__new__和__init__方法,这个只不过在元类的__call__方法中定义了。
类中的__init__就是跟所有的普通方法一样,没啥好讲的。
但有意思的是在__new__当中,谁有__new__的属性,所有的对象都有,这是为什么,因为object有,所有的类都继承与object,你就是所有的对象独有__new__属性,因为Object有这个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | In [ 102 ]: o = object In [ 103 ]: o_instance = object () In [ 104 ]: o.__repr__ is o_instance.__repr__ Out[ 104 ]: False In [ 105 ]: o.__new__ is o_instance.__new__ Out[ 105 ]: True In [ 106 ]: o.__eq__ is o_instance.__eq__ Out[ 106 ]: False In [ 107 ]: |
这里我将开始介绍第一个__new__的方法,是Python里面我知道的内置的没有__get__属性的一个可调用对象。
那说明了无论是类,还是类创建的实例,调用该对象,都按照一样的规则进行参数填写,没有以前的实例绑定方法那种,第一个参数self可以省略。
既然这样,对于我们__new__的使用可以知道,无论那种方式使用该对象,需要什么参数,都需要手动填写。
下面我通过一些案例来演示__new__的使用。
首先需要说明的是__new__能操作。__new__方法,创建一个属于某一个特定类的方法,所有的子类继承与object,object由type创造.
那既然object与type都有__new__的方法,那就实际操作一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | In [ 127 ]: # 创建对象时候,元类继承与哪里 ...: class Simpler( object ): ...: number = 88 ...: ...: def __init__( self , name, age): ...: print ( 'obj__init__' ) ...: self .name = name ...: self .age = age ...: ...: def __new__( cls , * args, * * kwargs): ...: print ( 'obj__new__' ) ...: return object .__new__( cls ) ...: ...: # 在一个类中定义一个__call__方法 ...: def __call__( self ): ...: return f 'My name is {self.name}, age is {self.age}' ...: ...: def say( self ): ...: return 'wangwang' ...: In [ 128 ]: s = object .__new__(Simpler) In [ 129 ]: vars (s) Out[ 129 ]: {} In [ 130 ]: s.number Out[ 130 ]: 88 In [ 131 ]: s.say Out[ 131 ]: <bound method Simpler.say of <__main__.Simpler object at 0x7f85aa0efcd0 >> In [ 132 ]: s.say() Out[ 132 ]: 'wangwang' In [ 133 ]: object .__new__? Signature: object .__new__( * args, * * kwargs) Docstring: Create and return a new object . See help ( type ) for accurate signature. Type : builtin_function_or_method |
神奇的事情发生了,前面说的实例的时候首先创建的空对象,其实准确的讲应该是创建一个没有实例属性的对象。不管是类还是实例(不包括type,
object的类的都是有type创建的,所以type的__new__应该是拿来创建类的,后面说明),只需要忘里面传入一个普通的类,就返回该类的一个实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | In [ 134 ]: class Ruby: ...: name = 'ruby' ...: In [ 135 ]: r = s.__new__(Ruby) obj__new__ In [ 136 ]: r Out[ 136 ]: <__main__.Ruby at 0x7f85aa118f10 > In [ 137 ]: r.name Out[ 137 ]: 'ruby' In [ 138 ]: def func(): ...: ... ...: In [ 139 ]: class Java(): ...: def say( self ): ...: return "I am java" ...: In [ 140 ]: j = func.__new__(Java) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TypeError Traceback (most recent call last) <ipython - input - 140 - 00ff8e68b6da > in <module> - - - - > 1 j = func.__new__(Java) TypeError: function.__new__(Java): Java is not a subtype of function In [ 141 ]: j = Simpler.__new__(Java) obj__new__ In [ 142 ]: j.say() Out[ 142 ]: 'I am java' |
我通过了实例,类都调用了__new__方法,只要你没有重写__new__方法,你用的就是object的__new__
1 2 | In [ 143 ]: Java.__new__ is object .__new__ Out[ 143 ]: True |
当我用函数对象使用__new__的时候,明显,他在里面设置了逻辑,传入的对象必须是func的子类,而且当我传入了,确还要另外参数,看来function的类实例化过程__new__并不是简单的回来继承与它的实例,里面作了很多另外的事情。
1 2 3 4 5 6 7 8 9 | In [ 144 ]: f = type ( lambda : 1 ) In [ 145 ]: j = func.__new__(f) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TypeError Traceback (most recent call last) <ipython - input - 145 - 24f2681d9291 > in <module> - - - - > 1 j = func.__new__(f) TypeError: function() missing required argument 'code' (pos 1 ) |
想到了这里,我就想写一个传入参数无需实例化的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class Goodmeta( type ): # 修改type的__call__方法 def __call__( self , * args, * * kwargs): # self是什么,是Goodmeta创建的对象,谁调用了他,创建了类,那self就是那个被创建的类 obj = self .__new__( self ) # 下面有两种写法,1 为通过类调用自身的函数对象属性 if args: raise NameError( '传入的参数的形式必须为关键字传参' ) for k, v in kwargs.items(): # 两种给实例自动写入私有属性的方式 # setattr(obj, k, v) obj.__dict__[k] = v # 第二个是对象直接调用__init__方法执行 # obj.__init__(*args, **kwargs) return obj # 创建对象时候,元类继承与哪里 class Simpler( object , metaclass = Goodmeta): number = 88 def __new__( cls , * args, * * kwargs): print ( 'obj__new__' ) return object .__new__( cls ) # 在一个类中定义一个__call__方法 def __call__( self ): return f 'My name is {self.name}, age is {self.age}' def say( self ): return 'wangwang' if __name__ = = '__main__' : s = Simpler(name = 'sidian' , age = 18 , add = 'China' ) print (s()) |
输出
1 2 | obj__new__ My name is sidian, age is 18 |
所有对于实例与类的操作,其实都可以在元类的__call__完成,当类运行到元类的__call__属性时,类已经创建完成,并完成了具体的初始化。但后期的就算是已经产生的类,也可以在__call__阶段作很多事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | class Goodmeta( type ): # 修改type的__call__方法 def __call__( self , * args, * * kwargs): # self是什么,是Goodmeta创建的对象,谁调用了他,创建了类,那self就是那个被创建的类 obj = self .__new__( self ) # 下面有两种写法,1 为通过类调用自身的函数对象属性 if args: raise NameError( '传入的参数的形式必须为关键字传参' ) for k, v in kwargs.items(): # 两种给实例自动写入私有属性的方式 # setattr(obj, k, v) obj.__dict__[k] = v # 通过猴子补丁的方式删除或者添加类的属性,改版实例的继承 if hasattr ( self , 'number' ): del self .number self .like = 'Python' # 第二个是对象直接调用__init__方法执行 # obj.__init__(*args, **kwargs) return obj # 创建对象时候,元类继承与哪里 class Simpler( object , metaclass = Goodmeta): number = 88 def __new__( cls , * args, * * kwargs): print ( 'obj__new__' ) return object .__new__( cls ) # 在一个类中定义一个__call__方法 def __call__( self ): return f 'My name is {self.name}, age is {self.age}' def say( self ): return 'wangwang' if __name__ = = '__main__' : s = Simpler(name = 'sidian' , age = 18 , add = 'China' ) print (s()) print (s.like) # 报错 print (s.number) |
既然一个类实例化的过程中,默认情况下,是进入__new__方法创建一个对象然后实例化,那我们就可以在这里进行一些操作实现单例。单例的是所有后续相同参数的实例[入参相同]都是同一个实例.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class Singler: def __init__( self ,name = 'sidian' , age = 18 ): self .name = name self .age = age def __new__( cls , * args, * * kwargs): args_tuple = tuple (args) or tuple (kwargs.values()) # 初始化一个类的容器 if not hasattr ( cls , 'obj' ): Singler.obj = {} if args_tuple in Singler.obj: return Singler.obj[args_tuple] else : Singler.obj[args_tuple] = super (Singler, cls ).__new__( cls ) return Singler.obj[args_tuple] if __name__ = = '__main__' : s = Singler() s_same = Singler() l = Singler(name = 'badian' , age = 90 ) l_same = Singler(name = 'badian' , age = 90 ) l_same_2 = Singler( 'badian' , 90 ) print (s is s_same) print (l is l_same is l_same_2) print (s is l_same) |
输出
True
True
Fals
这是一个比较简单逻辑的单例,可能还有一些情况没有处理。
对于普通类的__new__方法就说到这里,默认这是一个创建没有实例属性的实例开始的地方,通过父类的__new__[默认都是object]的__new__方法来创建对象。
普通类的实例化,具体操作由元类的__call__来实施操控。
啰啰嗦嗦讲了很多,很多已经都是反着讲了,但前面的理解了,再来反推就容易理解类的产生了。普通的类实例是由object的__new__方法产生的,难道类也是吗?object就是所有类的基类,很明显
object.__new__(object)实例化的是object的实例。
那类是由哪里产生的,基本懂点Python的都知道,类是由type产生的,而且type也是一个类,type创造了object,创造了type
type创造了类,类创造了实例。我们通过修改元类的__call__可以影响类创造实例的一些操作[__init__,__new__等],因为那个时候类已经产生了。
那我们能否修改 类-->type-->上一级的__call__ 的方法来修改类产生时候的一些逻辑,很遗憾我们做不到,type的上一级还是type。
所以我们无法修改 类-->type-->上一级的__call__ 的逻辑,一切按照默认的逻辑,先执行__new__, 再执行__init__。
1 2 3 4 5 6 7 8 9 10 11 12 | In [ 149 ]: class A: ...: ... ...: In [ 150 ]: A.__class__ Out[ 150 ]: type In [ 151 ]: object .__class__ Out[ 151 ]: type In [ 152 ]: type .__class__ Out[ 152 ]: type |
刚刚洗脚的时候想到一点,对于对象的采用()的调用,表面上看其调用的是自身的__call__方法,没错。
但更加通用的解释是调用了它的所属类中定义的__call__函数对象,由于实例的继承,实例才能直接通过()的方式调用该属性。
更加书面化的解释应该是这个__call__的属性对象通过内部的应该通过__get__的属性,让其成为具体对象的方法,故可以通过()调用。
具体到对象上面并不是对象本身一定具有__call__的属性[比如类],可以通过callable来判断,只要通过了,这个对象的所属类,肯定有定义了__call__的属性,用于创建的实例通过()使用。
那后面就看一下元类的__new__与__init__都可以做一些什么,中间先插入一条type创建类的方式,虽然我感觉对后面的理解没啥作用,但也可以让我们了解到
class只不过Python创建类的语法糖,真正产生类的调用type的__call__属性对象,
前面已经讲过了type也是一个类,它是创建类的类,所以它随便把自己创建了。
1月3日继续更新,做题晚上知乎上面又去看了一些别人的文章,官方对__new__定义为静态方法,就我的理解,通过Python语法糖def 创建的方法,就__new__自动不带__get__属性,也就是成为了所谓的静态方法。
object是所有类的祖宗的,object简单的通过()调用,可以创建一个属于object类的实例。如果你想创建一个属于专属类的对象的话,可以通过object.__new__(class)来实现。
我们可以再类中自定义__new__,写一些自己的逻辑,但最终调用的__new__方法肯定还是来至与object。
继续回来到type,type可以直接调用产生一个类,这个这一个属于所有type类创建的类,我们无法查看class->type->type的__call__方法,因为type中的__call__方法用于type创建的对象类通过()调用使用。
通过一些代码进行演示
1 2 3 4 5 6 7 8 9 | In [ 169 ]: Simpler = type ( 'Simpler' , ( object ,), { 'say' : lambda self : 'I am simple' }) In [ 170 ]: s = Simpler() In [ 171 ]: s.say Out[ 171 ]: <bound method < lambda > of <__main__.Simpler object at 0x7f85ae534520 >> In [ 172 ]: s.say() Out[ 172 ]: 'I am simple' |
这是type创建类的方式,直接调用绑定type的__call__方法,传入的参数分别为需要被创建的类名, 类的继承对象,以及通过容器绑定类的属性。
所以很明显这是一个type创建的类
1 2 | In [ 173 ]: Simpler.__class__ Out[ 173 ]: type |
同样type也有__new__的属性,它可以创建一个属于特定type类的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | In [ 174 ]: Simple = type .__new__( type , 'Simpler' , ( object ,), { 'say' : lambda self : 'I am simple' }) In [ 175 ]: class Mymeta( type ): ...: ... ...: In [ 176 ]: mm = type .__new__(Mymeta, 'Mm' , ( object ,), { 'say' : lambda self : 'I am simple' }) In [ 177 ]: Simple.__class__ Out[ 177 ]: type In [ 178 ]: mm.__class__ Out[ 178 ]: __main__.Mymeta In [ 179 ]: |
通过上面的代码示例可以看到由于创建一个类的时候需要的参数比较多,不像穿件默认的对象只需要()调用就可以。
通过__new__的方式创建属于特定类[type子类]的的类,当你通过继承type写了自定义的type,可以通过type.__new__的方式创建类,也可以通过自定义的类直接调用返回.
1 2 3 4 5 6 | In [ 179 ]: s = Mymeta( 'Simpler' , ( object ,), { 'say' : lambda self : 'I am simple' }) In [ 180 ]: s.__class__ Out[ 180 ]: __main__.Mymeta In [ 181 ]: |
这里我需要提示一下,type与object的__new__创建对象的逻辑区别,type创建类的时候,需要传入类名,类的继承对象,类的属性空间。创建了类以后,就是一个可以拿来使用的类。
元类中除了__new__,所有另外定义的所有方法[也就是带有__get__与__call__属性的对象],后续都可以称为创建的类绑定的方法,类调用这些方法无须传入类名。因为在元类中的self就是被创建的类本身。
通过type.__new__的方法创建的对象,第一个参数来至的元类会把元类内定义的函数传递给被创建的类的绑定方法,第三个参数,是对类的私有属性进行赋值,这个正常情况下,也可以在__init__环节执行,
但由于元类的特殊性,它再__new__的时候就执行了,这也就导致了,在元类中__init__的无足轻重,因为通过type.__new__就可以把需要初始化的类的私有属性,通过传参的形式,后面我也将实测元类中__init__的实际作用,感觉元类用了__call__与__new__另外的都是一些类方法,实属鸡肋。
object.__new__就相对比较简单,就会将被所属的类属性传递给实例,实例的私有属性可以再__init__环节进行赋值。
说了这么多废话,来个实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | class MyMeta( type ): # 可以被创建的类对象继承。 index = 99 # 这里的self就是被创建的类,以前__init__与另外的所有方法都一样,另类的只有__new__ def __init__( self , * args, * * kwargs): super (MyMeta, self ).__init__( * args, * * kwargs) def __new__( cls , * args, * * kwargs): return type .__new__( cls , * args, * * kwargs) # 刚刚忘记了写了返回值,__call__是需要返回值的,返回类创建的对象。 def __call__( self , * args, * * kwargs): return super (MyMeta, self ).__call__( * args, * * kwargs) # 这个方法对于被创建的类来说,就是被绑定的方法 def c_out( cls ): return 'I am Demo class' class Demo(metaclass = MyMeta): number = 8 def __init__( self , name): self .name = name def say( self ): print ( 'I am demo' ) if __name__ = = '__main__' : d = Demo( 'sidian' ) d.say() print (Demo.c_out()) print (Demo.index) |
输出
1 2 3 | I am demo I am Demo class 99 |
这里可以看到type类的属性可以传递给被创建的类,但这个是type类的属性,在被创建的类的私有属性里面无法看到,被创建的类的私有属性,可以通过type.__new__的最后一个参数传入。
输出__new__里面的参数
1 2 3 | def __new__( cls , * args, * * kwargs): print (args, kwargs, sep = '\n' ) return type .__new__( cls , * args, * * kwargs) |
输出
1 2 | ( 'Demo' , (), { '__module__' : '__main__' , '__qualname__' : 'Demo' , 'number' : 8 , '__init__' : <function Demo.__init__ at 0x7fdcd6d6dca0 >, 'say' : <function Demo.say at 0x7fdcd6d6dd30 >}) {} |
这些都是一些常规操作了,网上由很多介绍,通过修改字典中的参数,添加或删除被创建的类的私有属性。
理解了这些,整个元类已经接近尾声。
在聊元类的过程中,我现在觉的要是不聊__call__都是耍流氓,我以前也看过很多元类,基本没有聊__call__的
在我们的类执行()调用绑定自身的__call__方法时
首相需要创建一个初始的对象,然后再对对象进行私有属性赋值。这个只不过在在绑定的方法中写入的逻辑,才会自动执行。
假如你修改了元类的__call__逻辑,你完全可以自己定义类的初始化方法。
其实理解到这里,真的没啥好写的了。最难的就是需要理解__new__的含义,以及__call__的逻辑
这里是老男孩中的一个采用元类写的单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 |
我说过,__init__在元类里面就属于鸡肋,我直接改成用__call__实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Mymeta( type ): def __call__( self , * args, * * kwargs): if not hasattr ( self , '__instance' ): if args or kwargs: obj = object .__new__( self ) obj.__init__( * args, * * kwargs) self .__instance = obj return obj else : return self .__instance class Mysql(metaclass = Mymeta): def __init__( self ,host = 88 ,port = 99 ): self .host = host self .port = port if __name__ = = '__main__' : sql = Mysql() sql1 = Mysql() print (sql is sql1) |
本人对元类的__init__经过测试,在元类中定义__init__,直接pass,对创建的类与实例也没由影响。在__init__中能完成的事情,在元类中__call__与__new__都能完成。
最后写一个,直接用元类手动创建实例的方法,千万不要被吓到了。
就是通过继承与type类的元类手动调用,这个跟type的操作是一样,最后关于关于,对于用于元类创造类的过程中,需要添加metaclass参数,其实我们可以认为,每次创建类的时候,
都会添加这个参数,只不过这个参数的默认参数为type,所以默认的类的创造类都是type。
说了这里就接近尾声了,其实我感觉只要你能够彻底理解用类创造实例的过程,理解元类并不难,只不过是创造的对象不同,但创建的过程与原理都是一样的。
这次结束,我的元类学习我觉的我应该掌握的差不多了了,还有通过这次我觉的作为一门oop编程语言,对于Python自诩万物皆对象的语言。
在任何的Python书籍中,太强化函数这个观念,弱化了对象的理解。Python的大量语法糖,会让新手写起来感觉很容易,代码量很少,但很多逻辑都简化了。
这让学习的人,学到后面会觉的深入起来特别累。
Python的函数就是一个对象,拥有了__call__方法以及__get__方法的对象,我没有找到Python函数类的原型,等哪天有空了,自己写一个函数的累。
此博客花了我三天时间。效率实在偏低,元类我也学了多次,多次以为懂了,但过几天又不会了,只有理解了具体的过程,才能让我以后能够不用死记硬背。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2020-01-03 流畅的python,Fluent Python 第十三章笔记 (正确重载运算符)