提问人:scubbo 提问时间:6/26/2012 最后编辑:wjandreascubbo 更新时间:8/10/2023 访问量:8175
为什么这些列表方法(append、sort、extend、remove、clear、reverse)返回 None 而不是结果列表?
Why do these list methods (append, sort, extend, remove, clear, reverse) return None rather than the resulting list?
问:
我注意到,对列表的许多修改列表内容的操作将返回,而不是返回列表本身。例子:None
>>> mylist = ['a', 'b', 'c']
>>> empty = mylist.clear()
>>> restored = mylist.extend(range(3))
>>> backwards = mylist.reverse()
>>> with_four = mylist.append(4)
>>> in_order = mylist.sort()
>>> without_one = mylist.remove(1)
>>> mylist
[0, 2, 4]
>>> [empty, restored, backwards, with_four, in_order, without_one]
[None, None, None, None, None, None]
这个决定背后的思考过程是什么?
对我来说,这似乎很碍事,因为它阻止了列表处理的“链接”(例如)。我想可能是“The Powers That Be”认为列表理解是一种更好的范式(一个有效的观点),因此不想鼓励其他方法——但阻止直觉方法似乎是不正常的,即使存在更好的替代方案。mylist.reverse().append('a string')[:someLimit]
这个问题专门针对 Python 的设计决策,即从 .append
等可变列表方法返回 None
。但是,新手经常编写不正确的代码,这些代码期望 .append
(特别是)返回刚刚修改的相同列表。但是,请关闭与此问题重复的问题。“代码做错了事情,因为结果是 None
而不是 list”是这些情况下的 OP 应该通过调试独立发现的东西;创建一个适当的 MRE 会留下一个像这样的问题 - 因此,它可以被认为是重复的。
请参阅如何在列表、字典等中收集重复计算的结果(或复制每个修改了元素的列表)? 对于“如何重复追加到列表”的简单问题。(或调试归结为该问题的问题)。这是一本新的经典,专门准备以初学者缺乏的视角来解决该主题。
若要获取列表的修改版本,请参阅:
- 如何允许列表 append() 方法返回新列表
- 如何获取列表的排序副本?
- 如何在 Python 中连接两个列表?(将
.extend
) - 一种在 Python 中没有特定元素的情况下返回列表的快速方法(将
.remove
) - 如何获取列表的反向副本(在 .reverse 之后链接方法时避免单独的语句)?
同样的问题也适用于其他内置数据类型的某些方法,例如 set.discard
(请参阅如何使用列表推导式从列表中的集合中删除特定元素)和 dict.update(请参阅为什么 python dict.update() 不返回对象?
同样的道理也适用于设计你自己的 API。请参阅就地操作返回对象是个坏主意吗?。
答:
有人可能会争辩说,签名本身清楚地表明该函数会改变列表而不是返回一个新列表:如果该函数返回一个列表,它的行为就不那么明显了。
评论
typing
None
我不能代表开发人员,但我发现这种行为非常直观。
如果一个方法在原始对象上工作并就地修改它,它不会返回任何内容,因为没有新信息 - 你显然已经有了对(现在已变异的)对象的引用,那么为什么要再次返回它呢?
但是,如果方法或函数创建了一个新对象,那么它当然必须返回它。
所以不返回任何内容(因为现在列表已被反转,但标识器仍然指向该列表),但必须返回新生成的列表,因为仍然指向旧的、未修改的列表。l.reverse()
l
reversed(l)
l
编辑:我刚刚从另一个答案中了解到,这个原则被称为命令-查询分离。
评论
import this
Python 中的一般设计原则是用于就地改变对象以返回 None
的函数。我不确定这是否是我选择的设计选择,但它基本上是为了强调不会返回新对象。
Guido van Rossum(我们的 Python BDFL)在 Python-Dev 邮件列表上陈述了设计选择:
我想再次解释为什么我如此坚持 sort() 不应该 返回 'self'。
这来自一种编码风格(在其他各种语言中很流行,我 相信特别是Lisp陶醉于它)的一系列副作用 在单个对象上可以像这样链接:
x.compress().chop(y).sort(z)
这将与
x.compress() x.chop(y) x.sort(z)
我发现链接形式对可读性构成威胁;它要求 读者必须非常熟悉每种方法。这 第二种形式清楚地表明,这些调用中的每一个都作用于同一 对象,所以即使你不了解这个类及其方法 好了,您可以理解第二个和第三个调用适用于 x(并且所有调用都是为了它们的副作用),而不是 别的。
我想为返回新值的操作保留链接, 与字符串处理操作类似:
y = x.rstrip("\n").split(":").lower()
有一些标准库模块鼓励链接 副作用调用(我想到了 PSAT)。不应该有任何新的 的;pstat 在弱时滑过我的过滤器。
评论
pstats
仍然是逃脱的那个:stats.strip_dirs().add()
=> <pstats.Stats object at 0x0000018866977640>
x1 = x.compress(); x2=x1.chop(y); x3 = x2.sort(z)
如果您在请求帮助修复代码后被发送到这里:
将来,请尝试自己寻找代码中的问题,仔细研究代码运行时会发生什么。与其因为出现错误消息而放弃,不如检查每个计算的结果,并查看代码开始工作的地方与预期不同。
如果您有代码调用列表上的方法,例如 或,您会注意到返回值是 ,而列表已就地修改。仔细研究这个例子:.append
.sort
None
>>> x = ['e', 'x', 'a', 'm', 'p', 'l', 'e']
>>> y = x.sort()
>>> print(y)
None
>>> print(x)
['a', 'e', 'e', 'l', 'm', 'p', 'x']
y
得到了特殊值,因为这是返回的。 改变了,因为排序就位了。None
x
它故意以这种方式工作,因此代码会中断。查看其他答案,了解 Python 开发人员为什么希望这样做。x.sort().reverse()
解决问题
首先,仔细考虑代码的意图。x
应该改变吗?我们真的需要一个单独的吗?y
让我们先考虑一下。如果应该更改,则自行调用,而无需在任何地方分配结果。.sort
x
x.sort()
如果需要排序副本,请使用 。有关详细信息,请参阅如何获取列表的排序副本。y = x.sorted()
对于其他方法,我们可以像这样获得修改后的副本:
.clear
->这没有意义;列表的“已清除副本”只是一个空列表。只需使用 .y = []
.append
-> 可能最简单的方法是使用运算符。要从列表中添加多个元素,请使用 而不是 。要添加单个元素,请先将其包装在列表中: .在 3.5 及更高版本中,另一种方法是使用解包:for , for .另请参阅 如何允许列表 append() 方法返回新列表 和 如何在 Python 中连接两个列表? for 。.extend
+
l
y = x + l
.extend
e
y = x + [e]
y = [*x, *l]
.extend
y = [*x, e]
.append
.append
.extend
.reverse
-> 首先,考虑是否需要实际副本。内置提供了一个迭代器,可用于以相反的顺序循环元素。要制作实际副本,只需将该迭代器传递给 : 。有关详细信息,请参阅如何获取列表的反向副本(在 .reverse 之后链接方法时避免单独的语句)?reversed
list
y = list(reversed(x))
.remove
-> 找出要删除的元素的索引(使用 ),然后使用切片找到该点之前和之后的元素并将它们放在一起。作为功能:.index
def without(a_list, value):
index = a_list.index(value)
return a_list[:index] + a_list[index+1:]
(我们可以进行类似的翻译以制作修改后的副本,当然实际上会从列表中返回一个元素。.pop
.pop
另请参阅 Python 中不带特定元素的返回列表的快速方法。
(如果您计划删除多个元素,强烈建议改用列表推导式 (或 )。这将比在迭代列表时从列表中删除项目所需的任何解决方法都要简单得多。这种方式自然也会给出修改后的副本。filter
当然,对于上述任何一种情况,我们也可以通过显式制作副本,然后在副本上使用就地方法来制作修改后的副本。最优雅的方法将取决于上下文和个人品味。
上一个:展平不规则(任意嵌套)列表
下一个:如何获取多个列表的笛卡尔积
评论