web-dev-qa-db-ja.com

Pythonにおけるインクリメント演算子とデクリメント演算子の動作

インクリメント/デクリメント前の演算子を変数に適用できることに気付きました(++countのように)。コンパイルしますが、実際には変数の値を変更しません。

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

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

689
Ashwin Nanjappa

++は演算子ではありません。 2つの+演算子です。 +演算子はidentity演算子であり、何も行いません。 (明確化:+および-単項演算子は数字でのみ機能しますが、仮想++演算子が文字列で機能することを期待しないと思います。)

++count

として解析

+(+count)

に変換します

count

やりたいことをするために、少し長い+=演算子を使用する必要があります。

count += 1

++および--演算子は、一貫性と単純さのために省略されていると思われます。 Guido van Rossumがこの決定に与えた正確な議論はわかりませんが、いくつかの議論を想像できます。

  • より簡単な解析。技術的には、++countの解析はあいまいです。++count(2つの単項+演算子)は、++count(1つの単項++演算子)。重要な構文上のあいまいさではありませんが、存在します。
  • よりシンプルな言語。 +++= 1の同義語にすぎません。これは、Cコンパイラが愚かで、a += 1をほとんどのコンピューターが持つinc命令に最適化する方法を知らなかったために考案された速記です。コンパイラとバイトコード解釈言語を最適化する今日、特に一貫性があり読みやすいように設計されたPythonのような言語では、プログラマーがコードを最適化できるように言語に演算子を追加することは大嫌いです。
  • 紛らわしい副作用。 ++演算子を使用する言語での一般的な初心者エラーの1つは、前/後のインクリメント/デクリメント演算子の違い(優先順位と戻り値の両方)を混同し、Pythonが排除したいことです言語「おしゃべり」-s。 Cでの事前/事後増分優先順位の問題 はかなり毛並みが悪く、非常に簡単に台無しになります。
947
Chris Lutz

あなたがインクリメントまたはデクリメントしたいとき、あなたは通常整数でそれをしたいです。そのようです:

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の場合ほど必要ではない高レベルの動的言語であるため、Pythonの動作は異なります。たとえば、ループがあるたびにそれらを使用します。

361
Lennart Regebro

他の答えは単なる+が通常何をするのかを示す限り(つまり、1であれば数字をそのままにして)正解ですが、何が起こるのかを説明していない限りは不完全です。

正確に言うと、+xx.__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)
48
glglgl

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に変換する私のモジュールでこれらの関数を使用しました。

9
Piotr Dabkowski

Pythonでは、Common LISP、Scheme、Rubyなどの言語とは対照的に、式と文の間の区別は厳密に適用されます。

ウィキペディア

そのため、そのような演算子を導入することによって、式とステートメントの分割を中断することになります。

あなたが書くことができないのと同じ理由で

if x = 0:
  y = 1

そのような区別が保存されていない他のいくつかの言語であなたができるように。

9

ええ、私は++と - の機能性も逃しました。数百万行に及ぶ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

...そのTupleの中では、 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
4
fyngyrz

TL、DR

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

a += 1

詳細とゴチャ

しかし注意してください。 Cから来た人であれば、これでもpythonでは違います。 PythonがCのように「変数」を持っているのではなく、代わりにpythonは names および objects を使用し、pythonではintsは不変です。

だからあなたが言うと言うことができます

a = 1

これがpythonで意味することは、値1を持つタイプintのオブジェクトを作成し、それに名前aをバインドすることです。 object は、値1を持つintのインスタンスであり、 name aはそれを参照します。 aという名前とそれが参照するオブジェクトは区別されます。

今あなたがそう言う

a += 1

intsは不変なので、ここで起こることは以下の通りです:

  1. aが参照するオブジェクトを検索します(これは、IDが0x559239eeb380intです)。
  2. オブジェクト0x559239eeb380の値を調べます(それは1です)
  3. その値に1を加える(1 + 1 = 2)
  4. new intオブジェクトを値2で作成します(オブジェクトIDは0x559239eeb3a0です)
  5. この新しいオブジェクトにaという名前を付け直します
  6. aはオブジェクト0x559239eeb3a0を参照し、元のオブジェクト(0x559239eeb380)はaという名前で参照されなくなりました。元のオブジェクトを参照する名前が他にない場合は、後でガベージコレクションされます。

試してみてください。

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