Week 9: 描述器及其應用
阿新 • • 發佈:2018-12-12
一、描述器
用到魔術方法__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)