Python属性作为实例属性

时间:2013-11-06 21:13:09

标签: python properties

我正在尝试编写一个具有动态属性的类。请考虑以下具有两个只读属性的类:

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中使用属性的内容,但很少关于如何实现它们。如果“实例属性”没有意义,我想了解原因。

6 个答案:

答案 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的回答是同样的事情,只是用函数实现而不是集成到实例制作机器中。