Python中关于列表的__str__方法让我感到困惑

127

作为一个Java背景的开发者,我理解__str__是Python版的toString(尽管Python编程语言更早)。

因此,我定义了一个小类和一个__str__方法,如下所示:

class Node:

    def __init__(self, id):
        self.id = id
        self.neighbours = []
        self.distance = 0


    def __str__(self):
        return str(self.id)
我接着创建了几个实例:
uno = Node(1)    
due = Node(2)    
tri = Node(3)    
qua = Node(4)

现在,当尝试打印这些对象之一时,预期的行为是打印其关联值。这也会发生。

print uno
产生。
1

但是当我执行以下操作时:

uno.neighbours.append([[due, 4], [tri, 5]])

然后

print uno.neighbours

我得到

[[[<__main__.Node instance at 0x00000000023A6C48>, 4], [<__main__.Node instance at 0x00000000023A6D08>, 5]]]

我预期的地方

[[2, 4], [3, 5]]

我错过了什么?还有哪些让人难堪的事情我做错了? :)


有趣的是,这个问题被问了7年后才关闭。 - RW77
8个回答

150

Python有两种不同的方法将对象转换为字符串:str()repr()。打印一个对象使用str();打印包含对象的列表使用str()表示列表本身,但是list.__str__()的实现调用repr()来处理单个项目。

因此你还应该重写__repr__()。一个简单的

__repr__ = __str__

在类的结尾处处理即可解决问题。


34
当没有 __str__() 时,会调用 __repr__(),因此只定义 __repr__() 就足够了。 - glglgl
4
__repr__不应无条件设置为__str____repr__应创建一个"表示形式,看起来像是一个有效的Python表达式,可以用来重新创建具有相同值的对象"。在这种情况下,它应该是Node(2)而不是2 - trapicki
1
@trapicki 是的,不过这更像是一种指导方针而不是要求。OP希望问题中提到的列表输出看起来像[[2, 4],[3, 5]],这就是我的回答。类型偏离你提到的指南是非常普遍的,例如Numpy的浮点类型只是将数字显示为表示,而不是numpy.float64(42.0),在列表中变得非常冗长。 - Sven Marnach
我不是Python专家,但在这种情况下,eval(repr(obj))不会返回实际节点。@tapicki的答案更接近真相,但缺少OP所要求的基本部分。阅读此答案的任何人都可能想查看https://www.quora.com/What-does-repr-method-mean - user3081519

44

由于 Python 相对于 Java 有着无限的优越性,Python 不仅有一个,而是两个 toString 操作。

其中一个是 __str__,另一个是 __repr__

__str__ 将返回一个可读的字符串。 __repr__ 将返回一个内部表示。

可以通过调用 repr(obj) 或使用反引号`obj`来在对象上调用 __repr__

在打印列表以及其他容器类时,包含的元素将使用 __repr__ 进行打印。


7
请注意,Python 3 中使用反引号作为 repr() 的简写已经被取消。稍微挑毛病一下:你的第一句话可能会被那些过于严肃对待事情的人误解。 - Steven Rumbalski
14
我意识到我有可能冒犯那些缺乏幽默感的人。我认为我没有越线,你认为我应该编辑我的帖子吗?请注意,我只需要翻译内容,不会添加任何额外解释或信息。 - Hans Then
这是一个判断性的决定。可能不需要。我只是想指出一下。 - Steven Rumbalski
10
区分讽刺和挖苦经常很困难,这可能是为什么你的回答虽然本质上没问题,但却没有得到太多赞同的原因。而“幽默挑战”这句评论可能也没有起到帮助的作用。 - martineau
@martineau - 不,它们很容易辨别。无论如何,我认为这是一个很棒的答案。我认为幽默应该合法化,我因为你的评论而点赞了它。谢谢! - RW77
@RW77:确实有些难以辨别,因为讽刺是一种讽刺,没有人能在全文回答中看到你微笑 - 加上Stackoverlow不是幽默博客。 ;¬) - martineau

25

它提供了易于阅读的输出版本,而不是“Object”:例如:

class Pet(object):

    def __init__(self, name, species):
        self.name = name
        self.species = species

    def getName(self):
        return self.name

    def getSpecies(self):
        return self.species

    def Norm(self):
        return "%s is a %s" % (self.name, self.species)

if __name__=='__main__':
    a = Pet("jax", "human")
    print a 

返回

<__main__.Pet object at 0x029E2F90>

当使用 "str" 的代码返回不同的结果

class Pet(object):

    def __init__(self, name, species):
        self.name = name
        self.species = species

    def getName(self):
        return self.name

    def getSpecies(self):
        return self.species

    def __str__(self):
        return "%s is a %s" % (self.name, self.species)

if __name__=='__main__':
    a = Pet("jax", "human")
    print a 

返回值:

jax is a human

好的,很清晰! - AsheKetchum

12

问题的答案

正如另一个答案所指出的,以及您可以在PEP 3140中阅读到的那样,在list上的str会对每个项目调用__repr__。对于这一部分,您没有太多办法。

如果您实现了__repr__,则会得到更详细的信息,但如果实现正确,则不完全符合您的预期。

正确的实现

快速但错误的解决方案是将__repr__别名设置为__str__

__repr__不应该无条件地设置为__str____repr__应该创建一个表示形式,看起来像一个有效的Python表达式,可以使用它来重新创建具有相同值的对象。在这种情况下,这应该是Node(2)而不是2

__repr__的适当实现使得重新创建对象成为可能。在这个例子中,它还应包含其他重要的成员,如neighoursdistance

一个不完整的示例:

class Node:

    def __init__(self, id, neighbours=[], distance=0):
        self.id = id
        self.neighbours = neighbours
        self.distance = distance


    def __str__(self):
        return str(self.id)


    def __repr__(self):
        return "Node(id={0.id}, neighbours={0.neighbours!r}, distance={0.distance})".format(self)
        # in an elaborate implementation, members that have the default
        # value could be left out, but this would hide some information


uno = Node(1)    
due = Node(2)    
tri = Node(3)    
qua = Node(4)

print uno
print str(uno)
print repr(uno)

uno.neighbours.append([[due, 4], [tri, 5]])

print uno
print uno.neighbours
print repr(uno)

注意:使用正确的实现__eq____ne____cmp__,连同print repr(uno),可以重新创建对象并检查相等性。


9

容器对象的__str__方法将对其内容使用repr,而不是str。因此,您可以使用__repr__代替__str__,因为您正在使用ID作为结果。


4

__str__ 只有在需要一个对象的字符串表示时才会被调用。

例如 str(uno), print "%s" % uno 或者 print uno

然而,还有另一个名为__repr__的魔法方法,它是一个对象的表示。当您没有明确将对象转换为字符串时,将使用表示

如果您这样做 uno.neighbors.append([[str(due),4],[str(tri),5]]),它会按照您的预期进行。


1
关于类和在类内将无拘束的全局变量设置为某个值的问题,你实际上存储的是对该值所在内存位置的引用。你在输出中看到的就是这个原因。
在初始的全局变量上使用print方法时,由于字符串方法和print的工作方式,您可能能够看到值并使用它而没有问题,但是对于列表,您将无法这样做,因为嵌套在列表中的元素中存储的是值所在内存位置的引用 -- 如果您想了解更多,请阅读有关别名的文章。
此外,当使用列表并且失去别名和变量引用信息时,如果您在别名列表中更改列表元素的值,则可能会发现自己更改了原始列表元素的值,因为当您将列表元素设置为列表或嵌套列表中的元素时,新列表仅存储对内存位置的引用(它不会创建针对该新变量的新内存空间)。这就是深拷贝派上用场的地方!

-1

打印self.id.__str__()对你来说是可行的,但对你来说并不是很有用。

当你想要在程序开发过程中打印出网格或结构表示时,你的__str__方法将更加有用。

print self._grid.__str__()

def __str__(self):
    """
    Return a string representation of the grid for debugging.
    """
    grid_str = ""
    for row in range(self._rows):
        grid_str += str( self._grid[row] )
        grid_str += '\n'
    return grid_str

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