8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

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

aphoria 2月前

133 0

我注意到,许多修改列表内容的列表操作将返回 None,而不是返回列表本身。示例:>>> mylist = ['a', 'b', 'c']>>> empt...

我注意到,许多修改列表内容的列表操作都会返回 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] )。我想可能是“当权者”认为列表理解是一种更好的范例(一种有效的观点),因此不想鼓励其他方法 - 但阻止直观的方法似乎有悖常理,即使存在更好的替代方案。


This question is specifically about Python's design decision to return None from mutating list methods like .append . However, novices often write incorrect code that expects .append (in particular) to return the same list that was just modified. Please do close such questions as a duplicate of this one, however. "The code did the wrong thing because the result was None rather than the list" is something that the OP in these cases should have discovered independently via debugging; creating a proper MRE leaves behind a question like this one - therefore, it can be considered a duplicate.

See 如何将重复计算的结果收集到列表、字典等中(或复制每个元素均经过修改的列表)? for the simple question of " how do I append to a list repeatedly?" (or debugging questions that boil down to that problem). This is a new canonical that has been specifically prepared to address the topic with the perspective that beginners lack.

要获取列表的修改版本,请参阅:

The same issue applies to some methods of other built-in data types, e.g. set.discard (see 如何使用列表推导从列表内的集合中删除特定元素 ) and dict.update (see 为什么 python dict.update() 不返回对象? ).

The same reasoning applies to designing your own APIs. See 让就地操作返回对象是不是一个坏主意? .

帖子版权声明 1、本帖标题:为什么这些列表方法(附加、排序、扩展、删除、清除、反转)返回 None 而不是结果列表?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由aphoria在本站《algorithm》版块原创发布, 转载请注明出处!
最新回复 (0)
  • Python 中的一般设计原则是让 functions that mutate an object in-place to return None 。我不确定这是否是我选择的设计,但它基本上是为了强调不会返回新对象。

    在 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()
    

    有一些标准库模块鼓励副作用调用的链接(我想到了 pstat)。不应该有任何新的;当 pstat 很弱时,它就从我的过滤器中溜走了。

  • wxz 2月前 0 只看Ta
    引用 3

    \'pstat 在很弱的时候从我的过滤器中溜走了。\' 我喜欢它。 pstats 至今仍然是那个逃走的过滤器: stats.strip_dirs().add() =>

  • 在写这篇文章十年后再回过头来看(感谢@Karl Knechtel 出色的说明性编辑!),Guido 对这种范式的回应确实很合理。然而,在那段时间里,我爱上了 FP,我觉得它就像一个稻草人——如果没有方法就地改变对象,但总是返回一个新对象,那么被链接的代码可以改为这样表达:x1 = x.compress(); x2=x1.chop(y); x3 = x2.sort(z)——我认为,用链式代码来代替会更直观。话虽如此——Python 不是 FP(主要是?故意的?),所以范式不适用。

  • ax. 2月前 0 只看Ta
    引用 5

    或者,重申一下——“Python 中的一般设计原则是让函数就地变异对象并返回 None”是一个正确的陈述,但它并没有解释为什么这些操作会发生就地变异。

  • 因解释的准确性而点赞(如果可能的话,我想反对 Guido 的设计选择。)我很高兴 Guido [大部分?] 离开了:正是他不喜欢的这种模式是支持函数式编程的 [大多数] 语言的 [优越] 基础。我们只剩下余波了。

  • 我不能代表开发人员发言,但我发现这种行为非常直观。

    如果某个方法对原始对象起作用并对其进行了就地修改,则它不会返回任何内容,因为没有新的信息——您显然已经有了对(现在已变异的)对象的引用,那么为什么要再次返回它呢?

    但是,如果方法或函数创建了一个新对象,那么当然必须返回它。

    因此 l.reverse() 不返回任何内容(因为现在列表已经被反转,但是标识符 l 仍然指向该列表),但 reversed(l) 必须返回新生成的列表,因为 l 仍然指向旧的、未修改的列表。

    编辑:我刚从 另一个答案 ,这个原则被称为 命令查询分离 .

  • 这显然是我需要努力培养的一种心态,因为我对“...那为什么要再次返回它?”的最初反应是“为什么不呢?”。在我看来,返回引用允许使用不同的技术(“链接”)而不会阻止任何其他技术(顺序修改),因此如果链接本身是“坏的”,那么它只会“更糟”。然而,其他评论中提出的论点(由 Guido 本人提出!)断言链接确实很糟糕。感谢您的意见!我真的很喜欢向那些知道自己在说什么的人学习!

  • @scubbo:Python 之禅 (import this) 指出“应该有一种——最好只有一种——显而易见的方法来实现它”。这有助于回答“为什么不呢?”这个问题。不过我必须承认,在 Python 中,有好几个领域似乎以“有多种方法来实现它”为指导原则 :)

  • 我希望过去的自己能想到包含指向“Guido 的评论”的链接 :( 另外,蒂姆,你指向“另一个答案”的第一个链接也链接到维基百科页面(尽管我怀疑你目前也缺乏纠正该问题的上下文)

  • 另一种选择是“流畅”符号(调用链),例如 PHP、Scala、R tidyverse,有时也用 JS、Java、C++ 实现。\'我发现这种行为非常直观\'不是一个理由,而是一个循环论证;如果你学过流畅范式,你就不会这样了。(流畅是否更糟/更好,例如可维护性、多线程等,是另一个非常活跃的争论。)

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

  • 引用 13

    类型提示(以及在 IDE 中“查看”签名定义)当时是否广泛使用?如果没有,我觉得如果程序员无法通过鼠标悬停/快捷键观察方法的签名,那么期望他们定期思考方法的签名是不合理的 - 我原以为他们会假设签名符合他们的期望。或者是否有可能在没有类型提示的情况下(在类型提示之前)在 IDE 中显示签名定义?我真正开始使用 IDE 是在类型提示流行之后,所以我从未对此进行测试。

  • 类型模块是在 2015 年首次发布的 3.5 版中才添加的。IDE 必须从自己的内置类型信息数据库开始。我认为论点只是“知道函数返回 None 提醒它改变了对象”;但我认为逻辑更有可能反过来。除此之外,名称的选择方式应该使哪些方法是命令、哪些是查询一目了然(根据其他地方引用的“命令查询分离”模型)。

  • 如果您在寻求帮助修复代码后被发送到这里:

    以后, 请尝试 自己查找代码中的问题, 仔细研究 代码运行时发生的情况。不要因为出现错误消息而放弃,而是检查每次计算的结果,看看代码在哪里开始工作与你的预期不同。

    如果你的代码中有一个方法调用,比如 .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 Should x change? 我们真的需要一个单独的 吗 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 在迭代列表时从列表中删除项目 所需的 任何解决方法都要简单得多 自然 给出修改后的副本。)


    当然,对于上述任何一种情况,我们也可以通过显式 复制 然后在副本上使用就地方法来制作修改后的副本。最优雅的方法取决于上下文和个人喜好。

返回
作者最近主题: