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

目录:

  • 每篇前言:
  • 0. Python中的元类:
  • 1. 本文引子:
  • 2. Python中的mro机制:
  • 3. Python中类的魔法属性dict:
      • 注意事项:
    • 拓展——内建函数dir()
  • 4. 正式谈一谈元类(metaclass):
      • (1)引子:
      • (2)类由自定义type创建:
      • (3)类的基类中指定了metaclass,那么当前类也是由metaclass指定的类来创建的:
      • (4)实例化一个指定了metaclass的类:

每篇前言:

  • 🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者

  • 🔥🔥本文已收录于Flask框架从入门到实战专栏:《Flask框架从入门到实战》
  • 🔥🔥热门专栏推荐:《Python全栈系列教程》、《爬虫从入门到精通系列教程》、《爬虫进阶+实战系列教程》、《Scrapy框架从入门到实战》、《Flask框架从入门到实战》、《Django框架从入门到实战》、《Tornado框架从入门到实战》、《前端系列教程》。
  • 📝​📝本专栏面向广大程序猿,为的是大家都做到Python全栈技术从入门到精通,穿插有很多实战优化点。
  • 🎉🎉订阅专栏后可私聊进一千多人Python全栈交流群(手把手教学,问题解答); 进群可领取Python全栈教程视频 + 多得数不过来的计算机书籍:基础、Web、爬虫、数据分析、可视化、机器学习、深度学习、人工智能、算法、面试题等。
  • 🚀🚀加入我一起学习进步,一个人可以走的很快,一群人才能走的更远!

在这里插入图片描述

再来讲一下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)

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

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

详细解释一下:

  1. 调用 MyType 的 __call__ 方法:

    class MyType(type):
    
        def __call__(self, *args, **kwargs):
            pass
    

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

  2. 调用 Foo 的 __new__ 方法:

    class Foo(object, metaclass=MyType):
        
        def __init__(self):
            pass
    
        def __new__(cls, *args, **kwargs):
            pass
    

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

  3. 调用 Foo 的 __init__ 方法:

    obj = Foo()
    

    最后,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__)

在这里插入图片描述

以下是类 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()

在这里插入图片描述

在这个例子中,类 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])

应用合并规则:

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

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

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

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

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

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

    所以最终的MRO列表就是:

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

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

  • 对于类而言,Python 提供了 __dict__ 魔法属性,它是一个包含类的命名空间的字典。这个字典包含了类的所有属性和方法
class MyClass:
    x = 10

    def __init__(self, y):
        self.y = y


print(MyClass.__dict__)

在这里插入图片描述

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

在这里插入图片描述

注意事项:

  • 在一些特殊情况下,__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))
    
    

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

    print(dir())
    
    

    在这个例子中,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))
    
    

    在这个例子中,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})

很容易知道:下述两种其实一模一样,因为默认就是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
    

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

class MyType(type):
    pass


class Foo(object, metaclass=MyType):
    CITY = 'zz'

    def func(self, x):
        return x + 1

引子部分实现类的两种方法中的第二种告诉我们类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

上述代码跟引子中第一种创建类的方法一样!同时上述代码运行已经可以打印代码中两个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

这样会打印两遍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

上部分代码还是和上一次讲的一样,基类中指定了自定义的元类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

上述代码还是一个意思~

将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

总结:

  • 如果某个类指定了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. MyType的__init__
    obj = Foo()
  2. MyType的__call__
  3. Foo的__new__
  4. Foo的__init__

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/575904.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IIR滤波器的设计与实现(内含设计IIR滤波器的高效方法)

写在前面:初学者学习这部分内容,要直接上手写代码可能会感到比较困难,我这里推荐一种高效快速的设计IIR,FIR滤波器的方法——MATLAB工具箱:filterDesigner。打开的方法很简单,就是在命令行键入:filterDesig…

virtio-wayland

CrosVM是Chrome操作系统中,用于创建虚拟机的应用。是一个Rust编写的轻量级的虚拟机。借助于CrosVM 用户可以很容易的在ChromeOS中运行Linux、Android以及Windows应用程序 概述 目前crosvm实现了virtio wayland协议,实现了对linux虚拟机wayland协议支持 …

动态规划——斐波那契数列模型:面试题08.01.三步问题

文章目录 题目描述算法原理1.状态表示2.状态转移方程3.初始化4.填表顺序5.返回值 代码实现CJava 题目描述 题目链接:面试题08.01.三步问题 如果n是0走法可能是1也可能是0,所以本题范围并不需要考虑直接从1开始即可 因为以3为结尾有直接从0到3的方式&a…

Kafka 3.x.x 入门到精通(04)——对标尚硅谷Kafka教程

Kafka 3.x.x 入门到精通(04)——对标尚硅谷Kafka教程 2. Kafka基础2.1 集群部署2.2 集群启动2.3 创建主题2.4 生产消息2.5 存储消息2.5.1 存储组件2.5.2 数据存储2.5.2.1 ACKS校验2.5.2.2 内部主题校验2.5.2.3 ACKS应答及副本数量关系校验2.5.2.4 日志文…

从哪些角度优化数据资产管理?详解如何将数据转化为企业持续竞争力

在上一篇文章中我们介绍了数据资产管理的诸多保障措施,上篇文章指路👉如何保障数据资产管理有效开展?做好这几点就够了! 本文重点将转向数据资产管理的实践。在当今这个数据驱动的时代,数据已成为企业最宝贵的资产之一…

使用Excel生成sql脚本(insert/update/delete)

目录 前言 一、Excel文件脚本变量 二、操作示例 前言 在系统使用初期,存在某种原因,需要对数据库数据进行批量处理操作。往往都是通过制定Excel表格,通过Excel导入到数据库中,所以就弄一个excel生成sql的导入脚本,希…

呼叫中心常用名词解释

ACD Automatic Call Distribution 自动呼叫分配,即排队。一般是用于呼叫中心的功能。在一个呼叫中心中,会有很多的座席来应答用户的来话,但是每个座席所具有的技能或者所承担的工作负荷是 不同的,如何根据一定的算法来保证所有的座…

C语言中浮点型存储方式

前言 这次是上次博客的续写哦,如果有小伙伴不了解,可以点击链接跳转 C语言中整数与浮点数在内存中的存储 我们在上次的博客中给大家留了一段代码,不知道大家现在有没有想明白呢,让我来为大家揭秘吧!! int m…

Azure AKS集群监控告警表达式配置

背景需求 Azure AKS集群中,需要对部署的服务进行监控和告警,需要创建并启用预警规则,而这里怎么去监控每个pod级别的CPU和内存,需要自己写搜索查询 解决方法 搜索和查询的语句如下,需要自己替换其中的部分信息,其中…

python爬虫 - 爬取html中的script数据(36kr.com新闻信息)

文章目录 1. 分析页面内容数据格式2. 使用re.findall方法,爬取新闻3. 使用re.search 方法,爬取新闻 1. 分析页面内容数据格式 打开 https://36kr.com/ 按F12(或 在网页上右键 --> 检查(Inspect)) 找…

开箱机选型攻略:如何挑选适合你的自动化设备?

在如今快节奏的生产环境中,自动化设备的运用已成为企业提升效率、降低成本的关键。开箱机作为自动化生产线上的重要一环,其选型对于企业来说至关重要。星派将为您提供一份开箱机选型攻略,帮助您挑选出最适合自己的自动化设备。 一、了解开箱…

18 JavaScript学习:错误

JavaScript错误 JavaScript错误通常指的是在编写JavaScript代码时发生的错误。这些错误可能是语法错误、运行时错误或逻辑错误。以下是对这些错误的一些常见分类和解释: 语法错误: 这类错误发生在代码编写阶段,通常是由于代码不符合JavaScrip…

Transformer模型详解01-Word Embedding

文章目录 前言Transformer 整体结构Transformer 的输入单词 Embedding原理CBOW 模型one-hot构建 CBOW 训练数据集构建 CBOW 神经网络训练 CBOW 神经网络 Skip-gram 模型one-hot构建 Skip-gram训练数据集训练 Skip-gram神经网络 Word2Vec实例数据训练保存和加载 前言 Transform…

JavaScript-Vue入门

本文主要测分享Vue的一些基础 Vue简介 Vue.js 是一个构建数据驱动的 web 界面的渐进式框架。它的主要目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。 下是一些 Vue 的主要特点和概念: 1. 响应式数据绑定:Vue 使用基于 HTML 的模板语法…

文本高效拆分内容,根据空行高效拆分文本内容,文本文档管理更轻松

文本文档是我们日常生活和工作中不可或缺的一部分。然而,随着文本内容的不断增加,如何高效、有序地管理这些文档成为了一个挑战。传统的文本编辑工具往往无法满足我们对于文档整理的需求,而手动整理又费时费力。现在,我们为您带来…

【智能算法】蜉蝣算法(MA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2020年,K Zervoudakis等人受到自然界蜉蝣交配繁殖行为启发,提出了蜉蝣算法(Mayfly Algorithm, MA)。 2.算法原理 2.1算法思想 MA灵感来自蜉蝣交配…

“天才程序员”拼起命来有多狠?

本周 第四届ATEC科技精英赛(ATEC2023)线下赛——“燃烧吧!天才程序员” 在杭州蚂蚁A空间落幕了 这个比赛同时挑战16名选手脑力和体力的上限 连续三天三夜独立答题,末尾淘汰、组团PK,吃住赛场,每天仅睡4…

超实用的电脑桌面便签+待办清单app

对于上班族来说,桌面便签加待办清单软件是提升工作效率的得力助手。想象一下,在繁忙的工作中,你能够快速记录重要事项,设置待办任务提醒,一切都能有条不紊地进行。这种便捷性,尤其在处理多项任务和紧急事务…

VMware17Pro虚拟机安装macOS教程(超详细)

目录 1. 前言2. 下载所需文件3. 安装VMware3.1 安装3.2 启动并查看版本信息3.3 虚拟机默认位置配置 4. 安装补丁4.1 解压补丁4.2 结束VMware相关进程4.3 运行补丁包 5. 安装macOS5.1 新建虚拟机5.2 修改虚拟机配置5.3 安装操作系统5.3.1 选择 ISO 映像文件5.3.2 开启虚拟机5.2.…

Windows上在DLL中嵌入自定义/XML文件

Windows上在DLL中嵌入自定义文件(如:xml文件) 1、前言 最近都在开发适配Genicam项目,在开发CTI(Windows上可以看作DLL)时发现需要将多个XML文件嵌入到DLL文件中方便内部代码调用。 2、前期准备 一个xml…