2012-05-03 49 views
8

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

你知道为什么这不起作用吗?有什么解决办法吗?

+0

为什么不使用MyClass的不同子类与overriden描述符? – KurzedMetal

+0

编辑添加更多示例。我猜每个实例描述符不能完成。我已经使用__getattr__解决了它。尽管如此,仍然不明白底层语言约束。 –

+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. 覆盖SETATTRGETATTRdelattr对discriptors回应。

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

2

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

描述符在实例级别上没有意义。在所有__get__/__set__ 方法可以让您访问该类的instance

不知道正是你想做的事,我建议你把每个实例 逻辑__set__方法中,通过检查谁是“主叫/实例”,并采取相应的行动。

否则告诉我们你正在努力实现什么,以便我们可以提出替代解决方案。

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

真的吗?


不能删除属性

你好!


不能删除属性

真的吗?

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 
+0

在好的一面,性能会比[我的回答](http://stackoverflow.com/a/35657853/208880)更好;在另一方面,实例不是同一个类,并且可能会有混淆,因为它们都具有相同的类名。在适当的情况下仍然是一个很好的解决方案。 –