9

通常、Python記述子はクラス属性として定義されます。しかし、私の場合、すべてのオブジェクトインスタンスに、入力に応じて異なるセット記述子を持たせたいと思います。例えば:

class MyClass(object):
  def __init__(self, **kwargs):
    for attr, val in kwargs.items():
      self.__dict__[attr] = MyDescriptor(val)

各オブジェクトには、インスタンス化時に決定される異なる属性のセットがあります。これらは1回限りのオブジェクトであるため、最初にサブクラス化するのは便利ではありません。

tv = MyClass(type="tv", size="30")
smartphone = MyClass(type="phone", os="android")

tv.size   # do something smart with the descriptor

オブジェクトへの記述子の割り当てが機能していないようです。属性にアクセスしようとすると、次のようになります。

<property at 0x4067cf0>

なぜこれが機能しないのか知っていますか?回避策はありますか?

4

5 に答える 5

3

オブジェクトのクラスに記述子を割り当てる必要があるため、これは機能しません。

class Descriptor:

    def __get__(...):
        # this is called when the value is got

    def __set__(...
    def __del__(...

あなたが書くなら

obj.attr
=> type(obj).__getattribute__(obj, 'attr') is called
=> obj.__dict__['attr'] is returned if there else:
=> type(obj).__dict__['attr'] is looked up
if this contains a descriptor object then this is used.

したがって、タイプdictionairyはオブジェクトdictionairyではなく記述子を検索するため、機能しません。

考えられる回避策があります:

  1. 記述子をクラスに入れ、たとえばobj.xxxattrを使用して値を格納します。記述子の動作が1つしかない場合、これは機能します。

  2. setattrgetattrおよびdelattrを上書きして、記述子に応答します。

  3. オブジェクトdictionairyに格納されている記述子に応答する記述子をクラスに配置します。

于 2012-05-03T17:43:14.587 に答える
2

記述子を間違った方法で使用しています。

記述子は、インスタンスレベルでは意味がありません。結局のところ、__get__/メソッドはクラスの__set__ へのアクセスを提供します。instance

正確に何をしたいのかわからない場合__set__は、「呼び出し元/インスタンス」が誰であるかを確認し、それに応じてアクションを実行することにより、インスタンスごとのロジックをメソッド内に配置することをお勧めします。

それ以外の場合は、代替ソリューションを提案できるように、達成しようとしていることを教えてください。

于 2012-05-03T18:02:59.637 に答える
2

exec作り上げたクラスを使って動的にインスタンスを作成します。これはあなたのユースケースに合うかもしれません。

def make_myclass(**kwargs):

    class MyDescriptor(object):
        def __init__(self, val):
            self.val = val

        def __get__(self, obj, cls):
            return self.val

        def __set__(self, obj, val):
            self.val = val

    cls = 'class MyClass(object):\n{}'.format('\n'.join('    {0} = MyDescriptor({0})'.format(k) for k in kwargs))

    #check if names in kwargs collide with local names
    for key in kwargs:
        if key in locals():
            raise Exception('name "{}" collides with local name'.format(key))

    kwargs.update(locals())
    exec(cls, kwargs, locals())
    return MyClass()  

テスト;

In [577]: tv = make_myclass(type="tv", size="30")

In [578]: tv.type
Out[578]: 'tv'

In [579]: tv.size
Out[579]: '30'

In [580]: tv.__dict__
Out[580]: {}  

ただし、インスタンスは異なるクラスです。

In [581]: phone = make_myclass(type='phone')

In [582]: phone.type
Out[582]: 'phone'

In [583]: tv.type
Out[583]: 'tv'

In [584]: isinstance(tv,type(phone))
Out[584]: False

In [585]: isinstance(phone,type(tv))
Out[585]: False

In [586]: type(tv)
Out[586]: MyClass

In [587]: type(phone)
Out[587]: MyClass

In [588]: type(phone) is type(tv)
Out[588]: False
于 2016-03-04T11:11:30.170 に答える
1

これは、名前付きタプルのユースケースのように見えます

于 2013-10-28T16:50:37.847 に答える
1

これが機能しない理由は、Pythonがインスタンスではなく、クラスの属性を検索するときに記述子をチェックするだけだからです。問題のメソッドは次のとおりです。

クラスだけでなくインスタンスにも記述子プロトコルを実装するために、クラスでこれらのメソッドをオーバーライドすることができます。

# do not use in production, example code only, needs more checks
class ClassAllowingInstanceDescriptors(object):
    def __delattr__(self, name):
        res = self.__dict__.get(name)
        for method in ('__get__', '__set__', '__delete__'):
            if hasattr(res, method):
                # we have a descriptor, use it
                res = res.__delete__(name)
                break
        else:
            res = object.__delattr__(self, name)
        return res
    def __getattribute__(self, *args):
        res = object.__getattribute__(self, *args)
        for method in ('__get__', '__set__', '__delete__'):
            if hasattr(res, method):
                # we have a descriptor, call it
                res = res.__get__(self, self.__class__)
        return res
    def __setattr__(self, name, val):
        # check if object already exists
        res = self.__dict__.get(name)
        for method in ('__get__', '__set__', '__delete__'):
            if hasattr(res, method):
                # we have a descriptor, use it
                res = res.__set__(self, val)
                break
        else:
            res = object.__setattr__(self, name, val)
        return res
    @property
    def world(self):
        return 'hello!'

上記のクラスを以下のように使用する場合:

huh = ClassAllowingInstanceDescriptors()
print(huh.world)
huh.uni = 'BIG'
print(huh.uni)
huh.huh = property(lambda *a: 'really?')
print(huh.huh)
print('*' * 50)
try:
    del huh.world
except Exception, e:
    print(e)
print(huh.world)
print('*' * 50)
try:
    del huh.huh
except Exception, e:
    print(e)
print(huh.huh)

結果は次のとおりです。

こんにちは!

大きい

本当?


属性を削除できません

こんにちは!


属性を削除できません

本当?

于 2016-02-26T17:17:19.713 に答える