元类(meta class)
在python中一切皆对象,实例对象是由类创建的,类(又称类对象)是由元类创建的。 通常我们用type函数来检查一个对象是什么类型。
print(type(1))
print(type('a'))
print(type([1, 2, 3]))
class Foo:
pass
foo = Foo()
print(type(foo))
输出结果:
<class 'int'>
<class 'str'>
<class 'list'>
<class '__main__.Foo'>
上面结果显示了python的一些内置数据类型,以及我自定义的一个类。那么既然一切皆对象,这些内置类型,以及我自定义的Foo类对象本身又是什么类型呢?
print(type(type(1)))
print(type(type('a')))
print(type(type([1, 2, 3])))
print(type(Foo))
print(type(object))
输出结果:
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
注意,所有这些内置类型的类型都是type。Foo类的类型不是object,而是type,就连所有类的基类object的类型都是type。而且,type自己的类型也是type。print(type(type))
输出结果是 <class 'type'>
。
其实我们定义的类的类是元类,换个说法,就是我们定义的类是元类的对象。python内部是通过type函数来创建类的。
class Foo:
pass
上面的类定义语句其实是语法糖, python会将其转化为 Foo = type('Foo',(),{})
这行代码来创建Foo类。
下面写一个简单的元类使用示例:
class Meta(type):
def __new__(cls, name, bases, attrs):
print('元类 __new__被调用')
clazz = type(name, bases, attrs)
# 给创建的类做些手脚,比如加个属性
clazz.hehe = 'haha'
return clazz
class A(metaclass=Meta):
pass
print(vars(A))
输出结果:
元类 __new__被调用
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'hehe': 'haha'}
可以看到,通过定义一个类,让它继承自type,这样,它就是一个元类。 我们再声明一个普通类的时候,只要在括号中加上metaclass=元类名, 那么类创建的过程就会调用元类里定义的__new__方法。
元类也可以被继承,元类的子类仍然可以做为其他类被创建时的元类。使用元类创建的类再被继承后,它们的子类仍然会指向这个元类,即被创建的时候仍然会调用元类中的__new__
和__init__
方法。
class Meta(type):
pass
class SubMeta(Meta):
def __new__(cls, name, bases, attrs):
print('SubMeta.__new__被调用来创建', name, '类')
return super().__new__(cls, name, bases, attrs)
class Foo(metaclass=SubMeta):
pass
class Bar(Foo):
cls_prop = '我是类方法'
bar = Bar()
print(vars(Bar))
print(bar.cls_prop)
输出结果:
SubMeta.__new__被调用来创建 Foo 类
SubMeta.__new__被调用来创建 Bar 类
{'__module__': '__main__', 'cls_prop': '我是类方法', '__doc__': None}
我是类方法
构造方法__new__ 和 __init__
再进一步研究元类之前,我们先来看一下这两个构造方法。
像面向对象语言,比如C#,类的构造方法只有一个。而在python中,类的构造方法有两个,注意这里所说的构造的意思就是使用类来实例化一个对象的过程。
class Foo:
def __new__(cls, *args, **kwargs):
print('__new__ 被执行')
print('cls is ', cls)
print('args:',args)
print('kwargs:',kwargs)
obj = super().__new__(cls)
print(obj)
print('-' * 25, '分隔线', '-' * 25)
return obj
def __init__(self, *args, **kwargs):
print('__init__ 被执行')
print('self is ', self)
print('args:', args)
print('kwargs:', kwargs)
foo = Foo('abc',123, karg='我是命名参数')
输出结果:
__new__ 被执行
cls is <class '__main__.Foo'>
args: ('abc', 123)
kwargs: {'karg': '我是命名参数'}
<__main__.Foo object at 0x000001C259615910>
------------------------- 分隔线 -------------------------
__init__ 被执行
self is <__main__.Foo object at 0x000001C259615910>
args: ('abc', 123)
kwargs: {'karg': '我是命名参数'}
从输出结果可以发现几点信息:
- __new__函数先被执行,__init__后被执行。
- 使用类名加括号来实例化一个对象时,括号里的参数同时传给了这两个构造函数。
- __new__函数中已经生成了对象,并且需要通过return语句将对象返回。接下来__init__函数才能对这个对象做些比如赋值属性的一些操作。
- 对象的创建肯定有一些复杂的过程(比如内存空间的分配等),因此我们不可能只简单的重写__new__方法就能模拟这个过程,所以我们必须通过调用父类的__new__方法来完成这个创建的过程,当然一般就是指object.__new__方法,并且它只接受一个参数,就是cls本身。
官方文档里说__new__
方法是staticmethod,因此当我们手动调用__new__
方法时第一个参数也需要手动传入。当然如果我们没有重写__new__
方法的话,python在创建实例的时候会自动调用object.__new__
方法,并且自动将类名传递给第一个参数。
class Foo:
def __new__(cls, *args, **kwargs):
print('__new__ 被执行')
print('cls is ', cls)
print('args:',args)
print('kwargs:',kwargs)
return super().__new__(cls)
@classmethod
def cls_method(cls, name):
print('hello', name)
foo = Foo.__new__(Foo,'abc',123, karg='我是命名参数')
print(foo)
print('-' * 25, '分隔线', '-' * 25)
foo2 = foo.__new__(Foo,'abc',123, karg='我是命名参数')
print(foo2)
print('-' * 25, '分隔线', '-' * 25)
Foo.cls_method('Roland') # 类方法自动将类名做为第一个参数传入
foo.cls_method('Roland')
输出结果:
__new__ 被执行
cls is <class '__main__.Foo'>
args: ('abc', 123)
kwargs: {'karg': '我是命名参数'}
<__main__.Foo object at 0x000001EC04955A10>
------------------------- 分隔线 -------------------------
__new__ 被执行
cls is <class '__main__.Foo'>
args: ('abc', 123)
kwargs: {'karg': '我是命名参数'}
<__main__.Foo object at 0x000001EC04955A50>
------------------------- 分隔线 -------------------------
hello Roland
hello Roland
而__init__
方法则像是普通的实例方法,即实例对象会自动传给方法里的第一个参数。
class Foo:
def __new__(cls, *args, **kwargs):
print('__new__ 被执行')
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ 被执行')
self.hehe = 'haha'
foo = Foo.__new__(Foo,'abc',123, karg='我是命名参数')
foo.__init__('abc',123, karg='我是命名参数')
print(vars(foo))
输出结果:
__new__ 被执行
__init__ 被执行
{'hehe': 'haha'}
__call__
方法
接下来我们再研究一下__call__
方法
class Foo:
def __call__(self, *args, **kwargs):
print('__call__方法被执行')
print('self is', self)
print('args is', args, 'kwargs is ', kwargs)
foo = Foo()
foo('abc', x=10)
print('-' * 25, '分隔线', '-' * 25)
foo.__call__('abc', x=10) # 用实例名调用时,实例对象自动传给了形参self
print('-' * 25, '分隔线', '-' * 25)
Foo.__call__('abc', x=10) # 用类名调用时,第一个位置参数对应到了形参self
输出结果:
__call__方法被执行
self is <__main__.Foo object at 0x0000020956A453D0>
args is ('abc',) kwargs is {'x': 10}
------------------------- 分隔线 -------------------------
__call__方法被执行
self is <__main__.Foo object at 0x0000020956A453D0>
args is ('abc',) kwargs is {'x': 10}
------------------------- 分隔线 -------------------------
__call__方法被执行
self is abc
args is () kwargs is {'x': 10}
所以我们可以得出如下结论:
类在创建时会先调用类的__new__
方法生成实例对象,然后再调用__init__
方法用于进一步为实例对象的属性赋初值。
__call__
方法虽然是在类中定义的,但是在类的实例加()时才被触发。
现在,我们知道类是由元类(默认为type)创建的,我们可以把元类看做普通类,而把元类创建的类当做普通的类的实例。 这样,我们就能理解元类中的 __new__, __init__, __call__
都是在什么情况下被触发的了。
我们再看下面的例子,定义一个元类,并在元类中覆写 __new__, __init__, __call__
三个方法,尤其注意__call__
方法我们简单写个pass。
class MyMetaclass(type):
def __new__(cls, name, bases, attrs):
print("元类的__new__被调用")
return super().__new__(cls, name, bases, attrs)
def __init__(self, name, bases, attrs):
print("元类的__init__被调用")
super().__init__(name, bases, attrs)
def __call__(self, *args, **kwargs):
print("元类的__call__被调用")
pass
class MyClass(metaclass=MyMetaclass):
def __new__(cls, *args, **kwargs):
print("类的__new__被调用")
return super().__new__(cls)
def __init__(self):
print("类的__init__被调用")
obj = MyClass()
print('类实例化的对象obj是',obj)
输出结果:
元类的__new__被调用
元类的__init__被调用
元类的__call__被调用
类实例化的对象obj是 None
从输出结果上可以推断,在定义MyClass类时,元类的__new__ 和 __init__
方法都被调用了。 但创建MyClass的实例时却并未触发MyClass的__new__ 和 __init__
方法。这是为什么呢?
我们用实例名加()时调用的是类中定义的__call__
方法。由此我们可以推断我们的类名加()实际上会触发元类的__call__
方法。而我们知道如果我们不在元类中定义__call__
方法,类在实例化时是能成功调用类中的__new__和__init__方法的。所以可以得出结论,元类的__call__
方法实际上会调用类的__new__和__init__
方法。
下面我们重新覆写__call__
方法,并模拟实现一下调用类的__new__和__init__
方法的过程。
def __call__(self, *args, **kwargs):
print("元类的__call__被调用")
instance = self.__new__(self, *args, **kwargs)
if instance is not None:
self.__init__(instance, *args, **kwargs)
return instance
运行结果:
元类的__new__被调用
元类的__init__被调用
元类的__call__被调用
类的__new__被调用
类的__init__被调用
类实例化的对象obj是 <__main__.MyClass object at 0x0000017A658E6250>
这说明如果我们在元类的__call__方法中调用了类的__new__和__init__方法,就能实现类名加()来创建类的实例的过程。
这似乎说明了,任何一个对象加()的方法,都是在调用其类中定义的__call__
方法。
那么我们再想一下,我们用元类加()的方式来创建一个类,那么这是不是调用了元类的类里定义的__call__
方法呢? 那么元类的类又是谁呢?
下面代码就使用元类加()来创建一个类
class MyMetaclass(type):
def __new__(cls, name, bases, attrs):
print("元类的__new__被调用")
return super().__new__(cls, name, bases, attrs)
def __init__(self, name, bases, attrs):
print("元类的__init__被调用")
super().__init__(name, bases, attrs)
def __call__(self, *args, **kwargs):
print("元类的__call__被调用")
instance = self.__new__(self, *args, **kwargs)
if instance is not None:
self.__init__(instance, *args, **kwargs)
return instance
MyClass = MyMetaclass('MyClass', (), {})
obj = MyClass()
print('类实例化的对象obj是',obj)
输出结果:
元类的__new__被调用
元类的__init__被调用
元类的__call__被调用
类实例化的对象obj是 <__main__.MyClass object at 0x0000028FCD405A50>
可以看到,类被正确的创建了,同时类也能正常实例化一个对象。那么元类加()到底是调用谁的__call__
方法呢? 当然就是元类的类,也就是type。就连type的类也是type。前面我们已经验证过,print(type(type))
输出的还是type。
所以我们尝试使用下面的代码来模拟元类加():
TestClass = type.__call__(MyMetaclass,'TestClass', (), {})
obj = TestClass()
print('类实例化的对象obj是',obj)
输出结果:
元类的__new__被调用
元类的__init__被调用
元类的__call__被调用
类实例化的对象obj是 <__main__.TestClass object at 0x000002A5A34B5A90>
输出结果验证了我们的推测是正确的。
当我们没有自定义元类的时候,type就是默认的元类。
TestClass = type('TestClass', (), {})
这行代码等价于 TestClass = type.__call__(type,'TestClass', (), {})
。
依据我们的推断,默认元类type.__call__
会在内部调用 type.__new__
和type.__init__
。
再继续探索元类其它内容之间,我们先来研究一下__init_subclass__
的作用。
__init_subclass__
: 定义在父类中,当其被子类继承时,该方法就会被触发。
先看下简单用法示例:
class BaseFoo:
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('BaseFoo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
class Foo(BaseFoo, prop='abc'):
pass
输出结果:
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
其实想传给__init_subclass__
参数,只能通过关键字方式传入。因为在类定义时,类名后面的括号里放位置参数的话都会被认为是要继承的基类名称。所有,我这里定义的*args参数其实没有意义。
我尝试换种调用方法:
print('-' * 25, '分隔线', '-' * 25)
BaseFoo.__init_subclass__('456',wawa='gaga')
print('-' * 25, '分隔线', '-' * 25)
Foo.__init_subclass__('123',hehe='haha')
print('-' * 25, '分隔线', '-' * 25)
foo = Foo()
foo.__init_subclass__('789',foo='im foo')
输出结果:
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.BaseFoo'>
BaseFoo __init_subclass__被执行
args is ('456',)
kwargs is {'wawa': 'gaga'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ('123',)
kwargs is {'hehe': 'haha'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ('789',)
kwargs is {'foo': 'im foo'}
可以看出,__init_subclass__
方法是类方法,无论使用类名,或实例名加点的方式调用它,都会自动将类名或实例所属的类传递给该方法的第一个参数。
我们还可以观察到,由于Foo类内部没有定义__init_subclass__
方法,所以Foo.__init_subclass__
其实是调用了其基类的方法,也就是BaseFoo.__init_subclass__
如果我们在子类,即Foo中覆写__init_subclass__方法,我们观察到的结果如下:
class BaseFoo:
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('BaseFoo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
class Foo(BaseFoo, prop='abc'):
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('Foo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('-' * 25, '分隔线', '-' * 25)
BaseFoo.__init_subclass__('456',wawa='gaga')
print('-' * 25, '分隔线', '-' * 25)
Foo.__init_subclass__('123',hehe='haha')
print('-' * 25, '分隔线', '-' * 25)
foo = Foo()
foo.__init_subclass__('789',foo='im foo')
输出结果:
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.BaseFoo'>
BaseFoo __init_subclass__被执行
args is ('456',)
kwargs is {'wawa': 'gaga'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.Foo'>
Foo __init_subclass__被执行
args is ('123',)
kwargs is {'hehe': 'haha'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.Foo'>
Foo __init_subclass__被执行
args is ('789',)
kwargs is {'foo': 'im foo'}
继承链调用示例:
class BaseFoo:
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('BaseFoo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('-' * 25, '分隔线', '-' * 25)
class Foo(BaseFoo, prop='abc'):
print('定义Foo类')
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('Foo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('调用基类__init_subclass')
super().__init_subclass__(*args, **kwargs) # 可以根据需要来决定是否要继续调用上层基类的__init_subclass__方法
print('-' * 25, '分隔线', '-' * 25)
class Bar(Foo, bar='xiao bar'):
print('定义Bar类')
pass
输出结果:
------------------------- 分隔线 -------------------------
定义Foo类
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
------------------------- 分隔线 -------------------------
定义Bar类
cls is <class '__main__.Bar'>
Foo __init_subclass__被执行
args is ()
kwargs is {'bar': 'xiao bar'}
调用基类__init_subclass
cls is <class '__main__.Bar'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'bar': 'xiao bar'}
最后,需要注意的是,python中的最终基类的__init_subclass__
没有做任何事情,也不接收任何参数。比如,object.__init_subclass__(Foo)
执行是会报错的。
另外,python会自动将关键字参数metaclass排除在外,使它不会被传递给基类的__init_subclass__
class BaseFoo:
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('BaseFoo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('-' * 25, '分隔线', '-' * 25)
class Foo(BaseFoo, prop='abc', metaclass=type):
print('定义Foo类')
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('Foo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('调用基类__init_subclass')
super().__init_subclass__(*args, **kwargs) # 可以根据需要来决定是否要继续调用上层基类的__init_subclass__方法
输出结果:
------------------------- 分隔线 -------------------------
定义Foo类
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
可以看到,__init_subclass__
方法的kwargs没有捕获到metaclass参数。
让我们再看下__set_name__
方法,它也是类创建时的hook函数。一般在介绍描述器时会比较常用这个方法。
class A:
def __set_name__(self, owner, name):
print('-' * 25, '分隔线', '-' * 25)
print("A类的__set_name__被调用")
print('owner is', owner, ', name is', name)
class B:
a = A()
输出结果:
------------------------- 分隔线 -------------------------
A类的__set_name__被调用
owner is <class '__main__.B'> , name is a
__prepare__
方法: 类方法,需要手动加@classmethod装饰器。 此方法用在元类中,它被调用的时机要比元类的__new__
方法还早。它主要是返回一个用于存放元类中属性的命名空间(即字典)。之后,它会将其创建的namespace复制给元类的__new__
方法,而__new__
方法会最终产生一个新的只读的字典用于存放类属性,即__dict__
。
下面我们写一个更复杂的例子,自定义一个元类,元类里自定义__new__
方法,并且可以接收额外的参数。同时,我们把这些hook函数(__init_subclass__, __set_name__, __prepare__
)都加进来,以观察他们的作用以及被调用的时机。
class MyMetaclass(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
print('-' * 25, '分隔线', '-' * 25)
print("元类的__prepare__被调用")
print('metacls is', metacls)
print('name is', name)
print('kwargs is', kwargs)
namespace = {'metaprop': '__prepare__里加的属性'}
for k, v in kwargs.items():
namespace[f'{k}_prepare'] = v
return namespace
def __new__(cls, name, bases, attrs, *args, **kwargs):
print('-' * 25, '分隔线', '-' * 25)
print("元类的__new__被调用")
print('cls is', cls)
print('attrs is', attrs)
print('kwargs is ', kwargs)
clsins = super().__new__(cls, name, bases, attrs, *args, **kwargs) # 这行代码运行过程中,会跳转到要被创建的类的基类的__init_subclass__方法中。
# clsins = type.__new__(cls, name, bases, attrs, *args,**kwargs) # 这行代码运行过程中,会跳转到要被创建的类的基类的__init_subclass__方法中。
print('-' * 25, '分隔线', '-' * 25)
print('元类__new__创建好类对象后')
print('__dict__ is ',clsins.__dict__)
for k,v in kwargs.items():
setattr(clsins, f'{k}_new', v)
return clsins
def __init__(self, name, bases, attrs, *args, **kwargs):
print('-' * 25, '分隔线', '-' * 25)
print("元类的__init__被调用")
print('kwargs is ', kwargs)
super().__init__(name, bases, attrs)
for k, v in kwargs.items():
setattr(self, f'{k}_init', v)
class IndividualClass:
def __set_name__(self, owner, name):
print('-' * 25, '分隔线', '-' * 25)
print("IndividualClass的__set_name__被调用")
print('owner is', owner, ', name is', name)
setattr(owner, f'{name}_beset', '在__set_name__中被增加的属性')
class BaseClass:
def __init_subclass__(cls, *args, **kwargs):
print('-' * 25, '分隔线', '-' * 25)
print("BaseClass的__init_subclass__被调用")
print('cls is',cls)
print('args is', args)
print('kwargs is', kwargs)
for k, v in kwargs.items():
setattr(cls, f'{k}_subclass', v)
baseprop_individual = IndividualClass()
class MyClass(BaseClass, prop='Roland', metaclass=MyMetaclass, age=40):
def __new__(cls, *args, **kwargs):
print("类的__new__被调用")
return super().__new__(cls)
def __init__(self):
print("类的__init__被调用")
myprop_individual = IndividualClass()
normalprop = '这是普通类属性'
print('-' * 25, '分隔线', '-' * 25)
obj = MyClass()
print('类实例化的对象obj是',obj)
print(vars(MyClass))
输出结果:
------------------------- 分隔线 -------------------------
IndividualClass的__set_name__被调用
owner is <class '__main__.BaseClass'> , name is baseprop_individual
------------------------- 分隔线 -------------------------
元类的__prepare__被调用
metacls is <class '__main__.MyMetaclass'>
name is MyClass
kwargs is {'prop': 'Roland', 'age': 40}
------------------------- 分隔线 -------------------------
元类的__new__被调用
cls is <class '__main__.MyMetaclass'>
attrs is {'metaprop': '__prepare__里加的属性', 'prop_prepare': 'Roland', 'age_prepare': 40, '__module__': '__main__', '__qualname__': 'MyClass', '__new__': <function MyClass.__new__ at 0x000001BCE1E43CE0>, '__init__': <function MyClass.__init__ at 0x000001BCE1E43D80>, 'myprop_individual': <__main__.IndividualClass object at 0x000001BCE1E47390>, 'normalprop': '这是普通类属性', '__classcell__': <cell at 0x000001BCE1E4FC70: empty>}
kwargs is {'prop': 'Roland', 'age': 40}
------------------------- 分隔线 -------------------------
IndividualClass的__set_name__被调用
owner is <class '__main__.MyClass'> , name is myprop_individual
------------------------- 分隔线 -------------------------
BaseClass的__init_subclass__被调用
cls is <class '__main__.MyClass'>
args is ()
kwargs is {'prop': 'Roland', 'age': 40}
------------------------- 分隔线 -------------------------
元类__new__创建好类对象后
__dict__ is {'metaprop': '__prepare__里加的属性', 'prop_prepare': 'Roland', 'age_prepare': 40, '__module__': '__main__', '__new__': <staticmethod(<function MyClass.__new__ at 0x000001BCE1E43CE0>)>, '__init__': <function MyClass.__init__ at 0x000001BCE1E43D80>, 'myprop_individual': <__main__.IndividualClass object at 0x000001BCE1E47390>, 'normalprop': '这是普通类属性', '__doc__': None, 'myprop_individual_beset': '在__set_name__中被增加的属性', 'prop_subclass': 'Roland', 'age_subclass': 40}
------------------------- 分隔线 -------------------------
元类的__init__被调用
kwargs is {'prop': 'Roland', 'age': 40}
------------------------- 分隔线 -------------------------
类的__new__被调用
类的__init__被调用
类实例化的对象obj是 <__main__.MyClass object at 0x000001BCE1E47450>
{'metaprop': '__prepare__里加的属性', 'prop_prepare': 'Roland', 'age_prepare': 40, '__module__': '__main__', '__new__': <staticmethod(<function MyClass.__new__ at 0x000001BCE1E43CE0>)>, '__init__': <function MyClass.__init__ at 0x000001BCE1E43D80>, 'myprop_individual': <__main__.IndividualClass object at 0x000001BCE1E47390>, 'normalprop': '这是普通类属性', '__doc__': None, 'myprop_individual_beset': '在__set_name__中被增加的属性', 'prop_subclass': 'Roland', 'age_subclass': 40, 'prop_new': 'Roland', 'age_new': 40, 'prop_init': 'Roland', 'age_init': 40}
从输出结果可以总结以下几点内容:
- 类定义时,位置参数都是指继承的基类。其余参数会传递给元类的__new__方法,但metaclass关键字参数不会当成额外参数传递给元类的__new__。当然,覆写元类的__new__方法时,要提供接收额外参数的形参,即可以通过*args,**kwargs等,甚至是直接定义参数名称。
- 元类中如果定义的__prepare__方法,则该方法会先于元类的__new__方法被调用。 该方法用于创建一个命名空间,用于保存类中的属性。 稍后,该命名空间里的内容会被复制给__new__方法里的__dict__,这是一个只读的数据字典。
- 元类__new__方法要想真实模拟类对象的创建过程,肯定要调用super().new,注意,这里super()相当于type,而不是object。 并且,从输出内容顺序可以发现,其实type.__new__方法内部会调用要创建类对象的基类的__init_subclass__方法。
- type.__new__方法的真实签名为
type.__new__(cls, name, bases, dct, *args, **kwargs)
元类最终是通过type函数,或者说type.__new__
方法来创建实例,只使用cls, name, bases, dct这几个位置参数, 其余的参数是在其内部调用__init_subclass__
方法时做为参数传递给__init_subclass__
方法的。同时__prepare__
方法也会捕获这些额外的关键字参数.
5. 通过示例我们可以发现,想在类定义时对类做些手脚,我们可以使用元类,并覆写其__prepare__
,__new__
和__init__
方法,同时还可以通过其基类的__init_subclass__
方法。
上述的结论同样适用于最初的简单示例场景:
class Base():
def __init_subclass__(cls, **kwargs):
print('基类__init_subclass__被执行')
print('kwargs is', kwargs)
class Sub(Base, a=1, b='abc'):
pass
输出结果:
基类__init_subclass__被执行
kwargs is {'a': 1, 'b': 'abc'}
这个简单结果内部流程如下:
由于我们没有自定义元类, 所以类的定义会调用原始的 type.__new__(cls, name, bases, dct, *args, **kwargs)
, 在接收到额外的关键字参数后,内部先生成了类对象,然后又将额外的关键字参数传给了基类的__init_subclass__
方法。
还记得我们前面得出的结论么? 其实使用type函数来动态创建类,其实是调用的type.__call__
方法,其内部又会自动调用type.__new__
方法,然后再调用type.__init__
方法。 所以type函数仍然可以传递额外关键字参数:
class Base():
def __init_subclass__(cls, **kwargs):
print('基类__init_subclass__被执行')
print('kwargs is', kwargs)
# class Sub(Base, a=1, b='abc'):
# cls_prop = 'wahaha'
Sub = type('Sub', (Base,),{'cls_prop':'wahaha'}, a=1, b='abc')
print(Sub)
print(vars(Sub))
输出结果:
基类__init_subclass__被执行
kwargs is {'a': 1, 'b': 'abc'}
<class '__main__.Sub'>
{'cls_prop': 'wahaha', '__module__': '__main__', '__doc__': None}
类的创建过程
最后,再总结一下类的创建过程。 类其实是通过type()函数创建的。如果我们想在类创建的过程中做些手脚,可以通过定义元类来实现。 元类中可以定义__prepare__, __new__, __init__
这三个方法。 __prepare__
是可以最早捕获类定义时传入的关键字参数的地方。它必须返回一个命名空间,一般是字典类型。 这个字典稍后会复制给__new__
方法,这时可以发现__new__
方法里的attrs形参里就已经有__prepare__
里提前加的属性,以及一些像__module__
这种默认属性存在了。同时,类定义中的其他属性也会在命名空间了存在了,但是这些属性如果会触发一些hook函数的话,这时还没被触发。__new__
方法仍然可以接收类定义时传入的命名参数,并继续做些手脚。最终,我们需要使用type.__new__
方法来实现类对象的创建,而这个时候才会触发那些hook函数,比如__set_name__
, 以及__init_subclass__
。 最终,我们需要return这里创建的类对象。而这个类对象会传递给__init__
方法。 __init__
方法可以继续捕获这些关键字参数,并继续做手脚。