2010-03-12 34 views
31

Có ai có thể giải thích cách sử dụng các tham chiếu yếu không?Khi nào sử dụng các tham chiếu yếu trong Python?

documentation không giải thích chính xác, nó chỉ nói rằng GC có thể phá hủy đối tượng được liên kết với thông qua tham chiếu yếu bất kỳ lúc nào. Vậy thì có vấn đề gì khi có một vật thể có thể biến mất bất cứ lúc nào? Nếu tôi cần sử dụng nó ngay sau khi nó biến mất thì sao?

Bạn có thể giải thích chúng bằng một số ví dụ hay không?

Cảm ơn

Trả lời

24

Việc sử dụng tiêu biểu để tham khảo yếu là nếu A có một tham chiếu đến B và B có một tham chiếu đến A. Nếu không có một nhà sưu tập rác chu kỳ phát hiện thích hợp, hai đối tượng sẽ không bao giờ có được GC'd thậm chí nếu không có tham chiếu đến từ "bên ngoài". Tuy nhiên nếu một trong các tham chiếu là "yếu", các đối tượng sẽ nhận được GC'd đúng cách.

Tuy nhiên, Python không có một bộ thu rác chu kỳ phát hiện (từ 2.0!), Vì vậy mà không đếm :)

sử dụng khác để tham khảo yếu là dành cho lưu trữ. Nó được đề cập trong tài liệu weakref:

Việc sử dụng chính để tham chiếu yếu là thực hiện một đối tượng lớn không bị giữ chỉ vì nó xuất hiện trong bộ nhớ cache hoặc ánh xạ.

Nếu GC quyết định hủy một trong các đối tượng đó và bạn cần, bạn chỉ có thể tính toán lại/tải lại dữ liệu.

+1

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

+1

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 :) –

+0

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

25

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 đủ.

+0

Tôi không hiểu lý do đằng sau Listener._callbacks được thiết lập. Sẽ không gọi lại được giữ sống trong suốt cuộc đời của đối tượng Listener không? Đó là một phương pháp để đối tượng đó, sau khi tất cả. – Xion

+0

Hóa ra là không. 'Listener.callback' là một hàm, nhưng' l.callback' gọi 'yourmethod.__get __ (l, Listener) 'trả về một' instancemethod' có thể gọi được để ghi nhớ 'self' của nó. Nó luôn được tái tạo khi một phương pháp được nhìn lên. [Đọc tốt ở đây] (http://users.rcn.com/python/download/Descriptor.htm#functions-and-methods) – Kos

+0

Có thể bạn không muốn giữ tham chiếu đến đối tượng 'instancemetiond' tạm thời này, nhưng với chính đối tượng 'Listener' (kiểu Java). – Xion

1
- Weak references is an important concept in python, which is missing 
    in languages likes Java(java 1.5). 
- In Observer design pattern, generally Observable Object must maintain 
    weak references to the Observer object. 

    eg. A emits an event done() and B registers with A that, it want to 
    listen to event done(). Thus, whenever done() is emitted, B is 
    notified. But If B isn't required in application, then A must not 
    become an hinderance in the garbage collection in A(since A hold the 
    reference to B). Thus, if A has hold weak reference to B, and when 
    all the references to A are away, then B will be garbage collected. 
- It's also very useful in implementing caches. 
Các vấn đề liên quan