python中metaclass的理解

本文解析了元类Metaclass在Python中的作用,如何通过自定义元类修改类的行为,包括__new__、__init__和__call__的调用顺序。此外,讲解了类与type的关系,以及新式类的__new__和__init__的区别。
摘要由CSDN通过智能技术生成

 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)。

好的文章,参考 :

python中的metaclass - lincappu - 博客园

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值