为什么Python中没有++和——运算符?

为什么Python中没有++--操作符?

329745 次浏览

它就是这样设计的。自增和自减操作符只是x = x + 1的快捷方式。Python通常采用一种设计策略,减少执行操作的可选方法的数量。增强作业是Python中最接近自增/自减操作符的东西,它们直到Python 2.0才被添加。

这并不是因为它没有意义;将“x++”定义为“x+ = 1,求值到x的前一个绑定”非常有意义。

如果你想知道最初的原因,你必须在旧的Python邮件列表中寻找,或者询问当时在那里的人(例如。Guido),但事后很容易就能证明:

简单的递增和递减不需要像其他语言那样多。你不会经常用Python写for(int i = 0; i < 10; ++i)这样的东西;相反,你可以做for i in range(0, 10)这样的事情。

由于不经常需要它,所以没有理由给它自己的特殊语法;当你确实需要增量时,+=通常就可以了。

这不是一个是否有意义的决定,或者是否可以做到的决定——它确实是,它可以做到。问题在于,是否值得在该语言的核心语法中加入这种好处。记住,这是四个操作符——postinc, postdec, preinc, predec,每一个都需要有自己的类重载;它们都需要被指定和测试;它将为语言添加操作码(意味着更大,因此更慢的虚拟机引擎);每个支持逻辑增量的类都需要实现它们(在+=-=之上)。

这对于+=-=来说都是多余的,因此它将成为净损失。

我写的这个原始答案是计算机民间传说中的一个神话: Dennis Ritchie在给ACM通信 2012年7月doi: 10.1145/2209249.2209251编辑的信中指出,被揭穿为“历史上不可能的”


C加/减操作符是在C编译器还不是很聪明的时候发明的,作者希望能够指定应该使用机器语言操作符的直接意图,这为编译器节省了一些周期

load memory
load 1
add
store memory

而不是

inc memory

PDP-11甚至支持分别对应于*++p*p++的“自动递增”和“自动递增延迟”指令。如果非常好奇,请参阅手动的5.3节。

由于编译器足够聪明,可以处理C语法中内置的高级优化技巧,所以它们现在只是语法上的便利。

Python没有向汇编器传递意图的技巧,因为它不使用这些技巧。

我对python很陌生,但我怀疑原因是因为语言中可变对象和不可变对象之间的强调。现在,我知道x++可以很容易地解释为x = x+ 1,但它看起来像你在增加就地一个对象,可以是不可变的。

这只是我的猜测/感觉/预感。

也许一个更好的问题是问为什么这些操作符存在于C. K&R中,称自增和自减操作符为“不寻常”(第2.8节46页)。《导言》称它们“更简洁,通常也更高效”。我怀疑这些操作在指针操作中经常出现的事实也在它们的介绍中发挥了作用。 在Python中,可能已经决定尝试优化增量是没有意义的(事实上,我刚刚在C中做了一个测试,似乎gcc生成的程序集在两种情况下都使用addl而不是incl),并且没有指针算术;所以它将只是一种更多的方法来做它,我们知道Python讨厌这样。

因为,在Python中,整数是不可变的(int's +=实际上返回一个不同的对象)。

同样,使用++/——,你需要考虑前后的递增/递减,并且只需要再击一次键就可以编写x+=1。换句话说,它以很少的收益为代价避免了潜在的混乱。

当然,我们可以说“Guido只是决定那样做”,但我认为问题实际上是关于这个决定的原因。我认为有以下几个原因:

  • 它将语句和表达式混合在一起,这不是好的实践。看到http://norvig.com/python-iaq.html
  • 它通常鼓励人们编写可读性较差的代码
  • 语言实现中的额外复杂性,如前所述,这在Python中是不必要的

我相信这源于Python的信条“显式优于隐式”。

我一直认为这与python的禅意有关:

应该有一种——最好只有一种——明显的方法来做到这一点。

x++和X +=1做的是完全相同的事情,所以没有理由同时拥有两者。

首先,Python只是间接地受到C的影响;它深受美国广播公司的影响,而显然没有这些运算符则是美国广播公司,所以在Python中找不到它们也就不足为奇了。

其次,正如其他人所说,递增和递减已经由+=-=支持。

第三,对++--操作符集的完全支持通常包括对它们的前缀和后缀版本的支持。在C和c++中,这可能会导致各种“可爱的”构造,这些构造(对我来说)似乎违背了Python所信奉的简单和直接的精神。

例如,虽然C语句while(*t++ = *s++);对于有经验的程序员来说似乎简单而优雅,但对于初学者来说,它一点也不简单。加上前缀和后缀的增量和减量的混合,甚至许多专业人士也不得不停下来思考一下。

,因为我理解它,所以你不会认为内存中的值被改变了。 在c语言中,当你执行x++时,内存中的x值会发生变化。 但在python中,所有数字都是不可变的,因此x指向的地址仍然是x而不是x+1。当你写x++时,你会认为x改变了,实际上发生的是x引用被改变到内存中存储x+1的位置,或者如果doe不存在,重新创建这个位置。< / p >

这可能是因为@GlennMaynard将这个问题与其他语言进行比较,但在Python中,你用Python的方式做事。这不是一个“为什么”的问题。它就在那里,你可以用x+=做同样的事情。在Python的禅宗中,它被赋予:“应该只有一种方法来解决问题。”多重选择在艺术上很好(表达自由),但在工程上很糟糕。

操作符的++类是带有副作用的表达式。这在Python中通常是找不到的。

出于同样的原因,赋值在Python中不是表达式,从而避免了常见的if (a = f(...)) { /* using a here */ }习语。

最后,我怀疑这些操作符与python的引用语义不太一致。请记住,Python没有C/ c++中已知语义的变量(或指针)。

我对python为什么没有++操作符的理解如下:当你在python a=b=c=1中写这个时,你会得到三个变量(标签)指向同一个对象(值为1)。你可以使用id函数来验证这一点,它将返回一个对象内存地址:

In [19]: id(a)
Out[19]: 34019256


In [20]: id(b)
Out[20]: 34019256


In [21]: id(c)
Out[21]: 34019256

所有三个变量(标签)都指向同一个对象。现在增加变量之一,看看它是如何影响内存地址的:

In [22] a = a + 1


In [23]: id(a)
Out[23]: 34019232


In [24]: id(b)
Out[24]: 34019256


In [25]: id(c)
Out[25]: 34019256

你可以看到变量a现在指向另一个对象,变量bc。因为你已经使用了a = a + 1,所以它是明确的。换句话说,你完全将另一个对象赋值给a。想象一下,你可以编写a++,这将表明你没有给变量a分配新对象,而是增加旧对象。所有这些东西都是为了尽量减少混淆。为了更好地理解python变量是如何工作的:

在Python中,为什么函数可以修改调用者感知到的一些参数,而不能修改其他参数?< / >

Python是值调用还是引用调用?既不。< / >

Python是按值传递还是按引用传递?< / >

Python是引用传递还是值传递?< / >

Python:如何通过引用传递变量?< / >

了解Python变量和内存管理

在python中模拟值传递行为

Python函数通过引用调用

Code Like a Pythonista: Idiomatic Python

清晰!

Python有很多关于清晰的内容,没有程序员可能正确猜测--a的含义,除非他/她学过具有该结构的语言。

Python也有很多关于避免引起错误的构造的内容,而++操作符是已知的丰富的缺陷来源。 这两个原因足以在Python中不使用这些操作符

Python决定使用缩进来标记块 而不是句法手段,比如某种形式的开始/结束括号 或强制结束标记主要基于相同的考虑 为了说明,请看2005年Python中的关于引入条件运算符的讨论(在C: cond ? resultif : resultelse中)。 至少读取讨论第一个消息决策信息(前面有几个关于同一主题的前体)

< >强花絮: 其中经常提到的PEP是“Python增强建议”;PEP 308。LC表示列表理解, GE表示生成器表达式(如果这些让你感到困惑,请不要担心,它们不是Python中少数复杂的地方)

要完成那一页上已经很好的答案:

让我们假设我们决定这样做,前缀(++i)将打破一元的+和-操作符。

现在,前缀++--什么都不做,因为它使一元加运算符两次(什么都不做)或一元减运算符两次(两次:取消自身)

>>> i=12
>>> ++i
12
>>> --i
12

所以这可能会打破这个逻辑。

现在,如果需要它来进行列表推导或lambdas,从python 3.8开始,可以使用新的:=赋值操作符(PEP572)来实现。

预递增a并将其赋值给b:

>>> a = 1
>>> b = (a:=a+1)
>>> b
2
>>> a
2

后增量只需要通过减1来弥补过早的加法:

>>> a = 1
>>> b = (a:=a+1)-1
>>> b
1
>>> a
2

我认为这与对象的可变性和不变性的概念有关。2,3,4,5在python中是不可变的。参考下图。2在此python进程之前有固定的id。

常量和变量的ID

x++本质上意味着像C一样的原地增量。在C中,x++执行原地增量。因此,x=3, x++会将内存中的3增加到4,不像python中的3仍然存在于内存中。

因此,在python中,你不需要在内存中重新创建一个值。这可能会导致性能优化。

这是一个基于直觉的答案。

我知道这是一个旧线程,但最常见的用例++ I没有涵盖,即手动索引集时,没有提供索引。这种情况就是python提供enumerate()的原因

示例:在任何给定的语言中,当你使用像foreach这样的构造来遍历一个集合时——为了示例的目的,我们甚至会说它是一个无序的集合,你需要一个唯一的索引来区分它们

i = 0
stuff = {'a': 'b', 'c': 'd', 'e': 'f'}
uniquestuff = {}
for key, val in stuff.items() :
uniquestuff[key] = '{0}{1}'.format(val, i)
i += 1

在这种情况下,python提供了一个枚举方法,例如。

for i, (key, val) in enumerate(stuff.items()) :

其他答案描述了为什么迭代器不需要它,但有时它在赋值以inline增加变量时很有用,你可以使用元组和多次赋值达到相同的效果:

b = ++a就变成:

a,b = (a+1,)*2

b = a++变成:

a,b = a+1, a

Python 3.8引入了赋值操作符:=,允许我们用

foo(a:=a+1)

foo(a++)仍然难以捉摸。

除了这里的其他优秀答案,++--也因未定义行为而臭名昭著。例如,这段代码中发生了什么?

foo[bar] = bar++;

它看起来很无辜,但它是错误的C(和c++),因为你不知道第一个bar是否会被递增。一个编译器可能会以一种方式执行,另一个可能会以另一种方式执行,而第三个编译器可能会让恶魔从你的鼻子里飞出来。所有这些都完全符合C和c++标准。

在C和c++中,未定义行为被视为不可避免的邪恶,但在Python中,它只是邪恶,并尽可能避免。

这不是答案(只是我的一个日志),但我相信:它应该在那里。

确实,python有一种做事的方式,它不是循环计数器所需要的,但是:很少有情况下,除了循环的变量之外,还需要操作其他变量。

查看此线程的视图..肯定有一个用例。

我们需要游说才能让这个功能加入……尽管我认为这在很长一段时间内都不会开花结果。同时:是否有一种方法来做操作符重载模仿++?