• (二十九)加油站:面向对象重难点深入讲解【重点是元类】


    每篇前言:


    在这里插入图片描述

    再来讲一下python中的元类是个啥【虽然我在《Python全栈系列教程》中已经讲解的非常透彻了,但是多来一遍多加深一遍印象】?

    【本文内容在《Python全栈系列教程》中都十分细致地讲解过,本文的作用是复习,因为下一篇文章剖析wtforms源码需要这些知识点~】

    0. Python中的元类:

    在 Python 中,元类(metaclass)是用于创建类的类。你可以将元类视为类的 “类生成器”。通常情况下,我们定义类来创建对象,而元类用于定义类本身的行为。

    每个类都是一个对象,而这个类的创建过程也是由一个元类控制的。在 Python 中,大多数类都是通过元类 type 创建的,包括内置的类和用户定义的类。

    以下是一些关键概念:

    1. 类是对象: 在 Python 中,类本身也是对象。类对象用于创建类的实例对象。

    2. 元类是类的类: 元类是用于创建类的类。类定义了对象的行为,而元类定义了类的行为。

    3. type 是默认元类: 在 Python 中,如果没有显式指定元类,那么默认的元类是 typetype 实际上是一个元类,也是一个类。当你定义一个类时,Python 使用 type 来创建这个类的实例。

    4. 自定义元类: 你可以创建自定义的元类来控制类的创建过程。自定义元类可以继承自 type,并覆盖其方法,例如 __new____init__

    5. __metaclass__ 属性: 你可以在类中使用 __metaclass__ 属性来指定使用的元类。如果没有指定,Python 将使用模块中的 __metaclass__ 属性,如果仍未找到,则将使用 type 作为默认元类。

    6. 元类的作用: 元类的主要作用是允许你在创建类的时候定制类的行为。这使得元类成为一种高级的编程工具,通常在框架和库的开发中使用。

    快速上手——如何使用元类:

    class MyMeta(type):
        def __new__(cls, name, bases, attrs):
            # 在创建类的时候添加一个新的属性
            attrs['custom_attribute'] = 'This is a custom attribute'
            return super().__new__(cls, name, bases, attrs)
    
    
    class MyClass(metaclass=MyMeta):
        pass
    
    
    # 创建 MyClass 的实例
    obj = MyClass()
    
    # 访问自定义属性
    print(obj.custom_attribute)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    MyMeta 是一个简单的元类,通过继承 type 并覆盖 __new__ 方法,在创建类的时候添加了一个新的属性。这个新属性将出现在 MyClass 类的所有实例中。


    1. 本文引子:

    请大家认真分析一下下述代码的执行顺序(先不要看我的分析)

    class MyType(type):
    
        def __call__(self, *args, **kwargs):
            pass
    
    
    class Foo(object, metaclass=MyType):
        
        def __init__(self):
            pass
    
        def __new__(cls, *args, **kwargs):
            pass
    
    
    obj = Foo()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在上述代码中,由于我定义了自定义元类 MyType,并在类 Foo 中使用了这个元类,所以调用顺序将是 MyType__call__ 方法、Foo__new__ 方法,然后是 Foo__init__ 方法。

    详细解释一下:

    1. 调用 MyType 的 __call__ 方法:

      class MyType(type):
      
          def __call__(self, *args, **kwargs):
              pass
      
      • 1
      • 2
      • 3
      • 4

      当实例化 Foo 类时,由于使用了元类 MyType,所以MyType__call__ 方法被调用。

    2. 调用 Foo 的 __new__ 方法:

      class Foo(object, metaclass=MyType):
          
          def __init__(self):
              pass
      
          def __new__(cls, *args, **kwargs):
              pass
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      __call__ 方法调用之后,Foo 类的 __new__ 方法被调用。这个方法在对象实例化时被调用,通常用于创建并返回实例(当然我这里重在讲执行顺序,所以直接pass了)。

    3. 调用 Foo 的 __init__ 方法:

      obj = Foo()
      
      • 1

      最后,Foo 类的 __init__ 方法被调用。这个方法在对象实例创建后被调用,通常用于执行初始化操作。

    综合起来,执行顺序是 MyType.__call__ -> Foo.__new__ -> Foo.__init__


    2. Python中的mro机制:

    mro(Method Resolution Order【方法解析顺序】)是 Python 中的一个机制,用于确定类继承关系中方法的调用顺序。在多继承的情况下,mro 决定了方法的查找顺序,以确保在类层次结构中的不同类中定义的方法能够正确被调用。

    Python 使用 C3 线性化算法来计算 mromro 的计算规则如下:

    1. 深度优先: 在同一层级的继承关系中,首先会深入到下一级的类,而不是横向移动到同级的另一个类。

    2. 左右优先: 在多继承中,首先考虑继承列表中的左侧类,然后再考虑右侧的类。

    3. 子类优先: 在搜索过程中,子类的 mro 会优先于父类的 mro

    简单的例子1:

    class A(object):
        pass
    
    
    class B(A):
        pass
    
    
    class C(object):
        pass
    
    
    class D(B, C):
        pass
    
    
    print(D.__mro__)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    以下是类 D 的 MRO 的详细解释:

    1. D
    2. B
    3. A
    4. C
    5. object

    这意味着当我们尝试在类 D 的实例上访问属性或方法时,Python 首先会查找 D,然后是 B,接着是 A,然后是 C,最后是通用的基类 object,如果在前面的类中找不到所需的属性或方法的话。

    通过使用 __mro__ 属性,可以以编程方式访问 MRO,就像使用 print(D.__mro__) 打印出来的顺序一样。这提供了一种透明的方式,让我们了解 Python 在多继承的类层次结构中查找属性和方法的顺序。


    简单例子2:

    class A:
        def method(self):
            print("A method")
    
    
    class B(A):
        def method(self):
            print("B method")
            super().method()
    
    
    class C(A):
        def method(self):
            print("C method")
            super().method()
    
    
    class D(B, C):
        pass
    
    
    obj = D()
    obj.method()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    在这个例子中,类 D 继承自 BC,它们都是直接或间接继承自 A。当调用 obj.method() 时,Dmro 决定了方法调用的顺序。mro 的计算顺序是 [D, B, C, A]。因此,调用 obj.method() 时的实际调用顺序是 D.method() -> B.method() -> C.method() -> A.method()

    这样的 mro 计算确保了在多继承的情况下,方法的查找顺序是有序的,且保持了继承关系的结构。

    易错点强调一下:

    可能会有人上来就说这个例子的mro顺序应该是[D, B, A, C],所以我来反驳一下:

    要计算D的MRO,可以用如下的merge规则:

    D = [D] + merge(MRO(B), MRO(C), BC)
    D = [D] + merge([B, A, object], [C, A, object], [B, C])
    
    • 1
    • 2

    应用合并规则:

    • B出现在列表的第一个位置,因此B是下一个在MRO中的类;

    • 接下来,从其余的列表中删除B,并比较接下来的元素;

    • C现在出现在没有B作为首位的列表的第一个位置,所以C是下一个;

    • 然后同样的规则,删除C,比较剩下的元素;

    • 接下来A没有争议地第一;

    • 最后加入object(所有Python类最基本的类型,它们始终位于MRO列表的末尾)。

      所以最终的MRO列表就是:

      D = [D, B, C, A, object]
      
      • 1

    3. Python中类的魔法属性dict:

    • 对于类而言,Python 提供了 __dict__ 魔法属性,它是一个包含类的命名空间的字典。这个字典包含了类的所有属性和方法
    class MyClass:
        x = 10
    
        def __init__(self, y):
            self.y = y
    
    
    print(MyClass.__dict__)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    • 对于类的实例,__dict__ 包含了实例的属性。
    obj = MyClass(5)
    print(obj.__dict__)
    
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    注意事项:

    • 在一些特殊情况下,__dict__ 中可能会包含一些额外的属性,例如 __weakref__ 用于支持弱引用。
    • 对于内建类型(如 intstr 等),__dict__ 是不存在的。

    总的来说,__dict__ 是一个强大的工具,可以让我们动态地查看和修改类和实例的属性。然而,直接使用 __dict__ 有时不是最佳的做法,因为它绕过了类或实例中可能存在的一些定制行为,推荐使用更高层级的接口和方法【比如内建函数getattr()和setattr(),hasattr()等】

    拓展——内建函数dir()

    类的dict魔法属性得到的最终结果是key: value形式的字典格式;而dir()方法则只拿到key:

    • 对于类的实例,内建函数dir()作用如下:

      dir() 内建函数,用于获取对象的属性和方法列表。当调用 dir() 时,它返回一个包含对象所有属性和方法名称的列表。这包括对象的普通属性、方法、类属性、类方法等。如果没有提供参数,dir() 将返回当前作用域内的所有变量和函数的列表。

      class MyClass:
          x = 10
      
          def __init__(self, y):
              self.y = y
      
          def method(self):
              pass
      
      
      obj = MyClass(20)
      print(dir(obj))
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      在上面的例子中,dir(obj) 会返回一个包含 MyClass 类实例 obj 的所有属性和方法的列表。

      print(dir())
      
      
      • 1
      • 2

      在这个例子中,dir() 没有提供参数,因此它返回当前作用域内的所有变量和函数的列表。

      dir() 的输出结果包括以下类型的条目:

      • 字符串:表示对象的普通属性或方法。
      • __xxx__ 形式的字符串:表示对象的特殊方法(魔法方法)。
      • 类属性和类方法的名称。
    • 对于类来说,内建函数dir()的作用如下:

      如果传入 dir() 的参数是一个类,它会返回类的所有属性、方法以及基类的信息。下面是一个示例:

      class MyClass:
          x = 10
      
          def __init__(self, y):
              self.y = y
      
          def method(self):
              pass
      
      
      class DerivedClass(MyClass):
          z = 20
      
          def __init__(self, y, w):
              super().__init__(y)
              self.w = w
      
          def derived_method(self):
              pass
      
      
      # 使用 dir() 获取类的信息
      print(dir(MyClass))
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

      在这个例子中,dir(MyClass) 返回一个包含 MyClass 类的所有属性和方法的列表。这包括类属性 x、构造函数 __init__、普通方法 method 等。

      注意:

      • dir() 返回的列表包含了类的所有属性和方法,包括继承自基类的。
      • 如果子类有自己的属性或方法,也会包含在列表中。
      • 特殊方法(魔法方法)以 __xxx__ 形式显示在列表中。
      • 类的基类信息也会包括在列表中。

      这样,dir() 提供了一种查看类的结构和功能的方式,使你能够快速了解类的成员。


    4. 正式谈一谈元类(metaclass):

    (1)引子:

    创建类的两种方法:

    # 方法一:
    class Foo(object):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
    
    
    # 方法二【其实第一种方法底层也是通过type来实现创建类的~】:
    def func(self, x):
        return x + 1
    
    
    Foo = type('Foo', (object,), {'CITY': 'zz', 'func': func})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    很容易知道:下述两种其实一模一样,因为默认就是metaclass=type,所以加不加都一样~

    class Foo(object):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
    
    
    class Foo(object, metaclass=type):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (2)类由自定义type创建:

    class MyType(type):
        pass
    
    
    class Foo(object, metaclass=MyType):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    引子部分实现类的两种方法中的第二种告诉我们类Foo就是type加括号创建的。所以上部分代码中Foo类就是MyType加括号创建的(这样就会执行MyType类的构造方法init)。

    class MyType(type):
        def __init__(self, *args, **kwargs):
            print('创建类之前')
            super(MyType, self).__init__(*args, **kwargs)
            print('创建类之后')
    
    
    class Foo(object, metaclass=MyType):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上述代码跟引子中第一种创建类的方法一样!同时上述代码运行已经可以打印代码中两个print,因为类已经创建了!!!

    在这里插入图片描述

    (3)类的基类中指定了metaclass,那么当前类也是由metaclass指定的类来创建的:

    class MyType(type):
        def __init__(self, *args, **kwargs):
            print('创建类之前')
            super(MyType, self).__init__(*args, **kwargs)
            print('创建类之后')
    
    
    class Foo(object, metaclass=MyType):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
    
    
    class Bar(Foo):
        pass
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这样会打印两遍print,因为Bar没有指定metaclass,但是这个类也是由其继承的类的metaclass指定的type创建的,所以会打印两遍print。

    简单改动一下:

    class MyType(type):
        def __init__(self, *args, **kwargs):
            print('创建类之前')
            super(MyType, self).__init__(*args, **kwargs)
            print('创建类之后')
    
    
    Base = MyType('Base', (object, ), {})
    # 上一行等价于:
    # class Base(object, metaclass=MyType):
    #     pass
    
    
    class Foo(Base):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上部分代码还是和上一次讲的一样,基类中指定了自定义的元类MyType,所以打印两次~

    class MyType(type):
        def __init__(self, *args, **kwargs):
            print('创建类之前')
            super(MyType, self).__init__(*args, **kwargs)
            print('创建类之后')
    
    
    def with_metaclass():
        return MyType('Base', (object,), {})
    
    
    class Foo(with_metaclass()):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    上述代码还是一个意思~

    将object当参数传递给with_metaclass函数:

    class MyType(type):
        def __init__(self, *args, **kwargs):
            print('创建类之前')
            super(MyType, self).__init__(*args, **kwargs)
            print('创建类之后')
    
    
    def with_metaclass(arg):
        return MyType('Base', (arg,), {})
    
    
    class Foo(with_metaclass(object)):
        CITY = 'zz'
    
        def func(self, x):
            return x + 1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    总结:

    • 如果某个类指定了metaclass为MyType,那么当前类的所有派生类都是由这个MyType创建的!

    (4)实例化一个指定了metaclass的类:

    class MyType(type):
        def __init__(self, *args, **kwargs):
            super(MyType, self).__init__(*args, **kwargs)
    
    
    class Foo(object, metaclass=MyType):
        pass
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. MyType的__init__
      obj = Foo()
    2. MyType的__call__
    3. Foo的__new__
    4. Foo的__init__
  • 相关阅读:
    Linux入门基本操作3
    Java学习笔记——并发编程(三)
    19、Flink 的Table API 和 SQL 中的自定义函数及示例(2)
    登录注册实现
    [字符串和内存函数]strcpy和strncpy的区别
    kubeadm部署k8s教程(2)---部署
    重数和众数问题——C语言实现
    mysql 分库分表
    UniApp中的数据存储与获取指南
    MySQL 用户授权管理及白名单
  • 原文地址:https://blog.csdn.net/qq_44907926/article/details/138222972