Поведінка операторів приросту та зменшення в Python


797

Я помічаю, що оператор попереднього збільшення / зменшення може бути застосований до змінної (як ++count). Він компілюється, але фактично не змінює значення змінної!

Яка поведінка операторів попереднього збільшення / зменшення (++ / -) у Python?

Чому Python відхиляється від поведінки цих операторів, що спостерігається на C / C ++?


19
Python не є C або C ++. Різні дизайнерські рішення перейшли до прийняття мови. Зокрема, Python свідомо не визначає операторів присвоєння, які можна використовувати в довільному виразі; скоріше, є заяви про присвоєння та доповнені заяви про призначення. Дивіться посилання нижче.
Нед Дейлі

8
Що змусило вас подумати, що пітон ++і --оператори?
u0b34a0f6ae

29
Kaizer: Починаючи з C / C ++, я записую підрахунок ++, і він збирається в Python. Отже, я думав, що мова має операторів.
Ешвін Нанджаппа,

3
@Fox Ви припускаєте, що рівень планування та організації не підтверджується
Basic

4
@mehaase ++ і - не існують в c "як синтаксичний цукор для арифметики вказівника", вони існують тому, що багато процесорів мають автоматичні механізми доступу до пам'яті збільшення та зменшення (загалом індексація покажчиків, індексація стека) як частина їх рідної інструкції набір. Наприклад, в асемблері 6809: sta x++... атомна інструкція, в результаті якої зберігається aакумулятор, де xвказується, а потім з кроком xна розмір акумулятора. Це робиться тому, що це швидше арифметики вказівника, тому що це дуже часто і тому, що це легко зрозуміти. Як до, так і після.
fyngyrz

Відповіді:


1059

++не є оператором. Це два +оператори. +Оператор є особистість оператора, який нічого не робить. (Пояснення: оператори +і -одинарні працюють лише над числами, але я припускаю, що ви не очікуєте, що гіпотетичний ++оператор працює над рядками.)

++count

Розбирає як

+(+count)

Що перекладається на

count

Вам потрібно використовувати трохи довший +=оператор, щоб робити те, що ви хочете зробити:

count += 1

Я підозрюю , що ++і --оператори були опущені для забезпечення узгодженості та простоти. Я не знаю точного аргументу, який дав рішення Гідо ван Россум, але я можу уявити кілька аргументів:

  • Простіший аналіз. З технічної точки зору синтаксичного аналізу ++countнеоднозначно, так як це може бути +, +, count(дві унарні +оператори) так само легко , як це може бути ++, count(один унарний ++оператор). Це не значна синтаксична неоднозначність, але вона існує.
  • Простіша мова. ++є не що інше, як синонім += 1. Це скорочена винайдені тому , що компілятори були дурні і не знають , як оптимізувати a += 1в incінструкцію більшість комп'ютерів мають. У цей день оптимізація компіляторів та інтерпретованих байт-кодів, додавання операторів до мови, що дозволяє програмістам оптимізувати свій код, як правило, нахмурюється, особливо на такій мові, як Python, яка призначена бути послідовною та читаною.
  • Заплутані побічні ефекти. Однією поширеною помилкою новачка в мовах з ++операторами є змішування різниць (як в пріоритеті, так і в зворотному значенні) між операторами до і після збільшення / зменшення, і Python любить ліквідувати мовні "gotcha" -s. В питання старшинства по попередньої / пост-інкремент в C досить волосатий, і неймовірно легко зіпсувати.

13
"Оператор + - це оператор" ідентичності ", який нічого не робить". Тільки для числових типів; для іншого типу - це помилка за замовчуванням.
newacct

45
Також майте на увазі, що в Python + = і друзі не є операторами, які можна використовувати в виразах. Швидше, в Python вони визначаються як частина "доповненого заяви про призначення". Це узгоджується з рішенням мовного дизайну в Python, щоб не допускати присвоєння ("=") оператору в довільних виразах, на відміну від того, що можна зробити в C. Див. Docs.python.org/reference/…
Ned Deily

15
Одинарний +оператор має використання. Для десяткових десяткових об'єктів він округляється до поточної точності.
u0b34a0f6ae

21
Я роблю ставку на спрощення парсеру. Зауважте елемент PEP 3099 , "Речі, які не зміняться в Python 3000": "Аналізатор не буде складнішим за LL (1). Простий краще, ніж складний. Ця ідея поширюється на аналізатор. Обмеження граматики Python до аналізатор LL (1) - це благо, а не прокляття. Це ставить нас у наручники, які заважають нам переходити за борт і закінчувати нескінченними граматичними правилами, як деякі інші динамічні мови, які будуть без назви, наприклад, Perl. " Я не бачу, як розібратись + +і ++не порушити LL (1).
Майк Десімоне

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 + 1

Або простіше:

b += 1

Що буде перепризначити bв b+1. Це не приріст оператора, тому що він не збільшується b, він перепризначає його.

Коротше кажучи: Python поводиться тут по-різному, тому що це не C, і це не низький рівень обгортки навколо машинного коду, а динамічна мова високого рівня, де прирости не мають сенсу, а також не такі необхідні, як у C , де ви використовуєте їх щоразу, коли у вас є цикл, наприклад.


75
Цей приклад помилковий (і ви, мабуть, плутаєте незмінність з ідентичністю) - вони мають однаковий ідентифікатор через деяку оптимізацію vm, яка використовує ті самі об'єкти для чисел до 255 (або щось подібне). Напр. (Більша кількість): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
ionelmc

56
Заява про незмінність є хибною. Концептуально, i++означало б призначити i + 1на змінну i . i = 5; i++засіб для призначення 6на i, не зраджуємо intоб'єкт , на який вказує i. Тобто це не означає збільшення вартості5 !
Механічний равлик

3
@ Механічний равлик: у такому випадку це зовсім не буде операторами приросту. І тоді оператор + = чіткіший, явніший, гнучкіший і все одно робить те саме.
Леннарт Регебро

7
@LennartRegebro: У C ++ та Java i++працює лише на значеннях. Якби воно було призначене для збільшення об'єкта, на який вказує i, це обмеження було б зайвим.
Механічний равлик

4
Я вважаю цю відповідь досить збитковою. Чому ви припускаєте, що ++ означатиме щось, окрім скорочення для + = 1? Це саме те, що це означає в C (якщо при цьому повернене значення не використовується). Ви, здається, витягли з повітря якесь інше значення.
Дон Хетч

52

Хоча відповіді інших є правильними, оскільки вони показують, що +зазвичай робить звичайний (а саме, залиште число таким, яким воно є, якщо воно є одне), вони неповні, оскільки вони не пояснюють, що відбувається.

Якщо бути точним, +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)

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

Всередині функції ви повинні додати localals () як другий аргумент, якщо ви хочете змінити локальну змінну, інакше вона спробує змінити глобальну.

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 з новим синтаксисом для виразів Assignment (PEP-572 python.org/dev/peps/pep-0572 ). Ми зможемо написати, if (n := len(a)) > 10: y = n + 1наприклад. Зауважимо, що відмінність зрозуміла через запровадження нового оператора для цієї мети ( :=)
Zertrin

8

TL; DR

Python не має операторів унарного збільшення / зменшення ( --/ ++). Натомість для збільшення значення використовуйте

a += 1

Більш докладно та ґетчі

Але будьте обережні тут. Якщо ви родом із С, навіть у пітоні це відрізняється. У Python немає "змінних" у тому сенсі, що і C, замість цього python використовує імена та об'єкти , а в python ints незмінні.

тож скажемо, що ви робите

a = 1

Що це означає в python, це: створити об'єкт типу, що intмає значення, 1і прив’язати до нього ім'я a. Об'єкт є екземпляром , intякий має значення 1, а назва a відноситься до нього. Назва aта об'єкт, на який він посилається, відрізняються.

Тепер скажемо, що ви робите

a += 1

Оскільки ints незмінні, то тут відбувається таке:

  1. шукати об’єкт, на який aпосилається (це intідентифікатор 0x559239eeb380)
  2. шукати значення об'єкта 0x559239eeb380(воно є 1)
  3. до цього значення додайте 1 (1 + 1 = 2)
  4. створити новий int об'єкт зі значенням 2(він має ідентифікатор об'єкта 0x559239eeb3a0)
  5. відновити ім'я aдо цього нового об’єкта
  6. Тепер aпосилається на об'єкт, 0x559239eeb3a0і вихідний об'єкт ( 0x559239eeb380) більше не посилається на ім'я a. Якщо немає інших імен, що стосуються оригінального об'єкта, сміття збирається пізніше.

Спробуйте самі:

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

6

Так, я також пропустив ++ і - функціональність. Кілька мільйонів рядків коду с вбудували таке мислення в мою стару голову, а не боролися з нею ... Ось клас, який я змайстрував, реалізує:

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

Немає операторів після / попереднього збільшення / зменшення в python, як у мовах, таких як C.

Ми можемо бачити ++або --як множинні знаки, що розмножуються, як це робимо в математиці (-1) * (-1) = (+1).

Напр

---count

Розбирає як

-(-(-count)))

Що перекладається на

-(+count)

Тому що множення -знака на -знак є+

І, нарешті,

-count

1
Що це говорить про те, що інші відповіді не відповідають?
Даніель Б.

@DanielB. Інші відповіді не розповіли, що відбувається всередині країни. І ні вони не сказали, що буде, коли ти напишеш -----count.
Ануй

Перша, прийнята відповідь робить. ...
Даніель Б.

1
Немає жодної згадки про те, що проводиться множення, тому я вважав, що сумніви і відповідь до точки зору будуть корисними для інших користувачів. Без образи, якщо ви з цього зрозуміли. Навчання важливіше, ніж джерело, з якого ви навчаєтесь.
Ануй

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.