zoukankan      html  css  js  c++  java
  • Python3构造方法__new__在普通类和元类中的差别

    最近想自己写一个异步ORM框架,在构造方法遇到了几个问题,记录一下。Python中创建一个对象,会调用__new__方法,通常情况下我们是不需要定义这个方法的,会随着继承一路调用object类的__new__方法,如果想对这个实例对象做一些额外的处理,可以重写这个方法。

    方法一 直接重写构造方法

    直接在定义的类中重写__new__方法,此时我们实例化一个对象的流程为:

    实例代码

    class Person(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __new__(cls, *args, **kwargs):
            print('构造方法 args ->', args)
            print('构造方法 kwargs ->', kwargs)
            return super().__new__(cls)
    
    
    p1 = Person('张三', 18)
    p2 = Person(name='李四', age=21)
    p3 = Person()
    

    输出如下:

    构造方法 args -> ('张三', 18)
    构造方法 kwargs -> {}
    构造方法 args -> ()
    构造方法 kwargs -> {'name': '李四', 'age': 21}
    构造方法 args -> ()
    构造方法 kwargs -> {}
    Traceback (most recent call last):
      File "/Users/sw/test.py", line 122, in <module>
        p3 = Person()
    TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
    

    可以看到在调用构造方法__new__时,我们初始化参数也一并传入了__new__,随后再将参数传入__init__方法。但是我们在不传入任何参数实例化p3时__new__方法并没有报错,表明__new__方法是不依赖初始化参数的,我们在调用父类方法return super().__new__(cls)时也仅仅传入了cls。

    如果将__new__方法改成

        def __new__(cls):
            # print('构造方法 args ->', args)
            # print('构造方法 kwargs ->', kwargs)
            return super().__new__(cls)
    

    此时依旧实例化上面的三个对象,会直接报错

    Traceback (most recent call last):
      File "/Users/sw/test.py", line 122, in <module>
        p1 = Person('张三', 18)
    TypeError: __new__() takes 1 positional argument but 3 were given
    

    这表明虽然__new__方法默认不使用初始化参数,但它必须要有一个载体来传递初始化参数。

    接下来看看__new__中的cls这个变量是什么东西,对代码做一点点修改

    class Person(object):
        sex = 1
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __new__(cls, *args, **kwargs):
            print(cls)
            print(type(cls))
            print(cls.__dict__)
            return super().__new__(cls)
    
    
    p1 = Person('张三', 18)
    

    输出

    <class '__main__.Person'>
    <class 'type'>
    {'__module__': '__main__', 'sex': 1, '__init__': <function Person.__init__ at 0x10b01aae8>, '__new__': <staticmethod object at 0x10b04e320>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
    

    第一行表明cls变量是一个Person类,第二行表明cls属于type类,类的类是type没毛病。

    第三行打印了cls的属性字典,可以发现包含我们定义的sex属性,但没有__init__方法创建的nameage属性,这也符合预期:__new__方法优先于__init__方法调用。

    所以如果我们要在__new__方法中对类的属性执行一些操作,可以使用cls.__dict__

    方法二 使用元类

    使用元类其实也是重写__new__方法,对于实例化对象来说流程和方法一是一样的,但是实现元类中的__new__和方法一是存在明显区别的,使用元类的流程:

    此时的代码示例如下

    class MetaClass(type):
    
        def __new__(cls, name, bases, attrs_dict):
            print('元类构造方法 cls ->', cls)
            print('元类构造方法 name ->', name)
            print('元类构造方法 bases ->', bases)
            print('元类构造方法 attrs_dict ->', attrs_dict)
            # 此处 type.__new__ == super().__new__
            return type.__new__(cls, name, bases, attrs_dict)
    
    
    print('定义Person类')
    class Person(object, metaclass=MetaClass):
        sex = 1
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    
    print('实例化Person')
    p1 = Person('张三', 18)
    p2 = Person(name='李四', age=21)
    

    输出

    定义Person类
    元类构造方法 cls -> <class '__main__.MetaClass'>
    元类构造方法 name -> Person
    元类构造方法 bases -> (<class 'object'>,)
    元类构造方法 attrs_dict -> {'__module__': '__main__', '__qualname__': 'Person', 'sex': 1, '__init__': <function Person.__init__ at 0x104aa6ae8>}
    实例化Person
    

    先从代码来看,最明显的区别是__new__方法传入的参数固定为4个,传入的参数意义和内置方法type是一致,name参数为要创建的类名,bases参数是一个所继承的父类组成的元组,attrs_dict则是一个类属性组成的字典。而在方法一定义的__new__方法,传入参数的数量实际上是由__init__方法接受的参数数量决定的。其次cls参数变成了元类MateClass本身,而不是Person类,这意味不能使用变量cls来获取Person类的属性,而要使用attrs_dict

    再来观察输出,我们发现当我们实例化对象p1、p2时根本没有任何输出,那么输出的信息显然是我们在定义Person类中打印的,这表明使用元类时,只有在定义类时才会调用__new__方法,而实例化类对象时不会调用__new__方法。换言之,元类是构造类的方法

  • 相关阅读:
    【python】构造字典类型字典
    【python】序列化和反序列化
    【python】进程
    【python】类中属性方法@property使用
    【python】类中__slots__使用
    【python】类的继承和super关键字
    【python】类的访问限制
    【python】模块作用域
    【python】删除1~100的素数
    【python】函数相关知识
  • 原文地址:https://www.cnblogs.com/lazyfish007/p/13181525.html
Copyright © 2011-2022 走看看