python使用__dict__获取对象属性

chr*_*ise 1 python python-3.x

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

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

    @property
    def a(self):
        return 1
Run Code Online (Sandbox Code Playgroud)

访问属性 a via __dict__(with _a) 似乎不返回属性装饰值而是初始化值None

o = O()
print(o.a, o.__dict__['_a'])
>>> 1, None
Run Code Online (Sandbox Code Playgroud)

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

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

bru*_*ers 5

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

如果您也想要与属性一起使用的东西,则必须从dir(self.__class__)and手动获取它们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)
Run Code Online (Sandbox Code Playgroud)

请注意,这不会阻止_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)
Run Code Online (Sandbox Code Playgroud)

另请注意,这不会处理其他计算属性(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]) 
Run Code Online (Sandbox Code Playgroud)