【问题标题】:Python properties as instance attributesPython 属性作为实例属性
【发布时间】:2013-11-18 07:56:01
【问题描述】:

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

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

【问题讨论】:

  • 避免使用子类有什么原因吗?
  • 你为什么要这样做?如果你所有的 getter 函数都是从 dict 返回值的相同函数,为什么还要拥有它们呢?首先要有 getter 的唯一原因是如果您需要运行一些代码来生成值。
  • @abarnert:属性也常用于创建只读变量,请注意缺少设置器。不过你是对的,这是一个非常简单的例子,我的真实代码确实有更复杂的 getter。

标签: python properties


【解决方案1】:

我不一定推荐这个(__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 的回答是同一种东西,只是用一个函数实现,而不是集成到实例制作机制中。

【讨论】:

    【解决方案2】:

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

    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
    

    【讨论】:

      【解决方案3】:

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

      没有。

      如果不是,是什么原因?

      因为属性被实现为descriptors。描述符的神奇之处在于,它们在对象类型的字典中找到时与在对象的字典中找到时执行的操作不同。

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

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


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

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

      对于您的情况,您要做的就是使用_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
      

      【讨论】:

        【解决方案4】:

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

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

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

        m0 = Monster(name='X')
        m1 = Monster(name='godzilla', behaviour='godzilla behaviour')
        

        【讨论】:

          【解决方案5】:

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

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

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

          【讨论】:

            【解决方案6】:

            不,没有每个实例属性这样的东西;像所有描述符一样,属性总是在类中查找。请参阅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)
            

            【讨论】:

              猜你喜欢
              • 2011-11-24
              • 2017-04-24
              • 1970-01-01
              • 1970-01-01
              • 2019-05-17
              • 2012-09-17
              • 2012-10-07
              • 1970-01-01
              相关资源
              最近更新 更多