Python เป็นภาษาโปรแกรมเชิงวัตถุระดับสูงและตีความด้วยความหมายแบบไดนามิก โครงสร้างข้อมูลแบบบูรณาการระดับสูงรวมกับการเขียนแบบไดนามิกและการเชื่อมโยงทำให้น่าสนใจมากสำหรับไฟล์ การพัฒนาแอปพลิเคชันอย่างรวดเร็ว ตลอดจนใช้เป็นภาษาสคริปต์หรือกาวเพื่อเชื่อมต่อส่วนประกอบหรือบริการที่มีอยู่ Python ทำงานร่วมกับโมดูลและแพ็คเกจดังนั้นจึงส่งเสริมความเป็นโมดูลาร์ของโปรแกรมและการใช้โค้ดซ้ำ
ง่ายและสะดวกในการเรียนรู้ไวยากรณ์ Python คุณสามารถส่งไปที่ไฟล์ นักพัฒนาหลาม ไปในทิศทางที่ผิด - โดยเฉพาะผู้ที่เรียนภาษา - สูญเสียรายละเอียดปลีกย่อยบางอย่างไปพร้อมกันและประเมินพลังของ ภาษาที่หลากหลายของ Python .
ด้วยเหตุนี้บทความนี้จึงนำเสนอรายการ '10 อันดับแรก' ของข้อบกพร่องที่ละเอียดอ่อนและมองเห็นได้ยากซึ่งอาจจับได้แม้กระทั่งนักพัฒนา Python ขั้นสูงบางรายที่ไม่ระมัดระวัง
( บันทึก: บทความนี้มีไว้สำหรับผู้ชมขั้นสูงมากกว่าข้อผิดพลาดทั่วไปของโปรแกรมเมอร์ Python ซึ่งมุ่งเน้นไปที่ผู้ที่ยังไม่คุ้นเคยกับภาษา )
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()
หลายครั้ง (นั่นคือโดยไม่ต้องระบุอาร์กิวเมนต์บาร์) จะส่งกลับ baz
เสมอเนื่องจากสมมติฐานจะเป็นเช่นนั้นทุกครั้ง foo()
ถูกเรียกว่า (ไม่มีอาร์กิวเมนต์บาร์ที่ระบุ) ตั้งค่าเป็น []
(นั่นคือรายการว่างใหม่)
แต่มาดูกันว่าจะเกิดอะไรขึ้นเมื่อเสร็จสิ้น:
>>> foo() ['baz'] >>> foo() ['baz', 'baz'] >>> foo() ['baz', 'baz', 'baz']
เฮ้? เหตุใดจึงเป็นค่าเริ่มต้นของ baz
ไปยังรายการที่มีอยู่ ทุกเวลา ที่ foo()
ถูกเรียกแทนที่จะสร้างรายการใหม่ในทุกโอกาส? คำตอบการเขียนโปรแกรม Python ขั้นสูงที่สุดคือ ค่าดีฟอลต์สำหรับอาร์กิวเมนต์ของฟังก์ชันจะได้รับการประเมินเพียงครั้งเดียวในเวลาที่กำหนดฟังก์ชัน ดังนั้นอาร์กิวเมนต์บาร์จะเริ่มต้นเป็นค่าเริ่มต้น (นั่นคือรายการว่าง) เฉพาะเมื่อ foo()
ได้รับการกำหนดไว้ก่อน แต่จากนั้นเรียกไปที่ foo()
(นั่นคือไม่มีการระบุ bar
อาร์กิวเมนต์) พวกเขาจะยังคงใช้รายการเดียวกับ bar
เริ่มต้นครั้งแรก
โดยวิธีการแก้ปัญหาทั่วไปสำหรับสิ่งนี้มีดังต่อไปนี้:
>>> def foo(bar=None): ... if bar is None: # or if not bar: ... bar = [] ... bar.append('baz') ... return bar ... >>> foo() ['baz'] >>> foo() ['baz'] >>> foo() ['baz']
ลองพิจารณาตัวอย่างต่อไปนี้:
>>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print A.x, B.x, C.x 1 1 1
มีเหตุผล.
>>> B.x = 2 >>> print A.x, B.x, C.x 1 2 1
ใช่อีกครั้งตามที่คาดไว้
>>> A.x = 3 >>> print A.x, B.x, C.x 3 2 3
นี่คืออะไร? เราเปลี่ยน A.x
ทำไม C.x
เปลี่ยนไปด้วย?
ใน Python ตัวแปรคลาสจะได้รับการจัดการภายในเช่นพจนานุกรมและทำตามสิ่งที่มักเรียกกันว่า วิธีการแก้ปัญหา (MRO) . ดังนั้นในโค้ดด้านบนเนื่องจากไม่พบแอตทริบิวต์ x ในคลาส C จึงจะถูกค้นหาในคลาสพื้นฐาน (เฉพาะ A ในตัวอย่างด้านบนแม้ว่า Python จะรองรับการสืบทอดหลายรายการ) กล่าวอีกนัยหนึ่ง C ไม่มีคุณสมบัติ x เป็นของตัวเองโดยไม่ขึ้นกับ A. ดังนั้นการอ้างอิงถึง C.x จึงเป็นการอ้างอิงถึง A.x. สิ่งนี้ทำให้เกิดปัญหา Python เว้นแต่จะได้รับการจัดการอย่างเหมาะสม ข้อมูลเพิ่มเติมเกี่ยวกับ แอตทริบิวต์คลาสใน Python .
สมมติว่าคุณมีรหัสต่อไปนี้:
>>> try: ... l = ['a', 'b'] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... pass ... Traceback (most recent call last): File '', line 3, in IndexError: list index out of range
ปัญหาอยู่ที่รายงาน except
จะไม่ใช้รายการข้อยกเว้นที่ระบุไว้ในลักษณะนี้ ในทางตรงกันข้าม Python 2.x ไวยากรณ์ except Exception, e
ถูกใช้เพื่อผูกข้อยกเว้นกับพารามิเตอร์ที่สอง ไม่จำเป็น ระบุไว้ (ในกรณีนี้ e
) เพื่อให้สามารถตรวจสอบเพิ่มเติมได้ ด้วยเหตุนี้ในโค้ดด้านบนข้อยกเว้น IndexError
ไม่ถูกจับโดยรายงาน except
; แต่ข้อยกเว้นจะถูกผูกไว้กับพารามิเตอร์ที่เรียกว่า IndexError
วิธีที่ถูกต้องในการตรวจจับข้อยกเว้นหลายรายการในรายงาน except
คือการระบุพารามิเตอร์แรกเป็น a สองเท่า ที่มีข้อยกเว้นทั้งหมดที่จะจับได้ นอกจากนี้เพื่อความสะดวกในการพกพาสูงสุดให้ใช้คีย์เวิร์ด as
เนื่องจาก Python 2 และ Python 3 รองรับไวยากรณ์:
>>> try: ... l = ['a', 'b'] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
ความละเอียดขอบเขตของ Python ขึ้นอยู่กับสิ่งที่เรียกว่ากฎ LEGB ซึ่งย่อมาจาก ล ocal, คือ การปิดจมูก ช โลบอล ข uilt-in. ดูเหมือนจะตรงไปตรงมาใช่มั้ย? อันที่จริงมีรายละเอียดปลีกย่อยบางประการเกี่ยวกับวิธีการทำงานใน Python ซึ่งนำเราไปสู่ปัญหาการเขียนโปรแกรม Python ขั้นสูงทั่วไปด้านล่าง
พิจารณาสิ่งต่อไปนี้:
>>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File '', line 1, in File '', 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 '', line 1, in File '', line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment
เฮ้? ทำไม foo2
ล้มเหลวในขณะที่ foo1
ทำงานได้ดีมาก?
ความท้าทายที่นักพัฒนา Java เผชิญ
คำตอบก็เหมือนกับปัญหาในตัวอย่างก่อนหน้านี้ แต่แน่นอนว่ามันละเอียดกว่า foo1
ไม่ได้ทำ มอบหมาย a lst
, ในขณะที่ foo2
ถ้ามันเป็น. จำไว้ว่า lst += [5]
จริงๆแล้วย่อมาจาก lst = lst + [5]
เราจะเห็นว่าเรากำลังพยายามกำหนดค่าให้กับ lst
(ดังนั้น Python ถือว่าคุณอยู่ในขอบเขตท้องถิ่น) อย่างไรก็ตามค่าที่เราพยายามกำหนดให้กับ lst นั้นขึ้นอยู่กับ lst
เดียวกัน (อีกครั้งตอนนี้สันนิษฐานว่าเป็นของท้องถิ่น) ซึ่งยังไม่ได้กำหนด บูม
ปัญหาเกี่ยวกับรหัสต่อไปนี้ควรชัดเจนมาก:
>>> 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 '', line 2, in IndexError: list index out of range
การลบรายการออกจากรายการหรืออาร์เรย์ในขณะที่ทำซ้ำอยู่นั้นเป็นปัญหาของ Python ที่นักพัฒนาซอฟต์แวร์ที่มีประสบการณ์รู้จักกันดี อย่างไรก็ตามแม้ว่าตัวอย่างข้างต้นอาจค่อนข้างชัดเจน แต่แม้แต่นักพัฒนาขั้นสูงก็อาจถูกจับได้โดยไม่ได้ตั้งใจด้วยรหัสที่ซับซ้อนกว่านี้
โชคดีที่ Python รวมเอากระบวนทัศน์การเขียนโปรแกรมที่สวยงามเข้าไว้ด้วยกันซึ่งเมื่อใช้อย่างถูกต้องสามารถส่งผลให้โค้ดง่ายขึ้นและคล่องตัวขึ้นอย่างมาก ประโยชน์รองของสิ่งนี้คือโค้ดที่เรียบง่ายกว่ามีโอกาสน้อยที่จะถูกจับโดยข้อผิดพลาดในการลบรายการของรายการในขณะที่ทำซ้ำ - ทับมัน กระบวนทัศน์หนึ่งคือ [list compiltyions] ((https://docs.python.org/2/tutorial/datastructures.html#tut-listcomps) ในทางกลับกันการทำความเข้าใจรายการมีประโยชน์อย่างยิ่งในการหลีกเลี่ยงปัญหานี้เช่น แสดงในการใช้งานโค้ดอื่นที่แสดงด้านบนซึ่งทำงานได้อย่างสมบูรณ์:
>>> 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]
พิจารณาตัวอย่างต่อไปนี้:
>>> 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
แต่คุณจะได้รับ:
ย้ายเซิร์ฟเวอร์ sql ไปยัง oracle
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 สิ่งนี้เป็นสิ่งสำคัญที่ต้องเข้าใจ
สมมติว่าคุณมีสองไฟล์คือ a.py และ b.py ซึ่งแต่ละไฟล์จะนำเข้าไฟล์อื่น ๆ ดังนี้:
ใน a.py
:
import b def f(): return b.x print f()
และใน b.py
:
import a x = 1 def g(): print a.f()
ก่อนอื่นมาลองนำเข้า a.py
:
>>> import a 1
มันทำงานได้ดีมาก บางทีคุณอาจแปลกใจ ท้ายที่สุดเรามีการนำเข้าแบบวงกลมที่นี่ซึ่งน่าจะเป็นปัญหาใช่ไหม?
คำตอบก็คือเพียง การปรากฏตัว ของการนำเข้าแบบวงกลมไม่เป็นปัญหาใน Python หากนำเข้าโมดูลแล้ว Python ฉลาดพอที่จะไม่พยายามนำเข้าอีกครั้ง อย่างไรก็ตามขึ้นอยู่กับจุดที่แต่ละโมดูลพยายามเข้าถึงฟังก์ชันหรือตัวแปรที่กำหนดไว้ในอีกโมดูลหนึ่งคุณอาจประสบปัญหาได้
กลับไปที่ตัวอย่างของเราเมื่อเรานำเข้า a.py
ฉันไม่มีปัญหาในการนำเข้า b.py
ตั้งแต่ b.py
ไม่จำเป็นต้องมี b.py
ที่จะกำหนดเมื่อนำเข้า ข้อมูลอ้างอิงเดียวใน b.py
a a
เป็นการโทรไปที่ a.f()
แต่สายนั้นอยู่ใน g()
และไม่มีอะไรใน a.py
หรือ b.py
เรียก g()
. ดังนั้นชีวิตที่สวยงาม
แต่จะเกิดอะไรขึ้นถ้าคุณพยายามนำเข้า b.py
(แน่นอนโดยไม่เคยนำเข้ามาก่อน a.py
):
>>> import b Traceback (most recent call last): File '', line 1, in File 'b.py', line 1, in import a File 'a.py', line 6, in print f() File 'a.py', line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'
เอ่อโอ้. ไม่ดีแน่! ปัญหาคือในกระบวนการนำเข้า b.py
มันพยายามนำเข้า a.py
ซึ่งเป็นผลให้เรียก f()
ซึ่งจะพยายามเข้าถึง b.x
แต่ b.x
ยังไม่ได้กำหนด ดังนั้นข้อยกเว้น AttributeError
.
อย่างน้อยหนึ่งวิธีแก้ปัญหานี้เป็นเรื่องเล็กน้อย เพียงแค่ปรับเปลี่ยน b.py
เพื่อนำเข้า a.py
ภายใน g()
:
x = 1 def g(): import a # This will be evaluated only when g() is called print a.f()
เมื่อนำเข้าทุกอย่างเรียบร้อย:
>>> import b >>> b.g() 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'
ข้อดีอย่างหนึ่งของ Python คือโมดูลไลบรารีจำนวนมากที่มาพร้อมกับตั้งแต่เริ่มต้น แต่ด้วยเหตุนี้หากคุณไม่ระมัดระวังในการหลีกเลี่ยงสิ่งนี้จึงไม่ใช่เรื่องยากที่จะพบกับการปะทะกันระหว่างชื่อโมดูลหนึ่งของคุณและโมดูลที่มีชื่อเดียวกันในไลบรารีมาตรฐานที่มาพร้อมกับ Python (สำหรับ ตัวอย่างเช่นคุณอาจมีโมดูลชื่อ email.py
ในโค้ดของคุณซึ่งจะขัดแย้งกับโมดูลไลบรารีมาตรฐานที่มีชื่อเดียวกัน)
s corp กับ c corp
สิ่งนี้อาจนำไปสู่ปัญหาที่ลุกลามมากเช่นการนำเข้าไลบรารีอื่นซึ่งจะพยายามนำเข้าโมดูลเวอร์ชันไลบรารีมาตรฐาน Python แต่เนื่องจากคุณมีโมดูลที่มีชื่อเดียวกันอยู่แล้วแพ็กเกจอื่นจึงนำเข้าเวอร์ชันของคุณโดยไม่ถูกต้อง ที่พบในไลบรารีมาตรฐาน Python และนี่คือจุดที่เกิดข้อผิดพลาดร้ายแรงที่สุด
ดังนั้นจึงต้องระมัดระวังเพื่อหลีกเลี่ยงการใช้ชื่อเดียวกันกับโมดูลไลบรารี Python มาตรฐาน คุณสามารถเปลี่ยนชื่อโมดูลภายในแพ็คเกจได้ง่ายกว่าการเสนอข้อเสนอการปรับปรุง Python (PEP) เพื่อขอเปลี่ยนชื่อต้นน้ำและได้รับการอนุมัติ
พิจารณาไฟล์ต่อไปนี้ 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 bad() File 'foo.py', line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment
เกิดอะไรขึ้นที่นี่? 'ปัญหา' คือใน Python 3 วัตถุข้อยกเว้นไม่สามารถเข้าถึงได้นอกเหนือจากขอบเขตของบล็อก 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)
__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 ได้
เราหวังว่าคุณจะพบเคล็ดลับในบทความนี้และขอขอบคุณสำหรับความคิดเห็นของคุณ