我注意到很多修改列表内容的操作都会返回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]
)。我想这可能是因为《The Powers That Be》认为列表理解是一种更好的范式(一种有效的观点),因此不想鼓励其他方法--但阻止直觉方法似乎是不正当的,即使存在更好的替代方法。
这个问题是关于Python的“设计决策”,即从修改列表方法(如.append
)返回None
,新手经常编写错误的代码,期望.append
(特别是)返回刚刚修改过的相同列表。
对于“如何追加到列表中?”这个简单的问题(或者归结为这个问题的调试问题),请参见Why does "x = x.append([i])" not work in a for loop?。
要获取列表的修改版本,请参阅:
- 用于排序:How can I get a sorted copy of a list?
- 对于反向:How can I get a reversed copy of a list (avoid a separate statement when chaining a method after .reverse)?
同样的问题也适用于其他内置数据类型的一些方法,例如set.discard
(参见How to remove specific element from sets inside a list using list comprehension)和dict.update
(参见Why doesn't a python dict.update() return the object?)。
同样的道理也适用于设计自己的API。
6条答案
按热度按时间hk8txs481#
Python中的一般设计原则是让函数在原处改变一个对象以返回
None
,我不确定这是否是我所选择的设计选项,但基本上是为了强调不返回新对象。Guido货车Rossum(我们的Python BDFL)陈述了设计选择on the Python-Dev mailing list:
我想再一次解释一下为什么我坚持sort()不应该返回'self'。
这来自于一种编码风格(在其他各种语言中很流行,我相信特别是Lisp),在这种风格中,单个对象上的一系列副作用可以像这样链接起来:
也就是说
我发现链接形式对可读性有威胁;它要求读者必须非常熟悉每一个方法。第二种形式清楚地表明,这些调用中的每一个都作用于同一个对象,因此,即使你不太了解类及其方法,你也可以理解,第二个和第三个调用应用于x(所有调用都是为了它们的副作用),而不是其他东西。
我希望为返回新值的操作保留链接,比如字符串处理操作:
有一些标准的库模块鼓励副作用调用的链接(pstat)。pstat在它很弱的时候滑过了我的过滤器。
wfauudbj2#
我不能代表开发人员,但我发现这种行为非常直观。
如果一个方法在原始对象上工作,并就地修改它,它不会返回任何东西,因为没有新的信息--很明显,你已经有了对(现在变异的)对象的引用,那么为什么还要再次返回它呢?
然而,如果一个方法或函数创建了一个新对象,那么它当然必须返回它。
因此
l.reverse()
不返回任何内容(因为现在列表已经反转,但是标识符l
仍然指向那个列表),但是reversed(l)
必须返回新生成的列表,因为l
仍然指向旧的、未修改的列表。编辑:我刚刚从another answer了解到,这个原理被称为Command-Query separation。
a7qyws3x3#
有人可能会说,签名本身清楚地表明函数是变异列表,而不是返回一个新列表:如果函数返回一个列表,那么它的行为就不那么明显了。
bbmckpt74#
如果您在请求帮助修复代码后被转到此处:
将来,please try可以自己查找代码中的问题,通过carefully studying查找代码运行时发生的情况。不要因为出现错误消息而放弃,而是检查每次计算的结果,看看代码在哪里开始与您预期的不同。
如果你有代码在列表上调用
.append
或.sort
这样的方法,你会注意到 return value 是None
,而列表被修改了 in place。y
获得了特殊的None
值,因为返回的是这个值。x
发生了更改,因为排序发生在适当的位置。它是故意这样工作的,所以像
x.sort().reverse()
这样的代码会被破坏,参见其他答案来理解Python开发人员为什么希望这样做。要修复此问题
首先,仔细考虑代码的意图。**
x
应该更改吗?**我们真的需要一个单独的y
吗?首先考虑
.sort
,如果x
发生变化,则单独调用x.sort()
,而不将结果赋给任何地方。如果需要排序的 * 副本 *,则使用
y = x.sorted()
。有关详细信息,请参见How can I get a sorted copy of a list?。对于其他方法,我们可以得到修改后的副本如下:
.clear
-〉这没有意义;列表的“清除副本”只是一个空列表,只需使用y = []
。.append
和.extend
-〉可能最简单的方法是使用+
操作符。要添加列表l
中的多个元素,请使用y = x + l
而不是.extend
。要添加单个元素e
* 首先将其 Package 在列表中 *:y = x + [e]
。3.5及更高版本中的另一种方法是使用解包:y = [*x, *l]
对应于.extend
,y = [*x, e]
对应于.append
。另请参见How to allow list append() method to return the new list对应于.append
,以及How do I concatenate two lists in Python?对应于.extend
。.reverse
-〉首先,考虑是否需要一个实际的副本。内置的reversed
为您提供了一个 iterator,可用于以相反的顺序循环元素。要进行实际的副本,只需将该迭代器传递给list
:y = list(reversed(x))
。详细信息请参见How can I get a reversed copy of a list (avoid a separate statement when chaining a method after .reverse)?。.remove
-〉计算出将要删除的元素的索引(使用.index
),然后使用slicing查找该点之前和之后的元素,并将它们放在一起。(We可以类似地转换
.pop
以生成修改的副本,当然.pop
实际上返回列表中的元素。)另请参见A quick way to return list without a specific element in Python。
(If如果你打算删除 * 多个 * 元素,强烈考虑使用列表解析(或
filter
),它比needed中的任何一种从列表中删除元素的方法都要简单得多,这种方法也 * 自然地 * 给出了一个修改过的副本。)当然,对于以上任何一种情况,我们也可以显式地 * 制作一个副本 *,然后在副本上使用in-place方法来制作一个修改过的副本,最优雅的方法取决于上下文和个人喜好。
gojuced75#
正如我们所知,python中的
list
是一个可变对象,可变对象的特性之一是能够修改该对象的状态,而无需将其新状态赋给变量。我们应该演示更多关于该主题的内容,以了解问题的根源。一个内部状态可以改变的对象是mutable。另一方面,immutable不允许对象在创建后做任何改变。对象的可变性是使Python成为动态类型语言的特征之一。
python中的每个对象都有三个属性:
integer, list, string
等。str = "a"
。虽然ID和Type在创建后无法更改,但可以更改Mutable对象的值。
让我们一步一步地讨论下面的代码,来描述它在Python中的含义:
创建包含城市名称的列表
以十六进制格式打印在内存地址中创建的对象的位置
向列表城市添加新城市
打印列表cities中的元素,以逗号分隔
以十六进制格式打印在内存地址中创建的对象的位置
上面的例子向我们展示了我们能够通过向对象
cities
添加一个城市'Delhi'
来改变它的内部状态,但是对象的内存地址没有改变。这证实了我们没有创建新的对象,而是同一个对象被改变或变异了。因此,我们可以说,作为具有引用变量名cities
的列表类型的对象是可变对象。而不可变对象的内部状态不能被改变。例如,考虑下面的代码和相关的错误消息,同时试图改变索引
0
处的元组的值创建变量名为
foo
的元组将索引
0
值从1
更改为3
我们可以从例子中得出结论,为什么可变对象在执行操作时不应该返回任何东西,因为它直接修改了对象的内部状态,返回新修改的对象没有意义。不像不变对象,它应该在执行操作后返回修改状态的新对象。
lvmkulzt6#
首先,我应该告诉你,毫无疑问,我的建议是一个糟糕的编程实践,但是如果你想在lambda函数中使用append,并且你不关心代码的可读性,有办法做到这一点。
假设你有一个列表的列表,你想用
map
和lambda
给每个内部列表添加一个元素。下面是你的实现方法:工作原理:
当
lambda
要计算输出时,首先应计算[x.append(my_new_element), x]
表达式。要计算此表达式,将运行append
函数,表达式的结果将为[None, x]
,通过指定您需要列表的第二个元素,[None,x][1]
的结果将为x
使用自定义函数可读性更强,是更好的选择: