一、描述器
用到魔术方法__get__()、set()、delete()
1、用非数据描述器写staticmethod装饰器:
class StaticMethod:
def __init__(self,fn):
self.fn=fn
def __get__(self,instance,owner):
return self.fn
class A:
@StaticMethod # 要点一:等价式 foo=StaticMethod(foo),foo变成了StaticMethod类的实例
def foo():
print('static method called')
a=A()
a.foo() # 要点二:实例调用的形式,反推出dec=a.foo,使用为dec()
# 运行结果:
static method called
2、用非数据描述器写classmethod装饰器
# 错误写法:
class ClassMethod:
def __init__(self,fn):
self.fn=fn
def __get__(self,instance,owner):
return self.fn(owner)
class A:
@ClassMethod # 等价式 foo=ClassMethod(foo),foo变成了ClassMethod类的实例
def foo(cls):
print(cls,'class method called')
def __repr__(self):
return '< A >'
a=A()
a.foo # 不加括号就已完成函数调用
A.foo
可考虑用偏函数将cls这个参数固定,返回的新fn其cls参数已固定,也就是说变成了一个无参函数、直接fn()就可实现调用,其中新fn需变成用户输入a.foo或A.foo返回的一个可调用对象
from functools import partial
class ClassMethod:
def __init__(self,fn):
self.fn=fn
def __get__(self,instance,owner):
return partial(self.fn,owner)
class A:
@ClassMethod # 等价式 foo=ClassMethod(foo),foo变成了ClassMethod类的实例
def foo(cls):
print(cls,'class method called')
a=A()
a.foo()
A.foo()
# 运行结果:
<class '__main__.A'> class method called
<class '__main__.A'> class method called
3、用数据描述器写property属性装饰器
重点:实例的属性对应一个方法时,不会有绑定效果
class Property:
def __init__(self,fget,fset=None):
self.fget=fget
self.fset=fset
def __get__(self,instance,owner): # instance: a, owner: A
if instance is not None:
# 实例调方法、要看这个方法是注册在类上还是实例上,类上才有绑定效果,实例上则是普通的函数调用,需传参
return self.fget(instance) # 实例调实例的方法,没有绑定效果,需手动传第一参数
return self
def __set__(self,instance,value):
if instance is not None:
self.fset(instance,value) # fset对应下面的data函数,需正常传两个参数
def setter(self,fset): # fset对应data函数,是实例a的实例方法,调用时依然无绑定效果
self.fset=fset
return self
class A:
def __init__(self,data):
self._data=data
@Property # data=Property(data) 即Property实例
def data(self):
return self._data
# data=data.setter(data) 即data=Property实例.setter(data),右边返回data=Property实例
# 如果data的返回值不是Property实例,相当于把data=Property(data)这个类属性冲掉,a.data将不再对应描述器
# 上下必须一致,保证data属性是描述器,所以需设立setter方法
@data.setter
def data(self,value):
self._data=value
# 使用方法,先实例化、传参,再调用a.data,a是A的实例
# data是A的类属性,对应另一个类Property的实例,Property实现了描述器协议
# data属性现在是描述器,描述器一旦被访问,即print(a.data),就调用__get__
a=A('jerry')
print(a.data)
# data本是对应另一个类的实例,如果没有set方法就是覆盖了
a.data='tom'
print(a.data)
分析:加了这两个装饰器后,A类中以下两个函数的等价式均为:data=Property实例,且下面覆盖上面
@Property
def data(self):
return self._data
@data.setter
def data(self,value):
self._data=value
然后A类的定义就相当于:
class A:
def __init__(self,data):
self._data=data
data=Property实例
原来的上下两个data函数,分别被fset和fget记住,类属性data等于数据描述器Property的实例,对类属性data的访问相当于对描述器的访问
二、描述器应用
1、对实参进行例行检查
# 有硬编码的描述器
class TypeCheck:
def __init__(self,name,type):
self.name=name
self.type=type
def __get__(self,instance,owner):
if instance is not None:
return instance.__dict__[self.name]
def __set__(self,instance,value):
if not isinstance(value,self.type):
raise TypeError()
instance.__dict__[self.name]=value
class Person:
name=TypeCheck('name',str)
age=TypeCheck('age',int)
def __init__(self,name:str,age:int):
self.name=name # 只要初始化赋值,就送到描述器类的set处先拦截
self.age=age
p1=Person('tom',18)
print(p1.__dict__)
print(p1.name)
改进:将硬编码name=TypeCheck(‘name’,str) 和 age=TypeCheck(‘age’,int) 提到外面写成一个函数装饰器
class TypeCheck:
def __init__(self,name,type):
self.name=name
self.type=type
self.data={}
def __get__(self,instance,owner):
if instance is not None:
#print(self.data)
return self.data[self.name]
return self
def __set__(self,instance,value):
if not isinstance(value,self.type):
raise TypeError()
self.data[self.name]=value
import inspect
def typeassert(cls):
sig=inspect.signature(cls)
params=sig.parameters
for name,param in params.items():
if param.annotation != param.empty:
setattr(cls,name,TypeCheck(name,param.annotation))
return cls
@typeassert
class Person:
def __init__(self,name:str,age:int):
self.name=name
self.age=age
p1=Person('tom',18)
print(p1.__dict__)
print(p1.name,p1.age)
进一步改进:将这个函数装饰器改成类装饰器
要点1、等价式Person=TypeAssert(Person),Person变成了TypeAssert的实例;
要点2、实际调用时,p1=Person(‘tom’,18),也就是p1=TypeAssert实例(‘tom’,18),即TypeAssert的实例必须实现可调用
class TypeCheck:
def __init__(self,name,type):
self.name=name
self.type=type
self.data={}
def __get__(self,instance,owner):
if instance is not None:
#print(self.data)
return self.data[self.name]
return self
def __set__(self,instance,value):
if not isinstance(value,self.type):
raise TypeError()
self.data[self.name]=value
import inspect
class TypeAssert:
def __init__(self,cls):
self.cls=cls
sig=inspect.signature(cls)
params=sig.parameters
for name,param in params.items():
if param.annotation != param.empty:
setattr(cls,name,TypeCheck(name,param.annotation))
def __call__(self,*args,**kwargs):
return self.cls(*args,**kwargs) # 返回一个新的Person的实例
@TypeAssert
class Person: # Person=TypeAssert(Person),Person变成了TypeAssert的实例
def __init__(self,name:str,age:int):
self.name=name
self.age=age
p1=Person('tom',18)
print(p1.__dict__)
print(p1.name,p1.age)