スタックオーバーフローの記述子に関する質問から始めます。
classCelsius:
def __get__(self, instance, owner):return5*(instance.fahrenheit -32)/9
def __set__(self, instance, value):
instance.fahrenheit =32+9* value /5classTemperature:
celsius =Celsius()
def __init__(self, initial_f):
self.fahrenheit = initial_f
t =Temperature(212)print(t.celsius) #出力100.0
t.celsius =0print(t.fahrenheit) #出力32.0
上記のコードは、CelsiusとFahrenheitの温度間の自動変換を実現します。 Temperatureクラスには、インスタンス変数fahrenheitとクラス変数celsiusが含まれています。Celsiusは記述子Celsiusで表されます。このコードからの3つの質問:
__get__
、 __set__
、および __delete__
の3つのメソッドのパラメーター記述子は、 __get__
、 __set__
、および __delete__
の1つ以上のメソッドを実装するクラスオブジェクトです。クラス変数がそのようなデコレータを指す場合、このクラス変数にアクセスすると __get__
メソッドが呼び出され、このクラス変数に値を割り当てると __set__
メソッドが呼び出されます。このクラス変数は記述子と呼ばれます。
記述子は実際にはプロキシメカニズムです。クラス変数が記述子として定義されている場合、このクラス変数に対する操作はこの記述子によって表されます。
classdescriptor:
def __get__(self, instance, owner):print(instance)print(owner)return'desc'
def __set__(self, instance, value):print(instance)print(value)
def __delete__(self, instance):print(instance)classA:
a =descriptor()
del A().a #出力<__main__.A object at 0x7f3fc867cbe0>A().a #説明を返し、出力<__main__.A object at 0x7f3fc86741d0>,<class'__main__.A'>
A.a #説明を返し、出力なし、<class'__main__.A'>A().a =5 #出力<__main__.A object at 0x7f3fc86744a8>,5
A.a =5 #クラスAのクラス変数を直接変更します。つまり、aは記述子記述子によってプロキシされなくなります。
上記の出力結果から結論を導き出すことができます。
__ get __(self、instance、owner)
インスタンスは現在のインスタンスを表します所有者はクラス自体を表します。クラスアクセスを使用する場合、インスタンスはなしです__ set __(self、instance、value)
インスタンスは現在のインスタンスを表し、valueは正しい値であり、インスタンスのみが __set__
を呼び出します__ delete __(self、instance)
インスタンスは現在のインスタンスを表しますinstance.descriptor
は実際にはdescriptor .__ get __(self、instance、owner)
メソッドを呼び出し、値を返す必要がありますinstance.descriptor = value
は実際にはdescriptor .__ set __(self、instance、value)
メソッドを呼び出し、戻り値はNoneです。delinstance.descriptor
は実際にはdescriptor .__ delete __(self、obj_instance)
メソッドを呼び出し、戻り値はNoneです。新しい形式のインスタンス属性を作成したいのですが、変更とアクセスに加えて、型チェック、数値検証などの追加機能がいくつかあります。記述子「Pythonクックブック」を使用する必要があります。
つまり、記述子は主にインスタンス変数の操作を引き継ぐために使用されます。
from functools import partial
from functools import wraps
classClassmethod():
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):returnwraps(self.fn)(partial(self.fn, owner))
メソッドfnの最初のパラメーターをインスタンスのクラスに修正します。公式のpythonドキュメントで別の書き方を参照できます:[descriptor](https://docs.python.org/3.5/howto/descriptor.html#static-methods-and-class-methods)
classClassMethod(object):
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner=None):if owner is None:
owner =type(obj)
def newfunc(*args):return self.f(owner,*args)return newfunc
classStaticmethod:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, cls):return self.fn
classProperty:
def __init__(self, fget, fset=None, fdel=None, doc=''):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.doc = doc
def __get__(self, instance ,owner):if instance is not None:return self.fget(instance)return self
def __set__(self, instance, value):if not callable(self.fset):
raise AttibuteError('cannot set')
self.fset(instance, value)
def __delete__(self, instance):if not callable(self.fdel):
raise AttributeError('cannot delete')
self.fdel(instance)
def setter(self, fset):
self.fset = fset
return self
def deleter(self, fdel):
self.fdel = fdel
return self
カスタムプロパティを使用して、farenheitおよびcelsiusクラス変数を記述します。
classTemperature:
def __init__(self, cTemp):
self.cTemp = cTemp #インスタンス変数cTempがあります:セルシウス温度
def fget(self):return self.celsius *9/5+32
def fset(self, value):
self.celsius =(float(value)-32)*5/9
def fdel(self):print('Farenhei cannot delete')
farenheit =Property(fget, fset, fdel, doc='Farenheit temperature')
def cget(self):return self.cTemp
def cset(self, value):
self.cTemp =float(value)
def cdel(self):print('Celsius cannot delete')
celsius =Property(cget, cset, cdel, doc='Celsius temperature')
使用結果:
t =Temperature(0)
t.celsius #0を返します.0
del t.celsius #出力Celsiusは削除できません
t.celsius =5
t.farenheit #41を返します.0
t.farenheit =212
t.celsius #100を返します.0
del t.farenheit #出力ファレンヘイは削除できません
デコレータを使用して、Temperatureの2つのプロパティ、farenheitとcelsiusを装飾します。
classTemperature:
def __init__(self, cTemp):
self.cTemp = cTemp
@ Property # celsius =Property(celsius)
def celsius(self):return self.cTemp
@ celsius.setter
def celsius(self, value):
self.cTemp = value
@ celsius.deleter
def celsius(self):print('Celsius cannot delete')
@ Property # farenheit =Property(farenheit)
def farenheit(self):return self.celsius *9/5+32
@ farenheit.setter
def farenheit(self, value):
self.celsius =(float(value)-32)*5/9
@ farenheit.deleter
def farenheit(self):print('Farenheit cannot delete')
結果を使用して、記述子を使用してクラス変数を直接記述します
まず、型チェック記述子Typedを実装します
classTyped:
def __init__(self, name, expected_type):
# 各属性には名前と対応するタイプがあります
self.name = name
self.expected_type = expected_type
def __get__(self, instance, cls):if instance is None:return self
return instance.__dict__[self.name]
def __set__(self, instance ,value):if not isinstance(value, self.expected_type):
raise TypeError('Attribute {} expected {}'.format(self.name, self.expected_type))
instance.__dict__[self.name]= value
def __delete__(self, instance):
del instance.__dict__[self.name]
次に、Personクラスを実装します。Personクラスの属性名と年齢は、Typedで記述されます。
classPerson:
name =Typed('name', str)
age =Typed('age', int)
def __init__(self, name: str, age: int):
self.name = name
self.age = age
タイプチェックプロセス:
>>> Person.__dict__
mappingproxy({'__dict__':<attribute '__dict__'of'Person' objects>,'__doc__': None,'__init__':<function __main__.Person.__init__>,'__module__':'__main__','__weakref__':<attribute '__weakref__'of'Person' objects>,'age':<__main__.Typed at 0x7fe2f440bd68>,'name':<__main__.Typed at 0x7fe2f440bc88>})>>> p =Person('suncle',18)>>> p.__dict__
{' age':18,'name':'suncle'}>>> p =Person(18,'suncle')---------------------------------------------------------------------------
TypeError Traceback(most recent call last)<ipython-input-88-ca4808b23f89>in<module>()---->1 p =Person(18,'suncle')<ipython-input-84-f876ec954895>in__init__(self, name, age)45 def __init__(self, name: str, age: int):---->6 self.name = name
7 self.age = age
< ipython-input-83-ac59ba73c709>in__set__(self, instance, value)11 def __set__(self, instance ,value):12if not isinstance(value, self.expected_type):--->13 raise TypeError('Attribute {} expected {}'.format(self.name, self.expected_type))14 instance.__dict__[self.name]= value
15
TypeError: Attribute name expected <class'str'>
ただし、上記の型チェック方法にはいくつか問題があります。Personクラスには多くの属性がある可能性があるため、各属性をTyped記述子で1回記述する必要があります。この問題を解決するために、パラメーターを使用してクラスデコレーターを作成できます。
def typeassert(**kwargs):
def wrap(cls):for name, expected_type in kwargs.items():setattr(cls, name,Typed(name, expected_type)) #古典的な執筆
return cls
return wrap
次に、typeassertクラスデコレータを使用してPersonクラスを再定義します。
@ typeassert(name=str, age=int)classPerson:
def __init__(self, name, age):
self.name = name
self.age = age
typeassertクラスデコレータのパラメータは、渡された属性名とタイプのキーと値のペアであることがわかります。
typeassertクラスデコレータがクラスの初期化パラメータタイプを自動的に認識し、対応するクラス変数を追加する場合は、inspectライブラリとpythonタイプアノテーションを使用して次のことを実現できます。
import inspect
def typeassert(cls):
params = inspect.signature(cls).parameters
for name, param in params.items():if param.annotation != inspect._empty:setattr(cls, name,Typed(name, param.annotation))return cls
@ typeassert
classPerson:
def __init__(self, name: str, age: int): #タイプ注釈のないパラメータは管理されません
self.name = name
self.age = age
Pythonの内部メカニズムを使用して、属性値を取得および設定できます。全部で3つの方法があります:
GetterとSetterのデザインパターンはPythonicでは不十分です。C++とJAVAでは一般的ですが、Pythonは導入と直接アクセスを追求しています。
付録1、データ記述子およびデータなし記述子
中国語に翻訳されるのは、実際にはデータ記述子と非データ記述子です
__get__
メソッドと __set__
メソッドの両方を実装する記述子__get__
メソッドのみを実装する記述子2つの違いは次のとおりです。
instance .__ dict__
の優先度よりも低くなります。classInt:
def __get__(self, instance, cls):return3classA:
val =Int()
def __init__(self):
self.__dict__['val']=5A().val #5を返します
instance .__ dict__
の優先度よりも高いclassInt:
def __get__(self, instance, cls):return3
def __set__(self, instance, value):
pass
classA:
val =Int()
def __init__(self):
self.__dict__['val']=5A().val #3を返します
付録2.記述子メカニズム分析データ:
Recommended Posts