创建每个实例的属性描述符?

时间:2012-05-03 17:34:32

标签: python metaprogramming propertydescriptor

通常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>

你知道为什么这不起作用吗?有什么工作吗?

5 个答案:

答案 0 :(得分:2)

这不起作用,因为您必须将描述符分配给对象的类。

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来存储值。 如果只有一个描述符行为,则可行。

  2. 覆盖 setattr getattr 以及 delattr 以回复描述。

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

答案 1 :(得分:2)

您正在以错误的方式使用描述符。

描述符在实例级别上没有意义。毕竟__get__ / __set__ 方法可让您访问班级的instance

不知道你想要做什么,我建议你把每个实例 __set__方法中的逻辑,通过检查谁是“调用者/实例”并相应地采取行动。

否则告诉我们您要实现的目标,以便我们提出替代解决方案。

答案 2 :(得分:1)

这看起来像是named tuples

的用例

答案 3 :(得分:1)

它不起作用的原因是因为Python只在查找类上的属性时检查描述符,而不是在实例上查找;有问题的方法是:

可以在类上覆盖这些方法,以便在实例和类上实现descriptor protocol

# 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)

结果是:

  

您好!

     

BIG

     

确实

           

无法删除属性

     

您好!

           

无法删除属性

     

确实

答案 4 :(得分:1)

我通过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