• Python中的__new__、__init__以及metaclass


    在Python的面向对象编程中,首先得创建实例对象,然后初始化实例对象,Python中__new__负责创建实例对象, __init__ 负责初始化对象,本文介绍__new__ __init__ 的区别以及Python中的元类。

    __new__ __init__

    __new__ __init__ 主要具有如下区别:

    • __new__是在实例创建之前被调用的,用于创建实例然后返回该实例对象,是个静态方法。__new__必须要有返回值,也就是返回实例化出来的实例。
    • __new__的返回值(实例)将传递给__init__方法的第一个参数,然后__init__给这个实例设置一些参数。
    • __new__至少要有一个参数cls,代表当前类
    • __init__是实例对象创建完成后被调用,然后设置对象属性的一些初始值,通常用在初始化一个类实例的时候,是一个实例方法。
    • __init__的参数self就是__new__返回的实例,__init____new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值(__init__() should return None)。

    下面创建一个类:

    class Person(object):
        def __new__(cls, *args, **kwargs):
            print("__new__ is called")
            return object.__new__(cls)
    
        def __init__(self, x, y):
            print("__init__ is called")
            self.name = x
            self.height = y
    
    if __name__ == '__main__':
        p1 = Person("zhangsan",180)        
    

    执行结果:

    __new__ is called
    __init__ is called
    

    python实现单例模式

    单例(Singleton)模式就是一个类只能实例化一个对象,这个类必须自己创建自己的唯一实例。

    一般情况下,一个类可以实例化多个对象:

    p1 = Person("zhangsan",180)
    print(p1)
    print(p1.name)
    p2 = Person("lishi",175)
    print(p2)
    print(p2.name)
    

    执行结果:

    __new__ is called
    __init__ is called
    <__main__.Person object at 0x000001939679BC88>
    zhangsan
    __new__ is called
    __init__ is called
    <__main__.Person object at 0x000001939679BC48>
    lishi
    

    发现两个实例化对象的内存地址不一样,单例模式写法:

    class Singleton(object):
        # 单例模式
        _instance = None
        def __new__(cls, *args, **kwargs):
            print("__new__ is called")
            if cls._instance is None:
                cls._instance = object.__new__(cls)
            return cls._instance
    
        def __init__(self,x, y):
            print("__init__ is called")
            self.name = x
            self.height = y
    

    单例模式重写了__ new__ 方法,保证只存在一个实例化对象。

    执行:

    p1 = Singleton("zhangsan",180)
    print(p1)
    print(p1.name)
    p2 = Singleton("lishi", 175)
    print(p2)
    print(p2.name)
    print(p1.name)
    

    输出结果:

    __new__ is called
    __init__ is called
    <__main__.Singleton object at 0x00000243DD386E48>
    zhangsan
    __new__ is called
    __init__ is called
    <__main__.Singleton object at 0x00000243DD386E48>
    lishi
    lishi
    
    

    两个实例化对象指向了相同的内存地址,单例模式可以保证系统中一个类只有一个实例,如果希望某个类的对象只能存在一个,使用单例模式可以节省内存。

    python元类MetaClass

    __new__ 方法也用于自定义元类(MetaClass),下面先来介绍MetaClass的概念。

    什么是MetaClass

    metaclass定义为类中的类(the class of a class),Meta 起源于希腊词汇 meta,有“超越”和“改变”的意思,所以metaclass包含了“超越类”和“变形类”的含义。

    先来看一个例子:

    >>> class MyClass(object): data = 6
    ...
    >>> myobject = MyClass()
    >>> print(myobject.__class__)
    <class '__main__.MyClass'>
    >>> print(MyClass.__class__)
    <class 'type'>
    >>>
    

    上面的例子中,myobject对象的类为MyClass,MyClass的类是type,也就是说myobject是一个MyClass对象,而MyClass又是一个type对象。

    type就是一个元类,它是最常用的元类,是Python中所有类的默认元类,所有的 Python 的用户定义类,都是 type 这个类的实例。

    元类被用来构造类(就像类用来构造对象一样)。Python类的创建过程如下:

    1. 进行类定义时,Python收集属性到一个字典中
    2. 类定义完成后,确定类的元类Meta,执行Meta(name, bases, dct)进行实例化。
      • Meta是元类
      • name:类的名称,__name__属性
      • bases:类的基类元组,__bases__属性
      • dct:将属性名映射到对象中,列出类的所有属性,__dict__属性

    可以使用type直接创建类:

    >>> myobject = type('MyClass', (), {'data': 6})
    >>> print(myobject.__class__)
    <class 'type'>
    
    >>> print(myobject.__name__)
    MyClass
    >>> print(myobject.__bases__)
    (<class 'object'>,)
    >>> print(myobject.data)
    6
    >>> print(myobject.__dict__)
    {'data': 6, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
    

    也就是说当定义MyClass类时,真正执行的是:class = type(name, bases, dct) 语句,

    如果一个类或它的一个基类有__metaclass__属性,它就被当作元类。否则,type就是元类。对元类的自定义要用到__new__ __init__方法,接下来介绍元类的定义。

    定义元类

    元类可以实现在创建类时,动态修改类中定义的属性或者方法,一般使用__new__方法来修改类属性。

    下面的例子使用元类来添加属性方法:

    class MyMetaClass(type):
        def __new__(meta, name, bases, attrs):
            print(meta, "__new__ is called")
            # 动态添加属性
            attrs['name'] = "zhangsan"
            attrs['talk'] = lambda self: print("hello")
            return super(MyMetaClass, meta).__new__(meta, name, bases, attrs)
    
        @classmethod
        def __prepare__(metacls, name, bases, **kwargs):
            print(metacls, "__prepare__ is called")
            return super().__prepare__(name, bases, **kwargs)
    
        def __init__(cls, name, bases, attrs, **kwargs):
            print(cls, "__init__ is called")
            super().__init__(name, bases, attrs)
    
        def __call__(cls, *args, **kwargs):
            print(cls, "__call__ is called")
            return super().__call__(*args, **kwargs)
    
    
    class Myclass(metaclass=MyMetaClass):
        pass
    
    
    if __name__ == '__main__':
        cla = Myclass()
        print(cla.name)
        cla.talk()
        print(cla.__dir__())
    

    执行结果:

    <class '__main__.MyMetaClass'> __prepare__ is called
    <class '__main__.MyMetaClass'> __new__ is called
    <class '__main__.Myclass'> __init__ is called
    <class '__main__.Myclass'> __call__ is called
    zhangsan
    hello
    ['__module__', 'name', 'talk', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
    
    

    可以看到元类MyMetaClass的 __new__() 方法动态地为 Myclass 类添加了 name 属性和 talk() 方法。

    在元类的创建中,可以对name, bases, attrs进行修改,实现我们想要的功能,可以使用getattr()、setattr()等Python反射函数,Python反射机制介绍可参考Python反射介绍

    PyYAML的序列化和反序列化

    在实际应用中,Python 的YAML使用metaclass 的超越变形特性实现了序列化和反序列化(serialization & deserialization)。

    序列化和反序列化

    • 序列化:将结构化数据转换为可存储或可传输格式的过程,就是把对象转换成字节序列的过程。

    • 反序列化:把字节序列恢复成对象的过程。

    序列化的好处是实现了数据的持久化,可以把数据永久地保存到硬盘上;另外,利用序列化实现远程数据传输,在网络上传输对象的字节序列。

    PyYAML使用

    下面的例子中,使用yaml.load()反序列化文本中的Person对象,使用yaml.dump()来序列化创建的Person类。

    data.yaml文件内容:

    !Person
    height: 180
    name: zhangsan
    
    import yaml
    
    class Person(yaml.YAMLObject):
      yaml_tag = u'!Person'
      def __init__(self, name, height):
        self.name = name
        self.height = height
    
      def __repr__(self):
        return f"{self.name}‘s height is {self.height}cm"
    
    with open("data.yaml", encoding="utf-8") as f:
        p1 = yaml.load(f)
        print(p1)
    
    p2 = Person(name='lishi', height=175)
    print(p2)
    print(yaml.dump(p2))
    # with open("data.yaml", "w", encoding="utf-8") as f:
    #     yaml.dump(p2,f)
    

    执行结果:

    zhangsan‘s height is 180cm
    lishi‘s height is 175cm
    !Person
    height: 175
    name: lishi
    

    yaml.load()把 yaml 序列加载成一个 Python Object;yaml.dump()把YAMLObject 子类序列化。我们不需要提前知道任何类型信息,这实现了超动态配置编程。

    总结

    本文简要介绍了Python的__new__ __init__方法,__new__在实例创建之前调用并返回实例对象, __init__是在实例对象创建完成后被调用,用于初始化一个类实例,是一个实例方法。

    python的元类比较复杂,不好理解,一般在Python框架开发中使用,使用时要谨慎。除了YAML的序列化和反序列化外,对象关系映射(ORM)框架也使用了元类,比如Django的models。

    元类可以实现类似装饰器的功能,如果不想在方法前面加@decorator_func,可以使用元类来实现。

    --THE END--

    欢迎关注公众号:「测试开发小记」及时接收最新技术文章!

  • 相关阅读:
    MySQL-子查询,派生表,通用表达式
    MySQL-插入数据(INSERT)
    IDEA中如何使用debug调试项目 一步一步详细教程
    Java相对路径/绝对路径总结
    jsp九个内置对象、四个域对象及Servlet的三大域对象
    浅析MVC模式与三层架构的区别
    三层架构详解
    Java集合中List,Set以及Map等集合体系详解
    POJ3233 [C
    HDU 2829 [Lawrence] DP斜率优化
  • 原文地址:https://www.cnblogs.com/hiyong/p/14855577.html
Copyright © 2020-2023  润新知