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