4

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

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

4

6 回答 6

8

不,没有每个实例属性这样的东西;像所有描述符一样,总是在类上查找属性。请参阅描述符 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)
于 2013-11-06T21:16:59.180 回答
3

所以我的问题是:“实例属性”是否存在?

不。

如果不是,原因是什么?

因为属性是作为描述符实现的。描述符的神奇之处在于,它们在对象类型的字典中找到时与在对象的字典中找到时会做不同的事情。

我已经能够找到很多关于如何在 Python 中使用属性的信息,但很少了解它们是如何实现的。

阅读上面链接的描述符操作指南。


那么,有没有办法可以做到这一点?

好吧,是的,如果您愿意重新考虑一下设计。

对于您的情况,您要做的就是_traits代替__dict__,并且您正在动态生成无用的 getter 函数,因此您可以用几行代码替换整个内容,如 Martijn Pieters 的回答。

或者,如果你想重定向.foo._fooiff foois in a list (or, better, set) of _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
于 2013-11-06T21:16:07.677 回答
1

我在这里需要的是可以添加到实例中的属性。

property()是一个描述符,它们仅在存储在类中时才有效,而不是在存储在实例中时。

实现实例属性效果的一种简单方法是执行 def __getattr__。这将让您控制查找的行为。

于 2013-11-06T21:18:40.843 回答
0

如果您不需要将该属性设为只读 - 您可以__dict__使用 kwargs更新对象

class Monster(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

比您可以像这样创建该类的实例:

m0 = Monster(name='X')
m1 = Monster(name='godzilla', behaviour='godzilla behaviour')
于 2013-11-06T21:19:17.127 回答
0

做你想做的另一种方法可能是动态创建怪物类。例如

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
于 2013-11-06T21:56:33.733 回答
0

我不一定推荐这个(__getattr__解决方案通常更可取),但您可以编写您的类,以便由它创建的所有实例都有自己的类(嗯,它的子类)。这是该想法的快速hacky实现:

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)

现在您可以放心地设置属性,type(self)因为每个实例都有自己的类对象。

@Claudiu 的答案是同一种东西,只是用一个函数实现,而不是集成到实例制作机器中。

于 2013-11-06T23:10:57.610 回答