为什么 Python 中没有 ++ 和 -- 运算符?
- 2025-03-10 08:52:00
- admin 原创
- 65
问题描述:
为什么 Python 中没有++
and--
运算符?
解决方案 1:
这并不是因为它没有意义;将“x++”定义为“x += 1,计算 x 的先前绑定”是完全有意义的。
如果你想知道最初的原因,你要么必须仔细查阅旧的 Python 邮件列表,要么询问当时在那里的人(例如 Guido),但事后证明起来很容易:
与其他语言不同,简单的递增和递减并不那么需要。for(int i = 0; i < 10; ++i)
在 Python 中,你不会经常写类似的东西;相反,你会这样做for i in range(0, 10)
。
由于几乎不经常需要它,因此没有必要赋予它自己的特殊语法;当您确实需要增加时,+=
通常就足够了。
这不是关于它是否有意义或是否可以做到的决定——它确实有意义,而且可以做到。这是一个关于这种好处是否值得添加到语言的核心语法中的问题。请记住,这是四个运算符——postinc、postdec、preinc、predec,每个运算符都需要有自己的类重载;它们都需要指定和测试;它会将操作码添加到语言中(意味着更大,因此更慢的 VM 引擎);每个支持逻辑增量的类都需要实现它们(在+=
和之上-=
)。
+=
这对于和来说都是多余的-=
,因此会成为净损失。
解决方案 2:
我最初写的这个答案是来自计算民间传说的一个神话:丹尼斯·里奇在2012 年 7 月致ACM 通讯编辑的信中指出,该答案“在历史上是不可能的”。doi :10.1145/2209249.2209251
C 语言的递增/递减运算符是在 C 编译器还不是很智能的时候发明的,作者希望能够指定应该使用机器语言运算符的直接意图,这为编译器节省了一些周期,因为编译器可能会执行
load memory
load 1
add
store memory
而不是
inc memory
PDP-11 甚至支持分别对应于*++p
和的“自动增量”和“自动增量延迟”指令*p++
。如果非常好奇,请参阅手册的第 5.3 节。
由于编译器足够智能,能够处理 C 语法中内置的高级优化技巧,因此它们现在只是一种语法便利。
Python 没有技巧来向汇编程序传达意图,因为它不使用汇编程序。
解决方案 3:
我一直以为它与 Python 禅的这一行有关:
应该有一种(最好只有一种)明显的方法来做到这一点。
x++ 和 x+=1 的作用完全相同,因此没有理由同时拥有两者。
解决方案 4:
当然,我们可以说“Guido 就是这么决定的”,但我认为问题在于他做这个决定的原因。我认为有几个原因:
它将语句和表达式混合在一起,这不是好的做法。请参阅http://norvig.com/python-iaq.html
它通常会鼓励人们编写可读性较差的代码
语言实现中额外的复杂性,这在 Python 中是不必要的,正如前面提到的
解决方案 5:
因为在 Python 中,整数是不可变的(int 的 += 实际上返回一个不同的对象)。
此外,使用 ++/-- 时,您需要担心前增量/后增量/减量,并且只需再按一次键即可写入x+=1
。换句话说,它以很少的收益为代价避免了潜在的混淆。
解决方案 6:
明晰!
Python 非常注重清晰度--a
,除非程序员学过具有该结构的语言,否则不可能正确猜出其含义。
Python 也非常注重避免导致错误的构造,而这些++
运算符是众所周知的缺陷之源。这两个原因足以说明 Python 中不使用这些运算符。
Python 决定使用缩进来标记块而不是使用诸如某种形式的开始/结束括号或强制结束标记之类的语法手段,这在很大程度上基于相同的考虑。
为了说明,请看一下2005 年有关在 Python 中引入条件运算符(在 C: 中cond ? resultif : resultelse
)的讨论。至少阅读该讨论的第一条消息和决策消息(之前有关于同一主题的几个前兆)。
琐事:
其中经常提到的 PEP 是“Python 增强提案” PEP 308。LC 表示列表推导式,GE 表示生成器表达式(如果这些让您感到困惑,不要担心,它们并不是 Python 中为数不多的复杂之处之一)。
解决方案 7:
我对 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
现在作为变量b
和指向另一个对象c
。因为您已经使用了a = a + 1
它,所以非常清楚。换句话说,您将另一个对象完全分配给标签a
。想象一下,您可以编写a++
它,这表明您没有将a
新对象分配给变量,而是增加了旧对象。所有这些都是恕我直言,为了尽量减少混乱。为了更好地理解,请查看python变量的工作原理:
在 Python 中,为什么函数可以修改调用者所认为的某些参数,但不能修改其他参数?
Python 是按值调用还是按引用调用?都不是。
Python 是按值传递,还是按引用传递?
Python 是按引用传递还是按值传递?
Python:如何通过引用传递变量?
理解 Python 变量和内存管理
在 Python 中模拟按值传递行为
Python 函数通过引用调用
像 Pythonista 一样编写代码:惯用的 Python
解决方案 8:
它就是那样设计的。增量和减量运算符只是 的快捷方式x = x + 1
。Python 通常采用一种设计策略,以减少执行操作的替代方法的数量。 增强赋值是 Python 中最接近增量/减量运算符的东西,它们直到 Python 2.0 才被添加。
解决方案 9:
我对 Python 还很陌生,但我怀疑原因是因为该语言中强调了可变对象和不可变对象。现在,我知道 x++ 可以很容易地解释为 x = x + 1,但看起来您正在就地增加一个可能不可变的对象。
只是我的猜测/感觉/预感。
解决方案 10:
要完成该页面上已有的好的答案:
假设我们决定这样做,前缀(++i
)会破坏一元 + 和 - 运算符。
今天,在++
or前面加上前缀--
没有任何作用,因为它启用了两次一元加法运算符(什么都不做)或两次一元减法运算符(两次:取消自身)
>>> i=12
>>> ++i
12
>>> --i
12
所以这可能会破坏这个逻辑。
现在,如果需要它用于列表推导或 lambda,从 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
解决方案 11:
我相信它源于 Python 信条“显式优于隐式”。
解决方案 12:
首先,Python 只是间接地受到 C 的影响;它深受ABC的影响,而ABC显然没有这些运算符,因此在 Python 中也找不到这些运算符也就不足为奇了。
其次,正如其他人所说,增量和减量已得到+=
和支持-=
。
第三,对++
and--
运算符集的全面支持通常包括支持它们的前缀和后缀版本。在 C 和 C++ 中,这可能导致各种“可爱”的构造,在我看来,这些构造违背了 Python 所秉持的简单和直接的精神。
例如,虽然 C 语句while(*t++ = *s++);
对于经验丰富的程序员来说可能看起来简单而优雅,但对于初学者来说,它却一点也不简单。再加上前缀和后缀的增量和减量,甚至许多专业人士也不得不停下来思考一下。
解决方案 13:
运算符类++
是具有副作用的表达式。这在 Python 中通常不存在。
出于同样的原因,赋值在 Python 中不是表达式,因此阻止了常见的if (a = f(...)) { /* using a here */ }
习惯用法。
最后,我怀疑这些运算符与 Python 的引用语义不太一致。请记住,Python 没有具有 C/C++ 中已知语义的变量(或指针)。
解决方案 14:
正如我所理解的,所以你不会认为内存中的值发生了变化。在 c 中,当你执行 x++ 时,内存中 x 的值会发生变化。但在 python 中,所有数字都是不可变的,因此 x 指向的地址仍然是 x 而不是 x+1。当你写 x++ 时,你会认为 x 发生了变化,真正发生的是 x 引用更改为内存中存储 x+1 的位置,或者如果不存在则重新创建该位置。
解决方案 15:
其他答案已经描述了为什么迭代器不需要它,但有时在分配增加内联变量时它很有用,您可以使用元组和多重赋值来实现相同的效果:
b = ++a
变成:
a,b = (a+1,)*2
变成b = a++
:
a,b = a+1, a
Python 3.8 引入了赋值运算:=
符,让我们能够foo(++a)
实现
foo(a:=a+1)
foo(a++)
但仍然难以捉摸。
解决方案 16:
也许更好的问题是问为什么这些运算符存在于 C 中。K&R 称增量和减量运算符“不寻常”(第 2.8 节第 46 页)。简介称它们“更简洁,通常更高效”。我怀疑这些操作总是出现在指针操作中这一事实也对它们的引入起到了一定作用。在 Python 中,人们可能认为尝试优化增量是没有意义的(事实上,我刚刚在 C 中进行了测试,似乎 gcc 生成的汇编在这两种情况下都使用了 addl 而不是 incl),并且没有指针算法;所以它只是另一种方法,我们知道 Python 讨厌这一点。
解决方案 17:
这可能是因为@GlennMaynard正在将这个问题与其他语言进行比较,但在 Python 中,您可以按照 Python 的方式做事。这不是一个“为什么”的问题。它就在那里,你可以用 做同样的事情x+=
。在《Python 之禅》中,它给出了:“解决问题的方法应该只有一种。”多种选择在艺术上很棒(表达自由),但在工程上很糟糕。
解决方案 18:
我认为这与对象的可变性和不可变性概念有关。2、3、4、5 在 Python 中是不可变的。请参阅下图。2 的 ID 直到这个 Python 进程都是固定的。
x++ 本质上意味着像 C 一样的就地递增。在 C 中,x++ 执行就地递增。因此,x=3,x++ 会在内存中将 3 递增到 4,而 Python 中 3 仍会存在于内存中。
因此在 Python 中,你不需要在内存中重新创建值。这可能导致性能优化。
这是一个基于猜测的答案。
解决方案 19:
我知道这是一个老话题,但 ++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()) :
解决方案 20:
除了这里的其他优秀答案之外,++
还--
因未定义的行为而臭名昭著。例如,此代码中发生了什么?
foo[bar] = bar++;
它看上去很无害,但它是错误的 C(和 C++),因为你不知道第一个是否bar
会增加。一个编译器可能以一种方式执行,另一个可能以另一种方式执行,第三个可能让你大吃一惊。所有这些都完全符合 C 和 C++ 标准。
(编辑:C++17 已经改变了给定代码的行为,以便它被定义;它将等同于foo[bar+1] = bar; ++bar;
——尽管如此,这可能不是程序员所期望的。)
未定义行为在 C 和 C++ 中被视为必要之恶,但在 Python 中,它只是恶,需要尽可能避免。
扫码咨询,免费领取项目管理大礼包!