Sự kiện là một tình huống phổ biến cho các tham chiếu yếu.
Vấn đề
Xem xét một cặp đối tượng: Emitter và Receiver. Người nhận có tuổi thọ ngắn hơn so với bộ phát.
Bạn có thể thử một thực hiện như thế này:
class Emitter(object):
def __init__(self):
self.listeners = set()
def emit(self):
for listener in self.listeners:
# Notify
listener('hello')
class Receiver(object):
def __init__(self, emitter):
emitter.listeners.add(self.callback)
def callback(self, msg):
print 'Message received:', msg
e = Emitter()
l = Receiver(e)
e.emit() # Message received: hello
Tuy nhiên, trong trường hợp này, Emitter giữ một tham chiếu đến một phương pháp ràng buộc callback
mà giữ một tham chiếu đến Receiver. Vì vậy, Emitter giữ cho người nhận còn sống:
# ...continued...
del l
e.emit() # Message received: hello
Điều này đôi khi phiền hà. Hãy tưởng tượng rằng Emitter
là một phần của một số mô hình dữ liệu cho biết khi nào dữ liệu thay đổi và Receiver
được tạo bởi một cửa sổ hộp thoại nghe những thay đổi đó để cập nhật một số điều khiển giao diện người dùng.
Thông qua tuổi thọ của ứng dụng, nhiều hộp thoại có thể được sinh ra và chúng tôi không muốn người nhận của họ vẫn được đăng ký bên trong Trình phát sau khi cửa sổ đã đóng. Đó sẽ là một rò rỉ bộ nhớ.
Xóa các cuộc gọi lại theo cách thủ công là một tùy chọn (giống như phiền hà), sử dụng các tham chiếu yếu là một tham chiếu khác.
Giải pháp
Có một lớp đẹp WeakSet
trông giống như một bộ bình thường nhưng lưu trữ các thành viên sử dụng tài liệu tham khảo yếu và không còn lưu giữ chúng khi chúng được giải phóng.
Tuyệt vời! Hãy sử dụng nó:
def __init__(self):
self.listeners = weakref.WeakSet()
và chạy lại:
e = Emitter()
l = Receiver(e)
e.emit()
del l
e.emit()
Oh, không có gì xảy ra ở tất cả! Đó là bởi vì phương pháp bị ràng buộc (một người nhận cụ thể là callback
) là mồ côi ngay bây giờ - cả Emitter lẫn Người nhận đều không liên quan đến nó. Do đó nó được thu gom ngay lập tức.
Hãy làm cho Receiver (không phải là Emitter thời gian này) giữ một tham chiếu mạnh để gọi lại này:
class Receiver(object):
def __init__(self, emitter):
# Create the bound method object
cb = self.callback
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
Bây giờ chúng ta có thể quan sát hành vi mong đợi: Emitter chỉ giữ gọi lại chừng nào cuộc sống Receiver .
e = Emitter()
l = Receiver(e)
assert len(e.listeners) == 1
del l
import gc; gc.collect()
assert len(e.listeners) == 0
Dưới mui xe
Lưu ý rằng tôi đã phải chịu đựng một gc.collect()
đây để đảm bảo rằng người nhận được thực sự làm sạch ngay lập tức. Nó cần thiết ở đây bởi vì bây giờ có một chu kỳ tham chiếu mạnh mẽ: phương pháp ràng buộc đề cập đến người nhận và ngược lại.
Điều này không quá tệ; điều này chỉ có nghĩa là dọn dẹp của người nhận sẽ được hoãn lại cho đến khi người thu gom rác tiếp theo chạy. Không thể làm sạch các tham chiếu tuần hoàn bằng cơ chế đếm tham chiếu đơn giản.
Nếu bạn thực sự muốn, bạn có thể loại bỏ chu trình tham chiếu mạnh bằng cách thay thế phương thức bị ràng buộc bằng đối tượng hàm tùy chỉnh giữ nguyên tham chiếu yếu của nó.
def __init__(self, emitter):
# Create the bound method object
weakself = weakref.ref(self)
def cb(msg):
self = weakself()
self.callback(msg)
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
Hãy đặt logic đó vào một hàm helper:
def weak_bind(instancemethod):
weakref_self = weakref.ref(instancemethod.im_self)
func = instancemethod.im_func
def callback(*args, **kwargs):
self = weakref_self()
bound = func.__get__(self)
return bound(*args, **kwargs)
return callback
class Receiver(object):
def __init__(self, emitter):
cb = weak_bind(self.callback)
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
Bây giờ không có chu kỳ tài liệu tham khảo mạnh mẽ, vì vậy khi Receiver
được trả tự do, chức năng gọi lại cũng sẽ được trả tự do (và loại bỏ khỏi của Emitter WeakSet
) ngay lập tức mà không cần chu kỳ GC đầy đủ.
Hmm ... Tôi đã không nhận ra rằng bạn đã đề cập cache ... Tôi cho rằng tôi nên xóa câu trả lời của mình và đọc kỹ hơn lần sau :-). – Tom
Vâng, nó chỉ là một đề cập đến một lớp lót của cache. Sau đó tôi mở rộng câu trả lời, * sau * bạn đã đăng của bạn :) –
Ok, ít nhất là tôi không điên :-). Nó không nói rằng bài viết của bạn đã được chỉnh sửa mặc dù. Dù bằng cách nào .. Tôi thích câu trả lời của bạn sau khi tôi đọc nó một lần nữa ... và tôi không muốn đi tắt như danh tiếng đói :-). – Tom