Pythonでの増分演算子と減分演算子の動作


798

プリインクリメント/デクリメント演算子が変数(など++count)に適用できることに気づきました。コンパイルされますが、実際には変数の値は変更されません!

Pythonの事前インクリメント/デクリメント演算子(++ /-)の動作は何ですか?

PythonがC / C ++で見られるこれらの演算子の動作から逸脱するのはなぜですか?


19
PythonはCまたはC ++ではありません。言語の作成には、さまざまな設計上の決定が行われました。特に、Pythonは意図的に任意の式で使用できる代入演算子を定義していません。むしろ、代入文と拡張代入文があります。以下の参照を参照してください。
Ned Deily

8
python ++--演算子は何だと思いましたか?
u0b34a0f6ae 2009

29
Kaizer:C / C ++から来て、私は++ countを書いて、Pythonでコンパイルします。それで、言語には演算子があると思いました。
アシュウィンナンジャッパ

3
@Foxあなたは証拠とならないレベルの計画と組織を想定しています
基本的な

4
@mehaase ++および-「ポインター演算の構文糖として」cには存在しません。ネイティブインストラクションの一部として多くのプロセッサーが自動インクリメントおよびデクリメントメモリアクセスメカニズム(一般にポインターインデックス、スタックインデックス)を持っているため、存在します。セットする。たとえば、6809アセンブラの場合:sta x++...結果として、aアキュムレータxがポイントしている場所を格納するアトミック命令xは、アキュムレータのサイズによってインクリメントします。これは、ポインタ演算よりも高速であり、非常に一般的であり、理解しやすいためです。事前および事後の両方。
fyngyrz 2016年

回答:


1059

++は演算子ではありません。2つの+演算子です。+オペレータは、アイデンティティ何もしません演算子、。(説明:単項演算子+-単項演算子は数値に対してのみ機能しますが、架空の++演算子が文字列に対して機能するとは思わないでしょう。)

++count

として解析

+(+count)

に変換します

count

あなたが+=やりたいことをするために少し長い演算子を使わなければなりません:

count += 1

++--演算子は一貫性と単純さのために省略されたと思います。グイドファンロッサムが決定について与えた正確な議論はわかりませんが、いくつかの議論を想像することができます。

  • より簡単な解析。技術的には、構文解析は、++countそれができるよう、曖昧で++count(2つの単項+同じように簡単にそれができるよう事業者)++count(1つの単項++演算子)。これは重大な構文のあいまいさではありませんが、存在します。
  • よりシンプルな言語。++の同義語にすぎません+= 1。Cコンパイラは愚かで、ほとんどのコンピューターが持っている命令に最適化a += 1する方法を知らなかったため、これは発明された速記でしたinc。コンパイラとバイトコード解釈言語を最適化するこの日は、プログラマがコードを最適化できるようにするために言語に演算子を追加することは、特にPythonのような一貫性があり読みやすいように設計された言語では、通常不快になります。
  • 紛らわしい副作用。++演算子を使用する言語でよくある初心者のエラーの1つは、前/後の増分/減分演算子の違い(優先順位と戻り値の両方)を混同することです。Pythonは、言語の「問題」を排除することを好みます。Cでの前/後のインクリメント優先順位の問題はかなり複雑で、非常に簡単に失敗します。

13
「+演算子は、何もしない「識別」演算子です。」数値型のみ。他のタイプの場合、デフォルトではエラーになります。
newacct 2009

45
また、Pythonでは、+ =とその仲間は式で使用できる演算子ではないことに注意してください。むしろ、Pythonでは「拡張代入ステートメント」の一部として定義されています。これは、Cでできることとは異なり、任意の式内で演算子としての割り当て( "=")を許可しないというPythonの言語設計の決定と一致しています。docs.python.org/ reference /…を
Ned Deily

15
単項演算+子には用途があります。decimal.Decimalオブジェクトの場合、現在の精度に丸められます。
u0b34a0f6ae 2009

21
私はパーサーの単純化に賭けています。PEP 3099の「Python 3000で変更されないもの」の項目に注意してください:「パーサーはLL(1)よりも複雑ではありません。シンプルは複雑よりも優れています。この考え方はパーサーにも拡張されます。Pythonの文法をLL(1)パーサーは祝福であり、呪いではありません。Perlのような名前のない他の動的言語のように、行き過ぎてファンキーな文法規則になってしまうのを防ぐ手錠になります。」曖昧さをなくす方法+ +++LL(1)を壊さない方法はわかりません。
Mike DeSimone、

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
その例は間違っています(そして、IDと不変性を混同している可能性があります)。255までの数値に同じオブジェクトを使用する一部のvm最適化(またはそのようなもの)のため、同じIDを持っています。例(より大きい数):>>> a = 1231231231231 >>> b = 1231231231231 >>> id(a)、id(b)(32171144、32171168)
ionelmc

56
不変性の主張は偽です。概念的にi++i + 1変数 に割り当てることを意味しますii = 5; i++は、が指すオブジェクトを変更するのではなく、に割り当てること6を意味します。つまりの値増やすという意味ではありません。iinti5
機械式カタツムリ

3
@Mechanical snail:その場合、それはインクリメント演算子ではありません。そして、+ =演算子はより明確で、より明示的で、より柔軟で、とにかく同じことを行います。
Lennart Regebro、2011

7
@LennartRegebro:C ++およびJavaでは、i++左辺値のみを操作します。が指すオブジェクトをインクリメントする場合はi、この制限は不要です。
機械式カタツムリ

4
この答えはかなり不可解です。なぜ++が+ = 1の省略形以外の意味を持っていると想定するのですか それが正確にCでそれが意味することです(戻り値が使用されないと仮定した場合)。あなたは他の意味を空気から引き出したようです。
ドンハッチ

52

他の答えは、単なる+通常の動作を示している限り(つまり、1の場合はそのままにしておきます)正解ですが、何が起こっているのかを説明しない限り、不完全です。

正確には、+xto x.__pos__()++xtoを評価し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

関数内で、ローカル変数を変更する場合は、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()」辞書はスナップショットであり、スタックフレームを更新しません。
アダム

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 '26

8

TL; DR

Pythonには単項インクリメント/デクリメント演算子(--/ ++)はありません。代わりに、値をインクリメントするには、次を使用します

a += 1

詳細と落とし穴

ただし、ここでは注意してください。Cから来ている場合でも、これはPythonでは異なります。Pythonのではなく、用途パイソンCがないという意味で、「変数」、持っていない名前オブジェクトを、そしてPythonでintSは不変です。

あなたがそう言うとしましょう

a = 1

これがpythonで意味することは、int1を持つタイプのオブジェクトを作成し、それに名前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

Cのような言語のようなpythonには、ポスト/プリインクリメント/デクリメント演算子はありません。

数学で行うように、++または--複数の符号が乗算されるのを見ることができます(-1)*(-1)=(+1)。

例えば

---count

として解析

-(-(-count)))

に変換します

-(+count)

なぜなら、-符号と-符号の乗算は+

そして最後に、

-count

1
これは、他の答えがそうではないことを何と言っていますか?
ダニエルB.

@DanielB。他の答えは内部で何が起こるかを伝えていません。そして、彼らはあなたが書くとき何が起こるかについても言っていません-----count
Anuj

最初に、受け入れられた答えはそうします。...
ダニエルB.

2
乗算が実行されていることについては何も言及されていないので、私は、簡潔に言えば、答えは仲間のユーザーにとって役立つだろうと考えました。あなたがそれから理解できれば、害はありません。学ぶことは、あなたが学ぶ場所よりも重要です。
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に設定します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.