Python中递增和递减运算符的行为

如何使用前递增/递减运算符(++--),就像在C++?

为什么++count运行,但不改变变量的值?

1389886 次浏览

++不是运算符。它是两个+运算符。+运算符是身份运算符,它什么都不做。(澄清:+-一元操作符仅适用于数字,但我假设您不会期望假设的++运算符适用于字符串。)

++count

解析为

+(+count)

翻译过来就是

count

你必须使用稍微长一点的+=运算符来做你想做的事情:

count += 1

我怀疑++--运算符是出于一致性和简单性而被遗漏的。我不知道Guido van Rossum为这个决定给出的确切论点,但我可以想象一些论点:

  • 更简单的解析。从技术上讲,解析++count是有歧义的,因为它可能是++count(两个一元+运算符),就像它可能是++count(一个一元++运算符)一样容易。这不是一个重要的语法歧义,但它确实存在。
  • 更简单的语言。++只不过是+= 1的同义词。它是一种速记,因为C编译器很愚蠢,不知道如何将a += 1优化为大多数计算机的inc指令。在优化编译器和字节码解释语言的今天,在语言中添加运算符以允许程序员优化他们的代码通常是不赞成的,尤其是在像Python这样旨在保持一致性和可读性的语言中。
  • 令人困惑的副作用。在使用++运算符的语言中,一个常见的新手错误是混淆了前后递增/递减运算符之间的差异(优先级和返回值),Python喜欢消除语言的“问题”-s。C中的前/后增量中的优先权问题非常棘手,非常容易搞砸。

Python没有前置和后置增量运算符。

在Python中,整数是不可变。也就是说,您不能更改它们。这是因为整数对象可以在多个名称下使用。试试这个:

>>> b = 5>>> a = 5>>> id(a)162334512>>> id(b)162334512>>> a is bTrue

上面的a和b实际上是同一个对象。如果你增加a,你也会增加b。这不是你想要的。所以你必须重新分配。像这样:

b = b + 1

许多使用python的C程序员想要一个增量运算符,但该运算符看起来像是增加了对象,而实际上是重新分配了它。因此,添加的-=+=运算符要比b = b + 1短,同时比b++更清晰、更灵活,所以大多数人会使用:

b += 1

这将重新分配bb+1。这不是一个增量运算符,因为它不会递增b,它会重新分配它。

简而言之:Python在这里的行为不同,因为它不是C,也不是机器代码的低级包装器,而是一种高级动态语言,增量没有意义,也不像C那样必要,例如,每次有循环时都使用它们。

在Python中,表达式和语句之间的区别是严格的强制执行,与Common Lisp、Plan或红宝石。

维基百科

因此,通过引入这样的运算符,您将打破表达式/语句拆分。

跟你不能写作的原因一样

if x = 0:y = 1

就像你可以在其他一些语言中没有保留这种区别一样。

虽然其他答案是正确的,因为它们显示了仅仅+通常做的事情(即,保持数字不变,如果它是一个),它们是不完整的,因为它们没有解释发生了什么。

准确地说,+x计算为x.__pos__()++x计算为x.__pos__().__pos__()

我可以想象一个非常奇怪的班级结构(孩子们,不要在家里这样做!)像这样:

class ValueKeeper(object):def __init__(self, value): self.value = valuedef __str__(self): return str(self.value)
class A(ValueKeeper):def __pos__(self):print 'called A.__pos__'return B(self.value - 3)
class B(ValueKeeper):def __pos__(self):print 'called B.__pos__'return A(self.value + 19)
x = A(430)print x, type(x)print +x, type(+x)print ++x, type(++x)print +++x, type(+++x)

Python没有这些运算符,但如果你真的需要它们,你可以编写一个具有相同功能的函数。

def PreIncrement(name, local={}):#Equivalent to ++nameif name in local:local[name]+=1return local[name]globals()[name]+=1return globals()[name]
def PostIncrement(name, local={}):#Equivalent to name++if name in local:local[name]+=1return local[name]-1globals()[name]+=1return globals()[name]-1

用法:

x = 1y = PreIncrement('x') #y and x are both 2a = 1b = PostIncrement('a') #b is 1 and a is 2

在函数内部,如果要更改局部变量,则必须添加locals()作为第二个参数,否则它将尝试更改全局变量。

x = 1def test():x = 10y = PreIncrement('x') #y will be 2, local x will be still 10 and global x will be changed to 2z = PreIncrement('x', locals()) #z will be 11, local x will be 11 and global x will be unalteredtest()

还有这些功能,你可以做:

x = 1print(PreIncrement('x'))   #print(x+=1) is illegal!

但在我看来,以下方法更清晰:

x = 1x+=1print(x)

递减操作员:

def PreDecrement(name, local={}):#Equivalent to --nameif name in local:local[name]-=1return local[name]globals()[name]-=1return globals()[name]
def PostDecrement(name, local={}):#Equivalent to name--if name in local:local[name]-=1return local[name]+1globals()[name]-=1return globals()[name]+1

我在将javascript转换为python的模块中使用了这些函数。

是的,我也错过了++和--功能。几百万行c代码将这种想法根植在我的旧脑海中,而不是与之抗争…这是我拼凑起来的一个类,它实现了:

pre- and post-increment, pre- and post-decrement, addition,subtraction, multiplication, division, results assignableas integer, printable, settable.

这里是:

class counter(object):def __init__(self,v=0):self.set(v)
def preinc(self):self.v += 1return self.vdef predec(self):self.v -= 1return self.v
def postinc(self):self.v += 1return self.v - 1def postdec(self):self.v -= 1return self.v + 1
def __add__(self,addend):return self.v + addenddef __sub__(self,subtrahend):return self.v - subtrahenddef __mul__(self,multiplier):return self.v * multiplierdef __div__(self,divisor):return self.v / divisor
def __getitem__(self):return self.v
def __str__(self):return str(self.v)
def set(self,v):if type(v) != int:v = 0self.v = v

你可以像这样使用它:

c = counter()                          # defaults to zerofor listItem in myList:                # imaginary taskdoSomething(c.postinc(),listItem) # passes c, but becomes c+1

…已经有了c,你可以这样做…

c.set(11)while c.predec() > 0:print c

……或者只是…

d = counter(11)while d.predec() > 0:print d

…对于(重新)赋值到整数…

c = counter(100)d = c + 223 # assignment as integerc = c + 223 # re-assignment as integerprint type(c),c # <type 'int'> 323

…而这将保持c作为类型计数器:

c = counter(100)c.set(c + 223)print type(c),c # <class '__main__.counter'> 323

编辑:

然后有一些意想不到的(完全不需要的)行为

c = counter(42)s = '%s: %d' % ('Expecting 42',c) # but getting non-numeric exceptionprint s

…因为在该元组中,地质()不是使用的,而是将对对象的引用传递给格式化函数。叹息。所以:

c = counter(42)s = '%s: %d' % ('Expecting 42',c.v) # and getting 42.print s

…或者,更详细,明确地说,我们实际上想要发生什么,尽管在实际形式中用冗长来反指示(使用c.v代替)…

c = counter(42)s = '%s: %d' % ('Expecting 42',c.__getitem__()) # and getting 42.print s

太长别读

Python没有一元递增/递减运算符(--/++)。相反,要增加一个值,请使用

a += 1

更多细节和陷阱

但是在这里要小心。如果你来自C,即使这在python中也是不同的。Python没有C所具有的“变量”,而是python使用名字对象,并且在pythonint中是不可变的。

所以假设你愿意

a = 1

这在python中的意思是:创建一个值为1int类型的对象并将名称a绑定到它。对象int的实例,值为1姓名a引用它。名称a和它引用的对象是不同的。

现在假设你愿意

a += 1

由于int是不可变的,这里发生的事情如下:

  1. 查找a引用的对象(它是id0x559239eeb380int
  2. 查找对象0x559239eeb380的值(它是1
  3. 给这个值加1(1+1=2)
  4. 创建一个值为2新的int对象(它有对象id0x559239eeb3a0
  5. 将名称a重新绑定到这个新对象
  6. 现在a引用对象0x559239eeb3a0,原始对象(0x559239eeb380)不再由名称a引用。如果没有任何其他名称引用原始对象,它将在以后被垃圾回收。

自己试一试:

a = 1print(hex(id(a)))a += 1print(hex(id(a)))

Python中没有后/前递增/递减运算符,就像C语言一样。

我们可以看到++--是多个符号相乘,就像我们在数学(-1)*(-1)=(+1)中所做的那样。

例如。

---count

解析为

-(-(-count)))

翻译过来就是

-(+count)

因为,-符号与-符号的乘法是+

最后,

-count

在python 3.8+中,你可以这样做:

(a:=a+1) #same as ++a (increment, then return new value)(a:=a+1)-1 #same as a++ (return the incremented value -1) (useless)

你可以用这个做很多思考。

>>> a = 0>>> while (a:=a+1) < 5:print(a)
    
1234

或者如果你想写一些更复杂的语法(目标不是优化):

>>> del a>>> while (a := (a if 'a' in locals() else 0) + 1) < 5:print(a)
    
1234

它将返回0,即使'a'不存在也没有错误,然后将其设置为1

直截了当的解决方法

c = 0c = (lambda c_plusplus: plusplus+1)(c)print(c)1

不用再打字了

 c = c + 1

你也可以写c++并完成所有代码,然后搜索/替换“c++”,替换为“c=c+1”。确保正则表达式搜索已关闭。

扩展Henry的答案,我实验实现了一个实现a++hdytto的语法糖库。

用法很简单。从PyPI安装后,放置sitecustomize.py

from hdytto import register_hdyttoregister_hdytto()

在您的项目目录中。然后,制作main.py

# coding: hdytto
a = 5print(a++)print(++a)b = 10 - --aprint(b--)

并通过PYTHONPATH=. python main.py运行它。输出将是

574

hdytto在解码脚本文件时将a++替换为((a:=a+1)-1),因此它有效。