私はPythonの記述子が何であり、それらが何に役立つのかを理解しようとしています。
記述子は、次の特別なメソッドのいずれかを持つクラス属性(プロパティやメソッドなど)です。
__get__
(メソッド/関数などの非データ記述子メソッド)
__set__
(たとえば、プロパティインスタンスのデータ記述子メソッド)
__delete__
(データ記述子メソッド)
これらの記述子オブジェクトは、他のオブジェクトクラス定義の属性として使用できます。(つまり、それら__dict__
はクラスオブジェクトのにあります。)
記述子オブジェクトを使用するfoo.descriptor
と、通常の式、割り当て、および削除でさえも、ドット付きルックアップの結果(など)をプログラムで管理できます。
関数/メソッド、バウンド方法、property
、classmethod
、とstaticmethod
彼らは点線の検索を介してアクセスする方法を制御するために、すべての使用これらの特別な方法。
のようなデータ記述子property
を使用すると、オブジェクトのより単純な状態に基づいて属性の遅延評価が可能になり、可能な各属性を事前に計算した場合よりもインスタンスが使用するメモリが少なくなります。
別のデータ記述子aはmember_descriptor
、によって作成され__slots__
、クラスがより柔軟でスペースを消費するのではなく、可変タプルのようなデータ構造にデータを格納できるようにすることで、メモリを節約できます__dict__
。
非データディスクリプタは、通常、例えば、クラス、および静的メソッド、彼らの暗黙の最初の引数(通常は名前の取得cls
とself
、その非データ記述方法から、それぞれを、) __get__
。
Pythonのほとんどのユーザーは、簡単な使用法のみを学習する必要があり、記述子の実装をさらに学習または理解する必要はありません。
詳細:ディスクリプタとは何ですか?
記述子は、以下の方法のいずれかを持つオブジェクトである(__get__
、__set__
または__delete__
)、それがインスタンスの一般的な属性であるかのように点線のルックアップを介して使用されることを意図。所有者オブジェクトの、obj_instance
とdescriptor
オブジェクト。
obj_instance.descriptor
呼び出す
descriptor.__get__(self, obj_instance, owner_class)
返すvalue
これはどのようにすべてのメソッドで、get
不動産の仕事に。
obj_instance.descriptor = value
呼び出す
descriptor.__set__(self, obj_instance, value)
返すNone
これはどのようにあるsetter
プロパティ作品に。
del obj_instance.descriptor
呼び出す
descriptor.__delete__(self, obj_instance)
返すNone
これはどのようにあるdeleter
プロパティ作品に。
obj_instance
クラスが記述子オブジェクトのインスタンスを含むインスタンスです。記述子self
のインスタンスです(おそらくのクラスの1つだけです)obj_instance
これをコードで定義するには、属性のセットが必要な属性のいずれかと交差する場合、オブジェクトは記述子です。
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
A データ記述子はあり__set__
および/または__delete__
。
A 非データ記述子は、どちらも持っていない__set__
も__delete__
。
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
組み込み記述子オブジェクトの例:
classmethod
staticmethod
property
- 一般的な機能
非データ記述子
これを見ることができclassmethod
、staticmethod
非データ記述子です。
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
どちらも__get__
メソッドしかありません:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
すべての関数は非データ記述子でもあることに注意してください。
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
データ記述子、 property
ただし、property
データ記述子は次のとおりです。
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
点線のルックアップ順序
これらは、ドット付きルックアップのルックアップ順序に影響を与えるため、重要な違いです。
obj_instance.attribute
- まず、上記は属性がインスタンスのクラスのデータ記述子であるかどうかを確認します。
- そうでない場合、それは属性がであるかどうかを確認するために見える
obj_instance
の__dict__
は、
- 最終的に非データ記述子にフォールバックします。
この検索順序の結果は、関数/メソッドなどの非データ記述子がインスタンスによってオーバーライドされる可能性があることです。
要約と次のステップ
私たちは、記述子のいずれかを持つオブジェクトであることを学んできた__get__
、__set__
または__delete__
。これらの記述子オブジェクトは、他のオブジェクトクラス定義の属性として使用できます。次に、コードを例として使用して、それらの使用方法を確認します。
質問からのコードの分析
ここにあなたのコードがあり、それぞれにあなたの質問と答えが続きます:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
- 記述子クラスが必要なのはなぜですか?
記述子により、このクラス属性のfloatが常にあり、属性を削除するためにTemperature
使用できないことが保証されますdel
。
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
それ以外の場合、記述子は所有者のクラスと所有者のインスタンスを無視し、代わりに記述子に状態を保存します。単純なクラス属性を使用して、すべてのインスタンス間で状態を同じように簡単に共有できます(常にクラスにフロートとして設定し、それを削除しないか、またはそうすることでコードのユーザーに慣れている場合)。
class Temperature(object):
celsius = 0.0
これにより、例とまったく同じ動作が得られますが(下記の質問3の回答を参照)、Pythonの組み込み(property
)が使用され、より慣用的と見なされます。
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
- ここでインスタンスと所有者は何ですか?(getで)。これらのパラメーターの目的は何ですか?
instance
記述子を呼び出している所有者のインスタンスです。所有者は、記述子オブジェクトを使用してデータポイントへのアクセスを管理するクラスです。より説明的な変数名については、この回答の最初の段落の横にある記述子を定義する特別なメソッドの説明を参照してください。
- この例をどのように呼び出し/使用しますか?
ここにデモがあります:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
次の属性は削除できません。
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
また、floatに変換できない変数を割り当てることはできません。
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
それ以外の場合、ここにあるのはすべてのインスタンスのグローバル状態であり、インスタンスに割り当てることによって管理されます。
ほとんどの経験豊富なPythonプログラマーがこの結果を達成するために期待される方法は、property
デコレーターを使用することです。これは、内部で同じ記述子を使用しますが、所有者クラスの実装に動作をもたらします(これも、上記で定義したとおりです)。
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
これは、元のコードとまったく同じ動作を期待します。
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
結論
記述子を定義する属性、データ記述子と非データ記述子の違い、それらを使用する組み込みオブジェクト、および使用に関する特定の質問について説明しました。
もう一度、質問の例をどのように使用しますか?私はあなたがそうしないことを望みます。最初の提案(単純なクラス属性)から始めて、必要と思われる場合は2番目の提案(プロパティデコレータ)に進んでください。
self
とはinstance
?