使用每个实例属性的__str__方法作为类的__str__方法

4
在Python 3中,是否有一种方法可以以编程方式递归调用私有成员变量的唯一__str__函数?例如:
class A:
  def __str__(self):
    return "A"

class B:
  def __str__(self):
    return "B"

class C:
  def __init__(self):
    self._A = A()
    self._B = B()

  def __str__(self):
    for var in vars(self):
      return str(var)

当调用单个私有成员时,它可以正常工作。但是希望能够动态地执行它们。非常感谢。

1
__str__ should return a string. You don't do the print in __str__ - Patrick Haugh
啊,是的,打错了。谢谢指出。 - troy_s
1
标准的方法是使用PEP 557 dataclasses。在此之前,attrs项目最受欢迎,并且仍然是最灵活的。 - o11c
我很乐意听一下如何使用dataclass来完成这个。有人有例子吗? - troy_s
@BradSolomon 对的,但是一旦你有了元数据,如果你真的需要,你可以随意定义自己的函数。 - o11c
3个回答

3

vars 函数返回一个字典,其中键是变量名(以字符串形式表示),值是变量的值。因此迭代值应该可以工作。

class A:
  def __str__(self):
    return "A"

class B:
  def __str__(self):
    return "B"

class C:
  def __init__(self):
    self._A = A()
    self._B = B()

  def __str__(self):
    output = ""
    for _,var in vars(self).items(): #Iterate over the values
      output += str(var) #Use the str() function here to make the object return its string form
    return output #Need to return instead of print since that is what the __str__() function should do

你可以在需要的值之间添加某种分隔符(例如像 \n 这样),如果你想这么做。只需用 str(var) + "\n" 替换 str(var) 即可。

这个有效,非常感谢。如果其他人对这种方法有意见,我全听着呢。再次感谢!但是,这里的“_, var”究竟是在做什么?获取半私有变量? - troy_s
@BradSolomon 刚刚检查了一下。键是变量名,以字符串形式编写。因此,在这种情况下,它们是 "_A""_B" - kungfushark
@BradSolomon,键是对应于属性名称的字符串,与命名空间中对象的__str____repr__无关。 - juanpa.arrivillaga
@BradSolomon 如果你打印字典,那么这个表示将被使用。 - juanpa.arrivillaga
@troy_s _,var 是用于遍历字典的。下划线代表键,而 var 则代表值。在我不需要使用特定变量时,就会使用下划线,就像这个例子一样。 - kungfushark
@juanpa.arrivillaga 是的,我明白你的意思。我之前的评论承认是毫无意义的。出于自己的理解好奇,你是否发现下面的答案有什么问题。 - Brad Solomon

1

您也可以使用字典键;vars()self.__dict__

>>> class A:
...     def __str__(self):
...         return self.__class__.__name__
... 
>>> class B:
...     def __str__(self):
...         return self.__class__.__name__
... 
>>> str(A())
'A'
>>> repr(A())  # "long-form" the hex-string is id()
'<__main__.A object at 0x10f65a908>'

>>> class C:
...     def __init__(self):
...        self.A = A()
...        self.B = B()
...     def __str__(self):
...         return '\n'.join(self.__dict__)
... 
>>> C()
<__main__.C object at 0x10f65aa58>
>>> print(C())  # Uses str(C())
A
B

vars(self) 实际上就是 self。而 self.__dict__ 则是用来存储对象(可写)属性的 dict

>>> C().__dict__
{'A': <__main__.A object at 0x10f65aa90>, 'B': <__main__.B object at 0x10f65aac8>}

签名是'\n'.join(iterable),当您迭代字典时,您会迭代其键,在这种情况下足够了。

关于数据类的注意事项

我不确定(Python 3.7+),数据类是否是更简单的解决方案。因为据我所知,它们自动实现了__repr__()但没有实现__str__():

>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class C:
...     _A: object = A()
...     _B: object = B()
... 
>>> c = C()  # still uses repr() for each field
>>> str(c)
'C(_A=<__main__.A object at 0x10f373828>, _B=<__main__.B object at 0x10f373940>)'

换句话说,您需要将A.__str__替换为A.__repr__(对于B也是如此,但考虑到这两个类,这可能不是您想要做的事情)。

是的,这只是偶然起作用,因为您的属性名称恰好与由这些属性引用的对象返回的str相同。 - juanpa.arrivillaga
据我所知,这个不起作用。它在这个例子中工作的原因是成员的名称(AB)与它们的字符串表示相同。如果您将名称更改为 _A_B,就像在原始问题中一样,这将输出 "_A""_B",这些不是正确的字符串表示形式。 - kungfushark
那是因为 self.__class__.__name__,对吧?@kungfushark。如果你想手动更改它,只需要在相应位置使用一个 str 文本即可。 - Brad Solomon
@BradSolomon 我的意思是,如果类AB有实际的__str __()函数,那么这个解决方案将不起作用。例如,如果str(A("x"))应该是"x"(在A().__str __()中定义),则此解决方案将无法正常工作,并且只会输出C()中成员名称。 - kungfushark

0

你确定你不应该使用__repr__吗?

无论如何,这里有一个使用attrs的例子,因为我被困在Python 3.5上。使用dataclasses将以类似的方式工作。

import attr


class A:
    def __str__(self):
        return 'A'

class B:
    def __str__(self):
        return 'B'

@attr.s
class C:
    a = attr.ib(default=attr.Factory(A))
    b = attr.ib(default=attr.Factory(B))


if __name__ == '__main__':
    c = C()
    print(c) # __str__ defaults to __repr__

    def __str__(self):
        bits = ['<C']
        for a in self.__attrs_attrs__:
            bits.append(' %s=%s' % (a.name, getattr(self, a.name)))
        bits.append('>')
        return ''.join(bits)
    C.__str__ = __str__
    print(c) # custom __str__

这是为了漂亮的输出,因此字符串似乎在这里是最合逻辑的选择。但是不确定。 - troy_s

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接