私は試行 Pythonの記述子が何であり、それらが何に役立つかを理解しています。しかし、私はそれに失敗しています。私はそれらがどのように機能するか理解していますが、ここに私の疑問があります。次のコードを検討してください。
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()
記述子クラスが必要なのはなぜですか?
ここでinstance
およびowner
とは何ですか? (__get__
内)。これらのパラメーターの目的は何ですか?
この例をどのように呼び出し/使用しますか?
記述子は、Pythonのproperty
型の実装方法です。記述子は、単純に__get__
、__set__
などを実装し、その定義で別のクラスに追加されます(上記のTemperatureクラスで行ったように)。例えば:
temp=Temperature()
temp.celsius #calls celsius.__get__
記述子を割り当てたプロパティ(上記の例ではcelsius
)にアクセスすると、適切な記述子メソッドが呼び出されます。
__get__
のinstance
はクラスのインスタンスです(上記のように、__get__
はtemp
を受け取りますが、owner
は記述子を持つクラスです(したがって、Temperature
になります)。
記述子クラスを使用して、それを駆動するロジックをカプセル化する必要があります。そのようにして、(たとえば)高価な操作をキャッシュするために記述子を使用する場合、クラスではなく、自身に値を格納できます。
記述子に関する記事は こちら にあります。
編集:jchlがコメントで指摘したように、単にTemperature.celsius
を試してみると、instance
はNone
になります。
記述子クラスが必要なのはなぜですか?
これにより、属性の機能をさらに制御できます。たとえば、Javaのゲッターとセッターに慣れている場合は、Pythonの方法です。 1つの利点は、ユーザーに属性のように見えることです(構文に変更はありません)。したがって、通常の属性から始めて、空想的なことをする必要がある場合は、記述子に切り替えることができます。
属性は単なる変更可能な値です。記述子を使用すると、値の読み取りまたは設定(または削除)時に任意のコードを実行できます。したがって、たとえばORMのようなデータベース内のフィールドに属性をマッピングするためにそれを使用することを想像できます。
別の使用法は、__set__
で例外をスローすることで新しい値を受け入れることを拒否することです。つまり、「属性」を読み取り専用にします。
ここで
instance
およびowner
とは何ですか? (__get__
内)。これらのパラメーターの目的は何ですか?
これは非常に微妙です(そして、ここで新しい答えを書いている理由-私は同じことを疑問に思ってこの質問を見つけましたが、既存の答えをそれほど見つけませんでした)。
記述子はクラスで定義されますが、通常はインスタンスから呼び出されます。インスタンスから呼び出されると、instance
とowner
の両方が設定されます(また、owner
からinstance
を実行できるため、無意味に思えます)。ただし、クラスから呼び出された場合は、owner
のみが設定されます。これが存在する理由です。
これは__get__
にのみ必要です。クラスで呼び出すことができるのはこれだけだからです。クラス値を設定する場合、記述子自体を設定します。削除についても同様です。そこでowner
は必要ありません。
この例をどのように呼び出し/使用しますか?
さて、ここに同様のクラスを使用したクールなトリックがあります:
class Celsius:
def __get__(self, instance, owner):
return 5 * (instance.Fahrenheit - 32) / 9
def __set__(self, instance, value):
instance.Fahrenheit = 32 + 9 * value / 5
class Temperature:
celsius = Celsius()
def __init__(self, initial_f):
self.Fahrenheit = initial_f
t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.Fahrenheit)
(私はPython 3を使用しています; python 2の場合、それらの部門が/ 5.0
と/ 9.0
であることを確認する必要があります)。それは与える:
100.0
32.0
pythonで同じ効果を達成する他の、おそらく間違いなくより良い方法があります(たとえば、摂氏が同じ基本メカニズムであるが、すべてのソースをTemperatureクラス内に置くプロパティである場合)できます...
Pythonの記述子が何であり、何のために役立つかを理解しようとしています。
記述子は、次の特別なメソッドのいずれかを持つクラス属性(プロパティやメソッドなど)です。
__get__
(メソッド/関数などの非データ記述子メソッド)__set__
(たとえば、プロパティインスタンスのデータ記述子メソッド)__delete__
(データ記述子メソッド)これらの記述子オブジェクトは、他のオブジェクトクラス定義の属性として使用できます。 (つまり、クラスオブジェクトの__dict__
に存在します。)
記述子オブジェクトを使用して、通常の式、割り当て、さらには削除のドット付きルックアップ(foo.descriptor
など)の結果をプログラムで管理できます。
関数/メソッド、バインドメソッド、property
、classmethod
、およびstaticmethod
はすべて、これらの特別なメソッドを使用して、ドットルックアップによるアクセス方法を制御します。
データ記述子は、property
のように、オブジェクトのより単純な状態に基づいて属性の遅延評価を可能にし、インスタンスが使用する場合よりも少ないメモリを使用できるようにします可能な各属性を事前に計算しました。
member_descriptor
によって作成された別のデータ記述子__slots__
は、より柔軟でスペースを消費する__dict__
ではなく、クラスが可変のタプルのようなデータ構造にデータを格納できるようにすることで、メモリを節約できます。
通常、インスタンス、クラス、および静的メソッドである非データ記述子は、非データ記述子メソッド__get__
から暗黙の最初の引数(通常、それぞれcls
およびself
という名前)を取得します。
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
はdescriptorのインスタンスです(おそらくobj_instance
のクラスに1つだけです)
これをコードで定義するために、オブジェクトは、その属性セットが必要な属性のいずれかと交差する場合、記述子です。
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))
Data Descriptor には__set__
や__delete__
があります。
ANon-Data-Descriptorには__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__'])
これらは、ドットルックアップのルックアップ順序に影響するため、重要な distinctions です。
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()
- 記述子クラスが必要なのはなぜですか?
記述子により、Temperature
のこのクラス属性に対して常にfloatが使用され、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番目の提案(プロパティデコレータ)に進んでください。
記述子クラスが必要なのはなぜですか?
Fluent Python Buciano Ramalhoに触発
このようなクラスがあるイメージング
class LineItem:
price = 10.9
weight = 2.1
def __init__(self, name, price, weight):
self.name = name
self.price = price
self.weight = weight
item = LineItem("Apple", 2.9, 2.1)
item.price = -0.9 # it's price is negative, you need to refund to your customer even you delivered the Apple :(
item.weight = -0.8 # negative weight, it doesn't make sense
負の数を割り当てることを避けるために、重量と価格を検証する必要があります。
class Quantity(object):
__index = 0
def __init__(self):
self.__index = self.__class__.__index
self._storage_name = "quantity#{}".format(self.__index)
self.__class__.__index += 1
def __set__(self, instance, value):
if value > 0:
setattr(instance, self._storage_name, value)
else:
raise ValueError('value should >0')
def __get__(self, instance, owner):
return getattr(instance, self._storage_name)
次に、クラスLineItemを次のように定義します。
class LineItem(object):
weight = Quantity()
price = Quantity()
def __init__(self, name, weight, price):
self.name = name
self.weight = weight
self.price = price
さらに、Quantityクラスを拡張して、より一般的な検証を行うことができます
記述子の詳細に入る前に、Pythonの属性ルックアップがどのように機能するかを知ることが重要かもしれません。これは、クラスにメタクラスがなく、__getattribute__
のデフォルト実装を使用することを前提としています(両方とも動作を「カスタマイズ」するために使用できます)。
この場合の属性ルックアップ(Python 3.xまたはPython 2.xの新しいスタイルのクラス)の最良の例は nderstanding Pythonメタクラス(ionelのコードログ) 。イメージは、「カスタマイズ不可能な属性ルックアップ」の代わりとして:
を使用します。
これは、foobar
のinstance
の属性Class
のルックアップを表します。
ここでは2つの条件が重要です。
instance
のクラスに属性名のエントリがあり、__get__
および__set__
がある場合。instance
に属性名のnoエントリがあるが、クラスに1つあり、__get__
がある場合。それが記述子の出番です。
__get__
と__set__
の両方を持つデータ記述子。__get__
のみを持つ非データ記述子。どちらの場合も、戻り値は、インスタンスを最初の引数として、クラスを2番目の引数として呼び出される__get__
を経由します。
ルックアップは、クラス属性のルックアップではさらに複雑です(たとえば、 上記のブログのクラス属性のルックアップ )。
特定の質問に移りましょう:
記述子クラスが必要なのはなぜですか?
ほとんどの場合、記述子クラスを記述する必要はありません!ただし、おそらく非常に定期的なエンドユーザーです。例の関数。関数は記述子です。つまり、関数は、self
が暗黙的に最初の引数として渡されるメソッドとして使用できます。
def test_function(self):
return self
class TestClass(object):
def test_method(self):
...
インスタンスでtest_method
を検索すると、「バウンドメソッド」が返されます。
>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>
同様に、__get__
メソッドを手動で呼び出して関数をバインドすることもできます(実際にはお勧めしません。単に説明のためだけです):
>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>
これを「自己バインドメソッド」と呼ぶこともできます。
>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>
引数を指定せず、関数はバインドしたインスタンスを返しました。
関数は、非データ記述子です!
データ記述子のいくつかの組み込み例は、property
です。 getter
、setter
、およびdeleter
を無視すると、property
記述子は( Descriptor HowTo Guide "Properties" )から:
class Property(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
これはデータ記述子であるため、property
の「名前」を検索するたびに呼び出され、@property
、@name.setter
、および@name.deleter
(存在する場合)で装飾された関数に単純に委任します。
標準ライブラリには、staticmethod
、classmethod
などの他の記述子がいくつかあります。
記述子のポイントは簡単です(ほとんど必要ありませんが):属性アクセスのための抽象的な共通コード。 property
はインスタンス変数アクセスの抽象化、function
はメソッドの抽象化を提供し、staticmethod
はインスタンスアクセスを必要としないメソッドの抽象化を提供し、classmethod
はインスタンスアクセスではなくクラスアクセスを必要とするメソッドの抽象化を提供します(これは少し簡略化されています)。
別の例は、 クラスプロパティ です。
1つの楽しい例(Python 3.6の__set_name__
を使用)は、特定のタイプのみを許可するプロパティにすることもできます。
class TypedProperty(object):
__slots__ = ('_name', '_type')
def __init__(self, typ):
self._type = typ
def __get__(self, instance, klass=None):
if instance is None:
return self
return instance.__dict__[self._name]
def __set__(self, instance, value):
if not isinstance(value, self._type):
raise TypeError(f"Expected class {self._type}, got {type(value)}")
instance.__dict__[self._name] = value
def __delete__(self, instance):
del instance.__dict__[self._name]
def __set_name__(self, klass, name):
self._name = name
次に、クラスで記述子を使用できます。
class Test(object):
int_prop = TypedProperty(int)
少し遊んでみてください:
>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10
>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>
または「遅延プロパティ」:
class LazyProperty(object):
__slots__ = ('_fget', '_name')
def __init__(self, fget):
self._fget = fget
def __get__(self, instance, klass=None):
if instance is None:
return self
try:
return instance.__dict__[self._name]
except KeyError:
value = self._fget(instance)
instance.__dict__[self._name] = value
return value
def __set_name__(self, klass, name):
self._name = name
class Test(object):
@LazyProperty
def lazy(self):
print('calculating')
return 10
>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10
これらは、ロジックを共通の記述子に移動することが理にかなっている場合ですが、他の手段でそれらを解決することもできます(ただし、いくつかのコードを繰り返して).
ここで
instance
およびowner
とは何ですか? (__get__
内)。これらのパラメーターの目的は何ですか?
属性の検索方法によって異なります。インスタンスの属性を検索する場合:
クラスの属性を検索する場合(記述子がクラスで定義されていると仮定):
None
ですしたがって、クラスレベルのルックアップを行うときに動作をカスタマイズする場合は、基本的に3番目の引数が必要です(instance
はNone
であるため)。
この例をどのように呼び出し/使用しますか?
あなたの例は基本的に、float
に変換できる値のみを許可し、クラスのすべてのインスタンス間(およびクラス上)で共有されるプロパティです。ただし、クラスの「読み取り」アクセスのみ使用できます。インスタンス):
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius = 20 # setting it on one instance
>>> t2.celsius # looking it up on another instance
20.0
>>> Temperature.celsius # looking it up on the class
20.0
そのため、記述子は一般に2番目の引数(instance
)を使用して値を保存し、共有を避けます。ただし、インスタンス間で値を共有することが必要な場合もあります(現時点ではシナリオを考えることはできませんが)。ただし、温度クラスの摂氏温度プロパティについては、純粋にアカデミックな演習を除いて、事実上意味がありません。
https://docs.python.org/3/howto/descriptor.html#properties が表示されます
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
Andrew Cookeの答えからコードを(提案されたように小さな変更を加えて)試しました。 (python 2.7を実行しています)。
コード:
#!/usr/bin/env python
class Celsius:
def __get__(self, instance, owner): return 9 * (instance.Fahrenheit + 32) / 5.0
def __set__(self, instance, value): instance.Fahrenheit = 32 + 5 * value / 9.0
class Temperature:
def __init__(self, initial_f): self.Fahrenheit = initial_f
celsius = Celsius()
if __== "__main__":
t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.Fahrenheit)
結果:
C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212
3より前のPythonを使用して、古いスタイルクラスではgetマジックが機能しないため、記述子が正しく機能するオブジェクトのサブクラスを作成してください。