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

Behaviour of increment and decrement operators in Python

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

在python中,pre increment/decrement操作符(++/--)的行为是什么?

为什么Python偏离了C/C++中的这些操作符的行为?


++不是一个运算符。它是两个+运算符。+运算符是标识运算符,它不起任何作用。(说明:+-一元运算符只处理数字,但我假定您不会期望一个假设的++运算符处理字符串。)

1
++count

解析为

1
+(+count)

也就是说

1
count

您必须使用稍微长一点的+=操作符来执行您想要执行的操作:

1
count += 1

我怀疑++--操作符由于一致性和简单性而被排除在外。我不知道guido van rossum为这个决定所作的确切论证,但我可以想象一些论证:

  • 更简单的解析。从技术上讲,解析++count是不明确的,因为它可以是++count(两个一元+操作符),就像解析++count操作符一样容易。这不是一个明显的句法歧义,但它确实存在。
  • 更简单的语言。++只是+= 1的同义词。这是一个速记发明,因为C编译器很蠢,不知道如何将a += 1优化成大多数计算机都有的inc指令。在优化编译器和字节码解释语言的今天,向一种语言添加运算符以允许程序员优化其代码通常是不受欢迎的,特别是在像Python这样设计为一致和可读的语言中。
  • 令人困惑的副作用。在使用++运算符的语言中,一个常见的新手错误是混淆了前/后递增/递减运算符之间的差异(优先级和返回值),而python喜欢消除语言"gotcha"—s。C中的前/后递增的优先级问题相当复杂,而且非常容易混淆。


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

1
b++

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

1
2
3
4
5
6
7
8
>>> b = 5
>>> a = 5
>>> id(a)
162334512
>>> id(b)
162334512
>>> a is b
True

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

1
b = b + 1

或更简单:

1
b += 1

它将把b重新分配给b+1。这不是递增运算符,因为它不递增b,所以它重新分配它。

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


虽然其他答案是正确的,因为它们表明了一个简单的+通常做什么(也就是说,如果数字是1,就保留它的原样),但只要它们不解释发生了什么,它们就不完整。

准确地说,+xx.__pos__()++xx.__pos__().__pos__()进行评估。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)


python没有这些操作符,但是如果您真的需要它们,您可以编写一个具有相同功能的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

用途:

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

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

1
2
3
4
5
6
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()

还可以使用这些功能:

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

但在我看来,以下方法更为明确:

1
2
3
x = 1
x+=1
print(x)

减量运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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时使用了这些函数。


In Python, a distinction between expressions and statements is rigidly
enforced, in contrast to languages such as Common Lisp, Scheme, or
Ruby.

维基百科

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

因为同样的原因你不能写

1
2
if x = 0:
  y = 1

在某些其他语言中,这种区别是无法保留的。


是的,我也错过了++和--功能。几百万行C代码把这种思想灌输到我的老脑袋里,而不是与之抗争……下面是我整理的一个类,它实现了:

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

这里是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

您可以这样使用它:

1
2
3
c = counter()                          # defaults to zero
for listItem in myList:                # imaginary task
     doSomething(c.postinc(),listItem) # passes c, but becomes c+1

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

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

…或者只是…

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

…并将(重新)赋值为整数…

1
2
3
4
c = counter(100)
d = c + 223 # assignment as integer
c = c + 223 # re-assignment as integer
print type(c),c # <type 'int'> 323

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

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

编辑:

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

1
2
3
c = counter(42)
s = '%s: %d' % ('Expecting 42',c) # but getting non-numeric exception
print s

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

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

…或者,更确切地说,更明确地说,我们实际想要发生的事情,尽管详细程度以实际形式表示(使用c.v)。

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


DR

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

1
a += 1

更多细节和细节

但这里要小心。如果您来自C,即使这在Python中也是不同的。在C的意义上,python没有"变量",相反,python使用名称和对象,在python中,int是不可变的。

所以说你会的

1
a = 1

在python中,这意味着:创建一个值为1int类型的对象,并将名称a绑定到它。对象是具有值1int的实例,名称a是指它。名称a和它所指的对象是不同的。

现在我们假设你做了

1
a += 1

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

  • 查找a所指的对象(它是id为0x559239eeb380int)
  • 查找对象0x559239eeb380的值(它是1)。
  • 在该值上加1(1+1=2)
  • 新建一个值为2int对象(对象ID为0x559239eeb3a0)
  • 将名称a重新绑定到此新对象
  • 现在,a指的是0x559239eeb3a0,原来的对象(0x559239eeb380不再称为a。如果没有任何其他名称引用原始对象,则稍后将对其进行垃圾收集。
  • 你自己试试吧:

    1
    2
    3
    4
    a = 1
    print(hex(id(a)))
    a += 1
    print(hex(id(a)))