python中的元类

对象的创建过程

在Python中,对象的创建过程是一个复杂而精细的机制。这个过程主要涉及类型定义、内存分配和初始化等关键步骤。理解这一过程对于深入掌握Python语言特性、优化代码性能以及进行高级编程技巧都有重要意义。

1. 类型定义

在Python中,一切皆对象,包括基本数据类型(如整数、字符串)、函数、模块等。每个对象都有一个类型来描述它是什么。类型不仅定义了对象可以进行哪些操作,还规定了对象占用的内存大小以及布局方式。

2. 内存分配

当你创建一个新的对象时(比如通过调用类),Python解释器首先会为该对象分配内存。这个过程通常由Python的内存管理器自动完成,它会从预先分配好的内存池中找到足够大的一块空间来存储新对象。

对象的内存分配是由Python的内存管理器(memory manager)处理的。内存管理器负责跟踪和分配内存,确保对象的连续存储,并在对象不再使用时进行垃圾回收。

  • 对象头(Object Header):每个Python对象都有一个对象头,它包含了对象的类型信息、引用计数等。
  • 对象体(Object Body):对象体包含了对象的数据,比如实例属性和其他存储的数据。

3. 对象初始化

内存分配后,接下来就是初始化阶段。对于大多数类型而言,这涉及到设置初始状态------即将特定值赋给实例变量或执行其他某些设置操作。

具体到类实例化过程中,则涉及以下几个关键步骤:

  • __new__ 方法: 这是一个静态方法(使用 @staticmethod 装饰),负责返回一个新建立的实例。它是在新实例创建之前被调用,并且需要返回该类(cls) 的一个实例。
  • __init__ 方法:__new__ 方法返回一个该类(cls) 的实例时候, __init__ 方法会被自动调用, 完成后续属性初始化或者其他必要操作。
  • 创建实例字典 :每个类的实例都有一个关联的字典,称为__dict__。这个字典存储了实例的所有属性和方法。my_instance的__dict__现在包含了一个键my_attribute,对应值是10
  • 实例方法绑定 :实例方法与实例绑定,这意味着方法在被调用时会自动接收实例作为第一个参数(通常命名为self)。
python 复制代码
class MyClass:
    def __new__(cls):
        print("Creating instance")
        instance = super(MyClass, cls).__new__(cls)
        return instance

    def __init__(self, value):
        self.my_attribute = value
        print("Initializing instance")
        
    def my_method(self):
        return self.my_attribute

# 一旦类定义完成,你就可以使用`class`关键字后跟类名和一对圆括号来创建类的实例。这个过程称为实例化。
# 在这个例子中,`MyClass`被实例化为`my_instance`,同时传递了参数`10`给构造方法`__init__`。
my_instance = MyClass(10)

# 当调用my_instance.my_method()时,self指向my_instance
result = my_instance.my_method()

输出:

Creating instance
Initializing instance

理解__new____init__

在Python中,__init____new__是两个与类的实例化过程密切相关的特殊方法。为了通俗理解这两个方法,我们可以将它们比作一家餐厅的运作。

__new__ 方法:开餐厅

想象一下,你想要开一家新的餐厅(创建一个类的新实例)。首先,你需要找到一个地方(创建一个空的容器),准备桌椅、厨房设备等基础设施。这个过程就像是__new__方法的作用:它负责创建一个对象的"空壳",也就是对象的基本结构,但不涉及具体的业务(属性和方法)。

在Python中,__new__方法通常用于定制对象的创建过程。当一个类被实例化时,__new__方法首先被调用,它返回一个未初始化的对象。__new__方法通常需要接收类本身(cls)作为参数,以及可能的其他参数,用于创建对象。

python 复制代码
class Restaurant:
    def __new__(cls, *args, **kwargs):
        # 创建对象的"空壳"
        restaurant = super(Restaurant, cls).__new__(cls)
        # 可以在这里添加创建对象时的额外步骤
        return restaurant

__init__ 方法:装修和开业

一旦餐厅的基础设施准备好了(__new__方法执行完毕),接下来就是装修和准备开业(__init__方法)。这个过程包括选择餐厅的主题、购买家具、雇佣员工等。在Python中,__init__方法在__new__方法之后被调用,用于初始化对象的状态,设置属性等。

python 复制代码
class Restaurant:
    def __init__(self, name, cuisine):
        # 装修和准备开业
        self.name = name
        self.cuisine = cuisine
        # 可以在这里添加更多初始化代码

当你调用Restaurant("Delicious Dumplings", "Chinese")时,Python首先通过__new__方法创建一个Restaurant类的实例,然后通过__init__方法对这个实例进行初始化,设置它的namecuisine属性。

元类(Metaclass)

在面向对象编程的世界里,对象的创建和管理是构建复杂系统的基础。当我们谈论对象时,我们通常指的是类的实例------那些根据类定义的蓝图构建的数据结构,它们拥有属性和可以执行的方法。然而,如果我们深入挖掘,就会发现类本身也是对象,它们是由一种特殊的对象------元类(Metaclass)------所创建和管理的。

元类是面向对象语言中的一个高级概念,它不仅是类的模板,更是类的构造者。在Python等动态语言中,元类扮演着至关重要的角色,它们定义了类的行为和创建过程。

在Python中,元类(Metaclass)是创建类的"类",它们定义了如何创建类和如何构造实例。简而言之,如果你把普通的类比作制造对象的模板,那么元类就是制造这些模板的模板。

所有的类都继承自object,而object是由type创建的。type是Python中所有新式的元类,它提供了类创建的标准行为。在Python 3中,所有的类都是新式的,而Python 2中的类默认是旧式的,但可以通过继承object来变成新式的。

type本身也是一个类,由type的元类创建。这种递归的特性使得Python中的一切都是对象,包括类本身

关于新式和旧式,下面再补充

元类工作原理

元类用于创建其他普通的用户定义的类。type 是Python内建的一个元类型,实际上就是大多数内置类型和用户自定义类型背后默认使用的元类型。

当我们使用 class 关键字时,Python解释器会利用 type 来创建这个新类型(即新建立一个class)。但如果想要自定义这个过程,则需要自己定义一个元类型。

它允许我们控制类的创建过程,并在运行时动态地修改类的行为。

type也是一个内置的工厂函数,可以用来创建新的类。例如前面反射一章讲到的用type动态创建类:

python 复制代码
MyClass = type('MyClass', (BaseClass,), {'attribute1': value1, 'method1': function1})

自定义元类型

自定义一个元类型主要涉及继承自 type 并重写其 __new__ 或者 __init__ 方法来控制如何创建或初始化新型态(即新建立一个class)。下面是一个简单示例:

python 复制代码
# 定义一个名为 Meta 的元素, 继承自 type
# 这是一个类的类。
class Meta(type):
    def __new__(cls, name, bases, dct):
        print('__new__方法,创建一个类!不是实例!类名:', name)
        # 在类定义中添加一个新方法
        dct['new_method'] = lambda self: 'Hello from metaclass'
        return super(Meta, cls).__new__(cls, name, bases, dct)

    def __init__(cls, name, bases, dct):
        # dct['new_method'] = lambda self: 'Hello from metaclass'
        print('__init__方法,创建一个类!不是实例!类名:', name)
        super().__init__(name, bases, dct)


# 使用 Meta 元素来定制 MyClass 类型
class MyClass(metaclass=Meta):
    pass


# 实例化 MyClass 将会触发 Meta 的 __new__
obj = MyClass()
print(obj.new_method())

输出将会是:

__new__方法,创建一个类!不是实例!类名: MyClass
__init__方法,创建一个类!不是实例!类名: MyClass
Hello from metaclass

在上面代码中:

  1. 我们首先声明了一个名为 Meta 的新型态,并且继承了内置型态 type.
  2. 然后重写了该型态(Meta) 的构造方法 (__new__)。
  3. 当我们声明另外一种名为 MyClass, 并指定其使用 metaclass=Meta, Python 将会在创建该MyClass时调用到由Meta提供的构造方法。
  4. 最终结果就是,在声明MyClass时,对应打印内容被打印出来。
问题~!

为什么dct['new_method'] = lambda self: 'Hello from metaclass'这行代码放在__init__方法中的话结果会报错

/usr/bin/python3 /Users/fangyirui/PycharmProjects/pythonProject/base/62 元类.py 
__new__方法,创建一个类!不是实例!类名: MyClass
__init__方法,创建一个类!不是实例!类名: MyClass
Traceback (most recent call last):
  File "/Users/fangyirui/PycharmProjects/pythonProject/base/62 元类.py", line 23, in <module>
    print(obj.new_method())
AttributeError: 'MyClass' object has no attribute 'new_method'

dct['new_method'] = lambda self: 'Hello from metaclass' 这行代码是用来向类动态添加一个新方法的。这个操作在 __new__ 方法中是有效的,因为 __new__ 是在类创建时被调用的方法,负责返回一个新的类实例(即新创建的类对象)。此时修改 dct(代表类属性和方法字典)可以直接影响到最终创建出来的类。

然而,将这行代码放入 __init__ 方法中不起作用,原因如下:

  1. 执行时机不同 :元类的 __init__ 方法是在类对象已经被创建之后调用的。此时对传入参数 dct 的修改并不会影响到已经完成构造过程的类对象。换句话说,在通过元类型(Meta)构造完一个新类型(MyClass)之后再去尝试添加或修改属性、方法等内容,并不能改变已经生成好了结构和内容定义的那个类型。

  2. 参数差异 :虽然既 __new____init__ 都接收到了名为 dct 的字典参数,但只有在 new () 中对 dct 执行操作才能影响到最终生成的类型结构。当执行到 init() 时候, 类型已经被成功创建, 此刻 dct 参数更多地扮演着提供信息参考角色,并不能通过修改它来改变现有类型结构。

简而言之,在元类中使用 new () 方法动态地添加或修改属性/方法等操作是因为这一步骤发生在实际类型对象被正式创建之前;而 init() 更多地用于初始化工作,此刻进行上述操作已无法影响到类型本身定义。

所以,"正确"的做法就是将对 dct 的操作放置于 new() 中以确保新增加方法能够成功应用于目标类型 MyClass 上。

元类的作用

元类可以用来控制类的创建过程。你可以使用元类来修改类的定义,或者在类创建时添加额外的行为。以下是一些元类可能的应用场景:

  1. 注册机制:自动将类注册到某个字典或列表中,以便于查找或管理。
  2. 单例模式:确保一个类只有一个实例。
  3. 属性和方法的添加:在不修改类定义的情况下,为其添加额外的属性或方法。
  4. 子类检查:检查一个类是否是另一个类的子类。
  5. 自定义类的行为:修改类的创建过程,以实现自定义的行为。

注意事项

  1. 复杂性:元类是Python中一个非常强大的特性,但同时也增加了代码的复杂性。大多数情况下,你不需要使用元类,除非有特殊的需求。
  2. 新式类和旧式类 :在Python 2中,需要显式地继承object来创建新式类。从Python 3开始,所有的类都是新式的,这意味着它们都隐式或显式地继承自object
  3. 元类的使用:元类通常用于框架和库的实现中,例如Django的ORM系统就使用了元类来动态创建模型类。

元类在Python中提供了一种强大的机制来自定义类的创建过程。

是深度 Python 编程和框架设计中非常强大工具,在标准库和第三方框架比如 Django ORM 中都有应用。然而对于日常编程任务而言,直接使用 metaclasses 是非常罕见且往往不必要的;只有当涉及到复杂库或框架设计时才需要考虑到metaclasses 。

虽然在日常编程中不常直接使用,但理解元类的概念对于深入理解Python的工作原理非常有帮助。

扩展:新式和旧式

在Python中,新式类(New-style classes)和旧式类(Old-style classes)是两种不同的类定义方式,它们主要区别在于继承体系和Python版本的历史发展。

新式类和旧式类的概念主要是针对Python 2版本而言的。在Python 3中,所有的类都是新式类,因此不存在旧式类。新式类提供了更多的特性和改进,使得类的定义和使用更加灵活和强大。随着Python 2的逐渐淘汰,新式类成为了Python类定义的标准方式。

旧式类(Old-style classes)

旧式类是在Python 2中引入的,它们默认继承自object类,这是所有新式类的基类。在Python 2中,如果你定义一个类没有明确指定基类,那么它就是旧式类。旧式类的特点是它们不支持某些新式类的特性,比如__slots__属性、特定的描述符协议等。

python 复制代码
# 这是一个旧式类
class OldStyleClass(object):
    pass

新式类(New-style classes)

新式类是从Python 2.2版本开始引入的,它们提供了更多的灵活性和一些新的特性。在Python 3中,所有的类都是新式类,没有旧式类的概念。新式类通过显式地继承自object类来定义,这是创建新式类的唯一方式。

python 复制代码
# 这是一个新式类
class NewStyleClass(object):
    pass

新式类的主要特点包括:

  1. 更灵活的属性管理 :新式类使用__slots__属性来定义类级别的属性,这可以节省内存并防止不必要的属性添加。
  2. 更好的描述符支持:新式类支持更多的描述符协议,这使得属性和方法的管理更加灵活。
  3. 统一的基类 :新式类提供了一个统一的基类object,这使得所有的类都有共同的祖先,简化了继承体系。

为什么使用新式类

在Python 3中,所有的类都是新式类,因此没有选择的余地。但在Python 2中,推荐使用新式类,因为它们提供了更多的特性和更好的性能。此外,新式类也更容易与Python 3的代码兼容。

相关推荐
岑梓铭5 分钟前
(CentOs系统虚拟机)Standalone模式下安装部署“基于Python编写”的Spark框架
linux·python·spark·centos
游客52019 分钟前
opencv中的各种滤波器简介
图像处理·人工智能·python·opencv·计算机视觉
Eric.Lee202122 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
Dontla27 分钟前
vscode怎么设置anaconda python解释器(anaconda解释器、vscode解释器)
ide·vscode·python
qq_529025291 小时前
Torch.gather
python·深度学习·机器学习
数据小爬虫@1 小时前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
Cachel wood2 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
終不似少年遊*2 小时前
pyecharts
python·信息可视化·数据分析·学习笔记·pyecharts·使用技巧
Python之栈2 小时前
【无标题】
数据库·python·mysql
袁袁袁袁满2 小时前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程