九、元类编程
一、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 # 直接删除实例属性区别
- 优先级不同:数据描述符的优先级高于实例字典中的同名属性。即使实例字典中有同名属性,访问时仍会调用数据描述符的
__get__方法。而非数据描述符的优先级低于实例字典中的同名属性,如果实例字典中有同名属性,则直接返回该属性的值,不调用非数据描述符的__get__方法。 - 用途不同:数据描述符通常用于需要控制属性赋值和删除的场景,比如属性验证或类型检查。而非数据描述符通常用于只读属性或方法绑定。
- 优先级不同:数据描述符的优先级高于实例字典中的同名属性。即使实例字典中有同名属性,访问时仍会调用数据描述符的
示例:
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__不返回值,它主要用于设置实例的初始状态或属性。 - 它是对象创建的第二步,负责初始化实例的属性。
区别
-
调用时机不同:
-
__new__在实例创建之前被调用。 -
__init__在实例创建之后被调用。
-
-
职责不同:
-
__new__负责返回一个类的实例。 -
__init__负责初始化实例的属性。
-
-
返回值不同:
-
__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)是用于创建类的“类”。换句话说,元类是类的类,它决定了如何创建类以及类的行为。元类允许我们控制类的创建过程,可以用于自定义类的行为、属性和方法。
元类的作用
- 控制类的创建:元类可以拦截类的创建过程,并在类创建之前或之后进行自定义操作。
- 修改类的定义:元类可以动态地修改类的属性和方法。
- 实现单例模式:元类可以用于实现设计模式,如单例模式。
使用元类
要定义一个元类,可以继承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__来创建类。
元类的常见用法
- 验证类属性:在创建类时,验证类的属性是否符合要求。
- 自动注册类:在创建类时,自动将类注册到某个注册表中。
- 单例模式:确保一个类只有一个实例。
复杂示例
下面是一个更复杂的示例,展示了如何使用元类来自动注册类:
# 定义一个注册表
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
评论区