Zachowanie operatorów inkrementacji i dekrementacji w Pythonie

797

Zauważam, że operator zmiennej wstępnej / dekrementacji można zastosować do zmiennej (podobnej ++count). Kompiluje się, ale tak naprawdę nie zmienia wartości zmiennej!

Jakie jest zachowanie operatorów wstępnego zwiększania / zmniejszania (++ / -) w Pythonie?

Dlaczego Python odbiega od zachowania tych operatorów widocznych w C / C ++?

Ashwin Nanjappa
źródło
19
Python nie jest C ani C ++. Różne decyzje projektowe dotyczyły tworzenia języka. W szczególności Python celowo nie definiuje operatorów przypisania, których można użyć w dowolnym wyrażeniu; istnieją raczej instrukcje przypisania i rozszerzone instrukcje przypisania. Zobacz odnośnik poniżej.
Ned Deily,
8
Co sprawia, że ​​myślisz, że Python miał ++i --operatorów?
u0b34a0f6ae
29
Kaizer: Pochodzę z C / C ++, piszę ++ count i kompiluje się w Pythonie. Więc myślałem, że język ma operatorów.
Ashwin Nanjappa
3
@ Fox Zakładasz, że poziom planowania i organizacji nie jest widoczny
Podstawowy
4
@mehaase ++ i - nie istnieją w c "jako cukier syntaktyczny dla arytmetyki wskaźników", istnieją, ponieważ wiele procesorów ma automatyczne mechanizmy dostępu do pamięci inkrementacji i dekrementacji (ogólnie indeksowanie wskaźników, indeksowanie stosu) jako część natywnej instrukcji zestaw. Na przykład w asemblerze 6809: sta x++... wynikająca z niej instrukcja atomowa przechowuje aakumulator tam, gdzie xwskazuje, a następnie zwiększa xrozmiar akumulatora. Odbywa się to, ponieważ jest szybsze niż arytmetyka wskaźników, ponieważ jest bardzo powszechna i ponieważ jest łatwa do zrozumienia. Zarówno przed, jak i po.
fyngyrz

Odpowiedzi:

1059

++nie jest operatorem. To dwóch +operatorów. +Operator jest tożsamość operatora, który nie robi nic. (Wyjaśnienie: +i -operatorzy unarne działać tylko na liczbach, ale przypuszczam, że nie można spodziewać się hipotetyczny ++. Operatora do pracy na smyczki)

++count

Parses as

+(+count)

Co przekłada się na

count

Musisz użyć nieco dłuższego +=operatora, aby zrobić to, co chcesz:

count += 1

Podejrzewam, że ++i --operatorzy zostali pominięci ze względu na spójność i prostotę. Nie znam dokładnego argumentu Guido van Rossuma dotyczącego decyzji, ale mogę sobie wyobrazić kilka argumentów:

  • Prostsze parsowanie. Technicznie, analizowania ++countjest niejednoznaczny, jak mogłaby być +, +, count(dwa unarne +operatorzy) równie łatwo, jak to może być ++, count(jeden jednoargumentowy ++operator). Nie jest to znacząca dwuznaczność składniowa, ale istnieje.
  • Prostszy język. ++jest niczym więcej niż synonimem += 1. To była skróconą wymyślił ponieważ kompilatory C były głupie i nie wiedział, w jaki sposób zoptymalizować a += 1do incdyspozycji większość komputerów ma. W dzisiejszym dniu optymalizacji kompilatorów i języków interpretowanych za pomocą kodu bajtowego, dodawanie operatorów do języka, aby umożliwić programistom optymalizację kodu, jest zwykle lekceważone, szczególnie w języku takim jak Python, który ma być spójny i czytelny.
  • Mylące skutki uboczne. Jednym z powszechnych błędów początkujących w językach z ++operatorami jest mieszanie różnic (zarówno w wartości pierwszeństwa, jak i wartości zwracanej) między operatorami zwiększania / zmniejszania przed i po zwiększaniu, a Python lubi eliminować język „gotcha” -s. Te kwestie pierwszeństwa z pre- / post-przyrostu C są dość owłosione i niezwykle łatwe do bałagan.
Chris Lutz
źródło
13
„Operator + jest operatorem„ tożsamości ”, który nic nie robi.” Tylko dla typów numerycznych; dla innego typu jest to domyślnie błąd.
newacct
45
Pamiętaj też, że w Pythonie + = i przyjaciele nie są operatorami, których można używać w wyrażeniach. W Pythonie są one raczej zdefiniowane jako część „rozszerzonej instrukcji przypisania”. Jest to zgodne z decyzją dotyczącą projektowania języka w Pythonie, aby nie zezwalać na przypisywanie („=”) jako operatora w dowolnych wyrażeniach, w przeciwieństwie do tego, co można zrobić w C. Zobacz docs.python.org/reference/…
Ned Deily,
15
Jednoargumentowy +operator ma zastosowanie. W przypadku obiektów dziesiętnych. Dziesiętne zaokrągla do bieżącej precyzji.
u0b34a0f6ae
21
Stawiam na uproszczenie parsera. Zwróć uwagę na pozycję w PEP 3099 : „Rzeczy, które nie zmienią się w Pythonie 3000”: „Analizator składni nie będzie bardziej złożony niż LL (1). Prosty jest lepszy niż złożony. Ta idea rozciąga się na analizator składni. Ograniczenie gramatyki Pythona do parser LL (1) jest błogosławieństwem, a nie przekleństwem. Kładzie nas w kajdankach, które uniemożliwiają nam wypadnięcie za burtę i skończy się na funky regułach gramatycznych, takich jak niektóre inne dynamiczne języki, które pozostaną bezimienne, takie jak Perl. ” Nie widzę sposobu na jednoznaczne + +i ++bez zerwania LL (1).
Mike DeSimone
7
Nie jest poprawne stwierdzenie, że ++to nic innego jak synonim += 1. Istnieją warianty ++ przed i po kroku, więc nie jest to oczywiście to samo. Zgadzam się jednak z resztą twoich uwag.
PhilHibbs
384

Kiedy chcesz zwiększyć lub zmniejszyć, zwykle chcesz to zrobić na liczbach całkowitych. Tak jak:

b++

Ale w Pythonie liczby całkowite są niezmienne . To znaczy, że nie możesz ich zmienić. Wynika to z faktu, że obiektów liczb całkowitych można używać pod kilkoma nazwami. Spróbuj tego:

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

a i b powyżej są w rzeczywistości tym samym obiektem. Jeśli zwiększysz wartość a, zwiększysz również wartość b. Nie tego chcesz. Więc musisz zmienić przypisanie. Lubię to:

b = b + 1

Lub prościej:

b += 1

Który przeniesie się bdo b+1. To nie jest operator inkrementacji, ponieważ nie inkrementuje b, przypisuje go ponownie.

W skrócie: Python zachowuje się tutaj inaczej, ponieważ nie jest to C i nie jest opakowaniem niskiego poziomu wokół kodu maszynowego, ale dynamicznym językiem wysokiego poziomu, w którym przyrosty nie mają sensu, a także nie są tak konieczne, jak w C , gdzie używasz ich na przykład za każdym razem, gdy masz pętlę.

Lennart Regebro
źródło
75
Ten przykład jest zły (i prawdopodobnie mylisz niezmienność z tożsamością) - mają one ten sam identyfikator z powodu pewnej optymalizacji vm, która używa tych samych obiektów dla liczb do 255 (lub coś w tym rodzaju). Np. (Większe liczby): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
ionelmc
56
Twierdzenie o niezmienności jest fałszywe. Koncepcyjnie i++oznaczałoby przypisanie i + 1do zmiennej i . i = 5; i++środki, aby przypisać 6do inie modyfikować intobiekt wskazywany przez i. Oznacza to, że nie oznacza to zwiększenia wartości5 !
Mechaniczny ślimak
3
@ Ślimak mechaniczny: w takim przypadku nie byłyby to wcale operatory przyrostowe. A potem operator + = jest wyraźniejszy, bardziej wyraźny, bardziej elastyczny i robi to samo.
Lennart Regebro,
7
@LennartRegebro: W C ++ i Javie i++działa tylko na wartościach lv. Jeśli imiałoby to na celu zwiększenie wskazanego obiektu , to ograniczenie byłoby niepotrzebne.
Mechaniczny ślimak
4
Uważam tę odpowiedź za dość zaskakującą. Dlaczego zakładasz, że ++ znaczyłoby cokolwiek innego niż skrót dla + = 1? Właśnie to oznacza w C (zakładając, że wartość zwracana nie jest używana). Wygląda na to, że wyciągnąłeś z powietrza jakieś inne znaczenie.
Don Hatch
52

Podczas gdy inne odpowiedzi są poprawne, o ile pokazują, co +zwykle robi (a mianowicie pozostaw liczbę taką, jaka jest, jeśli jest jedna), ale są niekompletne, o ile nie wyjaśniają, co się dzieje.

Dokładniej, +xocenia do x.__pos__()i ++xdo x.__pos__().__pos__().

Mogę sobie wyobrazić BARDZO dziwną strukturę klas (dzieci, nie róbcie tego w domu!) W następujący sposób:

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)
glglgl
źródło
13

Python nie ma tych operatorów, ale jeśli naprawdę ich potrzebujesz, możesz napisać funkcję o tej samej funkcjonalności.

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

Stosowanie:

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

Wewnątrz funkcji musisz dodać locals () jako drugi argument, jeśli chcesz zmienić zmienną lokalną, inaczej spróbuje zmienić globalną.

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()

Również dzięki tym funkcjom możesz:

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

Ale moim zdaniem następujące podejście jest znacznie jaśniejsze:

x = 1
x+=1
print(x)

Operatory dekrementacji:

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

Użyłem tych funkcji w moim module tłumaczącym javascript na python.

Piotr Dabkowski
źródło
Uwaga: chociaż świetne, te metody pomocnicze nie będą działać, jeśli twoje locale istnieją na ramce stosu funkcji klasy. tzn. - wywołanie ich z metody klasy def nie będzie działać - dyktat „locals ()” jest migawką i nie aktualizuje ramki stosu.
Adam
11

W Pythonie rozróżnienie między wyrażeniami i wyrażeniami jest ściśle egzekwowane, w przeciwieństwie do języków takich jak Common Lisp, Scheme lub Ruby.

Wikipedia

Tak więc, wprowadzając takie operatory, zerwałbyś podział wyrażenia / instrukcji.

Z tego samego powodu nie możesz pisać

if x = 0:
  y = 1

jak w niektórych innych językach, w których takie rozróżnienie nie jest zachowane.

Vitalii Fedorenko
źródło
Co ciekawe, ograniczenie to zostanie zniesione w nadchodzącym wydaniu Python 3.8 dzięki nowej składni wyrażeń Assignment (PEP-572 python.org/dev/peps/pep-0572 ). Będziemy mogli if (n := len(a)) > 10: y = n + 1na przykład pisać . Zauważ, że rozróżnienie jest wyraźne ze względu na wprowadzenie nowego operatora do tego celu ( :=)
Zertrin
8

TL; DR

Python nie ma jednoargumentowych operatorów inkrementacji / dekrementacji ( --/ ++). Zamiast tego, aby zwiększyć wartość, użyj

a += 1

Więcej szczegółów i gotchas

Ale bądź ostrożny tutaj. Jeśli pochodzisz z C, nawet w Pythonie jest inaczej. Python nie ma „zmiennych” w tym sensie, co C, zamiast tego python używa nazw i obiektów , aw Pythonie intsą niezmienne.

więc powiedzmy, że tak

a = 1

W pythonie oznacza to: utwórz obiekt typu intmający wartość 1i powiąż z nim nazwę a. Obiekt jest przykładem intposiadającego wartość 1, a nazwa a odnosi się do niego. Nazwa ai obiekt, do którego się odnosi, są różne.

Teraz powiedzmy, że tak

a += 1

Ponieważ ints są niezmienne, dzieje się to w następujący sposób:

  1. wyszukaj obiekt, ado którego się odnosi (jest to intidentyfikator z identyfikatorem 0x559239eeb380)
  2. sprawdź wartość obiektu 0x559239eeb380(to jest 1)
  3. dodaj 1 do tej wartości (1 + 1 = 2)
  4. utwórz nowy int obiekt o wartości 2(ma identyfikator obiektu 0x559239eeb3a0)
  5. powiąż nazwę az tym nowym obiektem
  6. Teraz aodnosi się do obiektu, 0x559239eeb3a0a 0x559239eeb380nazwa obiektu nie odwołuje się już do pierwotnego obiektu ( ) a. Jeśli nie ma żadnych innych nazw odnoszących się do oryginalnego obiektu, zostanie on później wyrzucony.

Spróbuj sam:

a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))
RBF06
źródło
6

Tak, brakowało mi także ++ i - funkcjonalności. Kilka milionów wierszy kodu c zapuściło ten sposób myślenia w mojej starej głowie i zamiast z nim walczyć ... Oto klasa, którą opracowałem:

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

Oto:

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

Możesz użyć tego w następujący sposób:

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

... już mając c, możesz to zrobić ...

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

....Lub tylko...

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

... i dla (ponownego) przypisania do liczby całkowitej ...

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

... podczas gdy to zachowa c jako licznik typów:

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

EDYTOWAĆ:

A potem jest trochę niespodziewanego (i całkowicie niechcianego) zachowania ,

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

... ponieważ wewnątrz tej krotki nie jest używane getitem (), zamiast tego do funkcji formatowania przekazywane jest odwołanie do obiektu. Westchnienie. Więc:

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

... lub, bardziej szczegółowo, i wprost to, co naprawdę chcieliśmy się wydarzyć, chociaż przeciwwskazane w rzeczywistej formie przez gadatliwość (użyj c.vzamiast tego) ...

c = counter(42)
s = '%s: %d' % ('Expecting 42',c.__getitem__()) # and getting 42.
print s
Fyngyrz
źródło
2

W Pythonie nie ma operatorów post / wstępnej inkrementacji / dekrementacji, jak w językach takich jak C.

Widzimy ++lub --mnożymy wiele znaków, tak jak robimy to w matematyce (-1) * (-1) = (+1).

Na przykład

---count

Parses as

-(-(-count)))

Co przekłada się na

-(+count)

Ponieważ mnożenie -znaku przez -znak wynosi+

I w końcu,

-count
Anuj
źródło
1
Co to znaczy, że inne odpowiedzi nie?
Daniel B.
@DanielB. Inne odpowiedzi nie mówiły, co dzieje się wewnętrznie. I nie powiedzieli też, co się stanie, kiedy napiszesz -----count.
Anuj
Pierwsza zaakceptowana odpowiedź tak. ...
Daniel B.
1
Nie ma żadnej wzmianki o tym, że mnożenie jest przeprowadzane, więc pomyślałem, że konsul i do tego stopnia, że ​​odpowiedź będzie przydatna dla innych użytkowników. Bez obrazy, jeśli zrozumiałeś z tego. Uczenie się jest ważniejsze niż źródło, z którego się uczysz.
Anuj
0

W Pythonie 3.8+ możesz:

(a:=a+1) #same as a++

Dzięki temu możesz wiele przemyśleć.

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


1
2
3
4

Lub jeśli chcesz napisać coś z bardziej wyrafinowaną składnią (celem nie jest optymalizacja):

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


1
2
3
4

Dobrze zwraca 0, jeśli nie istnieje bez błędów, a następnie ustawi ją na 1

Henz
źródło