5

cố gắng sửa đổi một trang trí không sử dụng một weakref, tôi stumbled trên các hành vi sau đây:Memory rò rỉ khi gọi __iadd__ qua __get__ mà không sử dụng tạm thời

import weakref 

class descriptor(object): 
    def __get__(self, instance, owner): 
     return proxy(instance) 

class proxy(object): 
    def __init__(self, instance): 
     self.instance = instance 

    def __iadd__(self, other): 
     return self 

class A(object): 
    descr = descriptor() 

def is_leaky(test_fn): 
    a = A() 
    wr = weakref.ref(a) 
    test_fn(a) 
    del a 
    return wr() is not None 

def test1(a): 
    tmp = a.descr 
    tmp += object() 

def test2(a): 
    a.descr += object() 

print(is_leaky(test1)) # gives False 
print(is_leaky(test2)) # gives True!!! 

Điều này có vẻ rất lạ với tôi, như tôi muốn mong cả hai trường hợp cư xử như nhau. Hơn nữa, từ sự hiểu biết của tôi về đếm tham chiếu và tuổi thọ đối tượng, tôi đã tin rằng trong cả hai trường hợp đối tượng cần được giải phóng.

Tôi đã thử nghiệm cả trên python2.7 và python3.3.

Đây có phải là lỗi hoặc hành vi có chủ ý không? Có cách nào để nhận cả hai cuộc gọi để có kết quả mong đợi (giải phóng đối tượng được đề cập)?

Tôi không muốn sử dụng một weakref trong proxy vì đây phá hủy ngữ nghĩa đối tượng đời đúng cho các phương pháp ràng buộc:

a = A() 
descr = a.descr 
del a # a is kept alive since descr is a bound method to a 
descr() # should execute a.descr() as expected 

Trả lời

5

Hai codepaths không tương đương.

Thêm hành vi tại chỗ trên hai toán tử, mục tiêu gán và mục được thêm vào. Trong test1 đó là temp, một biến địa phương, và tại chỗ Ngoài ra được phiên dịch sang sau:

temp = temp.__iadd__(object()) 

và kể từ khi bạn trở selftemp đề cập đến cùng một đối tượng, điều này trở thành temp = temp và tài liệu tham khảo được dọn dẹp sau khi thoát khỏi hàm.

Trong test2, bạn vấn đề phức tạp, kể từ bây giờ mô tả đang nhận được tham gia một lần nữa:

a.descr += object() 

trở thành:

a.descr = A.__dict__['descr'].__get__(a, A).__iadd__(object()) 

do đó bạn gán kết quả của A.__dict__['descr'].__get__(a, A) đến thể hiện thuộc tính a.descr; bộ mô tả không có phương pháp __set__() và không được tư vấn.

Nhưng, và đây là đánh bắt, đối tượng proxy giữ một tham chiếu đến a chính nó, a.descr.instance là một tham chiếu đến a! Bạn đã tạo tham chiếu vòng tròn.

Tham chiếu này giữ cho đối tượng sống đủ lâu để hiển thị thông qua tham chiếu yếu, nhưng ngay sau khi quá trình thu thập rác chạy và phá vỡ chu kỳ này, a sẽ không hoạt động.

Đạo đức của câu chuyện này? Không sử dụng __iadd__ kết hợp với bộ mô tả phi dữ liệu; bao gồm cả __get____set__ vì bạn cần kiểm soát những gì sẽ xảy ra khi kết quả được chỉ định lại.

+0

Thật vậy, việc thêm 'gc.collect()' vào 'is_leaky' sẽ phá vỡ tham chiếu vòng tròn và làm cho' is_leaky (test2) 'trả về False. – unutbu

+0

Có, thay đổi test2 thành a.descr .__ iadd __ (object()) dẫn đến sai thứ hai. – BartoszKP

+1

WOW. Điều này là tinh tế. Thêm một NOP '__set__' vào bộ mô tả để khắc phục sự cố. Cảm ơn nhiều! – coldfix