26

質問に:

記述子をインスタンス属性にできないのはなぜですか?

次のように回答されています。

記述子オブジェクトは、インスタンスではなくクラスに存在する必要があります

__getattribute__それが実装されている方法だからです。

簡単な例。記述子を考えてみましょう:

class Prop(object):

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj._value * obj._multiplier

    def __set__(self, obj, value):
        if obj is None:
            return self
        obj._value = value

class Obj(object):

    val = Prop()

    def __init__(self):
        self._value = 1
        self._multiplier = 0

各 obj に複数の Prop がある場合を考えてみましょう。値と乗数を識別するために一意の名前を使用する必要があります (ここのように。インスタンスごとの記述子オブジェクトを使用すると、記述子自体に_multiplier(および) を格納でき、_valueいくつかのこと。

インスタンス記述子属性ごとに実装するには、次のいずれかを行う必要があります。

  1. インスタンスごとのクラスを作成するここを参照
  2. オーバーライド__getattribute__ ここを参照

同様の質問が以前に提起されたことは承知していますが、本当の説明は見つかりませんでした。

  1. なぜPythonはこのように設計されているのですか?
  2. 記述子が必要とするが、インスタンスごとに情報を保存するための推奨される方法は何ですか?
4

3 に答える 3

16

この正確な質問は、今年初めに Python-list で提起されました。Ian G. Kelly の回答を引用します。

動作は仕様です。まず、オブジェクトの動作をクラス定義に保持すると、実装が簡素化され、インスタンス チェックがより意味のあるものになります。Register の例を借りると、「M」記述子がクラスではなくいくつかのインスタンスによって定義されている場合、オブジェクト「reg」が Register のインスタンスであることを知っていても、「reg.M」が有効な属性またはエラー。その結果、「reg」が間違った種類のレジスタである場合に備えて、「reg.M」の事実上すべてのアクセスを try-except 構造で保護する必要があります。

第 2 に、インスタンスからクラスを分離することで、オブジェクトの動作をオブジェクト データから分離することもできます。次のクラスを検討してください。

class ObjectHolder(object):
    def __init__(self, obj):
        self.obj = obj

このクラスが何に役立つかについて心配する必要はありません。任意の Python オブジェクトへの無制限のアクセスを保持および提供するためのものであることを知っておいてください。

>>> holder = ObjectHolder(42)
>>> print(holder.obj) 42
>>> holder.obj = range(5)
>>> print(holder.obj) [0, 1, 2, 3, 4]

このクラスは任意のオブジェクトを保持することを意図しているため、誰かがそこに記述子オブジェクトを格納したいと思うかもしれません:

>>> holder.obj = property(lambda x: x.foo)
>>> print(holder.obj) <property object at 0x02415AE0>

ここで、Python がインスタンス属性に格納された記述子の記述子プロトコルを呼び出したとします。

>>> holder = ObjectHolder(None)
>>> holder.obj = property(lambda x: x.foo)
>>> print(holder.obj)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'ObjectHolder' object has no attribute 'foo'

この場合、ObjectHolder は単にプロパティ オブジェクトをデータとして保持することに失敗します。プロパティ オブジェクト (記述子) をインスタンス属性に割り当てるだけで、ObjectHolderの動作が変わります。「holder.obj」を単純なデータ属性として扱う代わりに、「holder.obj」へのアクセスで記述子プロトコルの呼び出しを開始し、最終的にそれらを存在しない無意味な「holder.foo」属性にリダイレクトします。クラスの作成者が意図したものではありません。

記述子の複数のインスタンスをサポートできるようにしたい場合は、その記述子のコンストラクターに名前引数 (プレフィックス) を持たせ、追加された属性にその名前のプレフィックスを付けます。クラス インスタンス内に名前空間オブジェクト (辞書) を作成して、すべての新しいプロパティ インスタンスを保持することもできます。

于 2012-09-28T18:09:51.107 に答える
13

多くの高度な機能は、インスタンスではなくクラスで定義されている場合にのみ機能します。たとえば、すべての特別なメソッド。これにより、コード評価がより効率的になるだけでなく、インスタンスと型の間の分離が明確になります。

これがどの程度推奨されるかはわかりませんが、インスタンスに記述子インスタンスから属性値へのマッピングを格納できます。

class Prop(object):
     def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj._value * obj._multiplier[self]

    def __set__(self, obj, value):
        if obj is None:
            return self
        obj._value = value

class Obj(object):
    val = Prop()

    def __init__(self):
        self._value = 1
        self._multiplier = {Obj.val: 0}

これには、他の 2 つの提案されたオプションよりも明らかな利点があります。

  1. インスタンスごとのクラスはオブジェクト指向を壊し、メモリ使用量を増やします。
  2. オーバーライド__getattribute__は非効率的で (すべての属性アクセスはオーバーライドされた特別なメソッドを経由する必要があるため)、脆弱です。

別の方法として、プロキシ プロパティを使用できます。

class PerInstancePropertyProxy(object):
    def __init__(self, prop):
        self.prop = prop
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.prop].__get__(instance, owner)
    def __set__(self, instance, value):
        instance.__dict__[self.prop].__set__(instance, value)
class Prop(object):
    def __init__(self, value, multiplier):
        self.value = value
        self.multiplier = multiplier
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return self.value * self.multiplier
    def __set__(self, instance, value):
        self.value = value
class Obj(object):
    val = PerInstancePropertyProxy('val')
    def __init__(self):
        self.__dict__['val'] = Prop(1.0, 10.0)
    def prop(self, attr_name):
        return self.__dict__[attr_name]
于 2012-09-28T18:19:58.900 に答える