Hành vi của toán tử tăng và giảm trong Python


798

Tôi nhận thấy rằng một toán tử tăng / giảm trước có thể được áp dụng trên một biến (như ++count). Nó biên dịch, nhưng nó không thực sự thay đổi giá trị của biến!

Hành vi của các toán tử tăng / giảm trước (++ / -) trong Python là gì?

Tại sao Python đi chệch khỏi hành vi của các toán tử này được thấy trong C / C ++?


19
Python không phải là C hay C ++. Các quyết định thiết kế khác nhau đã đi vào làm cho ngôn ngữ. Cụ thể, Python cố tình không định nghĩa các toán tử gán có thể được sử dụng trong một biểu thức tùy ý; thay vào đó, có các câu lệnh gán và câu lệnh gán tăng cường. Xem tài liệu tham khảo dưới đây.
Ned Deily

8
Điều gì khiến bạn nghĩ python có ++và các --nhà khai thác?
u0b34a0f6ae

29
Kaizer: Đến từ C / C ++, tôi viết số đếm ++ và nó biên dịch bằng Python. Vì vậy, tôi nghĩ rằng ngôn ngữ có các nhà khai thác.
Ashwin Nanjappa

3
@Fox Bạn đang giả định một mức độ lập kế hoạch và tổ chức không có bằng chứng
Cơ bản

4
@mehaase ++ và - không tồn tại trong c "như đường cú pháp cho số học con trỏ", chúng tồn tại bởi vì nhiều bộ xử lý có cơ chế truy cập bộ nhớ tăng và giảm tự động (trong lập chỉ mục con trỏ chung, lập chỉ mục ngăn xếp) như một phần của lệnh gốc bộ. Chẳng hạn, trong trình biên dịch chương trình 6809: sta x++... hướng dẫn nguyên tử lưu trữ bộ atích lũy nơi xchỉ trỏ, sau đó tăng xtheo kích thước của bộ tích. Điều này được thực hiện bởi vì nó nhanh hơn số học con trỏ, bởi vì nó rất phổ biến và bởi vì nó dễ hiểu. Cả trước và sau.
fyngyrz

Câu trả lời:


1059

++không phải là một nhà điều hành. Đó là hai +nhà khai thác. Các +nhà khai thác là bản sắc nhà điều hành, mà không làm gì. (Làm rõ: các toán tử đơn +-toán tử chỉ hoạt động trên các số, nhưng tôi cho rằng bạn sẽ không mong đợi một ++toán tử giả định hoạt động trên các chuỗi.)

++count

Phân tích như

+(+count)

Dịch ra

count

Bạn phải sử dụng +=toán tử dài hơn một chút để làm những gì bạn muốn làm:

count += 1

Tôi nghi ngờ ++và các --nhà khai thác đã bị bỏ rơi vì tính nhất quán và đơn giản. Tôi không biết lý lẽ chính xác mà Guido van Rossum đưa ra cho quyết định, nhưng tôi có thể tưởng tượng ra một vài lập luận:

  • Phân tích cú pháp đơn giản hơn. Về mặt kỹ thuật, phân tích cú pháp ++countlà mơ hồ, vì nó có thể là +, +, count(hai unary +nhà khai thác) chỉ dễ dàng như nó có thể là ++, count(một unary++ nhà điều hành). Nó không phải là một sự mơ hồ cú pháp đáng kể, nhưng nó tồn tại.
  • Ngôn ngữ đơn giản hơn. ++không có gì hơn một từ đồng nghĩa cho += 1. Đó là một tốc ký được phát minh vì trình biên dịch C là ngu ngốc và không biết cách tối ưu hóa a += 1vàoinc hướng dẫn mà hầu hết các máy tính có. Trong thời đại tối ưu hóa các trình biên dịch và các ngôn ngữ được dịch mã byte, việc thêm các toán tử vào một ngôn ngữ để cho phép các lập trình viên tối ưu hóa mã của họ thường được sử dụng, đặc biệt là trong một ngôn ngữ như Python được thiết kế phù hợp và dễ đọc.
  • Tác dụng phụ khó hiểu. Một lỗi người mới phổ biến trong các ngôn ngữ với các ++toán tử là trộn lẫn các khác biệt (cả về giá trị ưu tiên và giá trị trả về) giữa các toán tử tăng / giảm sau và giảm dần và Python thích loại bỏ ngôn ngữ "gotcha" -s. Các vấn đề ưu tiên của tiền tăng / hậu tăng trong C là khá nhiều lông và rất dễ gây rối.

13
"Toán tử + là toán tử" danh tính ", không làm gì cả." Chỉ dành cho các loại số; đối với loại khác, đó là một lỗi theo mặc định.
newacct

45
Ngoài ra, hãy lưu ý rằng, trong Python, + = và bạn bè không phải là toán tử có thể được sử dụng trong biểu thức. Thay vào đó, trong Python chúng được định nghĩa là một phần của "câu lệnh gán tăng cường". Điều này phù hợp với quyết định thiết kế ngôn ngữ trong Python khi không cho phép gán ("=") dưới dạng toán tử trong các biểu thức tùy ý, không giống như những gì người ta có thể làm trong C. Xem docs.python.org/reference/ trộm
Ned Deily

15
+Toán tử đơn nguyên có một công dụng. Đối với các đối tượng thập phân.Decimal, nó làm tròn đến độ chính xác hiện tại.
u0b34a0f6ae

21
Tôi đang đặt cược vào đơn giản hóa trình phân tích cú pháp. Lưu ý một mục trong PEP 3099 , "Những điều sẽ không thay đổi trong Python 3000": "Trình phân tích cú pháp sẽ không phức tạp hơn LL (1). Đơn giản là tốt hơn phức tạp. Ý tưởng này mở rộng cho trình phân tích cú pháp. Hạn chế ngữ pháp của Python một trình phân tích cú pháp LL (1) là một phước lành, không phải là một lời nguyền. Nó đặt chúng ta vào còng tay ngăn chúng ta vượt lên và kết thúc với các quy tắc ngữ pháp vui nhộn như một số ngôn ngữ động khác sẽ không được đặt tên, như Perl. " Tôi không thấy cách định hướng + +++không phá vỡ LL (1).
Mike DeSimone

7
Thật không đúng khi nói rằng ++không có gì khác hơn là một từ đồng nghĩa với += 1. Có các biến thể tăng trước và tăng sau của ++ nên rõ ràng nó không giống nhau. Tôi đồng ý với phần còn lại của bạn, mặc dù.
PhilHibbs

384

Khi bạn muốn tăng hoặc giảm, bạn thường muốn làm điều đó trên một số nguyên. Thích như vậy:

b++

Nhưng trong Python, số nguyên là bất biến . Đó là bạn không thể thay đổi chúng. Điều này là do các đối tượng số nguyên có thể được sử dụng dưới một số tên. Thử cái này:

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

a và b ở trên thực sự là cùng một đối tượng. Nếu bạn tăng a, bạn cũng sẽ tăng b. Đó không phải là những gì bạn muốn. Vì vậy, bạn phải phân công lại. Như thế này:

b = b + 1

Hoặc đơn giản hơn:

b += 1

Mà sẽ phân công lại bđến b+1. Đó không phải là toán tử gia tăng, vì nó không tăngb , nó gán lại nó.

Tóm lại: Python hành xử khác ở đây, vì nó không phải là C và không phải là trình bao bọc cấp thấp xung quanh mã máy, mà là ngôn ngữ động cấp cao, trong đó gia tăng không có ý nghĩa và cũng không cần thiết như trong C , nơi bạn sử dụng chúng mỗi khi bạn có một vòng lặp, ví dụ.


75
Ví dụ đó là sai (và có lẽ bạn nhầm lẫn tính bất biến với danh tính) - chúng có cùng id do một số tối ưu hóa vm sử dụng cùng một đối tượng cho các số cho đến 255 (hoặc đại loại như thế). Ví dụ: (số lớn hơn): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
ionelmc

56
Yêu cầu bất biến là giả mạo. Về mặt khái niệm, i++sẽ có nghĩa là gán i + 1cho biến i . i = 5; i++có nghĩa là gán 6cho i, không sửa đổi intđối tượng được trỏ bởi i. Đó là, nó không có nghĩa là tăng giá trị của5 !
Ốc cơ khí

3
@ Ốc cơ khí: Trong trường hợp đó, nó sẽ không phải là toán tử gia tăng. Và sau đó toán tử + = rõ ràng hơn, rõ ràng hơn, linh hoạt hơn và thực hiện điều tương tự.
Lennart Regebro

7
@LennartRegebro: Trong C ++ và Java, i++chỉ hoạt động theo giá trị. Nếu nó được dự định để tăng đối tượng được chỉ bởi i, hạn chế này sẽ không cần thiết.
Ốc cơ khí

4
Tôi thấy câu trả lời này khá khó hiểu. Tại sao bạn lại giả sử ++ có nghĩa là bất cứ điều gì khác ngoài cách viết tắt cho + = 1? Đó chính xác là ý nghĩa của nó trong C (giả sử giá trị trả về không được sử dụng). Bạn dường như đã kéo một số ý nghĩa khác ra khỏi không khí.
Don nở

52

Mặc dù các câu trả lời khác là chính xác cho đến khi chúng chỉ ra những gì mà một người +thường làm (cụ thể là để nguyên số, nếu nó là một), chúng không đầy đủ cho đến khi chúng không giải thích điều gì xảy ra.

Để được chính xác, +xđánh giá để x.__pos__()++xđể x.__pos__().__pos__().

Tôi có thể tưởng tượng một cấu trúc lớp kỳ lạ RẤT (Trẻ em, đừng làm điều này ở nhà!) Như thế này:

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 không có các toán tử này, nhưng nếu bạn thực sự cần chúng, bạn có thể viết một hàm có cùng chức năng.

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

Sử dụng:

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

Bên trong một hàm, bạn phải thêm locals () làm đối số thứ hai nếu bạn muốn thay đổi biến cục bộ, nếu không nó sẽ cố gắng thay đổi toàn cục.

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

Ngoài ra với các chức năng này bạn có thể làm:

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

Nhưng theo tôi cách tiếp cận sau thì rõ ràng hơn nhiều:

x = 1
x+=1
print(x)

Toán tử giảm dần:

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

Tôi đã sử dụng các chức năng này trong mô-đun dịch javascript sang python.


Lưu ý: mặc dù tuyệt vời, các phương thức trợ giúp này sẽ không hoạt động nếu địa phương của bạn tồn tại trên khung ngăn xếp chức năng lớp. tức là - gọi chúng từ bên trong một phương thức lớp def sẽ không hoạt động - dict 'locals ()' là một ảnh chụp nhanh và không cập nhật khung stack.
Adam

11

Trong Python, sự khác biệt giữa các biểu thức và câu lệnh được thi hành một cách cứng nhắc, trái ngược với các ngôn ngữ như Common Lisp, Scheme hoặc Ruby.

Wikipedia

Vì vậy, bằng cách giới thiệu các toán tử như vậy, bạn sẽ phá vỡ phân tách biểu thức / câu lệnh.

Vì lý do tương tự, bạn không thể viết

if x = 0:
  y = 1

như bạn có thể trong một số ngôn ngữ khác, nơi sự phân biệt như vậy không được bảo tồn.


Điều thú vị là hạn chế này sẽ được gỡ bỏ trong bản phát hành Python 3.8 sắp tới với cú pháp mới cho các biểu thức Chuyển nhượng (PEP-572 python.org/dev/peps/pep-0572 ). Chúng tôi sẽ có thể viết if (n := len(a)) > 10: y = n + 1ví dụ. Lưu ý rằng sự khác biệt là rõ ràng vì sự ra đời của một toán tử mới cho mục đích đó ( :=)
Zertrin

8

TL; DR

Python không có toán tử tăng / giảm đơn ( --/ ++). Thay vào đó, để tăng giá trị, hãy sử dụng

a += 1

Thêm chi tiết và vấn đề

Nhưng hãy cẩn thận ở đây. Nếu bạn đến từ C, thậm chí điều này khác với python. Python không có "biến" theo nghĩa C, thay vào đó python sử dụng tênđối tượng và trong python ints là bất biến.

vì vậy hãy nói rằng bạn làm

a = 1

Điều này có nghĩa là gì trong python là: tạo một đối tượng intcó kiểu có giá trị 1và liên kết tên avới nó. Đối tượng là một thể hiện của intviệc có giá trị 1tên a gọi nó. Tên avà đối tượng mà nó đề cập là khác biệt.

Bây giờ hãy nói rằng bạn làm

a += 1

ints là bất biến, nên những gì xảy ra ở đây như sau:

  1. tra cứu đối tượng ađề cập đến (nó là một intid 0x559239eeb380)
  2. tra cứu giá trị của đối tượng 0x559239eeb380(nó là 1)
  3. thêm 1 vào giá trị đó (1 + 1 = 2)
  4. tạo một đối tượng mới int có giá trị 2(nó có id đối tượng 0x559239eeb3a0)
  5. rebind tên acho đối tượng mới này
  6. Bây giờ ađề cập đến đối tượng 0x559239eeb3a0và đối tượng ban đầu ( 0x559239eeb380) không còn được gọi bằng tên a. Nếu không có bất kỳ tên nào khác đề cập đến đối tượng ban đầu, nó sẽ là rác được thu thập sau đó.

Hãy tự thử:

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

6

Vâng, tôi đã bỏ qua ++ và - chức năng là tốt. Một vài triệu dòng mã c khắc sâu suy nghĩ đó trong đầu cũ của tôi, và thay vì chiến đấu với nó ... Đây là một lớp học mà tôi đã tạo ra:

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

Đây là:

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

Bạn có thể sử dụng nó như thế này:

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

... đã có c, bạn có thể làm điều này ...

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

.... hoặc chỉ ...

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

... và cho (tái) gán thành số nguyên ...

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

... trong khi điều này sẽ duy trì c là bộ đếm loại:

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

BIÊN TẬP:

Và sau đó có một chút hành vi bất ngờ (và hoàn toàn không mong muốn) ,

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

... bởi vì bên trong bộ dữ liệu đó, getitem () không phải là thứ được sử dụng, thay vào đó, một tham chiếu đến đối tượng được chuyển đến hàm định dạng. Thở dài. Vì thế:

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

... Hoặc, rõ ràng hơn, và rõ ràng những gì chúng ta thực sự muốn xảy ra, mặc dù được chỉ định ngược lại ở dạng thực tế bởi tính dài dòng ( c.vthay vào đó sử dụng ) ...

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

2

Không có toán tử tăng / giảm bài / trước trong python như trong các ngôn ngữ như C.

Chúng ta có thể thấy ++hoặc --nhiều dấu hiệu được nhân lên, giống như chúng ta làm trong toán học (-1) * (-1) = (+1).

Ví dụ

---count

Phân tích như

-(-(-count)))

Dịch ra

-(+count)

Bởi vì, phép nhân của -dấu với -dấu là+

Và cuối cùng,

-count

1
Điều này nói gì mà những câu trả lời khác không có?
Daniel B.

@DanielB. Những câu trả lời khác chưa nói lên những gì xảy ra trong nội bộ. Và, họ cũng không nói điều gì sẽ xảy ra khi bạn viết -----count.
Anuj

Câu trả lời đầu tiên, được chấp nhận. ...
Daniel B.

2
Không có bất kỳ đề cập nào về việc nhân giống đang được thực hiện, vì vậy tôi nghĩ rằng một sự đồng ý và câu trả lời chính xác sẽ hữu ích cho người dùng đồng nghiệp. Không xúc phạm nếu bạn hiểu từ đó. Học tập quan trọng hơn nguồn mà bạn học từ đó.
Anuj

0

Trong python 3.8+ bạn có thể làm:

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

Bạn có thể làm rất nhiều suy nghĩ với điều này.

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


1
2
3
4

Hoặc nếu bạn muốn viết một cái gì đó với cú pháp tinh vi hơn (mục tiêu không phải là tối ưu hóa):

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


1
2
3
4

Nó cũng trả về 0 nếu không tồn tại mà không có lỗi, và sau đó sẽ đặt thành 1

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.