前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 学习-打开潘多拉的魔盒-元类(metaclass)学习

python 学习-打开潘多拉的魔盒-元类(metaclass)学习

作者头像
上海-悠悠
发布2023-08-22 11:11:45
2270
发布2023-08-22 11:11:45
举报
文章被收录于专栏:从零开始学自动化测试

前言

在 Python 里面大家都比较熟悉了,通过 class 关键字创建一个类,这是通过硬编码来实现的。 那么如何动态创建一个类呢,如果给一批数据,让它动态生成一个类?

学习警告:不要轻易打开潘多拉的魔盒,潘多拉出于好奇打开一个魔盒, 释放出人世间的所有邪恶:贪婪、虚无、诽谤、嫉妒、痛苦等等,当她再盖上盒子时,只剩下希望在里面。 不要轻易去开启python的 黑魔法—元类(metaclass)学习,可能会有2个极端

  • 打开之后,如果你能驾驭,会发现无所不能,真正掌握了面向对象的精髓,可以无所不能实现你想要的任何功能。
  • 如果你出于好奇是尝试它,恶魔被释放出来,可能会让你越来越痛苦,最后可能会怀疑自己的学习能力,逐渐失去学习的乐趣。

类与实例

先来理解一个非常简单的例子

代码语言:javascript
复制
class People:
    name = "zhangsan"
    age = 22

p = People()
print(p.name)
print(p.age)

上面代码 People 是一个类, p是 People 类的实例。

接着用 type 查看对象

代码语言:javascript
复制
print(type(p))  # <class '__main__.People'>
print(type(People))  # <class 'type'>

p 是 People 类的实例 People 是一个类,它是 type 的实例,也就是说 People 类是 type 创建的一个实例。 type 就是一个元类(metaclass),简单的理解,元类就是创建类的类。

再举个简单例子

  • 数字123 是一个实例,它是 Int 类的实例, Int类又是type 创建的。
  • 字符串“adc” 是一个实例,它是 Str 类的实例,Str 类又是 type 创建的。
代码语言:javascript
复制
x = 123
print(type(x))    # <class 'int'>
print(type(int))  # <class 'type'>

y = "abc"
print(type(y))    # <class 'str'>
print(type(str))  # <class 'type'>

学到这里也就理解了,python是面向对象的编程语言,python里面的str, int 等class 创建的类,都是type 类创建的,type 就是一个创建类的元类(metaclass)。 str, int 等class 创建的类都是 type 类的实例。

用一个图来表示对象(obj,或叫实例)、类(class)、元类(Metaclass)的关系。

可以这样理解:张三是人类的一个实例,人类是上帝创造的,那么人类是上帝的一个实例, type 就是 python 里面的上帝

type 动态创建类

type 创建类的部分源码

代码语言:javascript
复制
  def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
      """
      type(object_or_name, bases, dict)
      type(object) -> the object's type
      type(name, bases, dict) -> a new type
      # (copied from class doc)
      """
      pass

基本语法如下:

代码语言:javascript
复制
type(name of the class, 
  tuple of the parent class (for inheritance, can be empty), 
  dictionary containing attributes names and values)

传三个参数:

  • class 类的名称, 字符串类型
  • bases 是需要继承的类,默认继承object,可以为空,类型传元组
  • dict 字典类型,传类的属性和方法

接着我们用 type 动态创建一个类

代码语言:javascript
复制
# 通过 type 创建一个猫类
Cat = type("Cat", (object, ), {"name": "hello kitty", "age": 2})
c = Cat()
print(c.name)
print(c.age)
print(type(c))   # <class '__main__.Cat'>
print(type(Cat)) # <class 'type'>

Cat 就是 type 创建的一个类,等价于自己写的class Cat, 它是type 类的实例 c 是 Cat 类的实例。

学到这,就是掌握了使用 type 动态创建类的入门学习了~

自定义元类(metaclass)

如果想把一个类设计成 MetaClass 元类,其必须符合以下条件:

  • 必须显式继承自 type 类;
  • 类中需要定义并实现 __new__() 方法,该方法一定要返回该类的一个实例对象,因为在使用元类创建类时,该 __new__() 方法会自动被执行,用来修改新建的类。
代码语言:javascript
复制
class DemoMetaClass(type):
    def __init__(cls, what, bases=None, dict=None):
        """
        初始化,四个参数
        """
        print("metaclass 创建类初始化。。。。")
        super().__init__(what, bases, dict)

    def __new__(cls, name, bases, attrs):
        """
         创建类,四个参数
           cls 代表动态修改的类
           name 代表动态修改的类名
           bases 代表被动态修改的类的所有父类
           attr 代表被动态修改的类的所有属性、方法组成的字典
        """
        # 动态为该类添加一个name属性
        attrs['name'] = "zhangsan"
        attrs['age'] = lambda x: 20
        return super().__new__(cls, name, bases, attrs)

# 定义一个类,指定metaclass 元类创建,而不是由 type 创建
class NewDemo(object, metaclass=DemoMetaClass):
    pass

以上代码直接执行,会看到打印结果

代码语言:javascript
复制
metaclass 创建类初始化。。。。

上面代码中 DemoMetaClass 是一个类, 该类继承自 type 类,并且内部实现了 __new__() 方法,因此 DemoMetaClass 是一个元类。 NewDemo 类不再由默认的type 创建了,而是由自己写的 DemoMetaClass 来创建。

当使用class NewDemo 创建类时,DemoMetaClass 元类就会触发__init__ 初始化 和 __new__ 创建类。

同样的道理,当我们写一个类, 继承了NewDemo (它由DemoMetaClass 创建)

代码语言:javascript
复制
class Hello(NewDemo):
    pass

它也会触发元类 __new__ 创建类 和 __init__ 初始化。

还可以由 type 动态创建类

代码语言:javascript
复制
World = type("World", (NewDemo, ), {})
w = World()
print(w.name)     # zhangsan
print(w.age())    # 20

它也会触发元类 __new__ 创建类 和 __init__ 初始化 。 学到这,大家会发现目前自己创建的元类已经改变了python 创建类的默认操作,潘多拉的魔盒被你悄悄打卡了~~

掌握 __init____new__

如果创建的class 类里面也有__init____new__, 看下执行过程是怎样的

代码语言:javascript
复制
class DemoMetaClass(type):
    def __init__(cls, what, bases=None, dict=None):
        """
        初始化,四个参数
        """
        print("metaclass 创建类初始化。。。。")
        super().__init__(what, bases, dict)

    def __new__(cls, name, bases, attrs):
        """
         创建类,四个参数
           cls 代表动态修改的类
           name 代表动态修改的类名
           bases 代表被动态修改的类的所有父类
           attr 代表被动态修改的类的所有属性、方法组成的字典
        """
        # 动态为该类添加一个name属性
        attrs['name'] = "zhangsan"
        attrs['age'] = lambda x: 20
        print(f"metaclass __new__: {cls}, {name}, {bases}, {attrs}")
        return super().__new__(cls, name, bases, attrs)

# 定义一个类,指定metaclass 元类创建,而不是由 type 创建
class NewDemo(object, metaclass=DemoMetaClass):
    def __init__(self, x):
        self.x = x
        print(f"class __init__ : {self.x}")

    def __new__(cls, *args, **kwargs):  # 通过 new 来实例化 __init__.new 是用来创建实例的。
        print(f"class __new__:{cls}, {args}, {kwargs}")
        return object.__new__(cls)

print("------------------------------------------")
demo = NewDemo(x="yoyo")

运行结果

代码语言:javascript
复制
metaclass __new__: <class '__main__.DemoMetaClass'>, NewDemo, (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'NewDemo', '__init__': <function NewDemo.__init__ at 0x000002039EDB9310>, '__new__': <function NewDemo.__new__ at 0x000002039EDB93A0>, 'name': 'zhangsan', 'age': <function DemoMetaClass.__new__.<locals>.<lambda> at 0x000002039EDB9430>}
metaclass 创建类初始化。。。。
---------------------------
class __new__:<class '__main__.NewDemo'>, (), {'x': 'yoyo'}
class __init__ : yoyo

元类(metaclass) 实例—-yaml.YAMLObject

yaml.YAMLObject 类用到了metaclass, 相关源码如下

代码语言:javascript
复制
class YAMLObjectMetaclass(type):
    """
    The metaclass for YAMLObject.
    """
    def __init__(cls, name, bases, kwds):
        super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
        if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
            if isinstance(cls.yaml_loader, list):
                for loader in cls.yaml_loader:
                    loader.add_constructor(cls.yaml_tag, cls.from_yaml)
            else:
                cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)

            cls.yaml_dumper.add_representer(cls, cls.to_yaml)

class YAMLObject(metaclass=YAMLObjectMetaclass):
    """
    An object that can dump itself to a YAML stream
    and load itself from a YAML stream.
    """

    __slots__ = ()  # no direct instantiation, so allow immutable subclasses

    yaml_loader = [Loader, FullLoader, UnsafeLoader]
    yaml_dumper = Dumper

    yaml_tag = None
    yaml_flow_style = None

    @classmethod
    def from_yaml(cls, loader, node):
        """
        Convert a representation node to a Python object.
        """
        return loader.construct_yaml_object(node, cls)

    @classmethod
    def to_yaml(cls, dumper, data):
        """
        Convert a Python object to a representation node.
        """
        return dumper.represent_yaml_object(cls.yaml_tag, data, cls,
                flow_style=cls.yaml_flow_style)

pydantic 也用到了metaclass

pydantic 的基本使用

代码语言:javascript
复制
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name = 'yo yo'
    birth: Optional[datetime] = None
    friends: List[int] = []

external_data = {
    'id': '123',
    'birth': '2019-06-01 12:22',
    'friends': [1, 2, '3'],
}
user = User(**external_data)
print(user.dict())  # dict() 函数将对象转化成字典

BaseModel 类,就是通过自定义的metaclass创建,相关源码

代码语言:javascript
复制
class BaseModel(Representation, metaclass=ModelMetaclass):
    if TYPE_CHECKING:
        # populated by the metaclass, defined here to help IDEs only
        __fields__: Dict[str, ModelField] = {}
        __validators__: Dict[str, AnyCallable] = {}
        __pre_root_validators__: List[AnyCallable]
        __post_root_validators__: List[Tuple[bool, AnyCallable]]
        __config__: Type[BaseConfig] = BaseConfig
        __root__: Any = None
        __json_encoder__: Callable[[Any], Any] = lambda x: x
        __schema_cache__: 'DictAny' = {}
        __custom_root_type__: bool = False
        __signature__: 'Signature'
        __private_attributes__: Dict[str, Any]
        __class_vars__: SetStr
        __fields_set__: SetStr = set()

    Config = BaseConfig
    __slots__ = ('__dict__', '__fields_set__')
    __doc__ = ''  # Null out the Representation docstring

    def __init__(__pydantic_self__, **data: Any) -> None:
        """
        Create a new model by parsing and validating input data from keyword arguments.

        Raises ValidationError if the input data cannot be parsed to form a valid model.
        """
        # Uses something other than `self` the first arg to allow "self" as a settable attribute
        values, fields_set, validation_error = validate_model(__pydantic_self__.__class__, data)
        if validation_error:
            raise validation_error
        try:
            object_setattr(__pydantic_self__, '__dict__', values)
        except TypeError as e:
            raise TypeError(
                'Model values must be a dict; you may not have returned a dictionary from a root validator'
            ) from e
        object_setattr(__pydantic_self__, '__fields_set__', fields_set)
        __pydantic_self__._init_private_attributes()

ModelMetaclass 元类相关源码

代码语言:javascript
复制
class ModelMetaclass(ABCMeta):
    @no_type_check  # noqa C901
    def __new__(mcs, name, bases, namespace, **kwargs):  # noqa C901
        fields: Dict[str, ModelField] = {}
        config = BaseConfig
        validators: 'ValidatorListDict' = {}

class ABCMeta(type):
    """Metaclass for defining Abstract Base Classes (ABCs).

    Use this metaclass to create an ABC.  An ABC can be subclassed
    directly, and then acts as a mix-in class.  You can also register
    unrelated concrete classes (even built-in classes) and unrelated
    ABCs as 'virtual subclasses' -- these and their descendants will
    be considered subclasses of the registering ABC by the built-in
    issubclass() function, but the registering ABC won't show up in
    their MRO (Method Resolution Order) nor will method
    implementations defined by the registering ABC be callable (not
    even via super()).
    """
    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        _abc_init(cls)
        return cls

    def register(cls, subclass):
        """Register a virtual subclass of an ABC.

        Returns the subclass, to allow usage as a class decorator.
        """
        return _abc_register(cls, subclass)

元类用来创建 API 是非常好的方法,使用元类的编写对于 python 开发者来说很复杂, 但对于使用者可以非常简洁的调用 API。 学到这大概明白了,元类是给真正的python 开发者使用的(并不是会写个print 就是python开发者,这里对开发者的定义是能开发框架的开发者),而不是给 python 使用者用(python 使用者是会调用第三方库的人员,无框架开发能力。)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-05-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 从零开始学自动化测试 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 类与实例
  • type 动态创建类
  • 自定义元类(metaclass)
  • 掌握 __init__ 和 __new__
  • 元类(metaclass) 实例—-yaml.YAMLObject
  • pydantic 也用到了metaclass
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档