Phương thức __del__ là gì, Làm thế nào để gọi nó?


108

Tôi đang đọc mã. Có một lớp trong đó __del__phương thức được định nghĩa. Tôi đã phát hiện ra rằng phương thức này được sử dụng để hủy một thể hiện của lớp. Tuy nhiên, tôi không thể tìm thấy một nơi mà phương pháp này được sử dụng. Lý do chính là tôi không biết phương pháp này được sử dụng như thế nào, có lẽ không phải như vậy : obj1.del(). Vì vậy, câu hỏi của tôi là làm thế nào để gọi __del__phương thức?

Câu trả lời:


167

__del__là một hoàn thiện . Nó được gọi khi một đối tượng được thu thập rác , xảy ra vào một thời điểm nào đó sau khi tất cả các tham chiếu đến đối tượng đã bị xóa.

Trong trường hợp đơn giản, điều này có thể xảy ra ngay sau khi bạn nói del xhoặc, nếu xlà một biến cục bộ, sau khi hàm kết thúc. Đặc biệt, trừ khi có các tham chiếu vòng tròn, CPython (triển khai Python tiêu chuẩn) sẽ thu gom rác ngay lập tức.

Tuy nhiên, đây là một chi tiết triển khai của CPython. Thuộc tính bắt buộc duy nhất của thu thập rác Python là nó xảy ra sau khi tất cả các tham chiếu đã bị xóa, vì vậy điều này có thể không cần thiết xảy ra ngay sau đócó thể hoàn toàn không xảy ra .

Thậm chí nhiều hơn, các biến có thể tồn tại trong một thời gian dài vì nhiều lý do , ví dụ: ngoại lệ lan truyền hoặc nội quan mô-đun có thể giữ số lượng tham chiếu biến lớn hơn 0. Ngoài ra, biến có thể là một phần của chu kỳ tham chiếu - CPython với tính năng thu gom rác được bật hầu hết , nhưng không phải tất cả, các chu kỳ như vậy, và thậm chí sau đó chỉ định kỳ.

Vì bạn không đảm bảo nó được thực thi, nên không bao giờ được đặt mã mà bạn cần chạy __del__()- thay vào đó, mã này thuộc về finallymệnh đề của trykhối hoặc của trình quản lý ngữ cảnh trong một withcâu lệnh. Tuy nhiên, có những trường hợp sử dụng hợp lệ cho __del__: ví dụ: nếu một đối tượng Xtham chiếu Yvà cũng giữ một bản sao của Ytham chiếu trong global cache( cache['X -> Y'] = Y) thì X.__del__việc xóa mục nhập bộ nhớ cache sẽ là lịch sự .

Nếu bạn biết rằng destructor cung cấp (trong vi phạm các quy định trên) một dọn dẹp cần thiết, bạn có thể muốn gọi nó là trực tiếp , vì không có gì đặc biệt về nó như là một phương pháp: x.__del__(). Rõ ràng, bạn chỉ nên làm như vậy nếu bạn biết rằng bạn không ngại bị gọi hai lần. Hoặc, phương sách cuối cùng, bạn có thể xác định lại phương pháp này bằng cách sử dụng

type(x).__del__ = my_safe_cleanup_method  

5
Bạn nói rằng tính năng xóa một đối tượng ngay lập tức sau khi số lượng tham chiếu của nó giảm xuống 0 là một "chi tiết triển khai" của CPython. Tôi không tin. Bạn có thể cung cấp liên kết sao lưu tuyên bố đó không? (Ý tôi là, phông chữ đậm tự khá thuyết phục, nhưng các liên kết chỉ gần một giây ... :-)
Stuart Berg

14
Chi tiết triển khai CPython: CPython hiện sử dụng lược đồ đếm tham chiếu với (tùy chọn) phát hiện chậm rác được liên kết theo chu kỳ, ... Các triển khai khác hoạt động khác và CPython có thể thay đổi. ( docs.python.org/2/reference/datamodel.html )
ilya n.

__exit__Cái gì với trong bối cảnh này? Nó chạy sau hay trước __del__hay cùng nhau?
lony

1
"Có thể hoàn toàn không xảy ra" có bao gồm khi chương trình kết thúc không?
Andy Hayden

1
@AndyHayden: __del__các phương thức có thể không chạy ngay cả khi chương trình kết thúc và ngay cả khi chúng chạy khi kết thúc, việc viết một __del__phương thức hoạt động bình thường ngay cả khi trình thông dịch bận tự hủy xung quanh bạn đòi hỏi phải viết mã cẩn thận hơn nhiều lập trình viên áp dụng. (Dọn dẹp CPython thường nhận được __del__các phương thức để chạy khi tắt trình thông dịch, nhưng vẫn có trường hợp nó không đủ. Các chuỗi daemon, hình cầu cấp C và các đối tượng __del__được tạo trong khác __del__đều có thể dẫn đến __del__các phương thức không chạy.)
user2357112 hỗ trợ Monica

80

Tôi đã viết câu trả lời cho một câu hỏi khác, mặc dù đây là một câu hỏi chính xác hơn cho nó.

Các hàm tạo và hàm hủy hoạt động như thế nào?

Đây là một câu trả lời hơi cố chấp.

Không sử dụng __del__. Đây không phải là C ++ hoặc một ngôn ngữ được xây dựng cho hàm hủy. Các __del__phương pháp thực sự cần được đi bằng Python 3.x, mặc dù tôi chắc rằng ai đó sẽ tìm thấy một trường hợp sử dụng có ý nghĩa. Nếu bạn cần sử dụng __del__, hãy lưu ý các giới hạn cơ bản theo http://docs.python.org/reference/datamodel.html :

  • __del__được gọi khi bộ thu gom rác tình cờ thu thập các đối tượng, không phải khi bạn mất tham chiếu cuối cùng đến một đối tượng và không phải khi bạn thực thi del object.
  • __del__chịu trách nhiệm gọi bất kỳ __del__lớp nào trong lớp cha, mặc dù không rõ liệu điều này có theo thứ tự phân giải phương thức (MRO) hay chỉ gọi mỗi lớp cha.
  • Có một __del__phương tiện mà trình thu gom rác từ bỏ việc phát hiện và làm sạch bất kỳ liên kết tuần hoàn nào, chẳng hạn như mất tham chiếu cuối cùng đến danh sách được liên kết. Bạn có thể lấy danh sách các đối tượng bị bỏ qua từ gc.garbage. Đôi khi bạn có thể sử dụng các tham chiếu yếu để tránh hoàn toàn chu kỳ. Điều này đang được tranh luận ngay bây giờ và sau đó: xem http://mail.python.org/pipermail/python-ideas/2009-October/006194.html .
  • Các __del__chức năng có thể ăn gian, tiết kiệm một tham chiếu đến một đối tượng, và dừng thu gom rác thải.
  • Các trường hợp ngoại lệ được nêu rõ ràng __del__sẽ bị bỏ qua.
  • __del__bổ sung __new__nhiều hơn __init__. Điều này trở nên khó hiểu. Hãy xem http://www.algorithm.co.il/blogs/programming/python-gotchas-1- del -is-not-the-counter-of- init / để biết giải thích và những vấn đề cần hiểu.
  • __del__không phải là một đứa trẻ "được yêu thích" trong Python. Bạn sẽ nhận thấy rằng tài liệu sys.exit () không chỉ định xem rác có được thu thập trước khi thoát hay không và có rất nhiều vấn đề kỳ lạ. Gọi __del__trên toàn cầu gây ra các vấn đề đặt hàng kỳ lạ, ví dụ: http://bugs.python.org/issue5099 . Nên __del__gọi ngay cả khi __init__thất bại? Xem http://mail.python.org/pipermail/python-dev/2000-March/thread.html#2423 để biết một chuỗi dài.

Nhưng mặt khác:

Và lý do cá nhân của tôi vì không thích __del__chức năng này.

  • Mỗi khi ai đó nhắc đến __del__nó sẽ biến thành ba mươi thông điệp nhầm lẫn.
  • Nó phá vỡ các mục này trong Zen of Python:
    • Đơn giản là tốt hơn phức tạp.
    • Các trường hợp đặc biệt không đủ đặc biệt để phá vỡ các quy tắc.
    • Lỗi không bao giờ nên trôi qua một cách âm thầm.
    • Khi đối mặt với sự mơ hồ, hãy từ chối sự cám dỗ để đoán.
    • Nên có một - và tốt nhất là chỉ một - cách hiển nhiên để làm điều đó.
    • Nếu việc triển khai khó giải thích, đó là một ý tưởng tồi.

Vì vậy, hãy tìm lý do để không sử dụng __del__.


6
Ngay cả khi câu hỏi không chính xác: tại sao chúng ta không nên sử dụng __del__, nhưng làm thế nào để gọi __del__, câu trả lời của bạn là thú vị.
nbro

Cảm ơn. Đôi khi, ý tưởng tốt nhất là tránh xa những ý tưởng tồi tệ.
Charles Merriam

Trong một tin khác, tôi đã quên đề cập rằng PyPy (một trình thông dịch nhanh hơn cho các ứng dụng chạy lâu hơn) sẽ bị hỏng trên del .
Charles Merriam

Cảm ơn bạn @Gloin đã cập nhật liên kết bị hỏng!
Charles Merriam

@CharlesMerriam Cảm ơn bạn đã trả lời!
Tom Burrows

13

Các __del__phương pháp, nó sẽ được gọi khi đối tượng là thu gom rác thải. Lưu ý rằng nó không nhất thiết phải được gọi là mặc dù. Bản thân đoạn mã sau sẽ không nhất thiết phải làm điều đó:

del obj

Lý do là delchỉ giảm số lượng tham chiếu đi một. Nếu một cái gì đó khác có tham chiếu đến đối tượng, __del__sẽ không được gọi.

Tuy nhiên, có một số lưu ý khi sử dụng __del__. Nói chung, chúng thường không hữu ích lắm. Đối với tôi, có vẻ như bạn muốn sử dụng phương thức đóng hoặc có thể là câu lệnh with .

Xem tài liệu python về __del__các phương pháp .

Một điều khác cần lưu ý: __del__các phương pháp có thể hạn chế việc thu gom rác nếu lạm dụng quá mức. Đặc biệt, một tham chiếu vòng tròn có nhiều hơn một đối tượng với một __del__phương thức sẽ không được thu thập rác. Điều này là do người thu gom rác không biết phải gọi cái nào trước. Xem tài liệu về mô-đun gc để biết thêm thông tin.


8

Các __del__phương pháp (lưu ý chính tả!) Được gọi khi đối tượng của bạn cuối cùng cũng bị phá hủy. Nói về mặt kỹ thuật (trong cPython) đó là khi không còn tham chiếu nào đến đối tượng của bạn, tức là khi nó vượt ra khỏi phạm vi.

Nếu bạn muốn xóa đối tượng của mình và do đó hãy gọi __del__phương thức sử dụng

del obj1

sẽ xóa đối tượng (miễn là không có bất kỳ tham chiếu nào khác đến nó).

Tôi đề nghị bạn viết một lớp học nhỏ như thế này

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

Và điều tra trong trình thông dịch python, ví dụ:

>>> 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
>>>   

Lưu ý rằng jython và ironpython có các quy tắc khác nhau về chính xác thời điểm đối tượng bị xóa và __del__được gọi. __del__Mặc dù vậy, nó không được coi là phương pháp hay vì điều này và thực tế là đối tượng và môi trường của nó có thể ở trạng thái không xác định khi nó được gọi. Nó cũng không được đảm bảo tuyệt đối __del__sẽ được gọi - trình thông dịch có thể thoát theo nhiều cách khác nhau mà không xóa tất cả các đối tượng.


1
so với stackoverflow.com/a/2452895/611007stackoverflow.com/a/1481512/611007 , use del obj1có vẻ như là một ý tưởng tồi để dựa vào.
n611x007 10/09/13

0

Như đã đề cập trước đó, __del__chức năng này hơi không đáng tin cậy. Trong trường hợp nó có vẻ hữu ích, hãy xem xét sử dụng các phương thức __enter____exit__thay thế. Điều này sẽ cung cấp một hành vi tương tự như with open() as f: passcú pháp được sử dụng để truy cập tệp. __enter__được gọi tự động khi vào phạm vi with, trong khi __exit__được tự động gọi khi thoát khỏi phạm vi đó. Xem câu hỏi này để biết thêm chi tiết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.