Introduction
Les descripteurs (descripteurs) sont une magie noire profonde mais importante dans le langage Python. Ils sont largement utilisés dans le noyau du langage Python. La ma?trise des descripteurs bénéficiera aux programmeurs Python. astuce. Dans cet article, je décrirai la définition des descripteurs et quelques scénarios courants, et à la fin de l'article j'ajouterai __getattr__, __getattribute__ et __getitem__, trois méthodes magiques qui impliquent également l'accès aux attributs.
Définition du descripteur
descr__get__(self,?obj,?objtype=None)?-->?value descr.__set__(self,?obj,?value)?-->?None descr.__delete__(self,?obj)?-->?None
Tant qu'un attribut d'objet définit l'une des trois méthodes ci-dessus, alors cette classe peut être appelée une classe de descripteur.
Bases du descripteur
Dans l'exemple suivant, nous créons une classe RevealAcess et implémentons la méthode __get__. Cette classe peut maintenant être appelée une classe de descripteur.
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))
Attributs de l'instance EX1
Jetons ensuite un coup d'?il à la signification de chaque paramètre de la méthode __get__ Dans l'exemple suivant, self est l'instance x de la classe RevealAccess, et obj est l'instance m de la classe MyClass, objtype, comme son nom l'indique, est la classe MyClass elle-même. Comme le montre l'instruction de sortie, le descripteur d'accès m.x x appellera la méthode __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'>
Attribut de classe EX2
Si l'attribut x est accessible directement via la classe, alors la connexion obj est directement None, ce qui est plus facile à comprendre car il n'y a pas d'instance de MyClass.
>>>?MyClass.x self?in?RevealAccess:?<__main__.RevealAccess object at 0x7f53651070f0> self:?<__main__.RevealAccess object at 0x7f53651070f0> obj:?None objtype:?<class '__main__.MyClass'>
Principe du descripteur
Déclencheur de descripteur
Dans l'exemple ci-dessus, nous avons énuméré l'utilisation des descripteurs du point de vue des attributs d'instance et des attributs de classe respectivement. Examinons de plus près les principes internes?:
Si vous accédez aux attributs d'une instance, cela équivaut à appeler object.__getattribute__(), qui traduit obj.d en type (obj).__dict__[ 'd'].__get__(obj, tapez(obj)).
Si vous accédez à un attribut de classe, cela équivaut à appeler type.__getattribute__(), qui traduit cls.d en cls.__dict__['d'].__get__( Aucun, cls), converti en code 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
Parlons brièvement de la méthode magique __getattribute__ Cette méthode sera appelée de manière inconditionnelle lorsque nous accéderons aux attributs d'un objet, détails détaillés. comme la différence avec __getattr, __getitem__ Je ferai un supplément supplémentaire en fin d'article, nous n'y reviendrons pas pour le moment.
Priorité des descripteurs
Tout d'abord, les descripteurs sont divisés en deux types?:
Si un objet définit à la fois la méthode __get__() et __set__() , ce descripteur est appelé descripteur de données.
Si un objet définit uniquement la méthode __get__(), ce descripteur est appelé un descripteur non-données.
Il existe quatre situations dans lesquelles nous accédons aux attributs?:
descripteur de données
instance dict
descripteur de non-données
__getattr__()
leur priorité La taille est :
data?descriptor?>?instance?dict?>?non-data?descriptor?>?__getattr__()
Qu'est-ce que cela signifie ? C'est-à-dire que si le descripteur de données->d et l'attribut d'instance->d avec le même nom apparaissent dans l'objet d'instance obj, lorsque obj.d accède à l'attribut d, Python l'appellera car le descripteur de données a une priorité plus élevée. . type(obj).__dict__['d'].__get__(obj, type(obj)) au lieu d'appeler obj.__dict__['d']. Mais si le descripteur n'est pas un descripteur de données, Python appellera obj.__dict__['d'].
Propriété
Définir une classe de descripteur à chaque fois qu'un descripteur est utilisé semble très fastidieux. Python fournit un moyen concis d'ajouter des descripteurs de données aux propriétés.
property(fget=None,?fset=None,?fdel=None,?doc=None)?->?property?attribute
fget, fset et fdel sont respectivement les méthodes getter, setter et deleter de la classe. Nous utilisons l'exemple suivant pour illustrer comment utiliser 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.')
Si acct est une instance de Account, acct.acct_num appellera le getter, acct.acct_num = value appellera le setter, del acct_num. acct_num delete sera appelé.
>>>?acct?=?Account() >>>?acct.acct_num?=?1000 >>>?acct.acct_num 1000
Python fournit également le décorateur @property, qui peut être utilisé pour créer des propriétés pour des scénarios d'application simples. Un objet de propriété possède des méthodes de décorateur getter, setter et delete, qui peuvent être utilisées pour créer une copie de la propriété via la fonction accesseur de la fonction décorée correspondante.
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
Si vous souhaitez que la propriété soit en lecture seule, supprimez simplement la méthode setter.
Créer des descripteurs au moment de l'exécution
Nous pouvons ajouter des propriétés au moment de l'exécution?:
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'}
Méthodes statiques et méthodes de classe
Nous pouvons utiliser des descripteurs pour simuler l'implémentation de @staticmethod et @classmethod en Python. Parcourons d’abord le tableau ci-dessous?:
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è)處理專(zhuān)門(mén)數(shù)據(jù)的容器類(lèi),它提供了一些方法來(lái)求平均數(shù),中位數(shù)等統(tǒng)計(jì)數(shù)據(jù)方式,這些方法都是要依賴于相應(yīng)的數(shù)據(jù)的。但是類(lèi)中可能還有一些方法,并不依賴這些數(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
類(lèi)方法
Python的@classmethod和@staticmethod的用法有些類(lèi)似,但是還是有些不同,當(dāng)某些方法只需要得到類(lèi)的引用而不關(guān)心類(lèi)中的相應(yīng)的數(shù)據(jù)的時(shí)候就需要使用classmethod了。
使用非數(shù)據(jù)描述符來(lái)模擬一下類(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ù)方法,其中重寫(xiě)__getattr__,__getitem__來(lái)構(gòu)造一個(gè)自己的集合類(lèi)非常的常用,下面我們就通過(guò)一些例子來(lái)看一下它們的應(yīng)用。
__getattr__
Python默認(rèn)訪問(wèn)類(lèi)/實(shí)例的某個(gè)屬性都是通過(guò)__getattribute__來(lái)調(diào)用的,__getattribute__會(huì)被無(wú)條件調(diào)用,沒(méi)有找到的話就會(huì)調(diào)用__getattr__。如果我們要定制某個(gè)類(lèi),通常情況下我們不應(yīng)該重寫(xiě)__getattribute__,而是應(yīng)該重寫(xiě)__getattr__,很少看見(jiàn)重寫(xiě)__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ò)重寫(xiě)__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ò)重寫(xiě)__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è)忽略屬性大小寫(xiě)的字典類(lèi)。
程序有些復(fù)雜,我稍微解釋一下:由于這里比較簡(jiǎn)單,沒(méi)有使用描述符的需求,所以使用了@property裝飾器來(lái)代替,lower_keys的功能是將實(shí)例字典中的鍵全部轉(zhuǎn)換成小寫(xiě)并且存儲(chǔ)在字典self._lower_keys中。重寫(xiě)了__getitem__方法,以后我們?cè)L問(wèn)某個(gè)屬性首先會(huì)將鍵轉(zhuǎn)換為小寫(xiě)的方式,然后并不會(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è)類(lèi):
>>>?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è)類(lèi)就可以被稱(chēng)為描述符類(lèi)。
描述符基礎(chǔ)
下面這個(gè)例子中我們創(chuàng)建了一個(gè)RevealAcess類(lèi),并且實(shí)現(xiàn)了__get__方法,現(xiàn)在這個(gè)類(lèi)可以被稱(chēng)為一個(gè)描述符類(lèi)。
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類(lèi)的實(shí)例x,obj即MyClass類(lèi)的實(shí)例m,objtype顧名思義就是MyClass類(lèi)自身。從輸出語(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類(lèi)屬性
如果通過(guò)類(lèi)直接訪問(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)屬性的角度列舉了描述符的用法,下面我們來(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ì)類(lèi)屬性進(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è)描述符被稱(chēng)為data descriptor。
如果一個(gè)對(duì)象只定義了__get__()方法,則這個(gè)描述符被稱(chēng)為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),這樣看起來(lái)非常繁瑣。Python提供了一種簡(jiǎn)潔的方式用來(lái)向?qū)傩蕴砑訑?shù)據(jù)描述符。
property(fget=None,?fset=None,?fdel=None,?doc=None)?->?property?attribute
fget、fset和fdel分別是類(lèi)的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)方法
我們可以使用描述符來(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è)處理專(zhuān)門(mén)數(shù)據(jù)的容器類(lèi),它提供了一些方法來(lái)求平均數(shù),中位數(shù)等統(tǒng)計(jì)數(shù)據(jù)方式,這些方法都是要依賴于相應(yīng)的數(shù)據(jù)的。但是類(lèi)中可能還有一些方法,并不依賴這些數(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
類(lèi)方法
Python的@classmethod和@staticmethod的用法有些類(lèi)似,但是還是有些不同,當(dāng)某些方法只需要得到類(lèi)的引用而不關(guān)心類(lèi)中的相應(yīng)的數(shù)據(jù)的時(shí)候就需要使用classmethod了。
使用非數(shù)據(jù)描述符來(lái)模擬一下類(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ù)方法,其中重寫(xiě)__getattr__,__getitem__來(lái)構(gòu)造一個(gè)自己的集合類(lèi)非常的常用,下面我們就通過(guò)一些例子來(lái)看一下它們的應(yīng)用。
__getattr__
Python默認(rèn)訪問(wèn)類(lèi)/實(shí)例的某個(gè)屬性都是通過(guò)__getattribute__來(lái)調(diào)用的,__getattribute__會(huì)被無(wú)條件調(diào)用,沒(méi)有找到的話就會(huì)調(diào)用__getattr__。如果我們要定制某個(gè)類(lèi),通常情況下我們不應(yīng)該重寫(xiě)__getattribute__,而是應(yīng)該重寫(xiě)__getattr__,很少看見(jiàn)重寫(xiě)__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ò)重寫(xiě)__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ò)重寫(xiě)__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è)忽略屬性大小寫(xiě)的字典類(lèi)。
程序有些復(fù)雜,我稍微解釋一下:由于這里比較簡(jiǎn)單,沒(méi)有使用描述符的需求,所以使用了@property裝飾器來(lái)代替,lower_keys的功能是將實(shí)例字典中的鍵全部轉(zhuǎn)換成小寫(xiě)并且存儲(chǔ)在字典self._lower_keys中。重寫(xiě)了__getitem__方法,以后我們?cè)L問(wèn)某個(gè)屬性首先會(huì)將鍵轉(zhuǎn)換為小寫(xiě)的方式,然后并不會(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è)類(lèi):
>>>?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)!

Outils d'IA chauds

Undress AI Tool
Images de déshabillage gratuites

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Clothoff.io
Dissolvant de vêtements AI

Video Face Swap
échangez les visages dans n'importe quelle vidéo sans effort grace à notre outil d'échange de visage AI entièrement gratuit?!

Article chaud

Outils chauds

Bloc-notes++7.3.1
éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Le polymorphisme est un concept de base dans la programmation orientée objet Python, se référant à "une interface, plusieurs implémentations", permettant le traitement unifié de différents types d'objets. 1. Le polymorphisme est implémenté par la réécriture de la méthode. Les sous-classes peuvent redéfinir les méthodes de classe parent. Par exemple, la méthode Spoke () de classe animale a des implémentations différentes dans les sous-classes de chiens et de chats. 2. Les utilisations pratiques du polymorphisme comprennent la simplification de la structure du code et l'amélioration de l'évolutivité, tels que l'appel de la méthode Draw () uniformément dans le programme de dessin graphique, ou la gestion du comportement commun des différents personnages dans le développement de jeux. 3. Le polymorphisme de l'implémentation de Python doit satisfaire: la classe parent définit une méthode, et la classe enfant remplace la méthode, mais ne nécessite pas l'héritage de la même classe parent. Tant que l'objet implémente la même méthode, c'est ce qu'on appelle le "type de canard". 4. Les choses à noter incluent la maintenance

Les itérateurs sont des objets qui implémentent __iter __ () et __Next __ (). Le générateur est une version simplifiée des itérateurs, qui implémentent automatiquement ces méthodes via le mot clé de rendement. 1. L'ITERATOR renvoie un élément chaque fois qu'il appelle Next () et lance une exception d'arrêt lorsqu'il n'y a plus d'éléments. 2. Le générateur utilise la définition de la fonction pour générer des données à la demande, enregistrer la mémoire et prendre en charge les séquences infinies. 3. Utilisez des itérateurs lors du traitement des ensembles existants, utilisez un générateur lors de la génération de Big Data ou de l'évaluation paresseuse, telles que le chargement ligne par ligne lors de la lecture de fichiers volumineux. Remarque: les objets itérables tels que les listes ne sont pas des itérateurs. Ils doivent être recréés après que l'itérateur a atteint sa fin, et le générateur ne peut le traverser qu'une seule fois.

Une méthode courante pour parcourir deux listes simultanément dans Python consiste à utiliser la fonction zip (), qui appariera plusieurs listes dans l'ordre et sera la plus courte; Si la longueur de liste est incohérente, vous pouvez utiliser itertools.zip_langest () pour être le plus long et remplir les valeurs manquantes; Combiné avec enumerate (), vous pouvez obtenir l'index en même temps. 1.zip () est concis et pratique, adapté à l'itération des données appariées; 2.zip_langest () peut remplir la valeur par défaut lorsqu'il s'agit de longueurs incohérentes; 3. L'énumération (zip ()) peut obtenir des indices pendant la traversée, en répondant aux besoins d'une variété de scénarios complexes.

Assert est un outil d'affirmation utilisé dans Python pour le débogage et lance une affirmation d'établissement lorsque la condition n'est pas remplie. Sa syntaxe est affirmer la condition plus les informations d'erreur facultatives, qui conviennent à la vérification de la logique interne telle que la vérification des paramètres, la confirmation d'état, etc., mais ne peuvent pas être utilisées pour la sécurité ou la vérification des entrées des utilisateurs, et doit être utilisée en conjonction avec des informations d'invite claires. Il n'est disponible que pour le débogage auxiliaire au stade de développement plut?t que pour remplacer la manipulation des exceptions.

TypeHintsInpythonsolvetheproblebandofambigu?té et opposant à un montant de type de type parallèlement au développement de l'aménagement en fonction des types de type.

Inpython, itérateurslawjectsThatallowloopingthroughCollectionsbyImpleting __iter __ () et__Next __ (). 1) iteratorsworkVeatheitorat

Pour faire d'un objet un générateur, vous devez générer des valeurs à la demande en définissant une fonction contenant un rendement, en implémentant des classes itérables qui implémentent \ _ \ _ iter \ _ et \ _next \ _ méthodes, ou à l'aide d'expressions de générateur. 1. Définissez une fonction contenant le rendement, renvoyez l'objet générateur lorsqu'il est appelé et générez des valeurs successivement; 2. Implémentez le \ _ \ _ iter \ _ \ _ et \ _ \ _ Next \ _ \ _ \ _ dans une classe personnalisée pour contr?ler la logique itérative; 3. Utilisez des expressions de générateur pour créer rapidement un générateur léger, adapté aux transformations simples ou au filtrage. Ces méthodes évitent de charger toutes les données dans la mémoire, améliorant ainsi l'efficacité de la mémoire.

Pour appeler le code Python en C, vous devez d'abord initialiser l'interprète, puis vous pouvez réaliser l'interaction en exécutant des cha?nes, des fichiers ou en appelant des fonctions spécifiques. 1. Initialisez l'interpréteur avec py_initialize () et fermez-le avec py_finalalize (); 2. Exécuter le code de cha?ne ou pyrun_simplefile avec pyrun_simplefile; 3. Importez des modules via pyimport_importmodule, obtenez la fonction via pyObject_getattrstring, construisez des paramètres de py_buildvalue, appelez la fonction et le retour de processus
