通常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 :(得分: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。
有可能的解决方法:
将描述符放入类中并使其使用,例如obj.xxxattr来存储值。 如果只有一个描述符行为,则可行。
覆盖 setattr 和 getattr 以及 delattr 以回复描述。
将一个描述符放入响应存储在对象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