【问题标题】:Behaviour of increment and decrement operators in PythonPython中递增和递减运算符的行为
【发布时间】:2010-12-01 22:23:17
【问题描述】:

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

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

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

【问题讨论】:

  • Python 不是 C 或 C++。不同的设计决策进入了语言的制作过程。特别是,Python 故意不定义可以在任意表达式中使用的赋值运算符。相反,有赋值语句和扩充的赋值语句。请参阅下面的参考资料。
  • 是什么让你认为 python 有 ++-- 运算符?
  • Kaizer:来自 C/C++,我编写了 ++count,它用 Python 编译。所以,我认为语言有运算符。
  • 鉴于大多数当代 OOP 语言在 GVR 提出 Python 时都具有这些符号,是否为此构造包含语法警告?
  • @mehaase ++ 和 -- 在 c 中不存在“作为指针算术的语法糖”,它们存在是因为许多处理器具有自动递增和递减内存访问机制(通常是指针索引、堆栈索引)作为其本机指令集的一部分。例如,在 6809 汇编器中:sta x++ ...生成的原子指令将 a 累加器存储在 x 指向的位置,然后将 x 递增累加器的大小。这样做是因为它比指针算术更快,因为它很常见,而且很容易理解。前期和后期。

标签: python operators increment decrement


【解决方案1】:

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

++count

解析为

+(+count)

翻译成

count

你必须使用稍长的+= 操作符来做你想做的事情:

count += 1

我怀疑++-- 运算符是为了一致性和简单性而被排除在外的。我不知道 Guido van Rossum 给出的决定的确切论据,但我可以想象一些论据:

  • 更简单的解析。从技术上讲,解析++count 是模棱两可的,因为它可以是++count(两个一元+ 运算符),就像它可以是++count(一个一元@ 987654345@ 运营商)。这不是严重的语法歧义,但确实存在。
  • 更简单的语言。 ++ 只不过是 += 1 的同义词。这是发明的速记,因为 C 编译器很愚蠢,不知道如何将 a += 1 优化为大多数计算机拥有的 inc 指令。在优化编译器和字节码解释语言的今天,向语言添加运算符以允许程序员优化其代码通常是不受欢迎的,尤其是在 Python 等旨在保持一致和可读性的语言中。
  • 令人困惑的副作用。在具有++ 运算符的语言中,一个常见的新手错误是混淆了前后递增/递减运算符之间的差异(优先级和返回值),而 Python 喜欢消除语言“gotcha”-s。 pre-/post-increment in Cprecedence issues 毛茸茸的,而且非常容易搞砸。

【讨论】:

  • “+ 运算符是“身份”运算符,它什么也不做。”仅适用于数字类型;对于其他类型,默认为错误。
  • 另外,请注意,在 Python 中,+= 和朋友不是可用于表达式的运算符。相反,在 Python 中,它们被定义为“增强赋值语句”的一部分。这与 Python 中的语言设计决定一致,即不允许在任意表达式中将赋值(“=”)作为运算符,这与在 C 中可以做的不同。参见docs.python.org/reference/…
  • 一元 + 运算符有一个用途。对于 decimal.Decimal 对象,它会四舍五入到当前精度。
  • 我赌的是解析器的简化。注意PEP 3099 中的一个项目,“Python 3000 中不会改变的东西”:“解析器不会比 LL(1) 更复杂。简单胜于复杂。这个想法延伸到解析器。限制 Python 的语法对 LL(1) 解析器来说是一种祝福,而不是一种诅咒。它给我们戴上了手铐,防止我们走极端,最终得到时髦的语法规则,就像其他一些不会命名的动态语言,比如 Perl。我看不出如何在不破坏 LL(1) 的情况下消除 + +++ 的歧义。
  • ++ 只不过是+= 1 的同义词是不正确的。 ++ 有前增量和后增量的变体,所以它显然不是一回事。不过,我同意你的其他观点。
【解决方案2】:

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

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

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

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

b = b + 1

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

b += 1

这会将b 重新分配给b+1。这不是自增运算符,因为它不会自增 b,而是重新分配它。

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

【讨论】:

  • 那个例子是错误的(你可能会混淆不变性和身份) - 由于一些 vm 优化使用相同的对象作为数字直到 255(或类似的东西),它们具有相同的 id。例如(更大的数字): >>> a = 1231231231231 >>> b = 1231231231231 >>> id(a), id(b) (32171144, 32171168)
  • 不变性声明是虚假的。从概念上讲,i++ 意味着将i + 1 分配给变量 ii = 5; i++ 表示将6 赋值给i,而不是修改i 指向的int 对象。即不代表increment the value of 5!
  • @Mechanical snail:在这种情况下,它根本就不是增量运算符。然后 += 运算符更清晰、更明确、更灵活,并且无论如何都做同样的事情。
  • @LennartRegebro:在 C++ 和 Java 中,i++ 仅对左值起作用。如果它打算增加i 指向的对象,那么这个限制是不必要的。
  • 在这种情况下,不变性确实无关紧要。考虑一下:Clojure 有一个内置的增量运算符,并且所有数据结构默认情况下都是不可变的。虽然您确实获得了对新值的新引用,但这与 +++= 1 的纯语法选择几乎是正交的。
【解决方案3】:

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

确切地说,+x 的计算结果为 x.__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)

【讨论】:

    【解决方案4】:

    TL;DR

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

    a += 1
    

    更多细节和陷阱

    但是这里要小心。如果你来自 C,即使这在 python 中也是不同的。 Python 没有 C 的“变量”,而是使用 namesobjects,而在 python 中 ints 是不可变的。

    所以说你愿意

    a = 1
    

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

    现在假设你这样做了

    a += 1
    

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

    1. 查找a 所指的对象(它是一个int,id 为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)))
    

    【讨论】:

    • 除了小整数被“留存”外,它们永远不会被垃圾回收。
    【解决方案5】:

    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 是一个快照,不会更新堆栈帧。
    【解决方案6】:

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

    Wikipedia

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

    出于同样的原因你不能写

    if x = 0:
      y = 1
    

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

    【讨论】:

    • 有趣的是,这个限制将在即将发布的 Python 3.8 中取消,并使用新的赋值表达式语法 (PEP-572 python.org/dev/peps/pep-0572)。例如,我们将能够写if (n := len(a)) > 10: y = n + 1。请注意,区别很明显,因为为此目的引入了一个新运算符 (:=)
    【解决方案7】:

    在 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)
    
        
    1
    2
    3
    4
    

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

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

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

    【讨论】:

    • 很好的答案!只有一个建议:a++ 会增加但返回旧值,(a:=a+1) 更像是++a 会增加并返回新值。
    • 但是你能做到(a:+=1)吗?
    • @ingyhere 不,这是语法错误,因为 '+=' 是 2 个对象 (iadd) 之间的操作,您不能将语法操作分配给变量
    【解决方案8】:

    是的,我也错过了 ++ 和 -- 功能。几百万行 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
    

    【讨论】:

      【解决方案9】:

      python 中没有像 C 这样的语言那样的后/前递增/递减运算符。

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

      例如

      ---count
      

      解析为

      -(-(-count)))
      

      翻译成

      -(+count)
      

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

      最后,

      -count
      

      【讨论】:

      • 这说明了什么其他答案没有?
      • @DanielB。其他答案没有告诉内部发生了什么。而且,他们都没有告诉你写-----count时会发生什么。
      • 没有提到正在执行乘法,所以我认为一个简洁明了的答案对其他用户很有用。如果你从中理解的话,没有冒犯。学习比学习的来源更重要。
      【解决方案10】:

      直接的解决方法

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

      不再打字

       c = c + 1
      

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

      【讨论】:

        猜你喜欢
        • 2011-02-16
        • 2015-10-03
        • 2014-03-21
        • 1970-01-01
        • 2023-01-05
        • 1970-01-01
        • 2011-08-29
        • 1970-01-01
        相关资源
        最近更新 更多