为什么这些列表方法(附加、排序、扩展、删除、清除、反转)返回 None 而不是结果列表?
- 2024-11-15 08:36:00
- admin 原创
- 16
问题描述:
我注意到,许多修改列表内容的列表操作都会返回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]
这个决定背后的想法是怎样的?
在我看来,这似乎是一种阻碍,因为它阻止了列表处理的“链接”(例如mylist.reverse().append('a string')[:someLimit]
)。我想可能是“当权者”认为列表理解是一种更好的范例(一种有效的观点),因此不想鼓励其他方法 - 但阻止直观的方法似乎是一种不合情理的做法,即使存在更好的替代方案。我很想知道以这种方式设计语言时做出的决定和权衡。
这个问题专门针对 Python 的设计决策None
,即从 等变异列表方法中返回.append
。但是,新手经常会编写错误的代码,期望.append
(特别是)返回刚刚修改的相同列表。但是,请关闭此类问题,因为它们是此问题的重复。“代码做错了,因为结果是None
而不是列表”是 OP 在这些情况下应该通过调试独立发现的东西;创建适当的MRE会留下像这样的问题 - 因此,它可以被视为重复。
请参阅如何将重复计算的结果收集到列表、字典等中(或复制每个元素均已修改的列表)?以了解“如何重复追加到列表中?”这一简单问题(或归结为该问题的调试问题)。这是一本专门为解决初学者所缺乏的视角而准备的新规范。
要获取列表的修改版本,请参阅:
如何允许列表 append() 方法返回新列表
如何获得列表的排序副本?
如何在 Python 中连接两个列表?(替换
.extend
)Python 中返回不含特定元素的列表的快速方法(替换
.remove
)如何获取列表的反向副本(在 .reverse 后链接方法时避免使用单独的语句)?
同样的问题也适用于其他内置数据类型的某些方法,例如set.discard
(参见如何使用列表推导从列表内的集合中删除特定元素)和dict.update
(参见为什么 python dict.update() 不返回对象?)。
同样的道理也适用于设计您自己的 API。请参阅让就地操作返回对象是不是一个坏主意?。
解决方案 1:
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(并且所有调用都是为了它们的副作用),而不是其他东西。
我想保留返回新值的操作的链接,比如字符串处理操作:
").split(":").lower()
解决方案 2:
我不能代表开发人员发言,但我发现这种行为非常直观。
如果某个方法对原始对象起作用并对其进行了就地修改,则它不会返回任何内容,因为没有新的信息——您显然已经有了对(现在已变异的)对象的引用,那么为什么要再次返回它呢?
但是,如果方法或函数创建了一个新对象,那么当然必须返回它。
因此l.reverse()
不返回任何内容(因为现在列表已经被反转,但是标识符l
仍然指向该列表),但reversed(l)
必须返回新生成的列表,因为l
仍然指向旧的、未修改的列表。
编辑:我刚刚从另一个答案中了解到,这个原则被称为命令-查询分离。
解决方案 3:
有人可能会说,签名本身清楚地表明该函数会改变列表而不是返回一个新的列表:如果该函数返回一个列表,那么它的行为就不会那么明显了。
解决方案 4:
如果您在寻求帮助修复代码后被发送到这里:
以后,请尝试自己查找代码中的问题,仔细研究代码运行时发生的情况。不要因为出现错误消息而放弃,而是检查每次计算的结果,看看代码在哪里开始工作与你的预期不同。
如果你的代码中有一个方法调用,比如列表上的.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
已更改,因为排序已在原处进行。
它故意这样工作,这样代码就会x.sort().reverse()
中断。请参阅其他答案以了解 Python 开发人员为何希望这样做。
解决问题
首先,仔细思考代码的意图。应该x
改变吗?我们真的需要一个单独的吗y
?
我们.sort
先考虑一下。如果x
应该改变,则x.sort()
自行调用,而不将结果分配到任何地方。
如果需要已排序的副本y = x.sorted()
,请使用。有关详细信息,请参阅如何获取列表的已排序副本?
对于其他方法,我们可以像这样获得修改后的副本:
.clear
-> 这没有任何意义;列表的“清除副本”只是一个空列表。只需使用y = []
。
.append
和.extend
-> 可能最简单的方法是使用+
运算符。要从列表添加多个元素l
,请使用y = x + l
而不是.extend
。要添加单个元素,e
请先将其包装在列表中:y = x + [e]
。 3.5 及更高版本中的另一种方法是使用解包:y = [*x, *l]
for .extend
,y = [*x, e]
for .append
。另请参阅如何允许列表 append() 方法返回新列表for.append
和如何在 Python 中连接两个列表? for .extend
。
.reverse
-> 首先,考虑是否需要实际复制。内置函数reversed
为您提供了一个迭代器,可用于以相反的顺序循环元素。要进行实际复制,只需将该迭代器传递给list
:y = list(reversed(x))
。有关详细信息,请参阅如何获取列表的反向副本(在 .reverse 后链接方法时避免使用单独的语句)?。
.remove
-> 找出将被删除的元素的索引(使用.index
),然后使用切片查找该点之前和之后的元素并将它们放在一起。作为函数:
def without(a_list, value):
index = a_list.index(value)
return a_list[:index] + a_list[index+1:]
(我们可以进行.pop
类似的翻译以制作修改后的副本,尽管实际上.pop
返回的是列表中的一个元素。)
另请参阅在 Python 中返回不包含特定元素的列表的快速方法。
(如果您计划删除多个元素,强烈建议使用列表推导(或)。它比在迭代列表时从列表中删除项目所需filter
的任何解决方法都要简单得多。这种方式自然也会给出修改后的副本。)
当然,对于上述任何一种情况,我们也可以通过显式复制然后在副本上使用就地方法来制作修改后的副本。最优雅的方法取决于上下文和个人喜好。
解决方案 5:
在内存中,诸如“list.sort()”之类的操作并非为了返回值而创建的。因此,其返回类型为 None。它旨在对列表进行排序。话虽如此,如果您正在寻找诸如 sort 之类的操作的返回值,您始终可以使用 sorted()。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件