metaclass --元类, 为描述类的超类,同时可以改变子类的形态。metaclass就是Python中用来创建class object的class。我们可以将其看做能够产生class的类工厂。
# @Time:2021/9/16
# @File:test_metaclass.py
class Mymeta(type):
def __init__(self, name, bases, dic):
super().__init__(name, bases, dic)
print("===>Myeta.__init__")
print(self.__name__)
print(dic)
print(self.yaml_tag)
def __new__(cls, *args, **kwargs):
print("===>Mymeta.__new__")
print(cls.__name__)
#return type.__new__(cls, *args, **kwargs)
# 使用这种super的方式能更好一些
return super(Mymeta, cls).__new__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
print("===>Mymeta.__call__")
obj = cls.__new__(cls)
cls.__init__(cls, *args, **kwargs)
return obj
class Foo(metaclass=Mymeta):
yaml_tag = "!Foo"
def __init__(self, name):
print('Foo.__init__')
self.name = name
def __new__(cls, *args, **kwargs):
print('Foo.__new__')
return object.__new__(cls)
print("************************************************************")
foo = Foo('foo')
运行结果如下:
===>Mymeta.__new__
Mymeta
===>Myeta.__init__
Foo
{'__module__': '__main__', '__qualname__': 'Foo', 'yaml_tag': '!Foo', '__init__': <function Foo.__init__ at 0x0000029E60D49DC8>, '__new__': <function Foo.__new__ at 0x0000029E60D49E58>}
!Foo
************************************************************
===>Mymeta.__call__
Foo.__new__
Foo.__init__
从上面的运行结果可以发现在定义 class Foo() 定义时,
会依次调用 MyMeta 的 __new__
和 __init__
方法构建 Foo 类,
然后再调用 foo = Foo() 创建类的实例对象时,才会调用 MyMeta 的 __call__
方法来调用 Foo 类的 __new__
和 __init__
方法。
把上面的例子运行完之后就会明白很多了,正常情况下我们在父类中是不能对子类的属性进行操作,但是元类可以。换种方式理解:元类、装饰器、类装饰器都可以归为元编程。
Python 底层语言设计层面是如何实现 metaclass 的?
第一,所有的 Python 的用户定义类,都是 type 这个类的实例。
类本身不过是一个名为 type 类的实例。
class MyClass:
pass
instance =MyClass()
print(type(instance))
print(type(MyClass))
instance 是 MyClass 的实例,而 MyClass 是 type 的实例。
第二,用户自定义类,只不过是 type 类的 __call__
运算符重载
当定义一个类的语句结束时,真正发生的情况,是 Python 调用 type 的 __call__
运算符。简单来说,当定义一个类时,写成下面这样时:
class MyClass:
data = 1
Python 真正执行的是下面这段代码:
class = type(classname, superclasses, attributedict)
这里等号右边的 type(classname, superclasses, attributedict),就是 type 的 __call__
运算符重载,它会进一步调用:
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)
当然,这一切都可以通过代码验证,比如
第一种定义类的方式:
class MyClass:
data = 1
instance = MyClass()
print(MyClass)
print(instance)
print(instance.data)
输出结果:
<class '__main__.MyClass'>
<__main__.MyClass object at 0x000001CD0F3AF708>
1
第二种通过type定义类的方式:
MyClass = type('MyClass',(),{'data':1})
instance = MyClass()
print(MyClass)
print(instance)
print(instance.data)
输出结果:
<class '__main__.MyClass'>
<__main__.MyClass object at 0x000001DD5D28A848>
1
type(class_name, tuple_of_parent_class, dict_of_attribute_names_and_values)
type所接收的第一个参数'Myclass'
是该类的类名,同时我们使用了Myclass
作为存储该class object引用的变量。这二者可以不同。其中第二个参数tuple_of_parent_class
用来表示继承关系,可以为空。第三个参数用来描述我们所要创建的类所应该具有的attribute。
第三,metaclass 是 type 的子类,通过替换 type 的 __call__
运算符重载机制,“超越变形”正常的类
一旦把一个类型 MyClass 的 metaclass 设置成 MyMeta,MyClass 就不再由原生的 type 创建,而是会调用 MyMeta 的 __call__
运算符重载。
class = type(classname, superclasses, attributedict)
# 变为了
class = MyMeta(classname, superclasses, attributedict)
注意:
__new__
和__init__
功能上的区别
__new__是用来创造一个类的实例的(constructor),而__init__是用来初始化一个实例的(initializer)。
__new__和__init__参数的不同:__new__所接收的第一个参数是cls,而__init__所接收的第一个参数是self。这是因为当调用__new__的时候,该类的实例还并不存在(也就是self所引用的对象还不存在),所以需要接收一个类作为参数,从而产生一个实例。而当我们调用__init__的时候,实例已经存在,因此__init__接受self作为第一个参数并对该实例进行必要的初始化操作。这也意味着__init__是在__new__之后被调用的。
Python3中允许用户重载__new__和__init__方法,且这两个方法具有不同的作用。__new__作为构造器,起创建一个类实例的作用。而__init__作为初始化器,起初始化一个已被创建的实例的作用。
class newStyleClass(object):
# In Python2, we need to specify the object as the base.
# In Python3 it's default.
def __new__(cls):
print("__new__ is called")
return super(newStyleClass, cls).__new__(cls)
def __init__(self):
print("__init__ is called")
print("self is: ", self)
newStyleClass()
执行结果如下:
__new__ is called
__init__ is called
self is: <__main__.newStyleClass object at 0x000001D1D6CE2508>
创建类实例并初始化的过程中__new__和__init__被调用的顺序:__new__函数首先被调用,构造了一个newStyleClass的实例,接着__init__函数在__new__函数返回一个实例的时候被调用,并且这个实例作为self参数被传入了__init__函数。
这里需要注意的是,如果__new__函数返回一个已经存在的实例(不论是哪个类的),__init__不会被调用。如下面代码所示
如果我们在__new__函数中不返回任何对象,则__init__函数也不会被调用。
如果__new__函数不返回对象的话,不会有任何对象被创建,__init__函数也不会被调用来初始化对象。
__init__不能有返回值
__new__函数直接上可以返回别的类的实例。如上面例子中的returnExistedObj类的__new__函数返回了一个int值。
只有在__new__返回一个新创建属于该类的实例时当前类的__init__才会被调用。
class sample(object):
def __str__(self):
return ("sample")
class example(object):
def __new__(cls):
print("__new__ is called")
return sample()
def __init__(self):
print("__init__ is called")
print(example())
结果 :
__new__ is called
sample
在Python3中所有的类均默认继承object,所以并不需要显式地指定object为基类。
以object为基类可以使得所定义的类具有新类所对应的方法(methods)和属性(properties)。
好的文章,参考 :