はじめに
ディスクリプター (ディスクリプター) は、Python 言語の奧深く重要な黒魔術であり、Python 言語のカーネルで広く使用されており、Python プログラマーのツールボックスに追加のツールが追加されます。この記事では、記述子の定義といくつかの一般的なシナリオについて説明し、記事の最後に、屬性アクセスも関係する 3 つのマジック メソッド __getattr__、__getattribute__、および __getitem__ を追加します。
記述子の定義
descr__get__(self,?obj,?objtype=None)?-->?value descr.__set__(self,?obj,?value)?-->?None descr.__delete__(self,?obj)?-->?None
オブジェクト屬性が上記の 3 つのメソッドのいずれかを定義している限り、このクラスを記述子クラスと呼ぶことができます。
ディスクリプタの基本
次の例では、RevealAcess クラスを作成し、__get__ メソッドを実裝します。これで、このクラスをディスクリプタ クラスと呼ぶことができます。
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 インスタンス屬性
次に、__get__ メソッドの各パラメーターの意味を見てみましょう。次の例では、self は RevealAccess クラスのインスタンス x、obj は MyClass クラスのインスタンス m です。 objtype は、名前が示すように MyClass です。出力ステートメントからわかるように、m.x アクセス記述子 x は __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 クラス屬性
屬性 x がクラスを通じて直接アクセスされる場合、obj 接続は直接 None になります。これは、MyClass のインスタンスがないため理解しやすくなります。
>>>?MyClass.x self?in?RevealAccess:?<__main__.RevealAccess object at 0x7f53651070f0> self:?<__main__.RevealAccess object at 0x7f53651070f0> obj:?None objtype:?<class '__main__.MyClass'>
ディスクリプタの原理
ディスクリプタトリガー
上の例では、インスタンス屬性とクラス屬性の観點からディスクリプタの使用法をリストしました。內(nèi)部原則を注意深く分析してみましょう:
インスタンス屬性にアクセスする場合、これは、obj.d を type(obj).__dict__['d'].__get__(obj, type(obj)) に変換する object.__getattribute__() を呼び出すことと同じです。
クラス屬性にアクセスしている場合、それは type.__getattribute__() を呼び出すことと同じであり、これは cls.d を cls.__dict__['d'].__get__(None, cls) に変換し、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
__getattribute__ マジック メソッドについて簡単に説明します。このメソッドは、オブジェクトの屬性にアクセスするときに無條件で呼び出されます。__getattr と __getitem__ の違いなどの詳細については、この記事で説明します。最後の追加事項ですが、今は詳しく説明しません。
ディスクリプタの優(yōu)先順位
まず、ディスクリプタは 2 つのタイプに分けられます:
オブジェクトが __get__() メソッドと __set__() メソッドの両方を定義している場合、このディスクリプタはデータ ディスクリプタと呼ばれます。
オブジェクトが __get__() メソッドのみを定義している場合、この記述子は非データ記述子と呼ばれます。
屬性にアクセスする場合は 4 つの狀況があります:
データ記述子
インスタンス辭書
非データ記述子
__getattr__()
彼らの優(yōu)先サイズは次のとおりです:
りーこれはどういう意味ですか?つまり、同じ名前のデータ記述子->d とインスタンス屬性->d がインスタンス オブジェクト obj に存在する場合、obj.d が屬性 d にアクセスすると、データ記述子の優(yōu)先順位が高いため、Python はそれを呼び出します。 . obj.__dict__['d'] を呼び出す代わりに、type(obj).__dict__['d'].__get__(obj, type(obj)) を呼び出します。ただし、記述子が非データ記述子の場合、Python は obj.__dict__['d'] を呼び出します。
プロパティ
記述子を使用するたびに記述子クラスを定義するのは、非常に面倒に思えます。 Python は、データ記述子をプロパティに追加する簡潔な方法を提供します。
data?descriptor?>?instance?dict?>?non-data?descriptor?>?__getattr__()
fget、fset、fdel はそれぞれ、クラスのゲッター、セッター、デリーター メソッドです。次の例を使用して、Property の使用方法を説明します。
property(fget=None,?fset=None,?fdel=None,?doc=None)?->?property?attribute
acct が Account のインスタンスの場合、acct.acct_num はゲッターを呼び出し、acct.acct_num = value はセッターを呼び出し、del acct_num.acct_num はデリーターを呼び出します。 。
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.')
Python は @property デコレーターも提供しており、これを使用して単純なアプリケーション シナリオのプロパティを作成できます。プロパティ オブジェクトにはゲッター、セッター、および削除デコレーター メソッドがあり、これらを使用して、対応する裝飾関數(shù)のアクセサー関數(shù)を通じてプロパティのコピーを作成できます。
>>>?acct?=?Account() >>>?acct.acct_num?=?1000 >>>?acct.acct_num 1000
プロパティを読み取り専用にしたい場合は、setter メソッドを削除するだけです。
実行時に記述子を作成する
実行時にプロパティ屬性を追加できます:
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
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)
靜的メソッドとクラスメソッド
記述子を使用して、Python での @staticmethod と @classmethod の実裝をシミュレートできます。まず次の表を參照してみましょう:
Transformation | オブジェクトから呼び出される | クラスから呼び出される |
---|---|---|
function | f(obj, *args) | f(*args ) |
靜的メソッド | f(*args) | f(*args) |
クラスメソッド | f(type(obj), *args) | f(klass, *args) |
靜態(tài)方法
對于靜態(tài)方法f。c.f和C.f是等價的,都是直接查詢object.__getattribute__(c, ‘f’)或者object.__getattribute__(C, ’f‘)。靜態(tài)方法一個明顯的特征就是沒有self變量。
靜態(tài)方法有什么用呢?假設有一個處理專門數(shù)據(jù)的容器類,它提供了一些方法來求平均數(shù),中位數(shù)等統(tǒng)計數(shù)據(jù)方式,這些方法都是要依賴于相應的數(shù)據(jù)的。但是類中可能還有一些方法,并不依賴這些數(shù)據(jù),這個時候我們可以將這些方法聲明為靜態(tài)方法,同時這也可以提高代碼的可讀性。
使用非數(shù)據(jù)描述符來模擬一下靜態(tài)方法的實現(xiàn):
class?StaticMethod(object): ????def?__init__(self,?f): ????????self.f?=?f ????def?__get__(self,?obj,?objtype=None): ????????return?self.f
我們來應用一下:
class?MyClass(object): ????@StaticMethod ????def?get_x(x): ????????return?x print(MyClass.get_x(100))??#?output:?100
類方法
Python的@classmethod和@staticmethod的用法有些類似,但是還是有些不同,當某些方法只需要得到類的引用而不關心類中的相應的數(shù)據(jù)的時候就需要使用classmethod了。
使用非數(shù)據(jù)描述符來模擬一下類方法的實現(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
其他的魔術方法
首次接觸Python魔術方法的時候,我也被__get__, __getattribute__, __getattr__, __getitem__之間的區(qū)別困擾到了,它們都是和屬性訪問相關的魔術方法,其中重寫__getattr__,__getitem__來構造一個自己的集合類非常的常用,下面我們就通過一些例子來看一下它們的應用。
__getattr__
Python默認訪問類/實例的某個屬性都是通過__getattribute__來調用的,__getattribute__會被無條件調用,沒有找到的話就會調用__getattr__。如果我們要定制某個類,通常情況下我們不應該重寫__getattribute__,而是應該重寫__getattr__,很少看見重寫__getattribute__的情況。
從下面的輸出可以看出,當一個屬性通過__getattribute__無法找到的時候會調用__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__'
應用
對于默認的字典,Python只支持以obj['foo']形式來訪問,不支持obj.foo的形式,我們可以通過重寫__getattr__讓字典也支持obj['foo']的訪問形式,這是一個非常經(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) + '>'
我們來使用一下我們自定義的加強版字典:
>>>?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用于通過下標[]的形式來獲取對象中的元素,下面我們通過重寫__getitem__來實現(xiàn)一個自己的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]
這個實現(xiàn)非常的簡陋,不支持slice和step等功能,請讀者自行改進,這里我就不重復了。
應用
下面是參考requests庫中對于__getitem__的一個使用,我們定制了一個忽略屬性大小寫的字典類。
程序有些復雜,我稍微解釋一下:由于這里比較簡單,沒有使用描述符的需求,所以使用了@property裝飾器來代替,lower_keys的功能是將實例字典中的鍵全部轉換成小寫并且存儲在字典self._lower_keys中。重寫了__getitem__方法,以后我們訪問某個屬性首先會將鍵轉換為小寫的方式,然后并不會直接訪問實例字典,而是會訪問字典self._lower_keys去查找。賦值/刪除操作的時候由于實例字典會進行變更,為了保持self._lower_keys和實例字典同步,首先清除self._lower_keys的內(nèi)容,以后我們重新查找鍵的時候再調用__getitem__的時候會重新新建一個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
我們來調用一下這個類:
>>>?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),轉載請先與作者聯(lián)系。 首發(fā)于我的博客
引言
Descriptors(描述符)是Python語言中一個深奧但很重要的一個黑魔法,它被廣泛應用于Python語言的內(nèi)核,熟練掌握描述符將會為Python程序員的工具箱添加一個額外的技巧。本文我將講述描述符的定義以及一些常見的場景,并且在文末會補充一下__getattr__,__getattribute__, __getitem__這三個同樣涉及到屬性訪問的魔術方法。
描述符的定義
descr__get__(self,?obj,?objtype=None)?-->?value descr.__set__(self,?obj,?value)?-->?None descr.__delete__(self,?obj)?-->?None
只要一個object attribute(對象屬性)定義了上面三個方法中的任意一個,那么這個類就可以被稱為描述符類。
描述符基礎
下面這個例子中我們創(chuàng)建了一個RevealAcess類,并且實現(xiàn)了__get__方法,現(xiàn)在這個類可以被稱為一個描述符類。
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實例屬性
接下來我們來看一下__get__方法的各個參數(shù)的含義,在下面這個例子中,self即RevealAccess類的實例x,obj即MyClass類的實例m,objtype顧名思義就是MyClass類自身。從輸出語句可以看出,m.x訪問描述符x會調用__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類屬性
如果通過類直接訪問屬性x,那么obj接直接為None,這還是比較好理解,因為不存在MyClass的實例。
>>>?MyClass.x self?in?RevealAccess:?<__main__.RevealAccess object at 0x7f53651070f0> self:?<__main__.RevealAccess object at 0x7f53651070f0> obj:?None objtype:?<class '__main__.MyClass'>
描述符的原理
描述符觸發(fā)
上面這個例子中,我們分別從實例屬性和類屬性的角度列舉了描述符的用法,下面我們來仔細分析一下內(nèi)部的原理:
如果是對實例屬性進行訪問,相當于調用了object.__getattribute__(),它將obj.d轉譯成了type(obj).__dict__['d'].__get__(obj, type(obj))。
如果是對類屬性進行訪問,相當于調用了type.__getattribute__(),它將cls.d轉譯成了cls.__dict__['d'].__get__(None, cls),轉換成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
簡單講一下__getattribute__魔術方法,這個方法在我們訪問一個對象的屬性的時候會被無條件調用,詳細的細節(jié)比如和__getattr, __getitem__的區(qū)別我會在文章的末尾做一個額外的補充,我們暫時并不深究。
描述符優(yōu)先級
首先,描述符分為兩種:
如果一個對象同時定義了__get__()和__set__()方法,則這個描述符被稱為data descriptor。
如果一個對象只定義了__get__()方法,則這個描述符被稱為non-data descriptor。
我們對屬性進行訪問的時候存在下面四種情況:
data descriptor
instance dict
non-data descriptor
__getattr__()
它們的優(yōu)先級大小是:
data?descriptor?>?instance?dict?>?non-data?descriptor?>?__getattr__()
這是什么意思呢?就是說如果實例對象obj中出現(xiàn)了同名的data descriptor->d 和 instance attribute->d,obj.d對屬性d進行訪問的時候,由于data descriptor具有更高的優(yōu)先級,Python便會調用type(obj).__dict__['d'].__get__(obj, type(obj))而不是調用obj.__dict__['d']。但是如果描述符是個non-data descriptor,Python則會調用obj.__dict__['d']。
Property
每次使用描述符的時候都定義一個描述符類,這樣看起來非常繁瑣。Python提供了一種簡潔的方式用來向屬性添加數(shù)據(jù)描述符。
property(fget=None,?fset=None,?fdel=None,?doc=None)?->?property?attribute
fget、fset和fdel分別是類的getter、setter和deleter方法。我們通過下面的一個示例來說明如何使用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的一個實例,acct.acct_num將會調用getter,acct.acct_num = value將調用setter,del acct_num.acct_num將調用deleter。
>>>?acct?=?Account() >>>?acct.acct_num?=?1000 >>>?acct.acct_num 1000
Python也提供了@property裝飾器,對于簡單的應用場景可以使用它來創(chuàng)建屬性。一個屬性對象擁有getter,setter和deleter裝飾器方法,可以使用它們通過對應的被裝飾函數(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方法。
在運行時創(chuàng)建描述符
我們可以在運行時添加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)方法和類方法
我們可以使用描述符來模擬Python中的@staticmethod和@classmethod的實現(xiàn)。我們首先來瀏覽一下下面這張表:
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)方法
對于靜態(tài)方法f。c.f和C.f是等價的,都是直接查詢object.__getattribute__(c, ‘f’)或者object.__getattribute__(C, ’f‘)。靜態(tài)方法一個明顯的特征就是沒有self變量。
靜態(tài)方法有什么用呢?假設有一個處理專門數(shù)據(jù)的容器類,它提供了一些方法來求平均數(shù),中位數(shù)等統(tǒng)計數(shù)據(jù)方式,這些方法都是要依賴于相應的數(shù)據(jù)的。但是類中可能還有一些方法,并不依賴這些數(shù)據(jù),這個時候我們可以將這些方法聲明為靜態(tài)方法,同時這也可以提高代碼的可讀性。
使用非數(shù)據(jù)描述符來模擬一下靜態(tài)方法的實現(xiàn):
class?StaticMethod(object): ????def?__init__(self,?f): ????????self.f?=?f ????def?__get__(self,?obj,?objtype=None): ????????return?self.f
我們來應用一下:
class?MyClass(object): ????@StaticMethod ????def?get_x(x): ????????return?x print(MyClass.get_x(100))??#?output:?100
類方法
Python的@classmethod和@staticmethod的用法有些類似,但是還是有些不同,當某些方法只需要得到類的引用而不關心類中的相應的數(shù)據(jù)的時候就需要使用classmethod了。
使用非數(shù)據(jù)描述符來模擬一下類方法的實現(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
其他的魔術方法
首次接觸Python魔術方法的時候,我也被__get__, __getattribute__, __getattr__, __getitem__之間的區(qū)別困擾到了,它們都是和屬性訪問相關的魔術方法,其中重寫__getattr__,__getitem__來構造一個自己的集合類非常的常用,下面我們就通過一些例子來看一下它們的應用。
__getattr__
Python默認訪問類/實例的某個屬性都是通過__getattribute__來調用的,__getattribute__會被無條件調用,沒有找到的話就會調用__getattr__。如果我們要定制某個類,通常情況下我們不應該重寫__getattribute__,而是應該重寫__getattr__,很少看見重寫__getattribute__的情況。
從下面的輸出可以看出,當一個屬性通過__getattribute__無法找到的時候會調用__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__'
應用
對于默認的字典,Python只支持以obj['foo']形式來訪問,不支持obj.foo的形式,我們可以通過重寫__getattr__讓字典也支持obj['foo']的訪問形式,這是一個非常經(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) + '>'
我們來使用一下我們自定義的加強版字典:
>>>?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用于通過下標[]的形式來獲取對象中的元素,下面我們通過重寫__getitem__來實現(xiàn)一個自己的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]
這個實現(xiàn)非常的簡陋,不支持slice和step等功能,請讀者自行改進,這里我就不重復了。
應用
下面是參考requests庫中對于__getitem__的一個使用,我們定制了一個忽略屬性大小寫的字典類。
程序有些復雜,我稍微解釋一下:由于這里比較簡單,沒有使用描述符的需求,所以使用了@property裝飾器來代替,lower_keys的功能是將實例字典中的鍵全部轉換成小寫并且存儲在字典self._lower_keys中。重寫了__getitem__方法,以后我們訪問某個屬性首先會將鍵轉換為小寫的方式,然后并不會直接訪問實例字典,而是會訪問字典self._lower_keys去查找。賦值/刪除操作的時候由于實例字典會進行變更,為了保持self._lower_keys和實例字典同步,首先清除self._lower_keys的內(nèi)容,以后我們重新查找鍵的時候再調用__getitem__的時候會重新新建一個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
我們來調用一下這個類:
>>>?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黑魔法之描述符相關文章請關注PHP中文網(wǎng)!

ホットAIツール

Undress AI Tool
脫衣畫像を無料で

Undresser.AI Undress
リアルなヌード寫真を作成する AI 搭載アプリ

AI Clothes Remover
寫真から衣服を削除するオンライン AI ツール。

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中國語版
中國語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統(tǒng)合開発環(huán)境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック

AIによるテキストエラーの修正と構文最適化を実現(xiàn)するには、次の手順に従う必要があります。1。Baidu、Tencent API、またはオープンソースNLPライブラリなどの適切なAIモデルまたはAPIを選択します。 2。PHPのカールまたはガズルを介してAPIを呼び出し、返品結果を処理します。 3.アプリケーションにエラー修正情報を表示し、ユーザーが採用するかどうかを選択できるようにします。 4.構文の検出とコードの最適化には、PHP-LとPHP_CODESNIFFERを使用します。 5.フィードバックを継続的に収集し、モデルまたはルールを更新して効果を改善します。 AIAPIを選択するときは、PHPの精度、応答速度、価格、サポートの評価に焦點を當てます。コードの最適化は、PSR仕様に従い、キャッシュを合理的に使用し、円形クエリを避け、定期的にコードを確認し、Xを使用する必要があります。

ユーザー音聲入力がキャプチャされ、フロントエンドJavaScriptのMediareCorder APIを介してPHPバックエンドに送信されます。 2。PHPはオーディオを一時ファイルとして保存し、STTAPI(GoogleやBaiduの音聲認識など)を呼び出してテキストに変換します。 3。PHPは、テキストをAIサービス(Openaigptなど)に送信して、インテリジェントな返信を取得します。 4。PHPは、TTSAPI(BaiduやGoogle Voice Synthesisなど)を呼び出して音聲ファイルに返信します。 5。PHPは、音聲ファイルをフロントエンドに戻し、相互作用を完了します。プロセス全體は、すべてのリンク間のシームレスな接続を確保するためにPHPによって支配されています。

この記事では、いくつかのトップPython「完成した」プロジェクトWebサイトと、高レベルの「大ヒット作「學習リソースポータル」が選択されています。開発のインスピレーション、観察、學習のマスターレベルのソースコードを探している場合でも、実用的な機能を體系的に改善している場合でも、これらのプラットフォームは見逃せず、Pythonマスターに迅速に成長するのに役立ちます。

ユーザーの動作データを収集するには、閲覧、検索、購入、その他の情報をPHPを介してデータベースに記録し、それをクリーン化して分析して、関心の好みを調査する必要があります。 2。推奨アルゴリズムの選択は、データの特性に基づいて決定する必要があります。コンテンツ、共同フィルタリング、ルール、または混合推奨事項に基づいています。 3.共同フィルタリングをPHPに実裝して、ユーザーコサインの類似性を計算し、Kestose Yearborsを選択し、加重予測スコアを選択し、高得點製品を推奨します。 4.パフォーマンス評価は、精度、リコール、F1値とCTR、変換速度を使用し、A/Bテストを介して効果を検証します。 5.コールドスタートの問題は、製品屬性、ユーザー登録情報、一般的な推奨事項、専門家の評価を通じて緩和される可能性があります。 6.パフォーマンス最適化方法には、キャッシュされた推奨結果、非同期処理、分散コンピューティング、SQLクエリの最適化が含まれ、それにより推奨効率とユーザーエクスペリエンスが向上します。

適切なPHPフレームワークを選択する場合、プロジェクトのニーズに応じて包括的に検討する必要があります。Laravelは迅速な発展に適しており、データベースの操作と動的フォームレンダリングに便利なEloquentormおよびBladeテンプレートエンジンを提供します。 Symfonyは、より柔軟で複雑なシステムに適しています。 Codeigniterは軽量で、高性能要件を持つ簡単なアプリケーションに適しています。 2。AIモデルの精度を確保するには、高品質のデータトレーニング、評価インジケーター(精度、リコール、F1値など)の合理的な選択、定期的なパフォーマンス評価とモデルチューニング、およびユニットテストと統(tǒng)合テストを通じてコードの品質を確保しながら、入力データを継続的に監(jiān)視してデータドリフトを防ぐ必要があります。 3.ユーザーのプライバシーを保護するためには多くの手段が必要です:機密データを暗號化および保存する(AESなど

Seabornのジョイントプロットを使用して、2つの変數(shù)間の関係と分布をすばやく視覚化します。 2?;镜膜噬⒉紘恧?、sns.jointplot(data = tips、x = "total_bill"、y = "tip"、dind = "scatter")によって実裝され、中心は散布図であり、ヒストグラムは上部と右側と右側に表示されます。 3.回帰線と密度情報をdind = "reg"に追加し、marminal_kwsを組み合わせてエッジプロットスタイルを設定します。 4。データ量が大きい場合は、「ヘックス」を使用することをお勧めします。

1。PHPは、主にデータ収集、API通信、ビジネスルール処理、キャッシュの最適化、および複雑なモデルトレーニングを直接実行するのではなく、AIコンテンツ推奨システムでの推奨表示を引き受けます。 2.システムは、PHPを介してユーザーの動作とコンテンツデータを収集し、バックエンドAIサービス(Pythonモデルなど)を呼び出して推奨結果を得て、Redisキャッシュを使用してパフォーマンスを改善します。 3.共同フィルタリングやコンテンツの類似性などの基本的な推奨アルゴリズムは、PHPに軽量ロジックを実裝できますが、大規(guī)模なコンピューティングは依然としてプロのAIサービスに依存します。 4.最適化は、リアルタイム、コールドスタート、多様性、フィードバッククローズドループに注意を払う必要があり、課題には高い並行性パフォーマンス、モデルの更新安定性、データコンプライアンス、推奨解釈が含まれます。 PHPは、安定した情報、データベース、フロントエンドを構築するために協(xié)力する必要があります。

PHPのAIテキストの概要の開発の中核は、テキストの前処理、APIリクエスト、応答分析、結果表示を実現(xiàn)するためのコーディネーターとして外部AIサービスAPI(Openai、Huggingfaceなど)を呼び出すことです。 2。制限は、コンピューティングのパフォーマンスが弱く、AIエコシステムが弱いことです。応答戦略は、API、サービス分離、非同期処理を活用することです。 3.モデルの選択は、概要の品質、コスト、遅延、並行性、データプライバシー、およびGPTやBART/T5などの抽象モデルを推奨する必要があります。 4.パフォーマンスの最適化には、キャッシュ、非同期キュー、バッチ処理、近くのエリアの選択が含まれます。エラー処理は、システムの安定した効率的な動作を確保するために、現(xiàn)在の制限再生、ネットワークタイムアウト、キーセキュリティ、入力検証、ロギングをカバーする必要があります。
