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


796

我注意到,可以对变量(如++count)应用预增减算符。它可以编译,但实际上并不会改变变量的值!

Python中预增/减运算符(++ /-)的行为是什么?

为什么Python会偏离C / C ++中看到的这些运算符的行为?


19
Python不是C或C ++。语言的设计决策各不相同。特别是,Python故意不定义可在任意表达式中使用的赋值运算符。而是有赋值语句和扩充的赋值语句。请参阅下面的参考。
内德·迪利

8
是什么让您认为python具有++--运算符?
u0b34a0f6ae

29
Kaizer:来自C / C ++,我编写++ count,它在Python中编译。因此,我认为该语言具有运算符。
Ashwin Nanjappa,

3
@Fox您正在假设没有明显的规划和组织水平
基本的

4
@mehaase ++和-在c中不存在“作为指针算术的语法糖”,它们之所以存在,是因为许多处理器将自动递增和递减内存访问机制(通常是指针索引,堆栈索引)作为其本机指令的一部分组。例如,在6809汇编器中:sta x++...产生的原子指令将a累加器存储在x指向的位置,然后x按累加器的大小递增。这样做是因为它比指针算术更快,因为它很常见,并且很容易理解。前和后。
fyngyrz

Answers:


1058

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

++count

解析为

+(+count)

转化为

count

您必须使用稍长的+=运算符来完成您想做的事情:

count += 1

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

  • 更简单的解析。从技术上讲,解析++count是模糊的,因为它可能是++count(两个一元+经营者)一样容易,因为它可能是++count(一个一元++运算符)。它不是语法上的明显歧义,但确实存在。
  • 语言更简单。++只不过是的同义词+= 1。这是一种速记方法,因为C编译器很愚蠢,并且不知道如何优化大多数计算机所拥有a += 1inc指令。在优化编译器和字节码解释语言的这一天,通常不赞成在一种语言中添加运算符以允许程序员优化其代码,尤其是在像Python这样设计成一致且易读的语言中。
  • 令人困惑的副作用。带有++运算符的语言中一个常见的新手错误是将递增/递减运算符前后的差异(优先级和返回值)混合在一起,Python喜欢消除语言“陷阱”。该优先事项用C前置/后置增量是相当毛,和令人难以置信的容易陷入困境。

13
“ +运算符是“身份”运算符,它什么都不做。” 仅适用于数字类型;对于其他类型,默认情况下为错误。
newacct

45
另外,请注意,在Python中,+ =和friends不是可在表达式中使用的运算符。而是在Python中将它们定义为“增强的赋值语句”的一部分。这是在Python语言的设计决策相一致,不允许转让(“=”)的任意表达式中的经营,不像有什么人能在C.见做docs.python.org/reference/...
斯内德Deily

15
一元运算+符有用途。对于decimal.Decimal对象,将四舍五入到当前精度。
u0b34a0f6ae

21
我押注解析器的简化。请注意PEP 3099中的一个项目,“ Python 3000中不会发生的事情”:“解析器不会比LL(1)更复杂。简单胜于复杂。这个想法扩展到了解析器。将Python的语法限制为LL(1)解析器是一种福气,而不是诅咒。它把我们带到手铐上,可以防止我们过度学习并最终得到时髦的语法规则,如一些其他不愿透露名称的动态语言,例如Perl。” 我看不出如何消除歧义+ +并且++不破坏LL(1)。
Mike DeSimone 2010年

7
说这不过++是的同义词,这是不正确的+= 1。++有预增量和后增量变体,因此显然不是同一回事。不过,我同意您的其他观点。
PhilHibbs

384

当您想增加或减少时,通常需要对整数进行操作。像这样:

b++

但是在Python中,整数是不可变的。那是你不能改变他们。这是因为可以使用多个名称使用整数对象。尝试这个:

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

上面的a和b实际上是同一对象。如果增加a,也将增加b。那不是你想要的。因此,您必须重新分配。像这样:

b = b + 1

或更简单:

b += 1

哪个将重新分配bb+1。那不是增量运算符,因为它不会增量b,而是重新分配它。

简而言之:Python在这里的行为有所不同,因为它不是C,也不是机器代码的底层包装,而是高级动态语言,在这种语言中,增量没有意义,也没有C所必需,例如,每次有循环时在哪里使用它们。


75
这个例子是错误的(您可能会将不变性与身份混淆)-由于某些虚拟机优化,它们使用相同的对象直到255(或类似的数字),因此它们具有相同的ID。例如(更大的数字):>>> a = 1231231231231 >>> b = 1231231231231 >>> id(a),id(b)(32171144,32171168)
ionelmc 2010年

56
不变性主张是虚假的。从概念上讲,i++这意味着要分配i + 1变量 ii = 5; i++表示分配6i而不修改由int指向的对象的方法i。也就是说,这并不意味着增加5的值
机械蜗牛

3
@机械蜗牛:在这种情况下,它根本不是增量运算符。然后+ =运算符会更清晰,更明确,更灵活,并且仍然可以执行相同的操作。
Lennart Regebro 2011年

7
@LennartRegebro:在C ++和Java中,i++仅对左值起作用。如果要增加所指向的对象i,则不需要此限制。
机械蜗牛

4
我觉得这个答案令人困惑。为什么要假设++意味着+ = 1的简写呢?这正是C语言的含义(假设未使用返回值)。您似乎想出了一些其他含义。
唐·哈奇

52

尽管其他答案在表明仅仅+做某事上是正确的(即,保留数字,如果是一个,则保持不变),但就他们不解释会发生什么而言,它们是不完整的。

确切地说,+xx.__pos__()++x求值x.__pos__().__pos__()

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

class ValueKeeper(object):
    def __init__(self, value): self.value = value
    def __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)

13

Python没有这些运算符,但是如果您确实需要它们,则可以编写具有相同功能的函数。

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

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

用法:

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

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

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

使用这些功能,您还可以执行以下操作:

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

但是我认为以下方法更加清晰:

x = 1
x+=1
print(x)

减量运算符:

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

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

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


注意:虽然很棒,但是如果您的本地人存在于类函数堆栈框架中,则这些辅助方法将不起作用。即-从类方法def中调用它们将不起作用-'locals()'dict是快照,并且不会更新堆栈框架。
亚当

11

在Python中,与Common Lisp,Scheme或Ruby之类的语言相比,严格执行了表达式和语句之间的区别。

维基百科

因此,通过引入此类运算符,可以打破表达式/语句的拆分。

出于同样的原因,你不能写

if x = 0:
  y = 1

就像其他一些语言一样,这种语言没有保留。


有趣的是,此限制将在即将发布的Python 3.8中通过赋值表达式的新语法(PEP-572 python.org/dev/peps/pep-0572解除。我们将能够if (n := len(a)) > 10: y = n + 1举例。请注意,这种区别很明显,因为为此引入了一个新的运算符(:=
Zertrin,

8

TL; DR

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

a += 1

更多细节和陷阱

但是请注意这里。如果您来自C,即使在python中也是如此。在C的意义上,Python没有“变量”,而是python使用名称对象,并且在ints中是不可变的。

所以说你做

a = 1

这在python中的含义是:创建一个int具有值的类型的对象,1并将名称绑定a到该对象。的对象是的一个实例int具有值1,并且名称 a是指它。名称a和它引用的对象是不同的。

现在说你做

a += 1

由于ints是不可变的,因此这里发生的情况如下:

  1. 查找所a引用的对象(intID为0x559239eeb380
  2. 查找对象的值0x559239eeb380(为1
  3. 给该值加1(1 +1 = 2)
  4. 创建一个具有值的 int对象2(它具有对象id 0x559239eeb3a0
  5. 将名称重新绑定a到这个新对象
  6. 现在a引用对象,0x559239eeb3a0并且0x559239eeb380名称不再引用原始对象()a。如果没有其他名称引用原始对象,则稍后将对其进行垃圾回收。

自己尝试一下:

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

6

是的,我也错过了++和-功能。几百万行c代码使这种思想深深地扎根在我的脑海中,而不是与之抗争……这是我拼凑而成的一类,实现了:

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

这是:

class counter(object):
    def __init__(self,v=0):
        self.set(v)

    def preinc(self):
        self.v += 1
        return self.v
    def predec(self):
        self.v -= 1
        return self.v

    def postinc(self):
        self.v += 1
        return self.v - 1
    def postdec(self):
        self.v -= 1
        return self.v + 1

    def __add__(self,addend):
        return self.v + addend
    def __sub__(self,subtrahend):
        return self.v - subtrahend
    def __mul__(self,multiplier):
        return self.v * multiplier
    def __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 = 0
        self.v = v

您可以这样使用它:

c = counter()                          # defaults to zero
for listItem in myList:                # imaginary task
     doSomething(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 integer
c = c + 223 # re-assignment as integer
print 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 exception
print s

...因为在该元组中,没有使用getitem(),而是将对对象的引用传递给格式函数。叹。所以:

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

2

python中没有像C之类的语言中的post / pre增量/减量运算符。

我们可以看到++--随着多个符号成倍增加,就像我们在数学(-1)*(-1)=(+1)中一样。

例如

---count

解析为

-(-(-count)))

转化为

-(+count)

因为,-符号与-符号的乘积为+

最后,

-count

1
这说明其他答案没有什么意思?
Daniel B.

@DanielB。其他答案尚未说明内部发生了什么。而且,他们都没有告诉您写作时会发生什么-----count
Anuj

第一个被接受的答案是。...
Daniel B.

1
没有提及正在执行乘法运算,因此,我认为有一个要点,直截了当的答案对于其他用户将非常有用。如果您从中了解的话,就没有冒犯。学习比学习的来源更重要。
Anuj

0

在python 3.8+中,您可以执行以下操作:

(a:=a+1) #same as a++

您可以对此进行很多思考。

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


1
2
3
4

或者,如果您想使用更复杂的语法编写东西(目标不是优化):

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


1
2
3
4

如果不存在任何错误,它将很好地返回0,然后将其设置为1

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.