【小记一下】Python中的元类和__new__、__init__、__call__方法

本文介绍了Python中的元类及其与__new__、__init__、__call__方法的关系。元类用于创建类,__new__在实例化前调用,用于定制类的属性,__init__在实例化后调用,常用于初始化对象,而__call__使类实例可被调用。通过示例展示了如何在元类中使用这些方法来控制类的行为和实例化过程。
摘要由CSDN通过智能技术生成

Python中的元类和__new____init____call__方法

在Python中,元类是用于创建类的类。它允许我在定义类时自定义类的行为。元类是高级主题,但理解它们对于深入理解Python的面向对象编程是非常重要的。在本笔记中,我将重点讨论元类以及与元类相关的__new____init____call__方法。

元类(Metaclass)

元类是用于创建类的“类”。每当我定义一个类时,我实际上是使用元类创建一个新的类对象。Python中的所有类都是元类的实例。默认情况下,Python中的元类是type类,它是所有类的默认元类。

我可以通过定义自己的元类来自定义类的创建过程。为了创建一个自定义元类,我需要定义一个类并将其作为其他类的元类。我可以通过在类定义中使用metaclass参数来指定使用的元类。

1.__new__方法

__new__方法是在一个类实例化之前被调用的特殊方法。它的主要目的是创建并返回一个新的实例。__new__方法接受的参数有:元类本身、类名、基类、类的属性字典。我可以在__new__方法中修改类的属性字典,并返回修改后的实例。

在示例代码中的ModelMeta类中,我重写了__new__方法。它根据model_name属性修改了派生类的model_name,并记录了子类的信息。

__new__方法示例

class ModelMeta(type):
    def __new__(mcs, name, bases, attrs):
        # 在创建类之前,自定义类的属性
        attrs['model_name'] = name
        attrs["_sub_models"] = []

        # 创建类对象
        model_class = super().__new__(mcs, name, bases, attrs)

        # 根据 Meta 类中的 model_name 信息修改派生类的 model_name
        if meta := attrs.get('Meta'):
            if hasattr(meta, 'model_name'):
                model_class.model_name = meta.model_name

        # 记录子类
        for base in bases:
            base._sub_models.append({model_class.model_name: model_class})

        return model_class


class Model(metaclass=ModelMeta):
    def save(self):
        pass


class ModelA(Model):
    class Meta:
        model_name = 'ModelA_Updated'


class ModelB(Model):
    pass
    
    
class ModelC(ModelA):
    pass



print(ModelA.model_name)  # 输出:ModelA_Updated
print(ModelB.model_name)  # 输出:ModelB
print(Model._sub_models)  # 输出:[{'ModelA_Updated': <class '__main__.ModelA'>}, {'ModelB': <class '__main__.ModelB'>}, {'ModelC': <class '__main__.ModelC'>}]
print(ModelA._sub_models)  # 输出:[{'ModelC': <class '__main__.ModelC'>}]
print(ModelC._sub_models)  # 输出:[]

在上面的示例代码中,我定义了一个元类ModelMeta,它继承自type类。元类的作用是创建类的类,它控制类对象的创建过程。

__new__方法

在元类中,__new__方法是在创建类对象之前被调用的特殊方法。它接收的参数有:元类本身、类名、基类、类的属性字典。__new__方法的主要目的是创建并返回一个新的类对象。

在示例中的__new__方法中,我做了以下几件事情:

  1. 将类名赋值给类属性model_name,以便在类实例化后可以访问该属性。
  2. 创建一个空列表_sub_models,用于记录派生类的子类。
  3. 调用super().__new__()方法创建类对象。
修改派生类的model_name

接下来,我根据派生类的Meta类中的model_name属性修改派生类的model_name

在示例中,ModelA类内部定义了一个Meta类,并设置了model_name属性为'ModelA_Updated'。在元类的__new__方法中,我检查派生类的属性字典中是否存在Meta类,并且Meta类是否定义了model_name属性。如果满足条件,我将修改派生类的model_nameMeta类中指定的值。

记录子类信息

元类还负责记录派生类的子类信息。在示例中,我在元类的__new__方法中迭代基类,并将每个基类的model_name和对应的类对象添加到基类的_sub_models列表中。

这样,通过访问基类的_sub_models属性,我可以获取到基类的所有子类和它们的model_name

2.__init__方法

__init__方法是在类实例化之后被调用的特殊方法。它用于初始化新创建的对象。在元类中,__init__方法通常不用于修改类的属性字典,因为在__new__方法中已经完成了这个任务。但是,我可以在__init__方法中执行其他初始化操作,例如设置一些默认值或验证属性。

__new__方法与__init__方法的对比

在元类中,__new__方法和__init__方法分别用于控制类对象的创建和初始化过程。下面是一个示例代码,演示了如何重载元类中的__new__方法和__init__方法:

class MetaClass(type):
    def __new__(mcs, name, bases, attrs):
        print("Creating the class")
        attrs['custom_attr'] = "Custom attribute"
        cls = super().__new__(mcs, name, bases, attrs)
        return cls

    def __init__(cls, name, bases, attrs):
        print("Initializing the class")
        super().__init__(name, bases, attrs)

class MyClass(metaclass=MetaClass):
    pass

obj = MyClass()
print(obj.custom_attr)

在上面的示例代码中,我定义了一个元类MetaClass,并重载了其中的__new__方法和__init__方法。

  • __new__方法在创建类对象时被调用。在示例中,我在__new__方法中打印了一条消息,并向类的属性字典中添加了一个自定义属性custom_attr
  • __init__方法在初始化类对象时被调用。在示例中,我在__init__方法中打印了一条消息。

我还定义了一个使用元类MetaClass的类MyClass。该类没有显式定义任何属性或方法。

接下来,我创建了MyClass的实例,并观察输出结果:

obj = MyClass()
print(obj.custom_attr)

输出结果:

Creating the class
Initializing the class
Custom attribute

从输出结果可以看出,在创建类对象时,首先调用了元类MetaClass中的__new__方法,然后再调用了元类MetaClass中的__init__方法。在__new__方法中,我可以对类的属性进行自定义处理,而在__init__方法中,我可以执行其他的初始化操作。

小结一手,__new__方法的作用主要是创建实例,而__init__方法的作用主要是初始化实例。__new__方法在对象创建的早期被调用,而__init__方法在对象创建之后被调用。

3.__call__方法

__call__方法使得一个类的实例可以像函数一样被调用。当我调用一个类的实例时,实际上是调用了它的__call__方法。也就是说,我可以在元类中重写__call__方法,以控制在调用类的实例时发生的操作。

__call__方法示例

当我重载元类中的__call__方法时,我可以自定义在使用类作为可调用对象时发生的操作。下面是一个示例代码,演示了如何重载元类中的__call__方法:

class MetaClass(type):
    def __call__(cls, *args, **kwargs):
        print("Calling the class")
        instance = super().__call__(*args, **kwargs)
        return instance

class MyClass(metaclass=MetaClass):
    def __init__(self, *args, **kwargs):
        print("Initializing an instance")
        self.args = args
        self.kwargs = kwargs

obj = MyClass(1, 2, key='value')

在上面的示例代码中,我定义了一个元类MetaClass,并重载了其中的__call__方法。

  • __call__方法在创建类的实例时被调用。在示例中,我在__call__方法中打印了一条消息,并使用super().__call__()方法创建了实例,然后将其返回。

我还定义了一个使用元类MetaClass的类MyClass。在MyClass__init__方法中,我在实例化时打印了一条消息,并将传入的参数存储在实例的属性中。

接下来,我创建了MyClass的实例,并观察输出结果:

obj = MyClass(1, 2, key='value')

输出结果:

Calling the class
Initializing an instance

从输出结果可以看出,当我创建MyClass的实例时,首先调用了元类MetaClass中的__call__方法,然后再调用了类MyClass__init__方法。

小结一手,通过重载元类中的__call__方法,我可以自定义在使用类作为可调用对象时发生的操作。这样,我可以在实例化之前或之后执行特定的逻辑,实现更高级的自定义行为。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顾平安6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值