【问题标题】:python getting object properties with __dict__python使用__dict__获取对象属性
【发布时间】:2017-11-22 10:42:09
【问题描述】:

使用 py3,我有一个使用 @property 装饰器的对象

class O(object):
    def __init__(self):
        self._a = None

    @property
    def a(self):
        return 1

通过__dict__(使用_a)访问属性a似乎不会返回属性修饰值,而是返回初始化值None

o = O()
print(o.a, o.__dict__['_a'])
>>> 1, None

有没有通用的方法来完成这项工作?我最需要这个

def __str__(self):
    return ' '.join('{}: {}'.format(key, val) for key, val in self.__dict__.items())

【问题讨论】:

  • 属性属于,而不是实例。无论如何,装饰的不是 value _a,而是 method a

标签: python python-3.x


【解决方案1】:

当然self.__dict__["_a"] 会返回self._a(实际上是相反的——self._a 会返回self.__dict__["_a"] - 但无论如何),而不是self.aproperty 在这里做的唯一一件事就是自动调用它的 getter(你的 a(self) 函数),这样你就不必输入括号,否则它只是一个普通的方法调用。

如果您也想要一些与属性一起使用的东西,则必须从 dir(self.__class__)getattr(self.__class__, name) 手动获取它们,即:

def __str__(self):
    # py2
    attribs = self.__dict__.items()
    # py3
    # attribs = list(self.__dict__.items())

    for name in dir(self.__class__):
        obj = getattr(self.__class__, name)
        if isinstance(obj, property):
           val = obj.__get__(self, self.__class__)
           attribs.append((name, val))

    return ' '.join('{}: {}'.format(key, val) for key, val in attribs)

请注意,这不会阻止 _a 出现在 attribs 中 - 如果您想避免这种情况,您还必须从 attribs 列表中过滤掉受保护的名称(全部 受保护的名称,因为您要求的是通用名称):

def __str__(self):
    attribs = [(k, v) for k, v in self.__dict__.items() if not k.startswith("_")]

    for name in dir(self.__class__):
        # a protected property is somewhat uncommon but
        # let's stay consistent with plain attribs
        if name.startswith("_"):
            continue  
        obj = getattr(self.__class__, name)
        if isinstance(obj, property):
           val = obj.__get__(self, self.__class__)
           attribs.append((name, val))

    return ' '.join('{}: {}'.format(key, val) for key, val in attribs)

还要注意,这不会处理其他计算属性(property 只是描述符协议的一种通用实现)。在这一点上,对于仍然尽可能通用但可以在需要时自定义的东西的最佳选择是将上述实现为带有几个专门化钩子的 mixin 类:

class PropStrMixin(object):

    # add other descriptor types you want to include in the 
    # attribs list
    _COMPUTED_ATTRIBUTES_CLASSES = [property,] 

    def _get_attr_list(self):
        attribs = [(k, v) for k, v in self.__dict__.items() if not k.startswith("_")]

        for name in dir(self.__class__):
            # a protected property is somewhat uncommon but
            # let's stay consistent with plain attribs
            if name.startswith("_"):
                continue  
            obj = getattr(self.__class__, name)
            if isinstance(obj, *self._COMPUTED_ATTRIBUTES_CLASSES):
               val = obj.__get__(self, self.__class__)
               attribs.append((name, val))
        return attribs 

    def __str__(self):
        attribs = self._get_attr_list()
        return ' '.join('{}: {}'.format(key, val) for key, val in attribs)


class YouClass(SomeParent, PropStrMixin):
    # here you can add to _COMPUTED_ATTRIBUTES_CLASSES
    _COMPUTED_ATTRIBUTES_CLASSES = PropStrMixin + [SomeCustomDescriptor]) 

【讨论】:

  • 谢谢。这比我希望的要多得多。很好的答案
【解决方案2】:

属性基本上是一个“计算属性”。通常,属性的值不会存储在任何地方,它是按需计算的。这就是为什么您在__dict__ 中找不到它的原因。

@property 装饰器将类方法替换为描述符对象,然后调用原始方法作为其 getter。这发生在班级级别。

o.a 的查找从实例开始。它在那里不存在,在下一步中检查该类。 O.a存在并且是一个描述符(因为它对描述符协议有特殊的方法),所以调用描述符的getter并使用返回值。

(已编辑) 没有通用的方法来转储描述符的名称:值对。包括基地在内的班级都必须检查,这部分并不难。然而,检索这些值等同于函数调用,并且可能会产生意想不到的不良副作用。对于不同的观点,我想在这里引用 bruno desthuilliers 的评论:“属性获取不应该有不想要的副作用(如果有,那么就有明显的设计错误)”。

【讨论】:

  • 使用 dir 和 getattr 负责检查(包括整个继承层次结构),并且属性 get 不应该有不必要的副作用(如果有,则存在明显的设计错误)
  • @brunodesthuilliers 感谢您的反馈。明天我将更新关于班级检查的答案。使用dir,我想会容易得多。但是,我仍然认为副作用的风险是真实存在的。例如,任何与硬件通信都可能产生超时、OSErrors 和类似情况。
  • 我确实理解您对副作用的担忧,但属性不应该以任何方式出现此类可能的故障 - 如果确实如此,那么它应该是一个简单的方法。
  • @brunodesthuilliers 我已经更新了我的答案,以包括您对未阅读 cmets 的访问者的看法。我觉得这个话题很有趣。在这里,我只找到了这个:stackoverflow.com/questions/4739597/getter-with-side-effect。如果延迟初始化没问题,我可以想象访问 getter 会触发通常禁用的程序子系统的初始化,因此配置不正确。在这种情况下可能会出现缺失值错误。
【解决方案3】:

您还可以将self._a 更新为getter,因为getter 的返回应该始终反映self._a 存储的内容:

class O(object):
    def __init__(self):
        self._a = self.a

    @property
    def a(self):
        self._a = 1
        return self._a

可能有点多余,但在这种情况下,最初设置 self._a = None 是没有用的。

如果您需要二传手

如果删除 getter 中的第一行,这也是兼容的:

@a.setter
def a(self, value):
    self._a = value

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-27
    • 2011-11-21
    • 2020-10-24
    • 2019-12-02
    • 1970-01-01
    • 2011-06-20
    • 1970-01-01
    相关资源
    最近更新 更多