วิธี __del__ คืออะไรจะเรียกมันอย่างไร


111

ฉันกำลังอ่านรหัส มีคลาสที่__del__กำหนดเมธอด ฉันคิดว่าวิธีนี้ใช้เพื่อทำลายอินสแตนซ์ของคลาส อย่างไรก็ตามฉันไม่พบสถานที่ที่ใช้วิธีนี้ สาเหตุหลักคือฉันไม่รู้ว่าวิธีนี้ใช้อย่างไรอาจจะไม่ใช่อย่างนั้น: obj1.del(). ดังนั้นคำถามของฉันคือจะเรียก__del__เมธอดอย่างไร?

คำตอบ:


169

__del__เป็นfinalizer เรียกว่าเมื่อวัตถุถูกเก็บรวบรวมขยะซึ่งเกิดขึ้นในบางจุดหลังจากการอ้างอิงถึงวัตถุทั้งหมดถูกลบไปแล้ว

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

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

ยิ่งไปกว่านั้นตัวแปรสามารถมีชีวิตอยู่ได้เป็นเวลานานด้วยเหตุผลหลายประการเช่นข้อยกเว้นการเผยแผ่หรือการวิปัสสนาโมดูลสามารถทำให้จำนวนการอ้างอิงตัวแปรมากกว่า 0 ได้นอกจากนี้ตัวแปรยังสามารถเป็นส่วนหนึ่งของวงจรการอ้างอิงได้ - CPython ที่มีการรวบรวมขยะเปิดอยู่ในช่วงพักส่วนใหญ่ แต่ไม่ใช่ทั้งหมดวัฏจักรดังกล่าวและแม้กระทั่งเป็นระยะ ๆ

เนื่องจากคุณไม่มีการรับประกันว่าจะถูกเรียกใช้งานจึงไม่ควรใส่รหัสที่คุณต้องใช้ในการทำงาน__del__()แต่รหัสนี้เป็นของfinallyส่วนคำสั่งของtryบล็อกหรือตัวจัดการบริบทในwithคำสั่ง อย่างไรก็ตามมีกรณีการใช้งานที่ถูกต้องสำหรับ__del__: เช่นหากอ็อบเจ็กต์Xอ้างอิงYและยังเก็บสำเนาYการอ้างอิงไว้ใน global cache( cache['X -> Y'] = Y) ดังนั้นX.__del__การลบรายการแคชจะเป็นการดี

ถ้าคุณรู้ว่า destructor ให้ (ในการละเมิดแนวทางข้างต้น) ล้างจำเป็นคุณอาจต้องการที่จะเรียกมันว่าโดยตรงx.__del__()เนื่องจากมีอะไรพิเศษเกี่ยวกับเรื่องนี้เป็นวิธีการที่: เห็นได้ชัดว่าคุณควรทำเช่นนั้นก็ต่อเมื่อคุณรู้ว่าไม่คิดที่จะถูกเรียกสองครั้ง หรือเป็นทางเลือกสุดท้ายคุณสามารถกำหนดวิธีการนี้ใหม่ได้โดยใช้

type(x).__del__ = my_safe_cleanup_method  

5
คุณบอกว่าคุณลักษณะของ CPython ในการลบวัตถุทันทีหลังจากที่จำนวนอ้างอิงลดลงเป็นศูนย์คือ "รายละเอียดการใช้งาน" ฉันไม่เชื่อ คุณสามารถให้ลิงค์สำรองการอ้างสิทธิ์นั้นได้หรือไม่? (ผมหมายถึงอักษรตัวหนาเป็นที่น่าเชื่อสวยในตัวของมันเอง แต่การเชื่อมโยงใกล้ชิดที่สอง ... :-)
สจ๊วร์ Berg

15
รายละเอียดการใช้งาน CPython:ปัจจุบัน CPython ใช้รูปแบบการนับอ้างอิงที่มีการตรวจจับขยะที่เชื่อมโยงแบบวนซ้ำ (ไม่บังคับ) ล่าช้า ... การใช้งานอื่น ๆ จะทำหน้าที่แตกต่างกันและ CPython อาจเปลี่ยนแปลง ( docs.python.org/2/reference/datamodel.html )
ilya n.

อะไรที่มี__exit__ในบริบทนี้? มันวิ่งตามหรือก่อนหน้า__del__หรือพร้อมกัน?
lony

1
"อาจไม่เกิดขึ้นเลย" รวมถึงเมื่อโปรแกรมยุติหรือไม่
Andy Hayden

1
@AndyHayden: __del__วิธีการอาจไม่ทำงานแม้เมื่อสิ้นสุดโปรแกรมและแม้ว่าจะดำเนินการเมื่อสิ้นสุดการเขียน__del__วิธีการที่ทำงานได้อย่างถูกต้องแม้ในขณะที่ล่ามกำลังยุ่งอยู่กับการทำลายตัวเองรอบ ๆ ตัวคุณต้องใช้การเข้ารหัสอย่างระมัดระวังมากกว่าที่โปรแกรมเมอร์หลายคนใช้ (โดยปกติการล้างข้อมูล CPython จะได้รับ__del__เมธอดในการรันเมื่อปิดตัวแปล แต่ก็ยังมีบางกรณีที่ไม่เพียงพอเธรด Daemon, C-level globals และอ็อบเจ็กต์ที่__del__สร้างขึ้นในอื่น__del__อาจทำให้__del__เมธอดไม่ทำงานได้)
user2357112 รองรับ Monica

83

ฉันเขียนคำตอบสำหรับคำถามอื่นแม้ว่านี่จะเป็นคำถามที่ถูกต้องกว่าสำหรับคำถามนี้

ตัวสร้างและตัวทำลายทำงานอย่างไร?

นี่คือคำตอบที่แสดงความคิดเห็นเล็กน้อย

อย่าใช้__del__. นี่ไม่ใช่ภาษา C ++ หรือภาษาที่สร้างขึ้นสำหรับผู้ทำลาย __del__วิธีจริงๆควรจะหายไปในหลาม 3.x แต่ฉันแน่ใจว่าคนที่จะได้พบกับกรณีการใช้งานที่ทำให้รู้สึก หากคุณจำเป็นต้องใช้__del__โปรดทราบถึงข้อ จำกัด พื้นฐานตามhttp://docs.python.org/reference/datamodel.html :

  • __del__del objectจะถูกเรียกเมื่อเก็บขยะที่เกิดขึ้นจะได้รับการเก็บรวบรวมวัตถุที่ไม่ได้เมื่อคุณสูญเสียการอ้างอิงสุดท้ายที่วัตถุและไม่ได้เมื่อคุณดำเนินการ
  • __del__รับผิดชอบในการเรียกสิ่งใด ๆ__del__ในซูเปอร์คลาสแม้ว่าจะไม่ชัดเจนว่านี่เป็นคำสั่งการแก้ไขวิธีการ (MRO) หรือเพียงแค่เรียกซูเปอร์คลาสแต่ละรายการ
  • การมี__del__วิธีการที่ตัวเก็บรวบรวมขยะยอมแพ้ในการตรวจจับและทำความสะอาดลิงก์แบบวนซ้ำเช่นสูญเสียการอ้างอิงล่าสุดไปยังรายการที่เชื่อมโยง คุณสามารถรับรายการวัตถุที่ละเว้นจาก gc.garbage บางครั้งคุณสามารถใช้การอ้างอิงที่ไม่เหมาะสมเพื่อหลีกเลี่ยงวงจรโดยสิ้นเชิง นี้ได้รับการถกเถียงกันในขณะนี้แล้ว: ดูhttp://mail.python.org/pipermail/python-ideas/2009-October/006194.html
  • __del__ฟังก์ชั่นสามารถโกงประหยัดการอ้างอิงไปยังวัตถุและหยุดเก็บขยะ
  • ข้อยกเว้นที่ยกขึ้นอย่างชัดเจน__del__จะถูกละเว้น
  • __del__เติมเต็มมากกว่า__new__ __init__สิ่งนี้ทำให้สับสน โปรดดูhttp://www.algorithm.co.il/blogs/programming/python-gotchas-1- del -is-not-the-opposite-of- init /สำหรับคำอธิบายและ gotchas
  • __del__ไม่ใช่ลูกที่ "เป็นที่รัก" ใน Python คุณจะสังเกตเห็นว่าเอกสาร sys.exit () ไม่ได้ระบุว่ามีการรวบรวมขยะก่อนออกหรือไม่และมีปัญหาแปลก ๆ มากมาย โทร__del__ใน Globals ทำให้เกิดปัญหาการสั่งซื้อคี่เช่นhttp://bugs.python.org/issue5099 ควร__del__เรียกแม้ว่าจะ__init__ล้มเหลว? ดูhttp://mail.python.org/pipermail/python-dev/2000-March/thread.html#2423สำหรับเธรดแบบยาว

แต่ในทางกลับกัน:

  • __del__หมายความว่าคุณอย่าลืมเรียกแถลงการณ์ปิด ดูhttp://eli.thegreenplace.net/2009/06/12/safely-using-destructors-in-python/สำหรับ__del__มุมมองระดับมืออาชีพ โดยปกติจะเกี่ยวกับการปลดปล่อย ctypes หรือทรัพยากรพิเศษอื่น ๆ

และเหตุผล pesonal ของฉันที่ไม่ชอบ__del__ฟังก์ชั่นนี้

  • ทุกครั้งที่มีคนพูดขึ้นมา__del__จะกลายเป็นข้อความแห่งความสับสนสามสิบข้อความ
  • มันทำลายรายการเหล่านี้ใน Zen of Python:
    • ง่ายดีกว่าซับซ้อน
    • กรณีพิเศษไม่พิเศษพอที่จะทำลายกฎ
    • ข้อผิดพลาดไม่ควรผ่านไปอย่างเงียบ ๆ
    • เมื่อเผชิญกับความคลุมเครือให้ปฏิเสธสิ่งล่อใจที่จะคาดเดา
    • ควรมีวิธีเดียวและควรมีวิธีเดียวที่ชัดเจน
    • หากการนำไปใช้นั้นยากที่จะอธิบายก็เป็นความคิดที่ไม่ดี

__del__ดังนั้นหาเหตุผลที่จะไม่ใช้


6
แม้ว่าคำถามจะไม่ตรง: ทำไมเราไม่ควรใช้__del__แต่จะโทร__del__อย่างไรคำตอบของคุณก็น่าสนใจ
nbro

ขอบคุณ. บางครั้งความคิดที่ดีที่สุดคือหลีกเลี่ยงความคิดที่น่ากลัว
Charles Merriam

ในข่าวอื่น ๆ ฉันลืมที่จะพูดถึงว่า PyPy (ล่ามเร็วขึ้นสำหรับปพลิเคชันทำงานอีกต่อไป) จะทำลายในเดล
Charles Merriam

ขอบคุณ @Gloin สำหรับการอัปเดตลิงก์เสีย!
Charles Merriam

@CharlesMerriam ขอขอบคุณที่คุณสำหรับคำตอบ!
Tom Burrows

14

__del__วิธีการก็จะถูกเรียกว่าเมื่อวัตถุถูกเก็บขยะ โปรดทราบว่าไม่จำเป็นต้องรับประกันว่าจะถูกเรียกว่า รหัสต่อไปนี้ไม่จำเป็นต้องทำด้วยตัวเอง:

del obj

เหตุผลที่delเพียงแค่ลดจำนวนการอ้างอิงลงทีละรายการ หากมีสิ่งอื่นอ้างอิงถึงวัตถุ__del__จะไม่ถูกเรียก

มีข้อแม้บางประการในการใช้__del__แม้ว่า โดยทั่วไปมักจะไม่ค่อยมีประโยชน์ มันเสียงกับผมมากขึ้นเช่นคุณต้องการที่จะใช้วิธีการปิดหรืออาจจะมีคำสั่ง

ดูเอกสารหลามใน__del__วิธีการ

สิ่งที่ควรทราบอีกประการหนึ่ง: __del__วิธีการต่างๆสามารถยับยั้งการเก็บขยะได้หากใช้มากเกินไป โดยเฉพาะอย่างยิ่งการอ้างอิงแบบวงกลมที่มีวัตถุมากกว่าหนึ่งชิ้นด้วย__del__วิธีการจะไม่ได้รับการรวบรวมขยะ นี่เป็นเพราะคนเก็บขยะไม่รู้จะเรียกอันไหนก่อน ดูเอกสารประกอบเกี่ยวกับโมดูล gcสำหรับข้อมูลเพิ่มเติม


8

__del__วิธี (สะกดทราบ!) เรียกว่าเมื่อวัตถุของคุณจะถูกทำลายในที่สุด พูดในทางเทคนิค (ใน cPython) นั่นคือเมื่อไม่มีการอ้างอิงถึงวัตถุของคุณอีกต่อไปกล่าวคือเมื่อมันอยู่นอกขอบเขต

หากคุณต้องการลบวัตถุของคุณและเรียก__del__ใช้เมธอดนี้

del obj1

ซึ่งจะลบวัตถุ (หากไม่มีการอ้างอิงอื่นใด)

ฉันขอแนะนำให้คุณเขียนชั้นเรียนขนาดเล็กเช่นนี้

class T:
    def __del__(self):
        print "deleted"

และตรวจสอบในล่ามงูเหลือมเช่น

>>> a = T()
>>> del a
deleted
>>> a = T()
>>> b = a
>>> del b
>>> del a
deleted
>>> def fn():
...     a = T()
...     print "exiting fn"
...
>>> fn()
exiting fn
deleted
>>>   

โปรดทราบว่า jython และ ironpython มีกฎที่แตกต่างกันเมื่อวัตถุถูกลบและ__del__ถูกเรียกใช้ ไม่ถือว่าเป็นแนวทางปฏิบัติที่ดีที่จะใช้__del__แม้ว่าด้วยเหตุนี้และความจริงที่ว่าวัตถุและสภาพแวดล้อมอาจอยู่ในสถานะที่ไม่รู้จักเมื่อถูกเรียกใช้ ไม่รับประกัน__del__ว่าจะถูกเรียกอย่างใดอย่างหนึ่ง - ล่ามสามารถออกได้หลายวิธีโดยไม่ต้องลบวัตถุทั้งหมด


1
เมื่อเทียบกับstackoverflow.com/a/2452895/611007และstackoverflow.com/a/1481512/611007 , use del obj1ดูเหมือนว่าเป็นความคิดที่ดีที่จะพึ่งพา
n611x007

0

ดังที่ได้กล่าวไว้ก่อนหน้านี้การ__del__ทำงานค่อนข้างไม่น่าเชื่อถือ ในกรณีที่อาจดูเหมือนมีประโยชน์ให้พิจารณาใช้__enter__and __exit__method แทน สิ่งนี้จะให้ลักษณะการทำงานคล้ายกับwith open() as f: passไวยากรณ์ที่ใช้ในการเข้าถึงไฟล์ __enter__จะถูกเรียกโดยอัตโนมัติเมื่อเข้าสู่ขอบเขตของwithในขณะที่__exit__ถูกเรียกโดยอัตโนมัติเมื่อออกจากขอบเขต ดูคำถามนี้สำหรับรายละเอียดเพิ่มเติม

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