侧边栏壁纸
博主头像
顾小诺 博主等级

行动起来,活在当下

  • 累计撰写 30 篇文章
  • 累计创建 14 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

九、元类编程

顾小诺
2024-06-19 / 0 评论 / 0 点赞 / 15 阅读 / 0 字

九、元类编程

一、property动态属性

from datetime import date,datetime
class User:
    def __init__(self,name,birthday):
        self.name = name
        self.birthday = birthday
        self._age = None

    @property
    def age(self):
        if self._age is None:
            today = date.today()
            self._age = today.year - self.birthday.year
            if today < date(today.year,self.birthday.month,self.birthday.day):
                self._age -= 1
        return self._age

    #使用age.setter 还需要通过property 定义get属性
    @age.setter
    def age(self,value):
        if value<0:
            raise ValueError('年龄不能为负数')
        self._age = value


if __name__ == '__main__':
    user = User('张三', date(year=1993, month=1, day=1))
    print(user.age)


二、getattr、__getattribute__魔法函数

# __getattr__ 当查不到属性时,会调用__get_attr__方法
#__getattribute__ 无论查不查到属性,都会调用__get_attribute__方法
from datetime import date,datetime
class User:
    def __init__(self,name,birthday):
        self.name = name
        self.birthday = birthday

    def __getattr__(self, item):
        print("__getattr__")

    # 能不重写就不重写,风险高
    # def __getattribute__(self, item):
    #     print("__getattribute__")

if __name__ == '__main__':
    user = User('张三', date(year=1993, month=1, day=1))
    print(user.name)  #张三

    print(user.age) #__getattribute__   None

三、属性描述符和属性查找过程

在Python中,描述符是一种实现了特定协议(即__get__​、__set__​和__delete__​方法)的对象,用于管理对象属性的访问。描述符分为数据描述符和非数据描述符,它们之间的主要区别在于是否实现了__set__​或__delete__​方法。

  • 数据描述符(Data Descriptor) :实现了__get__​和__set__​方法,或者实现了__get__​和__delete__​方法。

  • 非数据描述符(Non-Data Descriptor) :仅实现了__get__​方法,没有实现__set__​或__delete__​方法。

    数据描述符示例

    class DataDescriptor:
        def __init__(self, name):
            self.name = name
    
        def __get__(self, instance, owner):
            print(f"Getting: {self.name}")
            return instance.__dict__.get(self.name)
    
        def __set__(self, instance, value):
            print(f"Setting: {self.name} to {value}")
            instance.__dict__[self.name] = value
    
        def __delete__(self, instance):
            print(f"Deleting: {self.name}")
            del instance.__dict__[self.name]
    
    class MyClass:
        attr = DataDescriptor('attr')
    
    # 使用数据描述符
    obj = MyClass()
    obj.attr = 42  # Setting: attr to 42
    print(obj.attr)  # Getting: attr \n 42
    del obj.attr  # Deleting: attr
    

    非数据描述符示例

    class NonDataDescriptor:
        def __init__(self, name):
            self.name = name
    
        def __get__(self, instance, owner):
            print(f"Getting: {self.name}")
            return instance.__dict__.get(self.name)
    
    class MyClass:
        attr = NonDataDescriptor('attr')
    
    # 使用非数据描述符
    obj = MyClass()
    obj.attr = 42  # 直接设置实例属性
    print(obj.attr)  # Getting: attr \n 42
    del obj.attr  # 直接删除实例属性
    

    区别

    1. 优先级不同:数据描述符的优先级高于实例字典中的同名属性。即使实例字典中有同名属性,访问时仍会调用数据描述符的__get__​方法。而非数据描述符的优先级低于实例字典中的同名属性,如果实例字典中有同名属性,则直接返回该属性的值,不调用非数据描述符的__get__​方法。
    2. 用途不同:数据描述符通常用于需要控制属性赋值和删除的场景,比如属性验证或类型检查。而非数据描述符通常用于只读属性或方法绑定。

示例:

import numbers

#属性描述符
class IntField():
    #数据描述符
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        if not isinstance(value, numbers.Integral):
            raise ValueError("int value need")
        self.value = value
    def __delete__(self, instance):
        pass

#非数据描述符
class NonDataIntField():
    def __get__(self, instance, owner):
        pass

class User:
    age = IntField()
'''
如果user是某个类的实例,那么user.age(以及等价的getattr(user,'age'))
首先调用getattribute_。如果类定义了getattr__方法,
那么在getattribute抛出AttributeError的时候就会调用到__getattr__
对于描述符(__get__)的调用,则是发生在_getattribute__内部的。
user = User(),那么user,age顺序如下:
(1)如果"age"是出现在User或其基类的__dict__中,且age是data descriptor,那么调用其__get__方法
(2)如果"age"出现在user(对象)的__dict__中,那么直接返回obj.__dict__['age'],否则
(3)如果“age”出现在User或其基类的__dict__中
(3.l)如果age是non-data descriptor,那么调用其__get__方法,否则
(3.2)返回 __dict__['age']
(4)如果User有__getattr__方法,调用__getattr__方法,否则
(5)抛出AttributeError
'''
if __name__ == "__main__":
    user = User()
    user.age = 30
    print(user.__dict__)   #{}
    print(user.age)         #30
    print(getattr(user, 'age')) #30
    print(getattr(user, 'age')) #30

四、__new__和__init__的区别

在Python中,__new__​和__init__​是两个用于对象创建和初始化的重要方法,它们有着不同的职责和用途。

__new__​方法

  • __new__​是一个静态方法,用于创建类的实例。它在类的实例创建之前被调用。
  • __new__​返回一个类的实例,通常是通过调用super().__new__(cls)​来实现。
  • 它是对象创建的第一步,负责分配内存空间。

__init__​方法

  • __init__​是一个实例方法,用于初始化类的实例。它在实例创建之后被调用。
  • __init__​不返回值,它主要用于设置实例的初始状态或属性。
  • 它是对象创建的第二步,负责初始化实例的属性。

区别

  1. 调用时机不同

    • __new__​在实例创建之前被调用。
    • __init__​在实例创建之后被调用。
  2. 职责不同

    • __new__​负责返回一个类的实例。
    • __init__​负责初始化实例的属性。
  3. 返回值不同

    • __new__​必须返回一个实例。
    • __init__​不返回值。

示例

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("Calling __new__")
        instance = super(MyClass, cls).__new__(cls)
        return instance

    def __init__(self, *args, **kwargs):
        print("Calling __init__")
        self.args = args
        self.kwargs = kwargs

# 创建实例
obj = MyClass(1, 2, 3, a=4, b=5)

在这个例子中,创建MyClass​的实例时,首先调用__new__​方法来创建实例,然后调用__init__​方法来初始化实例的属性。输出将是:

Calling __new__
Calling __init__

五、自定义元类

在Python中,元类(metaclass)是用于创建类的“类”。换句话说,元类是类的类,它决定了如何创建类以及类的行为。元类允许我们控制类的创建过程,可以用于自定义类的行为、属性和方法。

元类的作用

  1. 控制类的创建:元类可以拦截类的创建过程,并在类创建之前或之后进行自定义操作。
  2. 修改类的定义:元类可以动态地修改类的属性和方法。
  3. 实现单例模式:元类可以用于实现设计模式,如单例模式。

使用元类

要定义一个元类,可以继承type​类。type​是Python中所有类的元类。

示例

以下是一个简单的元类示例:

# 定义一个元类
class MyMeta(type):
    def __new__(cls, name, bases, dct):
        print(f"Creating class {name}")
        return super().__new__(cls, name, bases, dct)

# 使用元类创建一个类
class MyClass(metaclass=MyMeta):
    pass

# 创建实例
obj = MyClass()

输出将是:

Creating class MyClass

在这个示例中:

  • MyMeta​是一个元类,它继承自type​。
  • 当我们定义MyClass​时,元类MyMeta​的__new__​方法被调用。
  • __new__​方法打印一条消息,然后调用super().__new__​来创建类。

元类的常见用法

  1. 验证类属性:在创建类时,验证类的属性是否符合要求。
  2. 自动注册类:在创建类时,自动将类注册到某个注册表中。
  3. 单例模式:确保一个类只有一个实例。

复杂示例

下面是一个更复杂的示例,展示了如何使用元类来自动注册类:

# 定义一个注册表
class_registry = {}

# 定义一个元类
class RegisterMeta(type):
    def __new__(cls, name, bases, dct):
        new_class = super().__new__(cls, name, bases, dct)
        class_registry[name] = new_class
        return new_class

# 使用元类创建类
class BaseClass(metaclass=RegisterMeta):
    pass

class SubClass1(BaseClass):
    pass

class SubClass2(BaseClass):
    pass

# 查看注册表
print(class_registry)

输出将是:

{'BaseClass': <class '__main__.BaseClass'>, 'SubClass1': <class '__main__.SubClass1'>, 'SubClass2': <class '__main__.SubClass2'>}

在这个示例中:

  • RegisterMeta​是一个元类,它在创建类时将类注册到class_registry​字典中。
  • 当我们定义BaseClass​、SubClass1​和SubClass2​时,它们都会被自动注册到class_registry​中。

通过元类,我们可以高度定制类的创建过程,使其符合特定的需求。

六、通过元类实现-orm

0

评论区