9

通常 Python 描述符被定义为类属性。但就我而言,我希望每个对象实例都有不同的集合描述符,这些描述符取决于输入。例如:

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

每个对象都有不同的属性集,这些属性在实例化时决定。由于这些是一次性对象,因此首先对它们进行子类化并不方便。

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.

所以它不起作用,因为类型字典是查找描述符而不是对象字典。

有可能的解决方法:

  1. 将描述符放入类并使其使用例如 obj.xxxattr 来存储值。如果只有一个描述符行为,则此方法有效。

  2. 覆盖setattrgetattrdelattr以响应描述符。

  3. 将一个描述符放入响应存储在对象字典中的描述符的类中。

于 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 回答