พฤติกรรมของตัวดำเนินการที่เพิ่มขึ้นและลดลงใน Python


797

ฉันสังเกตเห็นว่าโอเปอเรเตอร์การเพิ่ม / ลดลงล่วงหน้าสามารถใช้กับตัวแปร (เช่น++count) มันรวบรวม แต่มันไม่ได้เปลี่ยนค่าของตัวแปรจริง ๆ !

พฤติกรรมของตัวดำเนินการเพิ่ม / ลดล่วงหน้า (++ / -) ใน Python คืออะไร

ทำไมงูหลามเบี่ยงเบนไปจากพฤติกรรมของผู้ประกอบการเหล่านี้เห็นใน C / C ++?


19
Python ไม่ใช่ C หรือ C ++ การตัดสินใจด้านการออกแบบที่แตกต่างกันทำให้เกิดภาษา โดยเฉพาะอย่างยิ่ง Python จงใจไม่กำหนดผู้ประกอบการที่ได้รับมอบหมายที่สามารถใช้ในการแสดงออกโดยพลการ; ค่อนข้างมีคำสั่งที่ได้รับมอบหมายและเพิ่มคำสั่งที่ได้รับมอบหมาย ดูการอ้างอิงด้านล่าง
เน็ด Deily

8
อะไรที่ทำให้คุณคิดว่าหลามมีตัวดำเนินการ++และ--ตัวดำเนินการอยู่
u0b34a0f6ae

29
Kaizer: มาจาก C / C ++, ฉันเขียน ++ นับและมันคอมไพล์ใน Python ดังนั้นฉันคิดว่าภาษามีตัวดำเนินการ
Ashwin Nanjappa

3
@ Fox คุณกำลังสมมติว่าระดับของการวางแผนและองค์กรไม่ได้อยู่ในหลักฐาน
พื้นฐาน

4
@mehaase ++ และ - ไม่มีอยู่ใน c "เป็น syntactic sugar สำหรับตัวชี้เลขคณิต" เนื่องจากมีโปรเซสเซอร์จำนวนมากที่มีกลไกการเข้าถึงหน่วยความจำที่เพิ่มขึ้นและลดลงโดยอัตโนมัติ (ในการทำดัชนีตัวชี้ทั่วไป, การจัดทำดัชนีสแต็ค) ชุด ตัวอย่างเช่นในแอสเซมเบลอร์ 6809: sta x++... คำสั่งปรมาณูที่เก็บผลลัพธ์ตัวaสะสมที่xชี้แล้วเพิ่มขึ้นxตามขนาดของตัวสะสม สิ่งนี้ทำเพราะมันเร็วกว่าเลขคณิตของตัวชี้เพราะมันเป็นเรื่องธรรมดามากและเพราะมันเข้าใจง่าย ทั้งก่อนและหลัง
fyngyrz

คำตอบ:


1059

++ไม่ใช่ผู้ประกอบการ มันเป็นสอง+ผู้ประกอบการ +ประกอบเป็นตัวตนของผู้ประกอบการที่ไม่ทำอะไรเลย (ความชัดเจน: ตัวดำเนินการ+และตัวอักษร-unary จะทำงานกับตัวเลขเท่านั้น แต่ฉันคิดว่าคุณจะไม่คาดหวังว่าตัว++ดำเนินการสมมุติจะทำงานบนสตริง)

++count

แยกวิเคราะห์เป็น

+(+count)

ซึ่งแปลว่า

count

คุณต้องใช้ตัว+=ดำเนินการที่ยาวขึ้นเล็กน้อยเพื่อทำสิ่งที่คุณต้องการ:

count += 1

ฉันสงสัยว่าผู้ปฏิบัติงาน++และ--ผู้ใช้งานถูกทิ้งไว้เพื่อความมั่นคงและเรียบง่าย ฉันไม่ทราบว่าข้อโต้แย้งที่แน่นอน Guido van Rossum ให้การตัดสินใจ แต่ฉันสามารถจินตนาการถึงข้อโต้แย้งบางอย่าง:

  • การแยกวิเคราะห์ที่ง่ายขึ้น เทคนิคการแยก++countเป็นคลุมเครือเท่าที่ควร+, +, count(สองเอก+ผู้ประกอบการ) ได้อย่างง่ายดายเพียงเท่าที่ควร++, count(หนึ่งเอก++ผู้ดำเนินการ) มันไม่ได้เป็นความคลุมเครือทางไวยากรณ์ที่สำคัญ แต่ก็มีอยู่
  • ภาษาที่เรียบง่าย เป็นอะไรมากไปกว่าคำพ้องความหมายสำหรับ++ += 1มันเป็นชวเลขที่คิดค้นขึ้นเพราะคอมไพเลอร์ C นั้นโง่และไม่รู้วิธีเพิ่มประสิทธิภาพa += 1ในการincสอนที่คอมพิวเตอร์ส่วนใหญ่มี ในวันนี้การเพิ่มประสิทธิภาพคอมไพเลอร์และการแปลภาษา bytecode การเพิ่มโอเปอเรเตอร์ในภาษาเพื่อให้โปรแกรมเมอร์เพิ่มประสิทธิภาพโค้ดของพวกเขามักจะขมวดคิ้วโดยเฉพาะอย่างยิ่งในภาษาอย่าง Python ที่ออกแบบมาให้สอดคล้องและอ่านได้
  • ผลข้างเคียงที่สับสน ข้อผิดพลาดทั่วไปของ newbie หนึ่งในภาษาที่มี++โอเปอเรเตอร์กำลังผสมความแตกต่าง (ทั้งก่อนหน้าและค่าตอบแทน) ระหว่างโอเปอเรเตอร์ pre- และ post-increment / decrement และ Python ชอบที่จะกำจัดภาษา "gotcha" -s ประเด็นความสำคัญของก่อน / หลังเพิ่มขึ้นใน Cมีขนสวยและง่ายอย่างเหลือเชื่อที่จะเลอะ

13
"ตัวดำเนินการ + คือตัวดำเนินการ" ตัวตน "ซึ่งไม่ทำอะไรเลย" สำหรับประเภทตัวเลขเท่านั้น สำหรับประเภทอื่น ๆ มันเป็นข้อผิดพลาดโดยค่าเริ่มต้น
newacct

45
นอกจากนี้โปรดทราบว่าใน Python + + และเพื่อนไม่ใช่ตัวดำเนินการที่สามารถใช้ในนิพจน์ได้ ค่อนข้างในงูหลามพวกเขาถูกกำหนดเป็นส่วนหนึ่งของ "คำสั่งการกำหนดเพิ่มเติม" สิ่งนี้สอดคล้องกับการตัดสินใจออกแบบภาษาใน Python เพื่อไม่อนุญาตให้มีการมอบหมาย ("=") ในฐานะผู้ดำเนินการภายในนิพจน์ตามอำเภอใจซึ่งแตกต่างจากสิ่งที่สามารถทำได้ใน C. ดูdocs.python.org/reference/ …
Ned Deily

15
+ผู้ประกอบการเอกมีการใช้งาน สำหรับทศนิยมวัตถุเล็ก ๆ มันจะปัดเศษเป็นความแม่นยำในปัจจุบัน
u0b34a0f6ae

21
ฉันเดิมพันในการแยกวิเคราะห์ง่าย บันทึกรายการในPEP 3099 "สิ่งที่จะไม่เปลี่ยนแปลงใน Python 3000": "parser จะไม่ซับซ้อนกว่า LL (1) ง่ายกว่าดีกว่าซับซ้อนแนวคิดนี้รวมถึง parser การ จำกัด ไวยากรณ์ของ Python ไปที่ ตัวแยกวิเคราะห์ LL (1) เป็นคำอวยพรไม่ใช่คำสาปมันทำให้เราใส่กุญแจมือที่ป้องกันไม่ให้เราไปลงน้ำและจบลงด้วยกฎไวยากรณ์ที่ขี้ขลาดเหมือนภาษาไดนามิกอื่น ๆ ที่จะไม่มีชื่อเช่น Perl " ฉันไม่เห็นวิธีที่จะทำให้เข้าใจผิด+ +และ++ไม่ทำผิดกฎหมาย LL (1)
Mike DeSimone

7
มันไม่ถูกต้องที่จะบอกว่าเป็นอะไรมากไปกว่าคำพ้องความหมายสำหรับ++ += 1มีตัวแปรเพิ่มล่วงหน้าและส่วนเพิ่มหลังของ ++ ดังนั้นจึงไม่ชัดเจนในสิ่งเดียวกัน ฉันเห็นด้วยกับคะแนนที่เหลือของคุณ
PhilHibbs

384

เมื่อคุณต้องการที่จะเพิ่มหรือลดลงคุณมักจะต้องการที่จะทำเช่นนั้นในจำนวนเต็ม ชอบมาก

b++

แต่ในหลามจำนวนเต็มมีไม่เปลี่ยนรูป นั่นคือคุณไม่สามารถเปลี่ยนแปลงได้ นี่คือเนื่องจากวัตถุจำนวนเต็มสามารถใช้ภายใต้ชื่อต่าง ๆ ลองสิ่งนี้:

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

a และ b ด้านบนเป็นวัตถุเดียวกันจริง ๆ หากคุณเพิ่มค่า a คุณจะเพิ่มค่า b นั่นไม่ใช่สิ่งที่คุณต้องการ ดังนั้นคุณต้องกำหนดใหม่ แบบนี้:

b = b + 1

หรือง่ายกว่า:

b += 1

ซึ่งจะโอนไปb b+1นั่นไม่ใช่ตัวดำเนินการที่เพิ่มขึ้นเพราะมันไม่ได้เพิ่มขึ้นbมันกำหนดใหม่

กล่าวโดยย่อ: Python ทำงานแตกต่างกันที่นี่เนื่องจากไม่ใช่ C และไม่ใช่ wrapper ระดับต่ำรอบรหัสเครื่อง แต่เป็นภาษาไดนามิกระดับสูงที่เพิ่มขึ้นไม่สมเหตุสมผลและไม่จำเป็นเท่า C ตัวอย่างเช่นคุณใช้ที่ใดเมื่อมีการวนซ้ำ


75
ตัวอย่างนั้นเป็นสิ่งที่ผิด (และคุณอาจสับสนกับความไม่สามารถเปลี่ยนแปลงได้กับตัวตน) - พวกเขามี ID เดียวกันเนื่องจากการเพิ่มประสิทธิภาพ vm บางอย่างที่ใช้วัตถุเดียวกันสำหรับตัวเลขจนถึง 255 (หรืออะไรทำนองนั้น) เช่น (ตัวเลขที่ใหญ่กว่า): >>> a = 1231231231231 >>> b = 1231231231231 >>> id (a), id (b) (32171144, 32171168)
ionelmc

56
การเรียกร้องที่ไม่สามารถเปลี่ยนแปลงได้นั้นเป็นของปลอม แนวคิดi++จะหมายถึงการที่จะกำหนดให้i + 1กับตัวแปร หมายถึงการกำหนดที่จะไม่ปรับเปลี่ยนวัตถุชี้ไปตาม นั่นคือมันไม่ได้หมายความว่าจะเพิ่มมูลค่าของ ! ii = 5; i++6iinti5
หอยทากวิศวกรรม

3
@ หอยทากเครื่องกล: ในกรณีนี้มันจะไม่เพิ่มตัวดำเนินการเลย จากนั้นตัวดำเนินการ + = จะชัดเจนขึ้นชัดเจนขึ้นยืดหยุ่นขึ้นและทำสิ่งเดียวกันอยู่ดี
Lennart Regebro

7
@LennartRegebro: ใน C ++ และ Java i++จะทำงานเฉพาะค่า lvalues ​​เท่านั้น หากมีวัตถุประสงค์เพื่อเพิ่มวัตถุที่ชี้ไปโดยiข้อ จำกัด นี้จะไม่จำเป็น
หอยทากวิศวกรรม

4
ฉันพบว่าคำตอบนี้ทำให้งงงัน เหตุใดคุณจึงสมมติว่า ++ จะหมายถึงสิ่งอื่นนอกเหนือจากชวเลขสำหรับ + ​​= 1 นั่นคือสิ่งที่มันหมายถึงอย่างแม่นยำใน C (สมมติว่าค่าตอบแทนไม่ได้ใช้) ดูเหมือนว่าคุณจะดึงความหมายอื่นออกไปจากอากาศ
Don Hatch

52

ในขณะที่คำตอบอื่น ๆ นั้นถูกต้องตราบใดที่พวกเขาแสดงให้เห็นว่า+ปกติแล้วจะทำอะไร (กล่าวคือให้ทิ้งหมายเลขตามที่เป็นถ้าเป็น) พวกเขาจะไม่สมบูรณ์ในขณะที่พวกเขาไม่อธิบายว่าเกิดอะไรขึ้น

เป็นที่แน่นอน+xประเมินx.__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)

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

ภายในฟังก์ชั่นที่คุณต้องเพิ่มคนในท้องถิ่น () เป็นอาร์กิวเมนต์ที่สองหากคุณต้องการเปลี่ยนตัวแปรท้องถิ่นมิฉะนั้นจะพยายามเปลี่ยนทั่วโลก

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

ฉันใช้ฟังก์ชั่นเหล่านี้ในโมดูลการแปลจาวาสคริปต์เป็นไพ ธ อน


หมายเหตุ: แม้ว่าจะดีวิธีการเหล่านี้จะไม่ทำงานหากชาวบ้านของคุณอยู่ในกรอบสแต็กฟังก์ชั่นคลาส เช่น - เรียกพวกเขาจากภายใน def วิธีการเรียนจะไม่ทำงาน - 'locals ()' dict เป็นภาพรวมและไม่ปรับปรุงกรอบสแต็ก
Adam

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

8

TL; DR

Python ไม่มีตัวดำเนินการเพิ่ม / ลดลง unary ( --/ ++) เพื่อเพิ่มค่าให้ใช้แทน

a += 1

รายละเอียดเพิ่มเติมและ gotchas

แต่ระวังที่นี่ หากคุณมาจาก C แม้จะเป็นงูหลาม Python ไม่มี "ตัวแปร" ในแง่ที่ C ใช้แทน python ใช้ชื่อและวัตถุและใน python ints นั้นไม่เปลี่ยนรูป

สมมติว่าคุณทำ

a = 1

สิ่งนี้หมายความว่าในหลาม: สร้างวัตถุประเภทที่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

ใช่ฉันพลาด ++ และ - ฟังก์ชั่นเช่นกัน โค้ด 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

... หรือมากขึ้น verbosely และชัดเจนในสิ่งที่เราต้องการที่จะเกิดขึ้นจริงแม้ว่าเคาน์เตอร์ - ระบุในรูปแบบที่แท้จริงโดย verbosity (ใช้c.vแทน) ...

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

2

ไม่มีตัวดำเนินการโพสต์ / เพิ่มขึ้น / ลดลงล่วงหน้าในหลามเช่นในภาษาเช่น C

เราสามารถเห็น++หรือเพิ่ม--จำนวนสัญญาณได้เหมือนที่เราทำในคณิตศาสตร์ (-1) * (-1) = (+1)

เช่น

---count

แยกวิเคราะห์เป็น

-(-(-count)))

ซึ่งแปลว่า

-(+count)

เพราะการคูณ-ด้วย-เครื่องหมายคือ+

และในที่สุดก็,

-count

1
สิ่งนี้บอกว่าคำตอบอื่น ๆ ทำไม่ได้?
แดเนียลบี

@DanielB คำตอบอื่น ๆ ยังไม่ได้บอกว่าเกิดอะไรขึ้นภายใน -----countและค่าที่พวกเขาได้บอกว่าอะไรจะเกิดขึ้นเมื่อคุณจะเขียน
Anuj

คำตอบแรกที่ยอมรับได้ทำ ...
Daniel B.

1
ไม่มีการพูดถึงว่ามีการดำเนินการคูณดังนั้นฉันจึงคิดว่า consice และสำหรับคำตอบนั้นน่าจะมีประโยชน์สำหรับผู้ใช้คนอื่น ๆ ไม่มีความผิดถ้าคุณเข้าใจจากสิ่งนั้น การเรียนรู้มีความสำคัญมากกว่าแหล่งที่คุณเรียนรู้
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 ถ้า dos ไม่มีอยู่โดยไม่มีข้อผิดพลาดจากนั้นจะตั้งเป็น 1

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.