为什么Python的列表理解不复制参数,这样实际对象就不会发生变化?

2024-10-08 19:32:20 发布

您现在位置:Python中文网/ 问答频道 /正文

也许我喝了太多的函数式编程Kool-Aid,但这种列表理解行为似乎是一个糟糕的设计选择:

>>> d = [1, 2, 3, 4, 5]
>>> [d.pop() for _ in range(len(d))]
[5, 4, 3, 2, 1]
>>> d
[]

为什么d没有被复制,然后被复制的词汇范围的版本没有改变(然后丢失)?列表理解的要点似乎应该是返回所需的列表,而不是返回一个列表并在幕后悄悄地改变其他对象。d的破坏有点含蓄,这似乎是不和谐的。有一个好的用例吗?在

为什么列表comp的行为与for循环完全相同,而不是更像函数(来自函数语言,具有局部作用域)有好处?在


Tags: 对象函数in版本列表forlen编程
3条回答

既然惯用代码不会产生副作用,为什么还要创建一个(可能非常昂贵)副本?为什么要禁止(罕见的,但现有的)需要副作用的用例?在

Python首先是一种命令式语言。可变状态不仅是被允许的,而且是必不可少的——是的,列表理解的目的是纯粹的,但是如果强制执行,它将与语言其他部分的语义不同步。所以d.pop()突变{},但前提是它不在列表理解中,而且星星是对的?那是毫无意义的。您可以自由地(并且应该)不使用它,但是没有人会在石头上设置更多的规则并使特性更加复杂—惯用代码(这是所有人应该关心的唯一代码;))不需要这样的规则。不管怎样,它都会这样做,如果需要的话,它也会这样做。在

Python从不进行复制,除非您明确要求它进行复制。这是一个非常简单,清晰,完全可以理解的规则。在它上面加上例外和区别,例如“除非在列表理解中的以下情况下……”,这将是完全愚蠢的:如果Python的设计曾经由一个有着如此疯狂想法的人管理,那么Python将是一种病态的、扭曲的、半衰不振的语言,不值得学习。感谢你让我再一次感到快乐,因为我意识到这绝对是不可能的!在

你要复印吗?制作副本!这是Python中的解决方案,因为您需要执行一些不能反映在原始版本中的更改,而您更喜欢副本的开销。也就是说,在一个干净的方法中,你应该这样做

dcopy = list(d)
[dcopy.pop() for _ in range(len(d))]

如果您非常希望在一个表达式中包含所有内容,您可以这样做,尽管它可能不是人们所称的“干净”的代码:

^{pr2}$

也就是说,当你真的想把一个赋值折叠成一个列表理解时,通常使用的技巧是(添加一个for子句,其中“control variable”是你想要赋值的名称,“loop”是你想要赋值的一个单项序列)。在

函数式语言从不改变数据,因此它们也不需要复制(也不需要)。Python不是一种函数式语言,但是在Python“函数式的方式”中,当然有很多事情可以用Python“函数式的方式”来实现,而且通常这是一种更好的方法。例如,对于你的列表理解来说,一个更好的替代品(保证有相同的结果并且不会影响d,而且非常更快、更简洁、更干净):

d[::-1]

(又名“火星笑脸”,根据我妻子安娜的说法;-)。切片(不是slice赋值,这是一个不同的操作)总是在核心Python(语言和标准库)中执行一个拷贝,当然不一定是在独立开发的第三方模块中执行的,比如流行的numpy(它更喜欢将切片看作原始numpy.array上的“视图”)。在

在这个表达式中:

[d.pop() for _ in range(len(d))]

您希望隐式复制或限定哪个变量的范围?这里唯一在理解中具有任何特殊状态的变量是_,它不是您想要保护的变量。在

我不明白你怎么能给出列表理解语义,以某种方式识别所有涉及的可变变量,并以某种方式隐式地复制它们。或者知道.pop()改变了它的对象?在

你提到了函数式语言,但是它们通过使所有变量都不可变来实现你想要的。Python并不是这样设计的。在

相关问题 更多 >

    热门问题