第八章 元类编程
8.1 property动态属性
from datetime import date, datetime
class User:
def __init__(self, name, birthday):
self.name = name
self.birthday = birthday # 保存一个用户年龄到数据库,首选保存生日,不去保存年龄,因为年龄会变
self._age = 0
@property
def age(self):
"""
如果不想知道birthday,只想知道age年龄
"""
return datetime.now().year - self.birthday.year
@age.setter # 对age属性进行设置
def age(self, value):
self._age = value
if __name__ == "__main__":
user = User("bobby", date(year=1987, month=1, day=1))
print(user.age)
user.age = 30
print(user._age)
33
30
@property装饰器会把函数age变成一个属性描述符,那么就可以使用user.age(属性的方式),而不是使用user.age()(函数调用的方式)。
@property只是做get,那还可以set,就是@age.setter。
8.2 __getattr__、__getattribute__魔法函数
魔法函数是python动态特性的最根本原因。__getattr__就是在查找不到属性的时候调用。
之前讲过python在类中属性查找的顺序,现在直接作为例子讲解
from datetime import date
class User:
def __init__(self,name,birthday):
self.name = name
self.birthday = birthday
def __getattr__(self, item):
return("not find attr")
if __name__ == "__main__":
user = User("bobby",date(year=1987,month=1,day=1))
print(user.age) # 如果没有__getattr__,会抛出异常AttributeError: 'User' object has # no attribute 'age'
not find attr
加上__getattr__后没有报错,返回了“not find attr”。user实例里查找不到age属性的时候,会进入__getattr__里,那么就可以在__getattr__里添加自己的逻辑。
比如,在属性里找不到name时,返回Name的值(相当于拼写纠错)
from datetime import date
class User:
def __init__(self,name,birthday):
self.Name = name
self.birthday = birthday
def __getattr__(self, item):
return self.Name
if __name__ == "__main__":
user = User("bobby",date(year=1987,month=1,day=1))
print(user.name)
bobby
比如,在User里面维护一个dict info
from datetime import date
class User:
def __init__(self, name, birthday,info={}):
self.name = name
self.birthday = birthday
self.info = info
def __getattr__(self, item):
return self.info[item]
if __name__ == "__main__":
user = User("bobby", date(year=1987, month=1, day=1),info={"company_name":"imooc"})
print(user.company_name)
imooc
比如,可以只使用一个dict,虽然在属性中找不到name,但是进入__getattr__后,在自己写的逻辑中可以查找到name
class User:
def __init__(self,info={}):
self.info = info
def __getattr__(self, item):
return self.info[item]
if __name__ == "__main__":
user = User(info={"company_name":"imooc", "name":"bobby"})
print(user.name)
bobby
上述三个例子都是__getattr__的好处,再讲一下__getattribute__这个魔法函数。
class User:
def __init__(self,info={}):
self.info = info
def __getattr__(self, item):
return self.info[item]
def __getattribute__(self, item):
return "bobby"
if __name__ == "__main__":
user = User(info={"company_name":"imooc", "name":"bobby"})
print(user.name)
__getattribute__的优先级更高,首先会进入__getattribute__,再去找属性。
class User:
def __init__(self,info={}):
self.info = info
def __getattr__(self, item):
return self.info[item]
def __getattribute__(self, item):
return "bobby1"
if __name__ == "__main__":
user = User(info={"company_name":"imooc", "name":"bobby"})
print(user.test)
bobby1
就算访问一个不存在的属性test,也会返回bobby1。__getattribute__不建议重写,因为__getattr__调用的是__getattribute__,如果__getattribute__没写好,python程序就会崩溃。
8.3 属性描述符和属性查找过程
from datetime import date, datetime
class User:
def __init__(self, name, email, birthday):
self.name = name
self.email = email
self.birthday = birthday
self._age = 0
# def get_age(self):
# return datetime.now().year - self.birthday.year
@property
def age(self):
return datetime.now().year - self.birthday.year
@age.setter
def age(self, value):
#检查是否是字符串类型
self._age = value
if __name__ == "__main__":
user = User("bobby", date(year=1987, month=1, day=1))
user.age = 30
对上面的User,我们可以理解为数据库中的一个表结构,现在能看到的字段有name、birthday。我们希望控制name输入的都为字符串,name=1的时候是错的。如果还有一个字段email也需要为字符串,我们知道可以在@age.setter中检查字符串类型,那么可能对email也要写一个@email.setter,如果这样的字段过多,可能就需要写很多重复代码。那如何来控制这个过程和复用代码?
这个就涉及到属性描述符,比如有一个字段IntFied来统一做Int类型的验证。类变成属性描述符,只需要实现__get__、__set__和__del__魔法函数中的任意一个即可。属性描述符可以放到User里做检查。
import numbers
from datetime import date, datetime
class IntField:
#数据描述符
def __get__(self, instance, owner):
return self.value # 返回实例属性,age=IntField(),就是把value赋值给age
def __set__(self, instance, value):
if not isinstance(value,numbers.Integral):
raise ValueError("int value need")
self.value = value # 不能使用instance.age,因为.age会调用set方法,这样会一直循环调用set
# 可以使用self.value,因为age是IntField的一个实例,age=IntField()
def __delete__(self, instance):
pass
class User:
age = IntField() # 把IntField实例化传给age,age是User的类属性不是实例属性(是属性描述符的对象)
if __name__ == "__main__":
user = User()
user.age = 30 # 实际上调用的IntField的set方法
print(user.age)
user.age = "abc"
print(user.age)
###
user.age = -1
print(user.age)
30
Traceback (most recent call last):
File "E:/pythonProject/AdvancePython-master/chapter08/test.py", line 25, in <module>
user.age = "abc"
File "E:/pythonProject/AdvancePython-master/chapter08/test.py", line 11, in __set__
raise ValueError("int value need")
ValueError: int value need
Process finished with exit code 1
##################################
Traceback (most recent call last):
File "E:/pythonProject/AdvancePython-master/chapter08/test.py", line 27, in <module>
user.age = -1
File "E:/pythonProject/AdvancePython-master/chapter08/test.py", line 15, in __set__
raise ValueError("positive value need")
ValueError: positive value need
属性描述符有两种:
1.一种是实现了__get__、__set__(数据描述符)
2.还有另外一种,只实现了__get__方法(非数据描述符)
class NoneDataIntField:
#非数据属性描述符
def __get__(self, instance, owner):
return self.value
数据描述符和非数据描述符对属性查找的顺序是不一样的。
class User:
age = IntField()
# age = NonDataIntField()
if __name__ == "__main__":
user = User()
user.age = 30
print(getattr(user,'age'))
'''
user.age
1.如果age是一个数据描述符,那么age出现在IntField的value中,不会出现在user实例中,user.dict没有age
2.如果age是一个非数据描述符,那么age出现在user.dict中
user.__dict__['age']='abc'
1.age出现在user.dict中,但是print(user.age)会出异常,因为user.age是去IntField的value中查找的
查找顺序是
1.如果age是一个数据描述符,那么user.age调用数据描述符的__get__方法,如果没有数据则到2
2.去user的__dict__中查找,有则返回obj.__dict__[‘age’],没有数据则到3
3.去基类User的__dict__中查找
3.1 如果age是非数据描述符,调用其__get__方法,否则到3.2
3.2 返回基类的__dict__[‘age’]
4.如果age不是描述符,则去查找基类User的__getattr__方法,查找不到则到5
5.抛出AttributeError
'''
8.4 __new__和__init__的区别
__new__魔法函数第一个参数是class(类),__new__允许我们在生成User对象之前加逻辑。和__init__的最根本区别是__new__方法里可以自定义类的生成过程,__init__方法第一个参数是实例对象(初始化实例对象),所以是在__new__生成类后再对对象进行操作。
class User:
def __new__(cls, *args, **kwargs): # cls是User这个类
print (" in new ")
def __init__(self, name):
self.name = name # name属性在对象dict当中
print (" in init")
pass
#new 是用来控制对象的生成过程, 在对象生成之前
#init是用来完善对象的
#如果new方法不返回对象, 则不会调用init函数
if __name__ == "__main__":
user = User(name="bobby")
in new
class User:
def __new__(cls, *args, **kwargs): # cls是User这个类
print (" in new ")
return super().__new__(cls)
def __init__(self, name):
self.name = name # name属性在对象dict当中
print (" in init")
pass
#new 是用来控制对象的生成过程, 在对象生成之前
#init是用来完善对象的
#如果new方法不返回对象, 则不会调用init函数
if __name__ == "__main__":
user = User(name="bobby")
in new
in init
8.5 自定义元类
#类也是对象,type创建类的类
def create_class(name):
"""
根据传进来的name动态创建类
:param name:
:return:
"""
if name == "user":
class User:
def __str__(self):
return "user"
return User # 直接返回类
elif name == "company":
class Company:
def __str__(self):
return "company"
return Company
if __name__ == "__main__":
MyClass = create_class("user") # MyClass是一个类
my_obj = MyClass()
print(my_obj)
user
通过一个函数和一个字符串可以动态获取一个类,这个在python中是很简单的。但是这个动态创建类还是比较复杂的,需要我们自己去函数里定义class语句,只是把定义class的语句放到了函数当中,而且不灵活。那如何动态创建类呢,不需要自己去写class这种语法,而使用到type。
type用得很多的是用来获取某个对象的类型
#类也是对象,type创建类的类
def create_class(name):
"""
根据传进来的name动态创建类
:param name:
:return:
"""
if name == "user":
class User:
def __str__(self):
return "user"
return User # 直接返回类
elif name == "company":
class Company:
def __str__(self):
return "company"
return Company
if __name__ == "__main__":
MyClass = create_class("user")
my_obj = MyClass()
print(type(my_obj))
<class '__main__.create_class.<locals>.User'>
type其实可以用来创建类,点进去看
可以看到有三种参数构造的方法,不同构造方法返回的结果是不一样的。
创建类用到的是第一种,传递(name,bases(基类),dict(属性))。
# type动态创建类
User = type("User", (), {})
# "User"类名称,
# ()一个tuple(传递类继承的基类),什么都不继承也必须写
# {}是属性,没有属性时为{}
if __name__ == "__main__":
User = type("User", (), {})
my_obj = User()
print(my_obj)
<__main__.User object at 0x0000016A713E2550>
可以看到,通过type和通过class创建出来的都是类。再进一步扩展
# type动态创建类
User = type("User", (), {})
# "User"类名称,
# ()一个tuple(传递类继承的基类),什么都不继承也必须写
# {}是属性,没有属性时为{}
if __name__ == "__main__":
User = type("User", (), {"name":"user"})
# 这个用法类似于
# class:
# name="user"
my_obj = User()
print(my_obj.name)
user
创建类的时候不光有属性,还有内部方法,那如何使用type创建方法呢?
# type动态创建类
User = type("User", (), {})
# "User"类名称,
# ()一个tuple(传递类继承的基类),什么都不继承也必须写
# {}是属性,没有属性时为{}
def say(self): # 必须有self
"""
和class中定义方法是一样的,也需要传递self
"""
return "i am user"
# return self.name
if __name__ == "__main__":
User = type("User", (), {"name":"user","say":say}) # 传入函数时,必须是函数名称,不能是say()
my_obj = User()
print(my_obj.say())
i am user
所以通过type函数,可以动态创建类,添加属性和方法。
如果User要继承一个基类
# type动态创建类
User = type("User", (), {})
# "User"类名称,
# ()一个tuple(传递类继承的基类),什么都不继承也必须写
# {}是属性,没有属性时为{}
def say(self): # 必须有self
"""
和class中定义方法是一样的,也需要传递self
"""
return "i am user"
# return self.name
class BaseClass:
def answer(self):
return "i am baseclass"
if __name__ == "__main__":
User = type("User", (BaseClass,), {"name":"user","say":say})
# 传入函数时,必须是函数名称,不能是say()
# 在()中继承类,必须加,
my_obj = User()
print(my_obj.answer())
i am baseclass
那么什么是元类?元类就是创建类的类,type实际上是一个类。
对象是由class创建的,而class是由元类创建的。但实际上,很少用type创建类,一般是用元类的写法来创建类。
在Python类的实例化过程中,如果不定义metaclass=MetaClass,会调用type这个类创建User的类对象。这个对象是类,且在全局中是唯一的。如果定义了metaclass,则在类的实例化过程中,首先会寻找metaclass,如果找到则调用该metaclass去创建User类,
class MetaClass(type): # 继承了type,因此是一个元类
pass
class BaseClass(metaclass=MetaClass):
def answer(self):
return "i am baseclass"
class User(BaseClass): # MetaClass控制User实例化的过程
pass
如果User继承BaseClass,BaseClass中定义了metaclass,那么创建User类时,首先会去寻找User类是否定义了metaclass,如果找不到则到BaseClass中寻找metaclass,如果再找不到就去模块中找metaclass,最后才会使用type创建User类。这个是python中控制类对象生成的过程。
class MetaClass(type): # 继承了type,因此是一个元类
def __new__(cls, *args, **kwargs):
pass # 在此处打断点debug
class User(metaclass=MetaClass): # MetaClass控制User实例化的过程
pass
if __name__ == "__main__":
my_obj = User()
print(my_obj.answer())
创建User实例的时候,先要创建User类,首先进入MetaClass(元类),传递args(类似type(object_or_name, bases, dict))。那么在MetaClass中可以控制生成User实例的过程,
class MetaClass(type): # 继承了type,因此是一个元类
def __new__(cls, *args, **kwargs):
return super().__new__(cls,*args, **kwargs) # metaclass中定义调用父类需要传入参数
class User(metaclass=MetaClass): # MetaClass控制User实例化的过程
def __init__(self,name):
self.name = name
def __str__(self):
return "user"
if __name__ == "__main__":
my_obj = User(name='wry')
print(my_obj)
user
把创建User类的过程,委托给元类,比如ABC模块中定义某些抽象基类的时候,调用了metaclass ABCMeta。
ABCMeta中重写了new方法,可以做很多检查,比如要实例化的类是否实现了某些魔法函数。
元类也有很多应用常见,比如实例化类时做检查(类是否实现某些方法),或者做一些事先的工作。
8.6 元类实现简单的orm
首先明白我们的需求是什么。
# 需求
class User:
"""
类映射到数据库中的一张表,对类进行操作,把数据写到数据库中,可以脱离sql语句
"""
name = CharField(db_column="",max_length=10) # db_column是数据库中的列名,max_length是数据库列最大长度
age = IntField(db_column="",min_value=0,max_value=100)
class Meta:
"""
内部一个类,和上面的列名区别开,定义一些其他东西
"""
db_table = "user" # 具体到哪一张表
if __name__ == "__main__":
user = User()
user.name = "bobby" # 定义name列
user.age = 28 # 定义age列
user.save() # 保存到数据库中
用属性描述符描述name,实现属性赋值报错功能。
import numbers
class IntField:
# 数据描述符
def __init__(self,db_column,min_value=None,max_value=None):
self._value = None # 属性描述符保存的值是存在value里的
self.min_value = min_value
self.max_value = max_value
self.db_column = db_column
if min_value is not None: # 用户一旦传进来min_value,就需要判断是否是整型
if not isinstance(min_value,numbers.Integral):
raise ValueError("min_value must be int")
elif min_value < 0:
raise ValueError("min_value must be positive int")
if max_value is not None: # 用户一旦传进来max_value,就需要判断是否是整型
if not isinstance(max_value,numbers.Integral):
raise ValueError("max_value must be int")
elif max_value < 0:
raise ValueError("max_value must be positive int")
if max_value and min_value is not None:
if min_value > max_value:
raise ValueError("min_value must be smaller than max_value")
def __get__(self, instance, owner):
return self._value
def __set__(self, instance, value):
if not isinstance(value, numbers.Integral):
raise ValueError("int value need")
if value < self.min_value or value > self.max_value:
raise ValueError("value must between min_value and max_value")
self._value = value
class CharField:
def __init__(self, db_column, max_length=None):
self._value = None
self.db_column = db_column
if max_length is None:
raise ValueError("you must spcify max_lenth for charfiled")
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("string value need")
if len(value) > self.max_length:
raise ValueError("value len excess len of max_length")
self._value = value
class ModelMetaClass(type):
def __new__(cls, *args, **kwargs):
# 可以在这里取属性值
pass # 在此行打断点debug
# 需求
class User(metaclass=ModelMetaClass):
"""
类映射到数据库中的一张表,对类进行操作,把数据写到数据库中,可以脱离sql语句
"""
name = CharField(db_column="",max_length=10) # db_column是数据库中的列名,max_length是数据库列最大长度
age = IntField(db_column="",min_value=0,max_value=100)
class Meta:
"""
内部一个类,和上面的列名区别开,定义一些其他东西
"""
db_table = "user" # 具体到哪一张表
if __name__ == "__main__":
user = User()
user.name = "bobby"
user.age = 28
user.save()
可以看到进入了ModelMetaClass的new方法,可以看到三个属性(类名称,tuple,属性值dict)。
如果把属性都取出来
class ModelMetaClass(type):
def __new__(cls, name,bases,attrs, **kwargs):
for key, value in attrs.items():
pass # 在此行打断点debug
通过attrs,把所有与表相关的fields(相当于表中的列)提取出来,但是需要判断是否是int还是char