Buggy Python Code: 10 ข้อผิดพลาดที่พบบ่อยที่สุดที่นักพัฒนา Python ทำ

เผยแพร่แล้ว: 2022-03-11

เกี่ยวกับ Python

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

เกี่ยวกับบทความนี้

ไวยากรณ์ที่เรียบง่ายและเรียนรู้ง่ายของ Python อาจทำให้นักพัฒนา Python เข้าใจผิด โดยเฉพาะอย่างยิ่งผู้ที่ยังใหม่กว่าภาษา ให้พลาดรายละเอียดปลีกย่อยบางส่วนและประเมินพลังของภาษา Python ที่หลากหลายต่ำไป

ด้วยเหตุนี้ บทความนี้จึงนำเสนอรายการ "10 อันดับแรก" ของข้อผิดพลาดที่ค่อนข้างละเอียดและจับยาก ซึ่งอาจกัดแม้แต่นักพัฒนา Python ขั้นสูงบางคนที่อยู่ด้านหลัง

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

ข้อผิดพลาดทั่วไป #1: การใช้นิพจน์ในทางที่ผิดเป็นค่าเริ่มต้นสำหรับอาร์กิวเมนต์ของฟังก์ชัน

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

 >>> def foo(bar=[]): # bar is optional and defaults to [] if not specified ... bar.append("baz") # but this line could be problematic, as we'll see... ... return bar

ข้อผิดพลาดทั่วไปคือการคิดว่าอาร์กิวเมนต์ที่เป็นทางเลือกจะถูกตั้งค่าเป็นนิพจน์เริ่มต้นที่ระบุ ทุกครั้ง ที่มีการเรียกใช้ฟังก์ชันโดยไม่ระบุค่าสำหรับอาร์กิวเมนต์ที่เป็นทางเลือก ตัวอย่างเช่น ในโค้ดด้านบน เราอาจคาดหวังว่าการเรียก foo() ซ้ำๆ (เช่น โดยไม่ระบุอาร์กิวเมนต์ bar ) จะส่งกลับ 'baz' เสมอ เนื่องจากสมมติฐานจะอยู่ที่ ทุกครั้งที่ เรียก foo() (ไม่มี bar ) อาร์กิวเมนต์ที่ระบุ) bar ตั้งค่าเป็น [] (เช่น รายการว่างใหม่)

แต่ลองดูว่าเกิดอะไรขึ้นเมื่อคุณทำเช่นนี้:

 >>> foo() ["baz"] >>> foo() ["baz", "baz"] >>> foo() ["baz", "baz", "baz"]

ฮะ? เหตุใดจึงผนวกค่าเริ่มต้นของ "baz" ต่อท้ายรายการ ที่มีอยู่ ทุกครั้งที่เรียก foo() แทนที่จะสร้างรายการ ใหม่ ทุกครั้ง

คำตอบในการเขียนโปรแกรม Python ขั้นสูงคือ ค่าเริ่มต้นสำหรับอาร์กิวเมนต์ของฟังก์ชันจะได้รับการประเมินเพียงครั้งเดียว ณ เวลาที่กำหนดฟังก์ชัน ดังนั้น อาร์กิวเมนต์ bar จะถูกเริ่มต้นเป็นค่าดีฟอลต์ (เช่น รายการว่าง) เฉพาะเมื่อมีการกำหนด foo() ในครั้งแรก แต่จากนั้นเรียกใช้ foo() (เช่น ไม่ได้ระบุอาร์กิวเมนต์ bar ) จะใช้รายการเดิมต่อไปเพื่อ bar ใดถูกตั้งค่าเริ่มต้น

FYI วิธีแก้ปัญหาทั่วไปสำหรับสิ่งนี้มีดังนี้:

 >>> def foo(bar=None): ... if bar is None: # or if not bar: ... bar = [] ... bar.append("baz") ... return bar ... >>> foo() ["baz"] >>> foo() ["baz"] >>> foo() ["baz"]

ข้อผิดพลาดทั่วไป #2: การใช้ตัวแปรคลาสอย่างไม่ถูกต้อง

พิจารณาตัวอย่างต่อไปนี้:

 >>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print Ax, Bx, Cx 1 1 1

มีเหตุผล.

 >>> Bx = 2 >>> print Ax, Bx, Cx 1 2 1

ใช่อีกครั้งตามที่คาดไว้

 >>> Ax = 3 >>> print Ax, Bx, Cx 3 2 3

อะไร $%#!& ?? เราเปลี่ยนแค่ Ax ทำไม Cx ถึงเปลี่ยนไปด้วย?

ใน Python ตัวแปรคลาสจะได้รับการจัดการภายในเป็นพจนานุกรมและปฏิบัติตามสิ่งที่มักเรียกว่า Method Resolution Order (MRO) ดังนั้นในโค้ดด้านบน เนื่องจากไม่พบแอตทริบิวต์ x ในคลาส C จึงจะถูกค้นหาในคลาสพื้นฐาน (เฉพาะ A ในตัวอย่างด้านบน แม้ว่า Python จะรองรับการสืบทอดหลายรายการ) กล่าวอีกนัยหนึ่ง C ไม่มีคุณสมบัติ x ของตัวเอง ไม่ขึ้นกับ A ดังนั้น การอ้างอิงถึง Cx แท้จริงแล้วการอ้างอิงถึง Ax สิ่งนี้ทำให้เกิดปัญหา Python เว้นแต่จะได้รับการจัดการอย่างเหมาะสม เรียนรู้เพิ่มเติมเกี่ยวกับแอตทริบิวต์คลาสใน Python

ข้อผิดพลาดทั่วไป #3: การระบุพารามิเตอร์ไม่ถูกต้องสำหรับบล็อกข้อยกเว้น

สมมติว่าคุณมีรหัสต่อไปนี้:

 >>> try: ... l = ["a", "b"] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... pass ... Traceback (most recent call last): File "<stdin>", line 3, in <module> IndexError: list index out of range

ปัญหาในที่นี้คือคำสั่ง except ไม่ รับรายการข้อยกเว้นที่ระบุในลักษณะนี้ แต่ใน Python 2.x ไวยากรณ์ except Exception, e ถูกใช้เพื่อผูกข้อยกเว้นกับพารามิเตอร์ ตัว ที่สองซึ่งเป็นทางเลือกที่ระบุ (ในกรณีนี้ e ) เพื่อให้พร้อมสำหรับการตรวจสอบเพิ่มเติม ด้วยเหตุนี้ในโค้ดข้างต้น ข้อยกเว้น IndexError จึง ไม่ ถูกจับโดยคำสั่ง except ค่อนข้าง ข้อยกเว้นจะถูกผูกไว้กับพารามิเตอร์ชื่อ IndexError แทน

วิธีที่เหมาะสมในการจับข้อยกเว้นหลายรายการในคำสั่ง except คือการระบุพารามิเตอร์แรกเป็นทูเพิลที่มีข้อยกเว้นทั้งหมดที่จะตรวจจับได้ นอกจากนี้ เพื่อการพกพาสูงสุด ให้ใช้ as คีย์เวิร์ด เนื่องจากไวยากรณ์นั้นรองรับทั้ง Python 2 และ Python 3:

 >>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>

ข้อผิดพลาดทั่วไป #4: ความเข้าใจผิดกฎขอบเขต Python

การแก้ไขขอบเขตของ Python ขึ้นอยู่กับสิ่งที่เรียกว่ากฎ LEGB ซึ่งเป็นชวเลขสำหรับ L ocal, E nclosing, G lobal, B uilt-in ดูเหมือนตรงไปตรงมาเพียงพอใช่ไหม? ที่จริงแล้ว มีรายละเอียดปลีกย่อยบางอย่างเกี่ยวกับวิธีการทำงานใน Python ซึ่งนำเราไปสู่ปัญหาการเขียนโปรแกรม Python ขั้นสูงทั่วไปด้านล่าง พิจารณาสิ่งต่อไปนี้:

 >>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment

มีปัญหาอะไร?

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

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

เป็นเรื่องปกติโดยเฉพาะอย่างยิ่งสำหรับสิ่งนี้ที่จะกระตุ้นนักพัฒนาเมื่อใช้รายการ พิจารณาตัวอย่างต่อไปนี้:

 >>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5) # This works ok... ... >>> foo1() >>> lst [1, 2, 3, 5] >>> lst = [1, 2, 3] >>> def foo2(): ... lst += [5] # ... but this bombs! ... >>> foo2() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment

ฮะ? ทำไม foo2 ถึงระเบิดในขณะที่ foo1 ทำงานได้ดี?

คำตอบก็เหมือนกับในปัญหาตัวอย่างก่อนหน้านี้ แต่ยอมรับได้ละเอียดกว่า foo1 ไม่ได้ทำการ มอบหมาย lst ในขณะที่ foo2 คือ จำได้ว่า lst += [5] เป็นเพียงชวเลขสำหรับ lst = lst + [5] เราเห็นว่าเรากำลังพยายาม กำหนด ค่าให้กับ lst (ดังนั้น Python จึงสันนิษฐานว่าอยู่ในขอบเขตท้องถิ่น) อย่างไรก็ตาม ค่าที่เราต้องการกำหนดให้กับ lst นั้นขึ้นอยู่ lst ตัวมันเอง (อีกครั้ง ซึ่งตอนนี้ถือว่าอยู่ในขอบเขตท้องถิ่น) ซึ่งยังไม่ได้กำหนดไว้ บูม.

ข้อผิดพลาดทั่วไป #5: การแก้ไขรายการขณะวนซ้ำ

ปัญหาเกี่ยวกับรหัสต่อไปนี้ควรค่อนข้างชัดเจน:

 >>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i] # BAD: Deleting item from a list while iterating over it ... Traceback (most recent call last): File "<stdin>", line 2, in <module> IndexError: list index out of range

การลบรายการออกจากรายการหรืออาร์เรย์ขณะวนซ้ำ เป็นปัญหา Python ที่นักพัฒนาซอฟต์แวร์ที่มีประสบการณ์ทุกคนทราบดี แต่ในขณะที่ตัวอย่างด้านบนอาจค่อนข้างชัดเจน แม้แต่นักพัฒนาขั้นสูงก็สามารถถูกสิ่งนี้กัดโดยไม่ได้ตั้งใจในโค้ดที่ซับซ้อนกว่ามาก

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

 >>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all >>> numbers [0, 2, 4, 6, 8]

ข้อผิดพลาดทั่วไป #6: สร้างความสับสนว่า Python ผูกกับตัวแปรอย่างไรในการปิด

พิจารณาตัวอย่างต่อไปนี้:

 >>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...

คุณอาจคาดหวังผลลัพธ์ต่อไปนี้:

 0 2 4 6 8

แต่คุณจะได้รับ:

 8 8 8 8 8

เซอร์ไพรส์!

สิ่งนี้เกิดขึ้นเนื่องจากพฤติกรรมการ ผูกมัดในช่วงปลาย ของ Python ซึ่งบอกว่าค่าของตัวแปรที่ใช้ในการปิดจะถูกค้นหาในเวลาที่เรียกใช้ฟังก์ชันภายใน ดังนั้นในโค้ดด้านบนนี้ เมื่อใดก็ตามที่มีการเรียกฟังก์ชันที่ส่งคืน ค่าของ i จะถูกค้นหา ในขอบเขตโดยรอบ ณ เวลาที่เรียกใช้ (และเมื่อถึงตอนนั้น ลูปก็เสร็จสิ้น ดังนั้น i จึงถูกกำหนดเป็นขั้นสุดท้ายแล้ว มูลค่า 4).

วิธีแก้ปัญหา Python ทั่วไปนี้ค่อนข้างเป็นการแฮ็ก:

 >>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ... >>> for multiplier in create_multipliers(): ... print multiplier(2) ... 0 2 4 6 8

โว้ว! เรากำลังใช้ประโยชน์จากอาร์กิวเมนต์เริ่มต้นที่นี่ เพื่อสร้างฟังก์ชันที่ไม่ระบุตัวตน เพื่อให้บรรลุพฤติกรรมที่ต้องการ บางคนเรียกสิ่งนี้ว่าสง่างาม บางคนเรียกว่าบอบบาง บางคนเกลียดมัน แต่ถ้าคุณเป็นนักพัฒนา Python สิ่งสำคัญคือต้องเข้าใจในทุกกรณี

ข้อผิดพลาดทั่วไป #7: การสร้างการพึ่งพาโมดูลแบบวงกลม

สมมติว่าคุณมีไฟล์สองไฟล์ a.py และ b.py ซึ่งแต่ละไฟล์นำเข้าไฟล์อื่นๆ ดังนี้:

ใน a.py :

 import b def f(): return bx print f()

และใน b.py :

 import a x = 1 def g(): print af()

ก่อนอื่น ให้ลองนำเข้า a.py :

 >>> import a 1

ทำงานได้ดี บางทีนั่นอาจทำให้คุณประหลาดใจ ท้ายที่สุด เรามีการนำเข้าแบบวงกลมที่นี่ ซึ่งน่าจะมีปัญหาใช่ไหม

คำตอบก็คือการ มีอยู่ ของการนำเข้าแบบวงกลมนั้นไม่ได้เป็นปัญหาในตัวของมันเองใน Python หากนำเข้าโมดูลแล้ว Python ก็ฉลาดพอที่จะไม่พยายามนำเข้าซ้ำ อย่างไรก็ตาม ขึ้นอยู่กับจุดที่แต่ละโมดูลพยายามเข้าถึงฟังก์ชันหรือตัวแปรที่กำหนดไว้ในโมดูลอื่น คุณอาจประสบปัญหาอย่างแท้จริง

กลับไปที่ตัวอย่างของเรา เมื่อเรานำเข้า a.py จะไม่มีปัญหาในการนำเข้า b.py เนื่องจาก b.py ไม่ต้องการสิ่งใดจาก a.py ที่จะกำหนด ในขณะที่นำเข้า การอ้างอิงเดียวใน b.py ถึง a คือการเรียก af() แต่การโทรนั้นอยู่ใน g() และไม่มีอะไรใน a.py หรือ b.py เรียก g() ชีวิตจึงดี

แต่จะเกิดอะไรขึ้นหากเราพยายามนำเข้า b.py (โดยไม่ต้องนำเข้า a.py มาก่อน นั่นคือ):

 >>> import b Traceback (most recent call last): File "<stdin>", line 1, in <module> File "b.py", line 1, in <module> import a File "a.py", line 6, in <module> print f() File "a.py", line 4, in f return bx AttributeError: 'module' object has no attribute 'x'

เอ่อโอ้. ที่ไม่ดี! ปัญหาที่นี่คือในกระบวนการนำเข้า b.py พยายามนำเข้า a.py ซึ่งจะเรียก f() ซึ่งพยายามเข้าถึง bx แต่ยังไม่ได้กำหนด bx ดังนั้นข้อยกเว้น AttributeError

อย่างน้อยหนึ่งวิธีแก้ปัญหานี้ค่อนข้างเล็กน้อย เพียงแก้ไข b.py เพื่อนำเข้า a.py ภายใน g() :

 x = 1 def g(): import a # This will be evaluated only when g() is called print af()

ไม่เมื่อเรานำเข้า ทุกอย่างเรียบร้อยดี:

 >>> import b >>> bg() 1 # Printed a first time since module 'a' calls 'print f()' at the end 1 # Printed a second time, this one is our call to 'g'

ข้อผิดพลาดทั่วไป #8: ชื่อที่ขัดแย้งกับโมดูล Python Standard Library

หนึ่งในความสวยงามของ Python คือความร่ำรวยของโมดูลไลบรารีที่มาพร้อมกับ "นอกกรอบ" แต่ด้วยเหตุนี้ หากคุณไม่หลีกเลี่ยงอย่างมีสติ การเรียกชื่อระหว่างชื่อโมดูลของคุณกับโมดูลที่มีชื่อเดียวกันในไลบรารีมาตรฐานที่มาพร้อมกับ Python นั้นไม่ใช่เรื่องยาก (เช่น คุณอาจมีโมดูลชื่อ email.py ในโค้ดของคุณ ซึ่งอาจขัดแย้งกับโมดูลไลบรารีมาตรฐานที่มีชื่อเดียวกัน)

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

ดังนั้น จึงควรระมัดระวังเพื่อหลีกเลี่ยงการใช้ชื่อเดียวกับชื่อในโมดูล Python Standard Library ง่ายกว่าสำหรับคุณในการเปลี่ยนชื่อโมดูลภายในแพ็คเกจของคุณ มากกว่าที่จะยื่นข้อเสนอ Python Enhancement Proposal (PEP) เพื่อขอเปลี่ยนชื่ออัปสตรีมและพยายามรับการอนุมัติ

ข้อผิดพลาดทั่วไป #9: ไม่สามารถระบุความแตกต่างระหว่าง Python 2 และ Python 3

พิจารณาไฟล์ต่อไปนี้ foo.py :

 import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e) bad()

ใน Python 2 สิ่งนี้ใช้ได้ดี:

 $ python foo.py 1 key error 1 $ python foo.py 2 value error 2

แต่ตอนนี้ มาลองใช้ Python 3: กัน

 $ python3 foo.py 1 key error Traceback (most recent call last): File "foo.py", line 19, in <module> bad() File "foo.py", line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment

เกิดอะไรขึ้นที่นี่? "ปัญหา" คือใน Python 3 วัตถุยกเว้นไม่สามารถเข้าถึงได้นอกเหนือขอบเขตของบล็อก except (เหตุผลของสิ่งนี้ก็คือ ไม่เช่นนั้น มันจะเก็บวงจรอ้างอิงไว้กับสแต็กเฟรมในหน่วยความจำ จนกว่าตัวรวบรวมขยะจะทำงานและล้างข้อมูลอ้างอิงออกจากหน่วยความจำ รายละเอียดทางเทคนิคเพิ่มเติมเกี่ยวกับสิ่งนี้มีให้ที่นี่)

วิธีหนึ่งในการหลีกเลี่ยงปัญหานี้คือการรักษาการอ้างอิงไปยังอ็อบเจ็กต์ข้อยกเว้นที่ อยู่นอก ขอบเขตของบล็อก except เพื่อให้สามารถเข้าถึงได้ ต่อไปนี้คือเวอร์ชันของตัวอย่างก่อนหน้าที่ใช้เทคนิคนี้ ซึ่งจะทำให้โค้ดที่เป็นมิตรกับ Python 2 และ Python 3 ใช้งานได้:

 import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def good(): exception = None try: bar(int(sys.argv[1])) except KeyError as e: exception = e print('key error') except ValueError as e: exception = e print('value error') print(exception) good()

เรียกใช้สิ่งนี้บน Py3k:

 $ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2

ไชโย

(โดยบังเอิญ คู่มือการจ้างงาน Python ของเรากล่าวถึงความแตกต่างที่สำคัญอื่นๆ อีกจำนวนหนึ่งที่ต้องระวังเมื่อทำการย้ายโค้ดจาก Python 2 ไปเป็น Python 3)

ข้อผิดพลาดทั่วไป #10: การใช้วิธี __del__ ในทางที่ผิด

สมมติว่าคุณมีสิ่งนี้ในไฟล์ชื่อ mod.py :

 import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)

แล้วคุณลองทำสิ่งนี้จาก another_mod.py :

 import mod mybar = mod.Bar()

คุณจะได้รับข้อยกเว้น AttributeError ที่น่าเกลียด

ทำไม? เนื่องจากตามที่รายงานในที่นี้ เมื่อล่ามปิดตัวลง ตัวแปรส่วนกลางของโมดูลจะถูกตั้งค่าเป็น None ทั้งหมด ด้วยเหตุนี้ ในตัวอย่างข้างต้น ณ จุดที่ __del__ ถูกเรียกใช้ ชื่อ foo ได้ถูกตั้งค่าเป็น None แล้ว

วิธีแก้ไขสำหรับปัญหาการเขียนโปรแกรม Python ขั้นสูงกว่านี้คือใช้ atexit.register() แทน ด้วยวิธีนี้ เมื่อโปรแกรมของคุณดำเนินการเสร็จสิ้น (เมื่อออกจากระบบตามปกติ) ตัวจัดการที่ลงทะเบียนของคุณจะถูกไล่ออก ก่อน ที่ล่ามจะปิดตัวลง

ด้วยความเข้าใจดังกล่าว การแก้ไขโค้ด mod.py ด้านบนอาจมีลักษณะดังนี้:

 import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle)

การใช้งานนี้เป็นวิธีที่สะอาดและเชื่อถือได้ในการเรียกใช้ฟังก์ชันการล้างข้อมูลที่จำเป็นเมื่อมีการยุติโปรแกรมตามปกติ แน่นอน มันขึ้นอยู่กับ foo.cleanup ที่จะตัดสินใจว่าจะทำอย่างไรกับวัตถุที่ผูกไว้กับชื่อ self.myhandle แต่คุณคงเข้าใจแล้ว

สรุป

Python เป็นภาษาที่ทรงพลังและยืดหยุ่นด้วยกลไกและกระบวนทัศน์มากมายที่สามารถปรับปรุงประสิทธิภาพการทำงานได้อย่างมาก เช่นเดียวกับเครื่องมือซอฟต์แวร์หรือภาษาใดๆ ก็ตาม การมีความเข้าใจที่จำกัดหรือเห็นคุณค่าในความสามารถของซอฟต์แวร์นั้น บางครั้งอาจเป็นอุปสรรคมากกว่าผลประโยชน์ โดยปล่อยให้อยู่ในสถานะสุภาษิตที่ว่า “รู้เพียงพอที่จะเป็นอันตราย”

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

คุณอาจต้องการดูคู่มือ Insider's Guide to Python Interviewing ของเรา เพื่อดูคำแนะนำเกี่ยวกับคำถามสัมภาษณ์ที่สามารถช่วยระบุผู้เชี่ยวชาญ Python

เราหวังว่าคุณจะพบว่าคำแนะนำในบทความนี้มีประโยชน์และยินดีรับฟังความคิดเห็นของคุณ