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

Ash*_*ppa 756 python increment operators decrement

我注意到一个预增量/减量运算符可以应用于变量(如++count).它编译,但它实际上并没有改变变量的值!

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

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

Chr*_*utz 990

++不是运营商.这是两个+运营商.该+运营商的身份运营,这什么都不做.(澄清:+-一元运算符只能处理数字,但我认为你不会指望一个假设的++运算符来处理字符串.)

++count
Run Code Online (Sandbox Code Playgroud)

解析为

+(+count)
Run Code Online (Sandbox Code Playgroud)

这转化为

count
Run Code Online (Sandbox Code Playgroud)

您必须使用稍长的+=运算符来执行您想要执行的操作:

count += 1
Run Code Online (Sandbox Code Playgroud)

我怀疑++--操作员是因为一致性和简单性而被排除在外.我不知道Guido van Rossum给出的确切论据,但我可以想象一些论点:

  • 更简单的解析.从技术上讲,解析++count是模糊的,因为它可能是+,+,count(两个一元+经营者)一样容易,因为它可能是++,count(一个一元++运算符).这不是一个重要的句法歧义,但确实存在.
  • 更简单的语言.++只不过是一个同义词+= 1.这是一个简化发明,因为C编译器是愚蠢的,不知道如何优化大多数计算机a += 1inc指令.在优化编译器和字节码解释语言的今天,向一种语言添加运算符以允许程序员优化其代码通常是不受欢迎的,特别是在像Python这样设计为一致且可读的语言中.
  • 令人困惑的副作用.使用++运算符的语言中的一个常见新手错误是混合了增量前/减值运算符之间的差异(优先级和返回值),Python喜欢消除语言"gotcha"-s.该优先问题前/后加用C是相当毛,和令人难以置信的容易陷入困境.

  • 另外,请注意,在Python中,+ =和friends不是可以在表达式中使用的运算符.相反,在Python中,它们被定义为"扩充赋值语句"的一部分.这与Python中的语言设计决策一致,不允许赋值("=")作为任意表达式中的运算符,这与C中可以做的不同.请参阅http://docs.python.org/reference/simple_stmts.html #增强赋值语句 (45认同)
  • 我打赌解析器简化.请注意[PEP 3099](http://www.python.org/dev/peps/pep-3099/)中的一项,"Python 3000中不会改变的事情":"解析器不会比LL(1).简单比复杂更好.这个想法扩展到解析器.将Python的语法限制为LL(1)解析器是一种祝福,而不是诅咒.它让我们戴上手铐,防止我们过火和结束与其他一些未命名的动态语言一样,时髦的语法规则,如Perl." 我没有看到如何在不破坏LL(1)的情况下消除歧义"+ +"和"++". (21认同)
  • 一元`+`运算符有用.对于decimal.Decimal对象,它将舍入为当前精度. (15认同)
  • "+运算符是"身份"运算符,它什么都不做." 仅适用于数字类型; 对于其他类型,默认情况下是一个错误. (13认同)
  • 说"++"只不过是"+ = 1"的同义词,这是不正确的.有++的预增量和后增量变体,所以它显然不是一回事.不过,我同意你的其他观点. (6认同)
  • 对.但话说回来,`++`和` - `应该只适用于数字类型. (4认同)
  • 这个答案是正确的,所以+1,但这只是Python的另一个烦恼,让我真的不喜欢它.`strip()`而不是`trim()`就像其他语言一样,没有开关,有些OO函数,有些没有([`len(s)`](http://stackoverflow.com/a/237150/156755) vs [`s.lower()`](http://stackoverflow.com/a/6797990/156755)).非常令人沮丧和不一致的语言 (3认同)
  • 'Python喜欢消除语言"gotcha"-s' - 这不是一种语言问题吗? (3认同)
  • 各自为自己.我一直使用++和 - 并且最终不会弄乱任何东西.Python是一种很好的语言,但是它有很多缺陷,我发现社区对各种语言结构都很虔诚.它是一个工具,而不是蒙娜丽莎.让我们完成工作并继续下一个任务......这很可能是用R,或Matlab或C++编写的 (3认同)
  • @CivFan 为什么这比从具有可以覆盖的长度方法/属性的对象继承更可取?据我所知,它只是增加了一层间接,没有任何实际好处 (2认同)
  • @CivFan然后a)`__len __()`不是真正的私有,它只是公开但通过间接调用b)我们现在需要知道当对象可能只有一个时调用私有方法的所有全局方法length()`方法,如果需要可以覆盖. (2认同)
  • @CivFan 我认为我们必须同意不同意,我仍然认为它看起来像噪音并且违反了面向对象的原则,但没有什么好处(如果有的话)。尽管如此,我也需要喝一杯。祝你周末愉快 (2认同)

Len*_*bro 378

当您想要递增或递减时,通常希望对整数执行此操作.像这样:

b++
Run Code Online (Sandbox Code Playgroud)

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

>>> b = 5
>>> a = 5
>>> id(a)
162334512
>>> id(b)
162334512
>>> a is b
True
Run Code Online (Sandbox Code Playgroud)

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

b = b + 1
Run Code Online (Sandbox Code Playgroud)

或者更简单:

b += 1
Run Code Online (Sandbox Code Playgroud)

哪个会重新分配bb+1.这不是增量运算符,因为它不会递增b,而是重新分配它.

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

  • 这个例子是错误的(你可能会混淆不可变性与身份) - 由于某些vm优化使用相同的对象进行数字直到255(或类似的东西),它们具有相同的id.例如(更大的数字):>>> a = 1231231231231 >>> b = 1231231231231 >>> id(a),id(b)(32171144,32171168) (70认同)
  • 不可变性主张是虚假的.从概念上讲,`i ++`意味着将`i + 1`分配给*变量*`i`.`i = 5; i ++`意味着将`6`分配给`i`,而不是修改`i`指向的`int`对象.也就是说,它并不意味着[增加"5"的值](http://mathworld.wolfram.com/SufficientlyLarge.html)! (53认同)
  • @LennartRegebro:在C++和Java中,`i ++'只对lvalues运行.如果它打算增加`i`指向的对象,则不需要这种限制. (7认同)
  • 我觉得这个答案令人困惑。为什么要假设++意味着+ = 1的简写呢?这正是C语言的含义(假设未使用返回值)。您似乎想出了一些其他含义。 (4认同)
  • @Mechanical snail:在这种情况下,它根本不是增量运算符.然后+ =运算符更清晰,更明确,更灵活,无论如何都会做同样的事情. (3认同)
  • @LennartRegebro - 是的; 我现在看到我的困惑只是"增量"的定义.我觉得"增量"的定义与语言的等义语义联系在一起,这意味着如果`f(x)== x + 1,任何没有副作用的函数`f`都可以称为"增量". `,始终适用于所有数值`x`.其他人显然不这么看,我将来会记住这一点.无论如何,+1都是一个发人深省的答案.PS - Clojure的增量运算符比`++`更明确:它是`(inc value)`. (3认同)
  • 在这种情况下,不变性确实无关紧要.考虑一下:Clojure有一个内置的增量运算符,默认情况下所有数据结构都是不可变的.虽然这是真的,你得到一个新值的新引用,这主要与`++`与`+ = 1`的纯语法选择正交. (2认同)
  • 这种解释尽管似乎并不合理:解释器可以使“ ++ a”与“ a = a + 1”具有完全相同的效果,并且使“ a”指向ID为6。我认为真正的问题在于+是一元运算符,而++ a可能意味着`increment a`或`+(+ a)`,这对于python解析器来说是无法区分的。 (2认同)
  • 这个解释(不可变的)也出现在我昨天阅读的Python书中. (2认同)
  • 正如Don Hatch所说的那样,在其他语言中(例如Java),整数也是不可变的,并且执行++ 1或* ++只是将变量指向的值更改为大于1的值。它当前指向。两个运算符都不会修改变量所指向的值。对于为什么没有添加`++`运算符,这个答案确实没有给出充分的理由。 (2认同)

glg*_*lgl 50

虽然其他答案是正确的,只要它们表明+通常只是做什么(即,保留数字,如果它是一个),它们是不完整的,因为它们不解释会发生什么.

确切的说,+x计算结果为x.__pos__()++xx.__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)
Run Code Online (Sandbox Code Playgroud)


RBF*_*F06 19

TL; 博士

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

a += 1
Run Code Online (Sandbox Code Playgroud)

更多细节和陷阱

但是这里要小心。如果你来自 C,即使这在 python 中也是不同的。Python 没有像 C 那样的“变量”,而是 python 使用名称对象,在 python 中int是不可变的。

所以让我们说你做

a = 1
Run Code Online (Sandbox Code Playgroud)

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

现在让我们说你做

a += 1
Run Code Online (Sandbox Code Playgroud)

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

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

自己试试看:

a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))
Run Code Online (Sandbox Code Playgroud)


Pio*_*ski 12

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
Run Code Online (Sandbox Code Playgroud)

用法:

x = 1
y = PreIncrement('x') #y and x are both 2
a = 1
b = PostIncrement('a') #b is 1 and a is 2
Run Code Online (Sandbox Code Playgroud)

在函数内部,如果要更改局部变量,则必须添加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()
Run Code Online (Sandbox Code Playgroud)

您还可以使用以下功能:

x = 1
print(PreIncrement('x'))   #print(x+=1) is illegal!
Run Code Online (Sandbox Code Playgroud)

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

x = 1
x+=1
print(x)
Run Code Online (Sandbox Code Playgroud)

减少运营商:

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
Run Code Online (Sandbox Code Playgroud)

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


Vit*_*nko 11

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

维基百科

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

出于同样的原因你不能写

if x = 0:
  y = 1
Run Code Online (Sandbox Code Playgroud)

正如你可以在其他语言中那样,不保留这种区别.


fyn*_*yrz 6

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

pre- and post-increment, pre- and post-decrement, addition,
subtraction, multiplication, division, results assignable
as integer, printable, settable.
Run Code Online (Sandbox Code Playgroud)

这是:

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
Run Code Online (Sandbox Code Playgroud)

您可以这样使用它:

c = counter()                          # defaults to zero
for listItem in myList:                # imaginary task
     doSomething(c.postinc(),listItem) # passes c, but becomes c+1
Run Code Online (Sandbox Code Playgroud)

...已经有了c,您可以执行此操作...

c.set(11)
while c.predec() > 0:
    print c
Run Code Online (Sandbox Code Playgroud)

....要不就...

d = counter(11)
while d.predec() > 0:
    print d
Run Code Online (Sandbox Code Playgroud)

...并用于(重新)分配为整数...

c = counter(100)
d = c + 223 # assignment as integer
c = c + 223 # re-assignment as integer
print type(c),c # <type 'int'> 323
Run Code Online (Sandbox Code Playgroud)

...这将使c保持为类型计数器:

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

编辑:

然后还有一些意想不到的(并且完全是不想要的)行为

c = counter(42)
s = '%s: %d' % ('Expecting 42',c) # but getting non-numeric exception
print s
Run Code Online (Sandbox Code Playgroud)

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

c = counter(42)
s = '%s: %d' % ('Expecting 42',c.v) # and getting 42.
print s
Run Code Online (Sandbox Code Playgroud)

…或更确切地说,是我们实际上想要发生的事情,尽管冗长程度以实际形式c.v相反(使用代替)……

c = counter(42)
s = '%s: %d' % ('Expecting 42',c.__getitem__()) # and getting 42.
print s
Run Code Online (Sandbox Code Playgroud)


Hen*_*nry 5

在 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)
Run Code Online (Sandbox Code Playgroud)

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

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

    
1
2
3
4
Run Code Online (Sandbox Code Playgroud)

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

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

    
1
2
3
4
Run Code Online (Sandbox Code Playgroud)

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

  • 很好的答案!只有一个建议:“a++”会递增但返回旧值,“(a:=a+1)”更像是“++a”,会递增并返回新值。 (2认同)