我正在尝试编写一个具有动态属性的类。请考虑以下具有两个只读属性的类:
class Monster(object):
def __init__(self,color,has_fur):
self._color = color
self._has_fur = has_fur
@property
def color(self): return self._color
@property
def has_fur(self): return self._has_fur
我想对此进行概括,以便__init__
可以获取任意字典并从字典中的每个项创建只读属性。我可以这样做:
class Monster2(object):
def __init__(self,traits):
self._traits = traits
for key,value in traits.iteritems():
setattr(self.__class__,key,property(lambda self,key=key: self._traits[key]))
但是,这有一个严重的缺点:每次我创建Monster
的新实例时,我实际上都在修改Monster
类。我没有为新的Monster
实例创建属性,而是有效地向所有 Monster
实例添加属性。要看到这个:
>>> hasattr(Monster2,"height")
False
>>> hasattr(Monster2,"has_claws")
False
>>> blue_monster = Monster2({"height":4.3,"color":"blue"})
>>> hasattr(Monster2,"height")
True
>>> hasattr(Monster2,"has_claws")
False
>>> red_monster = Monster2({"color":"red","has_claws":True})
>>> hasattr(Monster2,"height")
True
>>> hasattr(Monster2,"has_claws")
True
这当然有道理,因为我使用setattr(self.__class__,key,property(lambda self,key=key: self._traits[key]))
明确地将属性添加为类属性。我需要的是可以添加到实例的属性。 (即“实例属性”)。不幸的是,根据我阅读和尝试过的所有内容,属性始终是类属性,而不是实例属性。例如,这不起作用:
class Monster3(object):
def __init__(self,traits):
self._traits = traits
for key,value in traits.iteritems():
self.__dict__[key] = property(lambda self,key=key: self._traits[key])
>>> green_monster = Monster3({"color":"green"})
>>> green_monster.color
<property object at 0x028FDAB0>
所以我的问题是:“实例属性”存在吗?如果没有,原因是什么?我已经能够找到很多关于如何在Python中使用属性的内容,但很少关于如何实现它们。如果“实例属性”没有意义,我想了解原因。
答案 0 :(得分:8)
不,没有每个实例属性这样的东西;与所有描述符一样,属性总是在类上查找。请参阅descriptor HOWTO了解具体方法。
您可以使用__getattr__
挂钩来实现动态属性,它可以动态检查实例属性:
class Monster(object):
def __init__(self, traits):
self._traits = traits
def __getattr__(self, name):
if name in self._traits:
return self._traits[name]
raise AttributeError(name)
但这些属性并非真正动态;你可以直接在实例上设置这些:
class Monster(object):
def __init__(self, traits):
self.__dict__.update(traits)
答案 1 :(得分:3)
所以我的问题是:“实例属性”存在吗?
没有
如果没有,原因是什么?
因为属性实现为descriptors。描述符的神奇之处在于,当在对象的类型字典中找到它们时,它们会在对象的字典中找到不同的东西。
我已经能够找到很多关于如何在Python中使用属性的内容,但很少关于如何实现它们。
阅读上面链接的Descriptor HowTo指南。
那么,有没有办法可以做到这一点?
嗯,是的,如果你愿意重新考虑一下这个设计。
对于您的情况,您要做的就是使用_traits
代替__dict__
,并且您正在动态生成无用的getter函数,因此您可以用几行替换整个函数代码,如Martijn Pieters的回答。
或者,如果您想将.foo
重定向到._foo
,如果foo
位于_traits
的列表中(或更好地设置),那就简单了:
def __getattr__(self, name):
if name in self._traits:
return getattr(self, '_' + name)
raise AttributeError
但是,假设您实际上对getter函数有一些用处 - 每个属性实际上都需要一些代码来生成值,这些值已包含在函数中,并存储在_traits
中。在那种情况下:
def __getattr__(self, name):
getter = self._traits.get(name)
if getter:
return getter()
raise AttributeError
答案 2 :(得分:1)
我需要的是可以添加到实例的属性。
属性()是一个描述符,只有在存储在类中时才有效,而不是存储在实例中。
实现实例属性效果的简单方法是执行def __getattr__。这将允许您控制查找的行为。
答案 3 :(得分:0)
如果您不需要将该属性设置为只读 - 您只需使用kwargs更新对象__dict__
class Monster(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
比你可以像这样制作那个类的实例:
m0 = Monster(name='X')
m1 = Monster(name='godzilla', behaviour='godzilla behaviour')
答案 4 :(得分:0)
另一种做你想做的事情的方法是动态创建怪物类。 e.g。
def make_monster_class(traits):
class DynamicMonster(object):
pass
for key, val in traits.items():
setattr(DynamicMonster, key, property(lambda self, val=val: val))
return DynamicMonster()
blue_monster = make_monster_class({"height": 4.3, "color": "blue"})
red_monster = make_monster_class({"color": "red", "has_claws": True})
for check in ("height", "color", "has_claws"):
print "blue", check, getattr(blue_monster, check, "N/A")
print "red ", check, getattr(red_monster, check, "N/A")
输出:
blue height 4.3
red height N/A
blue color blue
red color red
blue has_claws N/A
red has_claws True
答案 5 :(得分:0)
我不一定推荐这个(__getattr__
解决方案通常更可取)但你可以编写你的类,以便所有由它制作的实例都有自己的类(嗯,a它的子类)。这是对这个想法的快速实施:
class MyClass(object):
def __new__(Class):
Class = type(Class.__name__, (Class,), {})
Class.__new__ = object.__new__ # to prevent infinite recursion
return Class()
m1 = MyClass()
m2 = MyClass()
assert type(m1) is not type(m2)
现在您可以使用aplomb在type(self)
上设置属性,因为每个实例都有自己的类对象。
@ Claudiu的回答是同样的事情,只是用函数实现而不是集成到实例制作机器中。