【问题标题】:Why does Python assignment not return a value?为什么 Python 赋值不返回值?
【发布时间】:2011-06-19 16:17:15
【问题描述】:

为什么 Python 赋值是语句而不是表达式?如果它是一个返回赋值右侧值的表达式,那么在某些情况下它会允许更少冗长的代码。有什么我看不到的问题吗?

例如:

# lst is some sequence
# X is come class
x = X()
lst.append(x)

可以改写为:

lst.append(x = X())

好吧,确切地说,上面的方法不起作用,因为x 将被视为关键字参数。但是另一对括号(或关键字参数的另一个符号)会解决这个问题。

【问题讨论】:

  • 您的代码示例与问题有什么关系?
  • @Ignacio:我的错。编辑的版本(@Laurence Gonsalves)很好。
  • “有什么我看不到的问题吗?”看起来“更简洁的代码”在某种程度上不是问题。简洁,神秘的代码高尔夫似乎是一个问题。您是否出于某种原因打折了那个?
  • @S Lott:我想我在想一些简单的例子(比如我给出的那个,我觉得不太神秘);我同意滥用此功能的能力可能是反对此功能的原因之一。
  • 我喜欢这个问题和其他类似的问题。我相信答案和 cmets 非常有建设性和帮助。我只是很惊讶关闭警察还没有像 NC 一样关闭它;-)

标签: python python-3.x language-design


【解决方案1】:

很多人认为赋值是表达式,特别是在 Python 这样的语言中,在条件中允许 any 值(不仅仅是某些布尔类型的值)容易出错。大概Guido是/曾经是那些有这种感觉的人之一。经典错误是:

if x = y: # oops! meant to say ==

在 Python 中的情况也比在像 C 这样的语言中要复杂一些,因为在 Python 中,对变量的第一个赋值也是它的声明。例如:

def f():
    print x

def g():
    x = h()
    print x

在这两个函数中,“print x”行做了不同的事情:一个引用全局变量x,另一个引用局部变量x。由于分配,g 中的 x 是本地的。如果可以将赋值隐藏在一些更大的表达式/语句中,这可能会更加令人困惑(比现在更加混乱)。

【讨论】:

  • “= vs ==”这件事是一个红鲱鱼。自从编译器学会如何警告它以来,多年来它一直不是 C/C++ 中的实际问题。我已经使用这两种语言十多年了,我不记得上次被它咬伤是什么时候了。
  • @Glenn:Python 没有编译器来警告你,没有类似“几乎语法错误”的概念。
  • @Glenn:但由于它在运行时使用,它不能以同样的方式警告这些事情。 C 中的编译器警告是一种“几乎语法错误”,它说“你真的确定你想要这个”。这个概念没有 Python 等价物。它要么是正确的,要么是语法错误。 Python 试图避免支持导致新手犯错误的东西。 :-)
  • @Lennart:试图防止初学者犯初学者错误是徒劳的,而且不是——至少不是靠它本身,而是以牺牲其他东西为代价——一个好的设计原理;并且“我们不能用警告来做到这一点,因为我们的警告系统不够可发现”指出警告系统或文档存在问题——你没有围绕警告系统设计语言,你做相反的事情.
  • 再次强调这个词的正常使用没有“编译时间”,因为编译是在运行期间按需完成的。这是我一直在这里指出的区别。重复自己变得很无聊,所以我现在要停止了。
【解决方案2】:

自 Python 3.8(2019 年 10 月发布)起支持赋值(子)表达式 (x := y),因此您现在确实可以将示例重写为 lst.append(x := X())

PEP 572 提案已于 2018 年 7 月被 Guido 正式接受。之前也有一些关于赋值表达式的提案,例如已撤回的 PEP 379

回想一下,在版本 3 之前,print 也是一个语句而不是一个表达式。

语句x = y = z 将相同的值分配给多个目标(或者更确切地说,多个目标列表,因为也允许解包)已经被支持(例如,从版本 1 开始)但已实现作为一个特殊的syntax,而不是通过链接连续的赋值子表达式。实际上,执行各个分配的顺序是相反的:嵌套的海象(x := (y := z)) 必须在x 之前分配给y,而x = y = zy 之前分配给x(如果您设置/分配给已重载的类的下标或属性以产生一些副作用)。

【讨论】:

  • 您应该只编辑文本以包含当前信息而不包含 update 部分:)
  • 顺便说一句,当 a = b = 3 不是表达式时,它首先如何工作?
  • @huggie (a*2 for a in b if a) 和其他化合物的工作方式相同。整个a = b = 3一个 赋值语句,而不是a = (b:=3) 中的两个表达式。与同样具有任意元素大小的容器文字进行比较 - (a, b, 3)(a, (b, 3)) 不同。
  • 它将在 3.8 中进行。
  • 谢谢!现在我可以写类似master = [] while(str.lower(n := input('Enter number to append to master or q to quit: ')) != 'q'): master.append(int(n))
【解决方案3】:

现实世界的答案:不需要。

您在 C 中看到的大多数情况是因为错误处理是手动完成的:

if((fd = open("file", O_RDONLY)) == -1)
{
    // error handling
}

许多循环的编写方式也是如此:

while(i++ < 10)
    ;

这些常见情况在 Python 中的处理方式不同。错误处理通常使用异常处理;循环通常使用迭代器。

反对它的论点不一定是惊天动地的,但他们权衡了它在 Python 中根本不那么重要的事实。

【讨论】:

  • 有时你可能需要像while (x[k] += 1) &lt;= limit: ...这样的东西;您可以在没有赋值的情况下将其重写为表达式,但这需要进行非平凡的重组。
【解决方案4】:

我相信这是 Guido 故意的,目的是防止某些经典错误。例如

if x = 3: print x

当你真正想说的时候

if x == 3: ...

我确实同意有时我希望它会起作用,但我也想念 {} 围绕一段代码,而且这肯定不会改变。

【讨论】:

    【解决方案5】:
    1. Python 的语法比 C 的语法简洁得多。
    2. 它的作用域规则比 C 复杂得多。
    3. 在每个表达式中使用括号会降低代码的可读性,而 python 避免了这种情况。

    如果分配是表达式,则必须重新设计这些以及许多其他功能。对我来说,这就像你必须达成的交易才能拥有如此可读的代码和有用的功能。 为了拥有

    if a and (h not in b): ...
    

    而不是

    if (a && !(h in b)) { ... }
    

    [不是在谈论经典的(如果 a = b:) 类型的错误。]

    【讨论】:

      【解决方案6】:

      进行品味讨论,或任何被认为是这样的讨论,通常是浪费时间,无论论点多么好。

      除了更一般的原则之外,我更喜欢将赋值作为表达式的实际原因是,当我想匹配具有多个模式(提取子模式)的字符串时,拥有一个可读的 case 语句等价物,直到成功为止。也许有人有一个我没有看到的不错的解决方案。

      无论如何......为什么不通过模块文件开头的可解释注释来使两个世界都可用,明确指出如果程序员愿意,赋值可以用作表达式,让每个人都开心呢?

      对当前情况感到满意的人不会看到任何变化,仍然会以同样的方式检测到他们的语法错误。

      想要将赋值用作表达式的人只需这么说。

      我不认为允许在未使用它的现有程序中使用赋值作为表达式(通过简单地添加上面建议的注释)会改变该程序的语义。

      和平。


      后sriptum - 这是在下面讨论的第 1 节的前五段发布之后添加的。

      我不知道 Python 设计者为什么会做出这样的选择。回避 公认的常见错误 if a=b : ... 而不是 if a==b : ... 是 几乎没有理由。

      首先,用另一种表示法可以很容易地避免它 赋值,如:=&lt;-,自赋值更合适 不是对称的,并且在 Python 的一些早期祖先中使用。

      其次,同样的问题在另一个环境中是可以容忍的。一罐 写a = b=c,而实际上是写a = b==c,即 很不一样。当bc 时,错误并不明显 是大表达式。此外,虽然它可能是 如果语言是静态类型的,则检测为类型错误,这 在像 Python 这样的动态类型语言中并非如此(这是 实际上,=== 在所有情况下都是如此)。

      这种容忍度比多重赋值更令人惊讶 a = b = c 的形式几乎不是该语言的基本特征, 几乎不是一个非常有用的功能。

      这一切看起来都像是早期设计决策的残余,有些是出于动机 由于 Python 与 C 或 Bash 等现有语言的相似性 也是一种脚本语言,但远不止于此。在 换句话说,这似乎比经过深思熟虑的设计更偶然。 希望这不是人们在谈论时所想到的 蟒蛇性。

      话虽如此,这些都是次要的句法限制,但很烦人。 该语言整体上似乎设计得更好(有一些心理限制 关于范围规则,直到我对它的逻辑下定决心)。

      这个讨论的一个有趣的方面是禁止 赋值为表达式(尽管它可以通过符号来解决 更改)也因缺少静态类型而变得必要。 但可以预料的是,没有静态类型,这是一个 合法的设计选择,使得很多错误更难被发现。这是 一个非常普遍的观察。然而,这是由 Python 设计师。就这样吧。

      但是他们几乎不会后悔平等和平等之间的混淆 任务将更难捕捉。这只是一个直接的后果, 他们的设计选择更灵活的许多后果之一 错误检测的费用。所以错误检测是一个糟糕的借口 对于这个分配限制。

      关于表达式赋值会混合函数的事实 和命令式风格,这不是问题。搅拌已经 语言无处不在。

      是否有关于 Python 的书面理由来说明一般的设计选择,特别是这里讨论的问题?

      【讨论】:

      • 该提议比这两个备选方案中的任何一个都更糟糕。你提出模棱两可。这让我觉得不是 Pythonic。
      • 什么歧义?您编写的任何内容都只能具有一种含义,但编译器检测到的语法可能允许也可能不允许。
      • 模棱两可的是,对于代码阅读器来说,仅仅看一个作业是不够的。要理解该语句的全部含义,还必须查看新提议的 pragma。
      • 含义对上下文的依赖对于你写的任何东西都是正确的。全局变量或函数就是这种情况,或者运算符就是这种情况,举几个例子。对于像 Python 这样没有静态类型的语言尤其如此,您不会认为它模棱两可。在我们的例子中,只有一个含义,没有歧义,只有编译器或解释器是否会接受它的问题。有人可能会质疑我上述提议的可取性……但模棱两可不是问题。 Python 设计者对编程风格的次要方面的专制态度更是一个问题。
      • 别担心,欢迎来到 SO!我担心这不是一个讨论网站。因此,尽管某些人(我喜欢这样的讨论)可能会认为您在答案中的沉思很有趣,但这里的大多数人会认为它们是题外话,没有建设性。请参阅faq 中的前两项。如果您在回答(最后一句话)中提出问题,您很可能不会得到答案。如果您将该部分移到一个新问题中,它很可能很快就会被关闭为 NC 或题外话。该主题应移至 Python 开发者邮件列表或类似论坛。
      猜你喜欢
      • 2018-12-06
      • 1970-01-01
      • 2013-02-27
      • 1970-01-01
      • 2014-02-12
      • 2014-04-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多