元类编程
一、property
先看下面例子:
依赖于birthday设置User对象的属性
class User: def __init__(self,name,birthday): self.name = name self.birthday = birthday #age依赖于当前时间,需要做一番计算 self.age = datetime.today().year - self.birthday.year user = User("ming",date(year=1992,month=8,day=12)) print(user.age)
但是这样好吗?如果是更加复杂的计算就必须以函数的方式返回。
from datetime import date,datetime class User: def __init__(self,name,birthday): self.name = name self.birthday = birthday def get_age(self): return datetime.today().year - self.birthday.year user = User("ming",date(year=1992,month=8,day=12)) print(user.get_age())
虽然可以实现功能,但是明明是属性值却还要调用方法?有没有解决方案了?
通过property关键字就可以实现。
from datetime import date,datetime class User: def __init__(self,name,birthday): self.name = name self.birthday = birthday @property def age(self): return datetime.today().year - self.birthday.year user = User("ming",date(year=1992,month=8,day=12)) print(user.age)
@property可以把一个函数当作一个属性来供用户操作
既然是属性操作,还可以进行set和del操作。
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): return datetime.today().year - self.birthday.year @age.setter def age(self,value): self._age = value @age.deleter def age(self): del self._age user = User("ming",date(year=1992,month=8,day=12)) print(user.age) #27 print(user._age) #None #需要注意的是属性名不能和@property的方法名相同 user._age = 28 print(user.age) #27 print(user._age) #28
二、__getattr__和__getattribute__
__getattr__:当找不到相应的属性的时候所作的逻辑操作。
如果没有定义__getattr__方法,对于找不到的属性会直接报错。
from datetime import date,datetime class User: def __init__(self,name,birthday): self.name = name self.birthday = birthday user = User("ming",date(year=1992,month=8,day=12)) print(user.age) #AttributeError: 'User' object has no attribute 'age'
如果设置了相应的方法找不到相应的属性会执行相应的逻辑。
from datetime import date,datetime class User: def __init__(self,name,birthday): self.name = name self.birthday = birthday def __getattr__(self, item): return "no find attr" user = User("ming",date(year=1992,month=8,day=12)) print(user.age) #no find attr
当然不单单只是返回报错。
from datetime import date,datetime class User: def __init__(self,name,birthday,info): self.name = name self.birthday = birthday self.info = info def __getattr__(self, item): return self.info.get(item) info = {"company_name":"正名", "position":"Director", "family_information":{"father":"farmer","monther":'farmer'}, "Working company":["百度","阿里","苹果"]} user = User("ming",date(year=1992,month=8,day=12),info) print(user.position) #Director
__getattribute__:会比__getattr__更加霸道,所有的属性访问都会走下面的逻辑,不论是否存在。
from datetime import date,datetime class User: def __init__(self,name,birthday): self.name = name self.birthday = birthday def __getattr__(self, item): return self.info.get(item) def __getattribute__(self, item): return "test" user = User("ming",date(year=1992,month=8,day=12)) print(user.position) #test #不存在的也可以
__getattribute__在某些时候想要控制属性访问还是特别有用的。
三、属性描述符和属性的查找范围
user.age看似简单,但是内部实现的原理并不简单,通过下面内容应该可以清楚user.age的整个生命周期。
1.数据属性描述符
我们在自定义一个属性的时候该如何对属性本身做一些限制了?
我们要知道,类里面的所有方法都是针对于对象本身的,而不是针对这个属性的。
因此,我们需要将属性也变成一个自定义的对象,这样我们就能对属性进行操作
在ORM中,我们使用的是如下方式创建以及定义表:
class BusinessUnitType(models.Model): """ 业务单元分类 """ name = models.CharField(max_length=64)
下面我们可以模拟上述操作。
class CharField: def __init__(self,max_length=32): self.max_length = max_length class User: name= CharField(max_length=6) user = User() user.name = "str" print(user.name)
但是现在还没有添加限制,比如必须是字符串,比如最大长度为6.
class CharField: def __init__(self,max_length): self.max_length = max_length def __get__(self, instance, owner): return self.value def __set__(self, instance, value): if not isinstance(value,str): raise ValueError("name必须是字符串") if len(value) > self.max_length: raise ValueError("超过规定长度") if not isinstance(self.max_length,int): raise ValueError("max_length 必须是整数") self.value = value def __delete__(self, instance): del self.value class User: name= CharField(max_length=6)
打印测试:
user = User() user.name = 1 #ValueError: name必须是字符串 user.name = "uuuuuuuu" #ValueError: 超过规定长度 user.name = "yannc" #ValueError: max_length 必须是整数 print(user.name)
现在我们回过头来看看instance和value是什么
instance是一个user对象,竟然将外层内当参数传进来了,现在还没用过。
value就是name对象,或者说是值。当我们不知道怎么操作这些方法的时候,可以使用debug打印下到底做了什么。
现在回过头来想,我们就必须这样吗?
我们写一个使用常用的方式:
class NewPerson: def __init__(self,name=None): self.name = name def __set__(self, instance, value): print("123") newperson = NewPerson() newperson.name = "kebi" #这个操作根本就不会经过__set__魔法方法,__set__属于对象操作。
我们可以定义一个set_name方法,对name的赋值进行限制,估且不说name.set_name("kebi")这种形式好不好,
但是每一个属性的设置方法我都要进行定义,你觉得麻烦不?就不能newperson.name=""这种形式吗?
对于属性的操作难道我们就毫无办法吗?
其实可以使用@property,这个可以实现。
class Person: @property def name(self): return self._name @name.setter def name(self,value): self._name = value person = Person() person.name = "kibi" print(person.name)
虽然可以实现,但是如果一个类的属性非常多,那么这个类就会显得非常臃肿。
对于属性的操作我们在类里面随便定义一个方法都可以操作,但是你把属性的值改变了之后,还是当前属性吗?
现在再来看看:
class CharField: def __init__(self,max_length): self.max_length = max_length def __get__(self, instance, owner): return self.value def __set__(self, instance, value): if not isinstance(value,str): raise ValueError("name必须是字符串") if len(value) > self.max_length: raise ValueError("超过规定长度") if not isinstance(self.max_length,int): raise ValueError("max_length 必须是整数") self.value = value def __delete__(self, instance): del self.value class User: name= CharField(max_length=6)
name对于User来说是一个属性,但是同时也是一个对象,给一个对象赋值name="kebi",name还是CharField对象吗?
这就要说到属性操作符,CharField既是一个类,也是一个属性描述符,这个一个有点特别的类。
为什么这样说了?name可以说是CharField的对象,但是又str的对象,CharField更像是一个辅助类。
什么是属性描述符?只要是__get__、__set__、__delete__三个方法中的任意一个就是一个属性描述符。、
2.非数据属性描述符
当你只使用__get__方法来构造描述符的时候,你所构造的描述符就是一个非数据描述符。
class CharField: def __init__(self,max_length): self.max_length = max_length def __get__(self, instance, owner): return self.value class User: name = CharField()
3.属性查找过程
class CharField: pass class User: name = CharField()
对于数据属性描述符、非数据属性描述符、类属性和对象属性的优先级说明:
如果user = User(),那么user.age的调用顺序如下:
(1)如果age在User类或者基类中,并且是数据属性描述符,那么调用其__get__方法。
关于属性描述符的定义:
class CharField: def __get__(self, instance, owner): return self.value def __set__(self, instance, value): pass def __delete__(self, instance): pass class User: name= CharField() user = User() user.name = “kebi”
(2)如果age在user的__dict__中,那么以return self.age的方式调用。
出现在user的__dict__中,可能有三种情况:
class User: def __init__(self,name): #方法1 self.name = name user = User('ming') #方法2 user.age = 28 # 方法3:回溯机制 user.__dict__['addr'] = "罗田"
(3)如果age出现在User类或者基类__dict__中,并且是非数据属性描述符,那么调用其__get__方法。
class CharField: def __get__(self, instance, owner): return self.value
(4)如果age出现在User类或者基类__dict__中,不是描述符,以return self.age调用。
(5)如果User有__getattr__方法,调用__getattr__
(6)抛出AttributeError异常
class User: pass user = User() print(user.name) #AttributeError: 'User' object has no attribute 'name'
优先级:
数据属性描述符 > 对象属性 > 非数据属性描述符 > 常规类属性 > __getattr__
下面是一个示例应该可以更好的阐述。
from datetime import date,datetime class Name1(): """数据属性描述符""" def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass class Name3(): """非数据属性描述符""" def __get__(self, instance, owner): pass class User: name1 = Name1() name3 = Name3() """普通类对象""" name4 = "kebi" def __init__(self,name,birthday): self.name2 = name self.birthday = birthday def __getattr__(self, item): return "56789" def __getattribute__(self, item): #第一步:调用数据属性描述符name1 #第二步:调用self.name2 #第三步:调用非数据属性描述符name3 #第四步:调用类属性name4 #第五步:调用__getattr__方法 #第六步:抛出AttributeError return "123" user = User("ming",date(year=1992,month=8,day=12))
从上述示例中,关于user.age获取操作都是在__getattribute__方法中实现。
下面示例会证明上述过程:
示例1:
class User: def __getattr__(self, item): return "__getattr__" user = User() print(user.name)
示例2:类属性 > __getattr__
class User: name = "class_attr" def __getattr__(self, item): return "__getattr__" user = User() print(user.name)
示例3:非数据描述符 > 类属性
class Name: def __get__(self, instance, owner): return "Non-data attribute descriptor" class User: name = Name() def __getattr__(self, item): return "__getattr__" user = User() print(user.name)
示例4:对象属性 > 非数据属性描述符
class Name: def __get__(self, instance, owner): return "Non-data attribute descriptor" class User: def __init__(self): self.name = "object_attr" user = User() print(user.name)
示例5:数据属性描述符 > 对象属性
class Name: def __get__(self, instance, owner): return "data attribute descriptor" def __set__(self, instance, value): pass class User: name = Name() def __init__(self): self.name = "object_attr" user = User() print(user.name)
最后来看一个示例:
class Name: def __get__(self, instance, owner): return "Non-data attribute descriptor" def __set__(self, instance, value): pass class User: name = Name() def __init__(self): self.name = "object_attr" def __getattr__(self, item): return "__getattr__" def __getattribute__(self, item): return "123" user = User() print(user.name) #"123"
你可能会纳闷,为啥是这个?因为__getattribute__屏蔽了所有,
整个属性调用本身就在__getattribute__中进行,你现在重写,不就是覆盖了原有操作了吗。
还有一点需要注意的是,当你使用属性描述符设置值的时候,属性并不会出现在__dict__中。
class Name: def __get__(self, instance, owner): return self.name def __set__(self, instance, value): self.name = value class User: name = Name() user = User() user.name = "maoxian" print(user.name) #maoxian print(user.__dict__) #{}
当你使用__dict__来添加属性的时候,尤其是混合数据操作符的时候,可能会有以下异常:
class Name: def __get__(self, instance, owner): return self.name def __set__(self, instance, value): self.name = value class User: name = Name() user = User() user.__dict__["name"] = "maoxian" print(user.__dict__) #{'name': 'maoxian'} #当你在调用的user.nage的时候,优先还是先找数据属性描述符 print(user.name) #AttributeError: 'Name' object has no attribute 'name' #当然下面方法还是可用: user.__dict__['name']
四、__init__和__new__的区别
__init__:构造器,用来初始化对象的信息
__new__:在对象创建之前被创建。如果__new__没有返回值,那么也不会执行__init__函数。
class User: def __new__(cls, *args, **kwargs): #这个cls代表当前类User print("new") def __init__(self): #self代表当前对象 print("init") user = User() #new 这里根本不会执行__init__函数
给__new__一个返回值就可以。
class User: def __new__(cls, *args, **kwargs): return super().__new__(cls) def __init__(self): print("init") user = User()
__new__只有在新式类中实现
五、自定义元类
1.动态创建类的方式
(1)通过函数创建
def create_class(name): if name == "User": class User: def __str__(self): return "User" return User else: class Person: def __str__(self): return "User" return Person User = create_class("User") print(User) user = User() print(user)
(2)通过type来动态创建类
type中提供了三种创建方法:
type(object_or_name, bases, dict) type(object) -> the object's type type(name, bases, dict) -> a new type
下面是实现示例:
创建一个类:
User = type("User",(),{}) print(User)
定义一个属性:
User = type("User",(),{"name":"mao"}) user = User() print(user.name)
定义一个方法:
def see(self): #这里必须要传递一个self参数 print("I see a dog") User = type("User",(),{"name":"mao","see":see}) user = User() user.see()
定义一个父类:
class Anlu: def go_to_anlu(self): print("go to anlu") User = type("User",(Anlu,),{"name":"mao","see":see}) user = User() user.go_to_anlu()
2.元类
元类就是创建类的类。比如type就是一个元类
a = 1 print(type(a)) #<class 'int'> print(type(int)) #<class 'type'> b = [1,2,3] print(type(b)) #<class 'list'> print(type(list)) #<class 'type'>
python中的int、str、list、dict、class都是type创建。
六、通过元类实现一个简单的ORM
在使用元类编程的时候,必须继承type类,一般在元类里面都是重写__new__方法,
用于在对象初始化之前做一些操作,比如数据的整理。
class IntegerField(): def __init__(self,db_column,max_length=0): self._value = None self.max_length = max_length self.db_column = db_column if max_length: #0 == Flase if not isinstance(max_length,numbers.Integral): raise ValueError("max_length must be int") elif max_length < 0: raise ValueError("max_length > 0") else: raise ValueError("max_length must bi have") def __get__(self, instance, owner): return self._value def __set__(self, instance, value): #这个value = name if not isinstance(value,numbers.Integral): raise ValueError("value must be int") if value < 0: raise ValueError("value must greater than 0") elif value < pow(10,self.max_length) -1: raise ValueError("value should not be greater than max") def __delete__(self, instance): del self._value class CharField(): def __init__(self,db_column,min_length=0,max_length=0): self._value = None #_value是name的值,这个一般都会预留 self.db_column = db_column self.min_length = min_length self.max_length = max_length if max_length: if not isinstance(max_length,numbers.Integral): raise ValueError("max_length must be int") elif max_length < 0: raise ValueError("max_length > 0") else: raise ValueError("max_length must bi have") if min_length: if not isinstance(min_length,numbers.Integral): raise ValueError("min_length must be int") elif min_length < 0: raise ValueError("min_length > 0") elif min_length > max_length: raise ValueError("min_length less than max_length") def __get__(self, instance, owner): return self._value def __set__(self, instance, value): if not isinstance(value,str): raise ValueError("value must be str") if len(value) > self.max_length or len(value) < self.min_length: raise ValueError("Value length is error") self._value = value def __delete__(self, instance): del self._value class Student(): name = CharField(db_column="",min_length=1,max_length=32) age = IntegerField(db_column="",max_length=32) class Meta: db_table = "student"
上方代码通过属性描述符实现了对数据的检测功能。
虽然上面有了ORM的雏形,但是还需要对一些初始信息进行封装。比如字段信息
创建元类:ModelMetaClass
class ModelMetaClass(type): def __new__(cls, name,bases,attrs, **kwargs): if name == "BaseModel": return super().__new__(cls, name, bases, attrs, **kwargs) #重新整理字段,这样方便表结构的初始化 fields = {} for key,value in attrs.items(): if isinstance(value,Field): fields[key] = value attrs_meta = attrs.get("Meta",None) _meta = {} db_table = name.lower() #如果student中有Meta配置 if attrs_meta: #取出db_table这个属性 table = getattr(attrs_meta,"db_table",None) if table: db_table = table #存在就会重新赋值 _meta["db_table"] = db_table #重新整理attrs中的属性 attrs["_meta"] = _meta attrs["fields"] = fields del attrs["Meta"] return super().__new__(cls, name, bases, attrs, **kwargs)
这就玩了吗?还需要一些初始化的改动,将初始化信息单独封装起来成BaseModel
class BaseModel(metaclass=ModelMetaClass): def __init__(self,*args,**kwargs): #下面这部分主要是怕用户不使用属性描述符来初始化信息。 for key,value in kwargs.items(): setattr(self,key,value) return super().__init__() #下面就可以定义数据库操作了。 def save(self): fields = [] values = [] for key,value in self.fields.items(): db_column = value.db_column if not db_column: db_column = key.lower() fields.append(db_column) value = getattr(self,key) values.append(str(value)) sql = "insert into {db_table} ({fields}) value({values})".format(db_table=self._meta["db_table"], fields=",".join(fields), values=",".join(values)) pass
下面是完整代码:
import numbers class Field: pass class IntegerField(Field): # 属性描述符做字段验证 def __init__(self,db_column,max_length=0): self._value = None self.max_length = max_length self.db_column = db_column if max_length: #0 == Flase if not isinstance(max_length,numbers.Integral): raise ValueError("max_length must be int") elif max_length < 0: raise ValueError("max_length > 0") else: raise ValueError("max_length must bi have") def __get__(self, instance, owner): return self._value def __set__(self, instance, value): #这个value = name if not isinstance(value,numbers.Integral): raise ValueError("value must be int") if value < 0: raise ValueError("value must greater than 0") elif value < pow(10,self.max_length) -1: raise ValueError("value should not be greater than max") def __delete__(self, instance): del self._value class CharField(Field): #属性描述符做字段验证 def __init__(self,db_column,min_length=0,max_length=0): self._value = None #_value是name的值,这个一般都会预留 self.db_column = db_column self.min_length = min_length self.max_length = max_length if max_length: if not isinstance(max_length,numbers.Integral): raise ValueError("max_length must be int") elif max_length < 0: raise ValueError("max_length > 0") else: raise ValueError("max_length must bi have") if min_length: if not isinstance(min_length,numbers.Integral): raise ValueError("min_length must be int") elif min_length < 0: raise ValueError("min_length > 0") elif min_length > max_length: raise ValueError("min_length less than max_length") def __get__(self, instance, owner): return self._value def __set__(self, instance, value): if not isinstance(value,str): raise ValueError("value must be str") if len(value) > self.max_length or len(value) < self.min_length: raise ValueError("Value length is error") self._value = value def __delete__(self, instance): del self._value class ModelMetaClass(type): # def __new__(cls, name,bases,attrs, **kwargs): if name == "BaseModel": return super().__new__(cls, name, bases, attrs, **kwargs) #重新整理字段,这样方便表结构的初始化 fields = {} for key,value in attrs.items(): if isinstance(value,Field): #找出字段,为了方便验证重新定义Field类 fields[key] = value attrs_meta = attrs.get("Meta",None) _meta = {} db_table = name.lower() #如果student中有Meta配置 if attrs_meta: #取出db_table这个属性 table = getattr(attrs_meta,"db_table",None) if table: db_table = table #存在就会重新赋值 _meta["db_table"] = db_table #重新整理attrs中的属性 attrs["_meta"] = _meta attrs["fields"] = fields del attrs["Meta"] return super().__new__(cls, name, bases, attrs, **kwargs) class BaseModel(metaclass=ModelMetaClass): def __init__(self,*args,**kwargs): #支持Student(name=xxx)这个赋值操作 for key,value in kwargs.items(): setattr(self,key,value) return super().__init__() #定义插入操作 def save(self): fields = [] values = [] for key,value in self.fields.items(): db_column = value.db_column if not db_column: db_column = key.lower() fields.append(db_column) value = getattr(self,key) values.append(str(value)) sql = "insert into {db_table} ({fields}) value({values})".format(db_table=self._meta["db_table"], fields=",".join(fields), values=",".join(values)) pass class Student(BaseModel): name = CharField(db_column="",min_length=1,max_length=32) age = IntegerField(db_column="",max_length=32) class Meta: db_table = "student" student = Student() student.name = "kebi" print(student.name) student.save()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理