为什么这些列表方法(附加、排序、扩展、删除、清除、反转)返回 None 而不是结果列表?

2024-11-15 08:36:00
admin
原创
15
摘要:问题描述:我注意到,许多修改列表内容的列表操作都会返回None,而不是返回列表本身。示例:>>> mylist = ['a', 'b', 'c'] >>> empty = mylist.clear() >>> restored = mylist.exten...

问题描述:

我注意到,许多修改列表内容的列表操作都会返回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 .extendy = [*x, e]for .append。另请参阅如何允许列表 append() 方法返回新列表for.append和如何在 Python 中连接两个列表? for .extend

.reverse-> 首先,考虑是否需要实际复制。内置函数reversed为您提供了一个迭代器,可用于以相反的顺序循环元素。要进行实际复制,只需将该迭代器传递给listy = 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()。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用