python 为什么这些列表操作(方法:清除/扩展/反转/追加/排序/删除)是否返回None,而不是返回结果列表?

wwwo4jvm  于 2023-02-15  发布在  Python
关注(0)|答案(6)|浏览(120)

我注意到很多修改列表内容的操作都会返回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?
要获取列表的修改版本,请参阅:

同样的问题也适用于其他内置数据类型的一些方法,例如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。

hk8txs48

hk8txs481#

Python中的一般设计原则是让函数在原处改变一个对象以返回None,我不确定这是否是我所选择的设计选项,但基本上是为了强调不返回新对象。
Guido货车Rossum(我们的Python BDFL)陈述了设计选择on the Python-Dev mailing list
我想再一次解释一下为什么我坚持sort()不应该返回'self'。
这来自于一种编码风格(在其他各种语言中很流行,我相信特别是Lisp),在这种风格中,单个对象上的一系列副作用可以像这样链接起来:

x.compress().chop(y).sort(z)

也就是说

x.compress()
x.chop(y)
x.sort(z)

我发现链接形式对可读性有威胁;它要求读者必须非常熟悉每一个方法。第二种形式清楚地表明,这些调用中的每一个都作用于同一个对象,因此,即使你不太了解类及其方法,你也可以理解,第二个和第三个调用应用于x(所有调用都是为了它们的副作用),而不是其他东西。
我希望为返回新值的操作保留链接,比如字符串处理操作:

y = x.rstrip("\n").split(":").lower()

有一些标准的库模块鼓励副作用调用的链接(pstat)。pstat在它很弱的时候滑过了我的过滤器。

wfauudbj

wfauudbj2#

我不能代表开发人员,但我发现这种行为非常直观。
如果一个方法在原始对象上工作,并就地修改它,它不会返回任何东西,因为没有新的信息--很明显,你已经有了对(现在变异的)对象的引用,那么为什么还要再次返回它呢?
然而,如果一个方法或函数创建了一个新对象,那么它当然必须返回它。
因此l.reverse()不返回任何内容(因为现在列表已经反转,但是标识符l仍然指向那个列表),但是reversed(l)必须返回新生成的列表,因为l仍然指向旧的、未修改的列表。
编辑:我刚刚从another answer了解到,这个原理被称为Command-Query separation

a7qyws3x

a7qyws3x3#

有人可能会说,签名本身清楚地表明函数是变异列表,而不是返回一个新列表:如果函数返回一个列表,那么它的行为就不那么明显了。

bbmckpt7

bbmckpt74#

如果您在请求帮助修复代码后被转到此处:

将来,please try可以自己查找代码中的问题,通过carefully studying查找代码运行时发生的情况。不要因为出现错误消息而放弃,而是检查每次计算的结果,看看代码在哪里开始与您预期的不同。
如果你有代码在列表上调用.append.sort这样的方法,你会注意到 return valueNone,而列表被修改了 in place

>>> 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()。有关详细信息,请参见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]对应于.extendy = [*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,可用于以相反的顺序循环元素。要进行实际的副本,只需将该迭代器传递给listy = 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查找该点之前和之后的元素,并将它们放在一起。

def without(a_list, value):
    index = a_list.index(value)
    return a_list[:index] + a_list[index+1:]

(We可以类似地转换.pop以生成修改的副本,当然.pop实际上返回列表中的元素。)
另请参见A quick way to return list without a specific element in Python
(If如果你打算删除 * 多个 * 元素,强烈考虑使用列表解析(或filter),它比needed中的任何一种从列表中删除元素的方法都要简单得多,这种方法也 * 自然地 * 给出了一个修改过的副本。)
当然,对于以上任何一种情况,我们也可以显式地 * 制作一个副本 *,然后在副本上使用in-place方法来制作一个修改过的副本,最优雅的方法取决于上下文和个人喜好。

gojuced7

gojuced75#

正如我们所知,python中的list是一个可变对象,可变对象的特性之一是能够修改该对象的状态,而无需将其新状态赋给变量。我们应该演示更多关于该主题的内容,以了解问题的根源。
一个内部状态可以改变的对象是mutable。另一方面,immutable不允许对象在创建后做任何改变。对象的可变性是使Python成为动态类型语言的特征之一。
python中的每个对象都有三个属性:

  • 标识-这是指对象在计算机内存中引用的地址。
  • 类型-这是指创建的对象的类型。例如integer, list, string等。
  • 值-这是指对象存储的值。例如str = "a"

虽然IDType在创建后无法更改,但可以更改Mutable对象的值。
让我们一步一步地讨论下面的代码,来描述它在Python中的含义:
创建包含城市名称的列表

cities = ['London', 'New York', 'Chicago']

以十六进制格式打印在内存地址中创建的对象的位置

print(hex(id(cities)))

Output [1]: 0x1691d7de8c8

向列表城市添加新城市

cities.append('Delhi')

打印列表cities中的元素,以逗号分隔

for city in cities:
    print(city, end=', ')

Output [2]: London, New York, Chicago, Delhi

以十六进制格式打印在内存地址中创建的对象的位置

print(hex(id(cities)))

Output [3]: 0x1691d7de8c8

上面的例子向我们展示了我们能够通过向对象cities添加一个城市'Delhi'来改变它的内部状态,但是对象的内存地址没有改变。这证实了我们没有创建新的对象,而是同一个对象被改变或变异了。因此,我们可以说,作为具有引用变量名cities的列表类型的对象是可变对象
而不可变对象的内部状态不能被改变。例如,考虑下面的代码和相关的错误消息,同时试图改变索引0处的元组的值
创建变量名为foo的元组

foo = (1, 2)

将索引0值从1更改为3

foo[0] = 3
    
TypeError: 'tuple' object does not support item assignment

我们可以从例子中得出结论,为什么可变对象在执行操作时不应该返回任何东西,因为它直接修改了对象的内部状态,返回新修改的对象没有意义。不像不变对象,它应该在执行操作后返回修改状态的新对象。

lvmkulzt

lvmkulzt6#

首先,我应该告诉你,毫无疑问,我的建议是一个糟糕的编程实践,但是如果你想在lambda函数中使用append,并且你不关心代码的可读性,有办法做到这一点。
假设你有一个列表的列表,你想用maplambda给每个内部列表添加一个元素。下面是你的实现方法:

my_list = [[1, 2, 3, 4],
           [3, 2, 1],
           [1, 1, 1]]
my_new_element = 10
new_list = list(map(lambda x: [x.append(my_new_element), x][1], my_list))

print(new_list)

工作原理:
lambda要计算输出时,首先应计算[x.append(my_new_element), x]表达式。要计算此表达式,将运行append函数,表达式的结果将为[None, x],通过指定您需要列表的第二个元素,[None,x][1]的结果将为x
使用自定义函数可读性更强,是更好的选择:

def append_my_list(input_list, new_element):
    input_list.append(new_element)
    return input_list

my_list = [[1, 2, 3, 4],
           [3, 2, 1],
           [1, 1, 1]]
my_new_element = 10
new_list = list(map(lambda x: append_my_list(x, my_new_element), my_list))

print(new_list)

相关问题