引言
Descriptors(描述符)是Python語(yǔ)言中一個(gè)深?yuàn)W但很重要的一個(gè)黑魔法,它被廣泛應(yīng)用于Python語(yǔ)言的內(nèi)核,熟練掌握描述符將會(huì)為Python程序員的工具箱添加一個(gè)額外的技巧。本文我將講述描述符的定義以及一些常見(jiàn)的場(chǎng)景,并且在文末會(huì)補(bǔ)充一下__getattr__,__getattribute__, __getitem__這三個(gè)同樣涉及到屬性訪問(wèn)的魔術(shù)方法。
描述符的定義
descr__get__(self,?obj,?objtype=None)?-->?value descr.__set__(self,?obj,?value)?-->?None descr.__delete__(self,?obj)?-->?None
只要一個(gè)object attribute(對(duì)象屬性)定義了上面三個(gè)方法中的任意一個(gè),那么這個(gè)類就可以被稱為描述符類。
描述符基礎(chǔ)
下面這個(gè)例子中我們創(chuàng)建了一個(gè)RevealAcess類,并且實(shí)現(xiàn)了__get__方法,現(xiàn)在這個(gè)類可以被稱為一個(gè)描述符類。
class?RevealAccess(object): ????def?__get__(self,?obj,?objtype): ????????print('self?in?RevealAccess:?{}'.format(self)) ????????print('self:?{}\nobj:?{}\nobjtype:?{}'.format(self,?obj,?objtype)) class?MyClass(object): ????x?=?RevealAccess() ????def?test(self): ????????print('self?in?MyClass:?{}'.format(self))
EX1實(shí)例屬性
接下來(lái)我們來(lái)看一下__get__方法的各個(gè)參數(shù)的含義,在下面這個(gè)例子中,self即RevealAccess類的實(shí)例x,obj即MyClass類的實(shí)例m,objtype顧名思義就是MyClass類自身。從輸出語(yǔ)句可以看出,m.x訪問(wèn)描述符x會(huì)調(diào)用__get__方法。
>>>?m?=?MyClass() >>>?m.test() self?in?MyClass:?<__main__.MyClass object at 0x7f19d4e42160> >>>?m.x self?in?RevealAccess:?<__main__.RevealAccess object at 0x7f19d4e420f0> self:?<__main__.RevealAccess object at 0x7f19d4e420f0> obj:?<__main__.MyClass object at 0x7f19d4e42160> objtype:?<class '__main__.MyClass'>
EX2類屬性
如果通過(guò)類直接訪問(wèn)屬性x,那么obj接直接為None,這還是比較好理解,因?yàn)椴淮嬖贛yClass的實(shí)例。
>>>?MyClass.x self?in?RevealAccess:?<__main__.RevealAccess object at 0x7f53651070f0> self:?<__main__.RevealAccess object at 0x7f53651070f0> obj:?None objtype:?<class '__main__.MyClass'>
描述符的原理
描述符觸發(fā)
上面這個(gè)例子中,我們分別從實(shí)例屬性和類屬性的角度列舉了描述符的用法,下面我們來(lái)仔細(xì)分析一下內(nèi)部的原理:
如果是對(duì)實(shí)例屬性進(jìn)行訪問(wèn),相當(dāng)于調(diào)用了object.__getattribute__(),它將obj.d轉(zhuǎn)譯成了type(obj).__dict__['d'].__get__(obj, type(obj))。
如果是對(duì)類屬性進(jìn)行訪問(wèn),相當(dāng)于調(diào)用了type.__getattribute__(),它將cls.d轉(zhuǎn)譯成了cls.__dict__['d'].__get__(None, cls),轉(zhuǎn)換成Python代碼就是:
def?__getattribute__(self,?key): ????"Emulate?type_getattro()?in?Objects/typeobject.c" ????v?=?object.__getattribute__(self,?key) ????if?hasattr(v,?'__get__'): ????????return?v.__get__(None,?self) ????return?v
簡(jiǎn)單講一下__getattribute__魔術(shù)方法,這個(gè)方法在我們?cè)L問(wèn)一個(gè)對(duì)象的屬性的時(shí)候會(huì)被無(wú)條件調(diào)用,詳細(xì)的細(xì)節(jié)比如和__getattr, __getitem__的區(qū)別我會(huì)在文章的末尾做一個(gè)額外的補(bǔ)充,我們暫時(shí)并不深究。
描述符優(yōu)先級(jí)
首先,描述符分為兩種:
如果一個(gè)對(duì)象同時(shí)定義了__get__()和__set__()方法,則這個(gè)描述符被稱為data descriptor。
如果一個(gè)對(duì)象只定義了__get__()方法,則這個(gè)描述符被稱為non-data descriptor。
我們對(duì)屬性進(jìn)行訪問(wèn)的時(shí)候存在下面四種情況:
data descriptor
instance dict
non-data descriptor
__getattr__()
它們的優(yōu)先級(jí)大小是:
data?descriptor?>?instance?dict?>?non-data?descriptor?>?__getattr__()
這是什么意思呢?就是說(shuō)如果實(shí)例對(duì)象obj中出現(xiàn)了同名的data descriptor->d 和 instance attribute->d,obj.d對(duì)屬性d進(jìn)行訪問(wèn)的時(shí)候,由于data descriptor具有更高的優(yōu)先級(jí),Python便會(huì)調(diào)用type(obj).__dict__['d'].__get__(obj, type(obj))而不是調(diào)用obj.__dict__['d']。但是如果描述符是個(gè)non-data descriptor,Python則會(huì)調(diào)用obj.__dict__['d']。
Property
每次使用描述符的時(shí)候都定義一個(gè)描述符類,這樣看起來(lái)非常繁瑣。Python提供了一種簡(jiǎn)潔的方式用來(lái)向?qū)傩蕴砑訑?shù)據(jù)描述符。
property(fget=None,?fset=None,?fdel=None,?doc=None)?->?property?attribute
fget、fset和fdel分別是類的getter、setter和deleter方法。我們通過(guò)下面的一個(gè)示例來(lái)說(shuō)明如何使用Property:
class?Account(object): ????def?__init__(self): ????????self._acct_num?=?None ????def?get_acct_num(self): ????????return?self._acct_num ????def?set_acct_num(self,?value): ????????self._acct_num?=?value ????def?del_acct_num(self): ????????del?self._acct_num ????acct_num?=?property(get_acct_num,?set_acct_num,?del_acct_num,?'_acct_num?property.')
如果acct是Account的一個(gè)實(shí)例,acct.acct_num將會(huì)調(diào)用getter,acct.acct_num = value將調(diào)用setter,del acct_num.acct_num將調(diào)用deleter。
>>>?acct?=?Account() >>>?acct.acct_num?=?1000 >>>?acct.acct_num 1000
Python也提供了@property裝飾器,對(duì)于簡(jiǎn)單的應(yīng)用場(chǎng)景可以使用它來(lái)創(chuàng)建屬性。一個(gè)屬性對(duì)象擁有g(shù)etter,setter和deleter裝飾器方法,可以使用它們通過(guò)對(duì)應(yīng)的被裝飾函數(shù)的accessor函數(shù)創(chuàng)建屬性的拷貝。
class?Account(object): ????def?__init__(self): ????????self._acct_num?=?None ????@property ?????#?the?_acct_num?property.?the?decorator?creates?a?read-only?property ????def?acct_num(self): ????????return?self._acct_num ????@acct_num.setter ????#?the?_acct_num?property?setter?makes?the?property?writeable ????def?set_acct_num(self,?value): ????????self._acct_num?=?value ????@acct_num.deleter ????def?del_acct_num(self): ????????del?self._acct_num
如果想讓屬性只讀,只需要去掉setter方法。
在運(yùn)行時(shí)創(chuàng)建描述符
我們可以在運(yùn)行時(shí)添加property屬性:
class?Person(object): ????def?addProperty(self,?attribute): ????????#?create?local?setter?and?getter?with?a?particular?attribute?name ????????getter?=?lambda?self:?self._getProperty(attribute) ????????setter?=?lambda?self,?value:?self._setProperty(attribute,?value) ????????#?construct?property?attribute?and?add?it?to?the?class ????????setattr(self.__class__,?attribute,?property(fget=getter,?\ ????????????????????????????????????????????????????fset=setter,?\ ????????????????????????????????????????????????????doc="Auto-generated?method")) ????def?_setProperty(self,?attribute,?value): ????????print("Setting:?{}?=?{}".format(attribute,?value)) ????????setattr(self,?'_'?+?attribute,?value.title()) ????def?_getProperty(self,?attribute): ????????print("Getting:?{}".format(attribute)) ????????return?getattr(self,?'_'?+?attribute)
>>>?user?=?Person() >>>?user.addProperty('name') >>>?user.addProperty('phone') >>>?user.name?=?'john?smith' Setting:?name?=?john?smith >>>?user.phone?=?'12345' Setting:?phone?=?12345 >>>?user.name Getting:?name 'John?Smith' >>>?user.__dict__ {'_phone':?'12345',?'_name':?'John?Smith'}
靜態(tài)方法和類方法
我們可以使用描述符來(lái)模擬Python中的@staticmethod和@classmethod的實(shí)現(xiàn)。我們首先來(lái)瀏覽一下下面這張表:
Transformation | Called from an Object | Called from a Class |
---|---|---|
function | f(obj, *args) | f(*args) |
staticmethod | f(*args) | f(*args) |
classmethod | f(type(obj), *args) | f(klass, *args) |
靜態(tài)方法
對(duì)于靜態(tài)方法f。c.f和C.f是等價(jià)的,都是直接查詢object.__getattribute__(c, ‘f’)或者object.__getattribute__(C, ’f‘)。靜態(tài)方法一個(gè)明顯的特征就是沒(méi)有self變量。
靜態(tài)方法有什么用呢?假設(shè)有一個(gè)處理專門數(shù)據(jù)的容器類,它提供了一些方法來(lái)求平均數(shù),中位數(shù)等統(tǒng)計(jì)數(shù)據(jù)方式,這些方法都是要依賴于相應(yīng)的數(shù)據(jù)的。但是類中可能還有一些方法,并不依賴這些數(shù)據(jù),這個(gè)時(shí)候我們可以將這些方法聲明為靜態(tài)方法,同時(shí)這也可以提高代碼的可讀性。
使用非數(shù)據(jù)描述符來(lái)模擬一下靜態(tài)方法的實(shí)現(xiàn):
class?StaticMethod(object): ????def?__init__(self,?f): ????????self.f?=?f ????def?__get__(self,?obj,?objtype=None): ????????return?self.f
我們來(lái)應(yīng)用一下:
class?MyClass(object): ????@StaticMethod ????def?get_x(x): ????????return?x print(MyClass.get_x(100))??#?output:?100
類方法
Python的@classmethod和@staticmethod的用法有些類似,但是還是有些不同,當(dāng)某些方法只需要得到類的引用而不關(guān)心類中的相應(yīng)的數(shù)據(jù)的時(shí)候就需要使用classmethod了。
使用非數(shù)據(jù)描述符來(lái)模擬一下類方法的實(shí)現(xiàn):
class?ClassMethod(object): ????def?__init__(self,?f): ????????self.f?=?f ????def?__get__(self,?obj,?klass=None): ????????if?klass?is?None: ????????????klass?=?type(obj) ????????def?newfunc(*args): ????????????return?self.f(klass,?*args) ????????return?newfunc
其他的魔術(shù)方法
首次接觸Python魔術(shù)方法的時(shí)候,我也被__get__, __getattribute__, __getattr__, __getitem__之間的區(qū)別困擾到了,它們都是和屬性訪問(wèn)相關(guān)的魔術(shù)方法,其中重寫__getattr__,__getitem__來(lái)構(gòu)造一個(gè)自己的集合類非常的常用,下面我們就通過(guò)一些例子來(lái)看一下它們的應(yīng)用。
__getattr__
Python默認(rèn)訪問(wèn)類/實(shí)例的某個(gè)屬性都是通過(guò)__getattribute__來(lái)調(diào)用的,__getattribute__會(huì)被無(wú)條件調(diào)用,沒(méi)有找到的話就會(huì)調(diào)用__getattr__。如果我們要定制某個(gè)類,通常情況下我們不應(yīng)該重寫__getattribute__,而是應(yīng)該重寫__getattr__,很少看見(jiàn)重寫__getattribute__的情況。
從下面的輸出可以看出,當(dāng)一個(gè)屬性通過(guò)__getattribute__無(wú)法找到的時(shí)候會(huì)調(diào)用__getattr__。
In?[1]:?class?Test(object): ????...:?????def?__getattribute__(self,?item): ????...:?????????print('call?__getattribute__') ????...:?????????return?super(Test,?self).__getattribute__(item) ????...:?????def?__getattr__(self,?item): ????...:?????????return?'call?__getattr__' ????...: In?[2]:?Test().a call?__getattribute__ Out[2]:?'call?__getattr__'
應(yīng)用
對(duì)于默認(rèn)的字典,Python只支持以obj['foo']形式來(lái)訪問(wèn),不支持obj.foo的形式,我們可以通過(guò)重寫__getattr__讓字典也支持obj['foo']的訪問(wèn)形式,這是一個(gè)非常經(jīng)典常用的用法:
class?Storage(dict): ????""" ????A?Storage?object?is?like?a?dictionary?except?`obj.foo`?can?be?used ????in?addition?to?`obj['foo']`. ????""" ????def?__getattr__(self,?key): ????????try: ????????????return?self[key] ????????except?KeyError?as?k: ????????????raise?AttributeError(k) ????def?__setattr__(self,?key,?value): ????????self[key]?=?value ????def?__delattr__(self,?key): ????????try: ????????????del?self[key] ????????except?KeyError?as?k: ????????????raise?AttributeError(k) ????def?__repr__(self): ????????return?'<Storage ' + dict.__repr__(self) + '>'
我們來(lái)使用一下我們自定義的加強(qiáng)版字典:
>>>?s?=?Storage(a=1) >>>?s['a'] 1 >>>?s.a 1 >>>?s.a?=?2 >>>?s['a'] 2 >>>?del?s.a >>>?s.a ... AttributeError:?'a'
__getitem__
getitem用于通過(guò)下標(biāo)[]的形式來(lái)獲取對(duì)象中的元素,下面我們通過(guò)重寫__getitem__來(lái)實(shí)現(xiàn)一個(gè)自己的list。
class?MyList(object): ????def?__init__(self,?*args): ????????self.numbers?=?args ????def?__getitem__(self,?item): ????????return?self.numbers[item] my_list?=?MyList(1,?2,?3,?4,?6,?5,?3) print?my_list[2]
這個(gè)實(shí)現(xiàn)非常的簡(jiǎn)陋,不支持slice和step等功能,請(qǐng)讀者自行改進(jìn),這里我就不重復(fù)了。
應(yīng)用
下面是參考requests庫(kù)中對(duì)于__getitem__的一個(gè)使用,我們定制了一個(gè)忽略屬性大小寫的字典類。
程序有些復(fù)雜,我稍微解釋一下:由于這里比較簡(jiǎn)單,沒(méi)有使用描述符的需求,所以使用了@property裝飾器來(lái)代替,lower_keys的功能是將實(shí)例字典中的鍵全部轉(zhuǎn)換成小寫并且存儲(chǔ)在字典self._lower_keys中。重寫了__getitem__方法,以后我們?cè)L問(wèn)某個(gè)屬性首先會(huì)將鍵轉(zhuǎn)換為小寫的方式,然后并不會(huì)直接訪問(wèn)實(shí)例字典,而是會(huì)訪問(wèn)字典self._lower_keys去查找。賦值/刪除操作的時(shí)候由于實(shí)例字典會(huì)進(jìn)行變更,為了保持self._lower_keys和實(shí)例字典同步,首先清除self._lower_keys的內(nèi)容,以后我們重新查找鍵的時(shí)候再調(diào)用__getitem__的時(shí)候會(huì)重新新建一個(gè)self._lower_keys。
class?CaseInsensitiveDict(dict): ????@property ????def?lower_keys(self): ????????if?not?hasattr(self,?'_lower_keys')?or?not?self._lower_keys: ????????????self._lower_keys?=?dict((k.lower(),?k)?for?k?in?self.keys()) ????????return?self._lower_keys ????def?_clear_lower_keys(self): ????????if?hasattr(self,?'_lower_keys'): ????????????self._lower_keys.clear() ????def?__contains__(self,?key): ????????return?key.lower()?in?self.lower_keys ????def?__getitem__(self,?key): ????????if?key?in?self: ????????????return?dict.__getitem__(self,?self.lower_keys[key.lower()]) ????def?__setitem__(self,?key,?value): ????????dict.__setitem__(self,?key,?value) ????????self._clear_lower_keys() ????def?__delitem__(self,?key): ????????dict.__delitem__(self,?key) ????????self._lower_keys.clear() ????def?get(self,?key,?default=None): ????????if?key?in?self: ????????????return?self[key] ????????else: ????????????return?default
我們來(lái)調(diào)用一下這個(gè)類:
>>>?d?=?CaseInsensitiveDict() >>>?d['ziwenxie']?=?'ziwenxie' >>>?d['ZiWenXie']?=?'ZiWenXie' >>>?print(d) {'ZiWenXie':?'ziwenxie',?'ziwenxie':?'ziwenxie'} >>>?print(d['ziwenxie']) ziwenxie #?d['ZiWenXie']?=>?d['ziwenxie'] >>>?print(d['ZiWenXie']) ziwenxie
References
HOWTO-GUIDE
DOCUMENTATION
IBM-DEVELOPWORKS
ZHIHU
REQUESTS
WEBPY
本文為作者原創(chuàng),轉(zhuǎn)載請(qǐng)先與作者聯(lián)系。 首發(fā)于我的博客
引言
Descriptors(描述符)是Python語(yǔ)言中一個(gè)深?yuàn)W但很重要的一個(gè)黑魔法,它被廣泛應(yīng)用于Python語(yǔ)言的內(nèi)核,熟練掌握描述符將會(huì)為Python程序員的工具箱添加一個(gè)額外的技巧。本文我將講述描述符的定義以及一些常見(jiàn)的場(chǎng)景,并且在文末會(huì)補(bǔ)充一下__getattr__,__getattribute__, __getitem__這三個(gè)同樣涉及到屬性訪問(wèn)的魔術(shù)方法。
描述符的定義
descr__get__(self,?obj,?objtype=None)?-->?value descr.__set__(self,?obj,?value)?-->?None descr.__delete__(self,?obj)?-->?None
只要一個(gè)object attribute(對(duì)象屬性)定義了上面三個(gè)方法中的任意一個(gè),那么這個(gè)類就可以被稱為描述符類。
描述符基礎(chǔ)
下面這個(gè)例子中我們創(chuàng)建了一個(gè)RevealAcess類,并且實(shí)現(xiàn)了__get__方法,現(xiàn)在這個(gè)類可以被稱為一個(gè)描述符類。
class?RevealAccess(object): ????def?__get__(self,?obj,?objtype): ????????print('self?in?RevealAccess:?{}'.format(self)) ????????print('self:?{}\nobj:?{}\nobjtype:?{}'.format(self,?obj,?objtype)) class?MyClass(object): ????x?=?RevealAccess() ????def?test(self): ????????print('self?in?MyClass:?{}'.format(self))
EX1實(shí)例屬性
接下來(lái)我們來(lái)看一下__get__方法的各個(gè)參數(shù)的含義,在下面這個(gè)例子中,self即RevealAccess類的實(shí)例x,obj即MyClass類的實(shí)例m,objtype顧名思義就是MyClass類自身。從輸出語(yǔ)句可以看出,m.x訪問(wèn)描述符x會(huì)調(diào)用__get__方法。
>>>?m?=?MyClass() >>>?m.test() self?in?MyClass:?<__main__.MyClass object at 0x7f19d4e42160> >>>?m.x self?in?RevealAccess:?<__main__.RevealAccess object at 0x7f19d4e420f0> self:?<__main__.RevealAccess object at 0x7f19d4e420f0> obj:?<__main__.MyClass object at 0x7f19d4e42160> objtype:?<class '__main__.MyClass'>
EX2類屬性
如果通過(guò)類直接訪問(wèn)屬性x,那么obj接直接為None,這還是比較好理解,因?yàn)椴淮嬖贛yClass的實(shí)例。
>>>?MyClass.x self?in?RevealAccess:?<__main__.RevealAccess object at 0x7f53651070f0> self:?<__main__.RevealAccess object at 0x7f53651070f0> obj:?None objtype:?<class '__main__.MyClass'>
描述符的原理
描述符觸發(fā)
上面這個(gè)例子中,我們分別從實(shí)例屬性和類屬性的角度列舉了描述符的用法,下面我們來(lái)仔細(xì)分析一下內(nèi)部的原理:
如果是對(duì)實(shí)例屬性進(jìn)行訪問(wèn),相當(dāng)于調(diào)用了object.__getattribute__(),它將obj.d轉(zhuǎn)譯成了type(obj).__dict__['d'].__get__(obj, type(obj))。
如果是對(duì)類屬性進(jìn)行訪問(wèn),相當(dāng)于調(diào)用了type.__getattribute__(),它將cls.d轉(zhuǎn)譯成了cls.__dict__['d'].__get__(None, cls),轉(zhuǎn)換成Python代碼就是:
def?__getattribute__(self,?key): ????"Emulate?type_getattro()?in?Objects/typeobject.c" ????v?=?object.__getattribute__(self,?key) ????if?hasattr(v,?'__get__'): ????????return?v.__get__(None,?self) ????return?v
簡(jiǎn)單講一下__getattribute__魔術(shù)方法,這個(gè)方法在我們?cè)L問(wèn)一個(gè)對(duì)象的屬性的時(shí)候會(huì)被無(wú)條件調(diào)用,詳細(xì)的細(xì)節(jié)比如和__getattr, __getitem__的區(qū)別我會(huì)在文章的末尾做一個(gè)額外的補(bǔ)充,我們暫時(shí)并不深究。
描述符優(yōu)先級(jí)
首先,描述符分為兩種:
如果一個(gè)對(duì)象同時(shí)定義了__get__()和__set__()方法,則這個(gè)描述符被稱為data descriptor。
如果一個(gè)對(duì)象只定義了__get__()方法,則這個(gè)描述符被稱為non-data descriptor。
我們對(duì)屬性進(jìn)行訪問(wèn)的時(shí)候存在下面四種情況:
data descriptor
instance dict
non-data descriptor
__getattr__()
它們的優(yōu)先級(jí)大小是:
data?descriptor?>?instance?dict?>?non-data?descriptor?>?__getattr__()
這是什么意思呢?就是說(shuō)如果實(shí)例對(duì)象obj中出現(xiàn)了同名的data descriptor->d 和 instance attribute->d,obj.d對(duì)屬性d進(jìn)行訪問(wèn)的時(shí)候,由于data descriptor具有更高的優(yōu)先級(jí),Python便會(huì)調(diào)用type(obj).__dict__['d'].__get__(obj, type(obj))而不是調(diào)用obj.__dict__['d']。但是如果描述符是個(gè)non-data descriptor,Python則會(huì)調(diào)用obj.__dict__['d']。
Property
每次使用描述符的時(shí)候都定義一個(gè)描述符類,這樣看起來(lái)非常繁瑣。Python提供了一種簡(jiǎn)潔的方式用來(lái)向?qū)傩蕴砑訑?shù)據(jù)描述符。
property(fget=None,?fset=None,?fdel=None,?doc=None)?->?property?attribute
fget、fset和fdel分別是類的getter、setter和deleter方法。我們通過(guò)下面的一個(gè)示例來(lái)說(shuō)明如何使用Property:
class?Account(object): ????def?__init__(self): ????????self._acct_num?=?None ????def?get_acct_num(self): ????????return?self._acct_num ????def?set_acct_num(self,?value): ????????self._acct_num?=?value ????def?del_acct_num(self): ????????del?self._acct_num ????acct_num?=?property(get_acct_num,?set_acct_num,?del_acct_num,?'_acct_num?property.')
如果acct是Account的一個(gè)實(shí)例,acct.acct_num將會(huì)調(diào)用getter,acct.acct_num = value將調(diào)用setter,del acct_num.acct_num將調(diào)用deleter。
>>>?acct?=?Account() >>>?acct.acct_num?=?1000 >>>?acct.acct_num 1000
Python也提供了@property裝飾器,對(duì)于簡(jiǎn)單的應(yīng)用場(chǎng)景可以使用它來(lái)創(chuàng)建屬性。一個(gè)屬性對(duì)象擁有g(shù)etter,setter和deleter裝飾器方法,可以使用它們通過(guò)對(duì)應(yīng)的被裝飾函數(shù)的accessor函數(shù)創(chuàng)建屬性的拷貝。
class?Account(object): ????def?__init__(self): ????????self._acct_num?=?None ????@property ?????#?the?_acct_num?property.?the?decorator?creates?a?read-only?property ????def?acct_num(self): ????????return?self._acct_num ????@acct_num.setter ????#?the?_acct_num?property?setter?makes?the?property?writeable ????def?set_acct_num(self,?value): ????????self._acct_num?=?value ????@acct_num.deleter ????def?del_acct_num(self): ????????del?self._acct_num
如果想讓屬性只讀,只需要去掉setter方法。
在運(yùn)行時(shí)創(chuàng)建描述符
我們可以在運(yùn)行時(shí)添加property屬性:
class?Person(object): ????def?addProperty(self,?attribute): ????????#?create?local?setter?and?getter?with?a?particular?attribute?name ????????getter?=?lambda?self:?self._getProperty(attribute) ????????setter?=?lambda?self,?value:?self._setProperty(attribute,?value) ????????#?construct?property?attribute?and?add?it?to?the?class ????????setattr(self.__class__,?attribute,?property(fget=getter,?\ ????????????????????????????????????????????????????fset=setter,?\ ????????????????????????????????????????????????????doc="Auto-generated?method")) ????def?_setProperty(self,?attribute,?value): ????????print("Setting:?{}?=?{}".format(attribute,?value)) ????????setattr(self,?'_'?+?attribute,?value.title()) ????def?_getProperty(self,?attribute): ????????print("Getting:?{}".format(attribute)) ????????return?getattr(self,?'_'?+?attribute)
>>>?user?=?Person() >>>?user.addProperty('name') >>>?user.addProperty('phone') >>>?user.name?=?'john?smith' Setting:?name?=?john?smith >>>?user.phone?=?'12345' Setting:?phone?=?12345 >>>?user.name Getting:?name 'John?Smith' >>>?user.__dict__ {'_phone':?'12345',?'_name':?'John?Smith'}
靜態(tài)方法和類方法
我們可以使用描述符來(lái)模擬Python中的@staticmethod和@classmethod的實(shí)現(xiàn)。我們首先來(lái)瀏覽一下下面這張表:
Transformation | Called from an Object | Called from a Class |
---|---|---|
function | f(obj, *args) | f(*args) |
staticmethod | f(*args) | f(*args) |
classmethod | f(type(obj), *args) | f(klass, *args) |
靜態(tài)方法
對(duì)于靜態(tài)方法f。c.f和C.f是等價(jià)的,都是直接查詢object.__getattribute__(c, ‘f’)或者object.__getattribute__(C, ’f‘)。靜態(tài)方法一個(gè)明顯的特征就是沒(méi)有self變量。
靜態(tài)方法有什么用呢?假設(shè)有一個(gè)處理專門數(shù)據(jù)的容器類,它提供了一些方法來(lái)求平均數(shù),中位數(shù)等統(tǒng)計(jì)數(shù)據(jù)方式,這些方法都是要依賴于相應(yīng)的數(shù)據(jù)的。但是類中可能還有一些方法,并不依賴這些數(shù)據(jù),這個(gè)時(shí)候我們可以將這些方法聲明為靜態(tài)方法,同時(shí)這也可以提高代碼的可讀性。
使用非數(shù)據(jù)描述符來(lái)模擬一下靜態(tài)方法的實(shí)現(xiàn):
class?StaticMethod(object): ????def?__init__(self,?f): ????????self.f?=?f ????def?__get__(self,?obj,?objtype=None): ????????return?self.f
我們來(lái)應(yīng)用一下:
class?MyClass(object): ????@StaticMethod ????def?get_x(x): ????????return?x print(MyClass.get_x(100))??#?output:?100
類方法
Python的@classmethod和@staticmethod的用法有些類似,但是還是有些不同,當(dāng)某些方法只需要得到類的引用而不關(guān)心類中的相應(yīng)的數(shù)據(jù)的時(shí)候就需要使用classmethod了。
使用非數(shù)據(jù)描述符來(lái)模擬一下類方法的實(shí)現(xiàn):
class?ClassMethod(object): ????def?__init__(self,?f): ????????self.f?=?f ????def?__get__(self,?obj,?klass=None): ????????if?klass?is?None: ????????????klass?=?type(obj) ????????def?newfunc(*args): ????????????return?self.f(klass,?*args) ????????return?newfunc
其他的魔術(shù)方法
首次接觸Python魔術(shù)方法的時(shí)候,我也被__get__, __getattribute__, __getattr__, __getitem__之間的區(qū)別困擾到了,它們都是和屬性訪問(wèn)相關(guān)的魔術(shù)方法,其中重寫__getattr__,__getitem__來(lái)構(gòu)造一個(gè)自己的集合類非常的常用,下面我們就通過(guò)一些例子來(lái)看一下它們的應(yīng)用。
__getattr__
Python默認(rèn)訪問(wèn)類/實(shí)例的某個(gè)屬性都是通過(guò)__getattribute__來(lái)調(diào)用的,__getattribute__會(huì)被無(wú)條件調(diào)用,沒(méi)有找到的話就會(huì)調(diào)用__getattr__。如果我們要定制某個(gè)類,通常情況下我們不應(yīng)該重寫__getattribute__,而是應(yīng)該重寫__getattr__,很少看見(jiàn)重寫__getattribute__的情況。
從下面的輸出可以看出,當(dāng)一個(gè)屬性通過(guò)__getattribute__無(wú)法找到的時(shí)候會(huì)調(diào)用__getattr__。
In?[1]:?class?Test(object): ????...:?????def?__getattribute__(self,?item): ????...:?????????print('call?__getattribute__') ????...:?????????return?super(Test,?self).__getattribute__(item) ????...:?????def?__getattr__(self,?item): ????...:?????????return?'call?__getattr__' ????...: In?[2]:?Test().a call?__getattribute__ Out[2]:?'call?__getattr__'
應(yīng)用
對(duì)于默認(rèn)的字典,Python只支持以obj['foo']形式來(lái)訪問(wèn),不支持obj.foo的形式,我們可以通過(guò)重寫__getattr__讓字典也支持obj['foo']的訪問(wèn)形式,這是一個(gè)非常經(jīng)典常用的用法:
class?Storage(dict): ????""" ????A?Storage?object?is?like?a?dictionary?except?`obj.foo`?can?be?used ????in?addition?to?`obj['foo']`. ????""" ????def?__getattr__(self,?key): ????????try: ????????????return?self[key] ????????except?KeyError?as?k: ????????????raise?AttributeError(k) ????def?__setattr__(self,?key,?value): ????????self[key]?=?value ????def?__delattr__(self,?key): ????????try: ????????????del?self[key] ????????except?KeyError?as?k: ????????????raise?AttributeError(k) ????def?__repr__(self): ????????return?'<Storage ' + dict.__repr__(self) + '>'
我們來(lái)使用一下我們自定義的加強(qiáng)版字典:
>>>?s?=?Storage(a=1) >>>?s['a'] 1 >>>?s.a 1 >>>?s.a?=?2 >>>?s['a'] 2 >>>?del?s.a >>>?s.a ... AttributeError:?'a'
__getitem__
getitem用于通過(guò)下標(biāo)[]的形式來(lái)獲取對(duì)象中的元素,下面我們通過(guò)重寫__getitem__來(lái)實(shí)現(xiàn)一個(gè)自己的list。
class?MyList(object): ????def?__init__(self,?*args): ????????self.numbers?=?args ????def?__getitem__(self,?item): ????????return?self.numbers[item] my_list?=?MyList(1,?2,?3,?4,?6,?5,?3) print?my_list[2]
這個(gè)實(shí)現(xiàn)非常的簡(jiǎn)陋,不支持slice和step等功能,請(qǐng)讀者自行改進(jìn),這里我就不重復(fù)了。
應(yīng)用
下面是參考requests庫(kù)中對(duì)于__getitem__的一個(gè)使用,我們定制了一個(gè)忽略屬性大小寫的字典類。
程序有些復(fù)雜,我稍微解釋一下:由于這里比較簡(jiǎn)單,沒(méi)有使用描述符的需求,所以使用了@property裝飾器來(lái)代替,lower_keys的功能是將實(shí)例字典中的鍵全部轉(zhuǎn)換成小寫并且存儲(chǔ)在字典self._lower_keys中。重寫了__getitem__方法,以后我們?cè)L問(wèn)某個(gè)屬性首先會(huì)將鍵轉(zhuǎn)換為小寫的方式,然后并不會(huì)直接訪問(wèn)實(shí)例字典,而是會(huì)訪問(wèn)字典self._lower_keys去查找。賦值/刪除操作的時(shí)候由于實(shí)例字典會(huì)進(jìn)行變更,為了保持self._lower_keys和實(shí)例字典同步,首先清除self._lower_keys的內(nèi)容,以后我們重新查找鍵的時(shí)候再調(diào)用__getitem__的時(shí)候會(huì)重新新建一個(gè)self._lower_keys。
class?CaseInsensitiveDict(dict): ????@property ????def?lower_keys(self): ????????if?not?hasattr(self,?'_lower_keys')?or?not?self._lower_keys: ????????????self._lower_keys?=?dict((k.lower(),?k)?for?k?in?self.keys()) ????????return?self._lower_keys ????def?_clear_lower_keys(self): ????????if?hasattr(self,?'_lower_keys'): ????????????self._lower_keys.clear() ????def?__contains__(self,?key): ????????return?key.lower()?in?self.lower_keys ????def?__getitem__(self,?key): ????????if?key?in?self: ????????????return?dict.__getitem__(self,?self.lower_keys[key.lower()]) ????def?__setitem__(self,?key,?value): ????????dict.__setitem__(self,?key,?value) ????????self._clear_lower_keys() ????def?__delitem__(self,?key): ????????dict.__delitem__(self,?key) ????????self._lower_keys.clear() ????def?get(self,?key,?default=None): ????????if?key?in?self: ????????????return?self[key] ????????else: ????????????return?default
我們來(lái)調(diào)用一下這個(gè)類:
>>>?d?=?CaseInsensitiveDict() >>>?d['ziwenxie']?=?'ziwenxie' >>>?d['ZiWenXie']?=?'ZiWenXie' >>>?print(d) {'ZiWenXie':?'ziwenxie',?'ziwenxie':?'ziwenxie'} >>>?print(d['ziwenxie']) ziwenxie #?d['ZiWenXie']?=>?d['ziwenxie'] >>>?print(d['ZiWenXie']) ziwenxie
更多Python黑魔法之描述符相關(guān)文章請(qǐng)關(guān)注PHP中文網(wǎng)!

熱AI工具

Undress AI Tool
免費(fèi)脫衣服圖片

Undresser.AI Undress
人工智能驅(qū)動(dòng)的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover
用于從照片中去除衣服的在線人工智能工具。

Clothoff.io
AI脫衣機(jī)

Video Face Swap
使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的代碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
功能強(qiáng)大的PHP集成開(kāi)發(fā)環(huán)境

Dreamweaver CS6
視覺(jué)化網(wǎng)頁(yè)開(kāi)發(fā)工具

SublimeText3 Mac版
神級(jí)代碼編輯軟件(SublimeText3)

要實(shí)現(xiàn)PHP結(jié)合AI進(jìn)行文本糾錯(cuò)與語(yǔ)法優(yōu)化,需按以下步驟操作:1.選擇適合的AI模型或API,如百度、騰訊API或開(kāi)源NLP庫(kù);2.通過(guò)PHP的curl或Guzzle調(diào)用API并處理返回結(jié)果;3.在應(yīng)用中展示糾錯(cuò)信息并允許用戶選擇是否采納;4.使用php-l和PHP_CodeSniffer進(jìn)行語(yǔ)法檢測(cè)與代碼優(yōu)化;5.持續(xù)收集反饋并更新模型或規(guī)則以提升效果。選擇AIAPI時(shí)應(yīng)重點(diǎn)評(píng)估準(zhǔn)確率、響應(yīng)速度、價(jià)格及對(duì)PHP的支持。代碼優(yōu)化應(yīng)遵循PSR規(guī)范、合理使用緩存、避免循環(huán)查詢、定期審查代碼,并借助X

用戶語(yǔ)音輸入通過(guò)前端JavaScript的MediaRecorderAPI捕獲并發(fā)送至PHP后端;2.PHP將音頻保存為臨時(shí)文件后調(diào)用STTAPI(如Google或百度語(yǔ)音識(shí)別)轉(zhuǎn)換為文本;3.PHP將文本發(fā)送至AI服務(wù)(如OpenAIGPT)獲取智能回復(fù);4.PHP再調(diào)用TTSAPI(如百度或Google語(yǔ)音合成)將回復(fù)轉(zhuǎn)為語(yǔ)音文件;5.PHP將語(yǔ)音文件流式返回前端播放,完成交互。整個(gè)流程由PHP主導(dǎo)數(shù)據(jù)流轉(zhuǎn)與錯(cuò)誤處理,確保各環(huán)節(jié)無(wú)縫銜接。

本文為您精選了多個(gè)頂級(jí)的Python“成品”項(xiàng)目網(wǎng)站與高水平“大片”級(jí)學(xué)習(xí)資源入口。無(wú)論您是想尋找開(kāi)發(fā)靈感、觀摩學(xué)習(xí)大師級(jí)的源代碼,還是系統(tǒng)性地提升實(shí)戰(zhàn)能力,這些平臺(tái)都是不容錯(cuò)過(guò)的寶庫(kù),能幫助您快速成長(zhǎng)為Python高手。

收集用戶行為數(shù)據(jù)需通過(guò)PHP記錄瀏覽、搜索、購(gòu)買等信息至數(shù)據(jù)庫(kù),并清洗分析以挖掘興趣偏好;2.推薦算法選擇應(yīng)根據(jù)數(shù)據(jù)特征決定:基于內(nèi)容、協(xié)同過(guò)濾、規(guī)則或混合推薦;3.協(xié)同過(guò)濾在PHP中可實(shí)現(xiàn)為計(jì)算用戶余弦相似度、選K近鄰、加權(quán)預(yù)測(cè)評(píng)分并推薦高分商品;4.性能評(píng)估用準(zhǔn)確率、召回率、F1值及CTR、轉(zhuǎn)化率并通過(guò)A/B測(cè)試驗(yàn)證效果;5.冷啟動(dòng)問(wèn)題可通過(guò)商品屬性、用戶注冊(cè)信息、熱門推薦和專家評(píng)價(jià)緩解;6.性能優(yōu)化手段包括緩存推薦結(jié)果、異步處理、分布式計(jì)算與SQL查詢優(yōu)化,從而提升推薦效率與用戶體驗(yàn)。

選擇合適的PHP框架需根據(jù)項(xiàng)目需求綜合考慮:Laravel適合快速開(kāi)發(fā),提供EloquentORM和Blade模板引擎,便于數(shù)據(jù)庫(kù)操作和動(dòng)態(tài)表單渲染;Symfony更靈活,適合復(fù)雜系統(tǒng);CodeIgniter輕量,適用于對(duì)性能要求較高的簡(jiǎn)單應(yīng)用。2.確保AI模型準(zhǔn)確性需從高質(zhì)量數(shù)據(jù)訓(xùn)練、合理選擇評(píng)估指標(biāo)(如準(zhǔn)確率、召回率、F1值)、定期性能評(píng)估與模型調(diào)優(yōu)入手,并通過(guò)單元測(cè)試和集成測(cè)試保障代碼質(zhì)量,同時(shí)持續(xù)監(jiān)控輸入數(shù)據(jù)以防止數(shù)據(jù)漂移。3.保護(hù)用戶隱私需采取多項(xiàng)措施:對(duì)敏感數(shù)據(jù)進(jìn)行加密存儲(chǔ)(如AES

使用Seaborn的jointplot可快速可視化兩個(gè)變量間的關(guān)系及各自分布;2.基礎(chǔ)散點(diǎn)圖通過(guò)sns.jointplot(data=tips,x="total_bill",y="tip",kind="scatter")實(shí)現(xiàn),中心為散點(diǎn)圖,上下和右側(cè)顯示直方圖;3.添加回歸線和密度信息可用kind="reg",并結(jié)合marginal_kws設(shè)置邊緣圖樣式;4.數(shù)據(jù)量大時(shí)推薦kind="hex",用

1.PHP在AI內(nèi)容推薦系統(tǒng)中主要承擔(dān)數(shù)據(jù)收集、API通信、業(yè)務(wù)規(guī)則處理、緩存優(yōu)化與推薦展示等角色,而非直接執(zhí)行復(fù)雜模型訓(xùn)練;2.系統(tǒng)通過(guò)PHP收集用戶行為與內(nèi)容數(shù)據(jù),調(diào)用后端AI服務(wù)(如Python模型)獲取推薦結(jié)果,并利用Redis緩存提升性能;3.基礎(chǔ)推薦算法如協(xié)同過(guò)濾或內(nèi)容相似度可在PHP中實(shí)現(xiàn)輕量級(jí)邏輯,但大規(guī)模計(jì)算仍依賴專業(yè)AI服務(wù);4.優(yōu)化需關(guān)注實(shí)時(shí)性、冷啟動(dòng)、多樣性及反饋閉環(huán),挑戰(zhàn)包括高并發(fā)性能、模型更新平穩(wěn)性、數(shù)據(jù)合規(guī)與推薦可解釋性,PHP需協(xié)同消息隊(duì)列、數(shù)據(jù)庫(kù)與前端共同構(gòu)建穩(wěn)

PHP開(kāi)發(fā)AI文本摘要的核心是作為協(xié)調(diào)器調(diào)用外部AI服務(wù)API(如OpenAI、HuggingFace),實(shí)現(xiàn)文本預(yù)處理、API請(qǐng)求、響應(yīng)解析與結(jié)果展示;2.局限性在于計(jì)算性能弱、AI生態(tài)薄弱,應(yīng)對(duì)策略為借力API、服務(wù)解耦和異步處理;3.模型選擇需權(quán)衡摘要質(zhì)量、成本、延遲、并發(fā)、數(shù)據(jù)隱私,推薦使用GPT或BART/T5等抽象式模型;4.性能優(yōu)化包括緩存、異步隊(duì)列、批量處理和就近區(qū)域選擇,錯(cuò)誤處理需覆蓋限流重試、網(wǎng)絡(luò)超時(shí)、密鑰安全、輸入驗(yàn)證及日志記錄,以確保系統(tǒng)穩(wěn)定高效運(yùn)行。
