2014-10-02 14 views
8

Tôi đang làm việc trong Python 2.7 và tôi thích vấn đề đó khiến tôi khó hiểu.Tại sao thiết lập một phương pháp ràng buộc đối tượng python tạo tham chiếu vòng tròn?

Đó là ví dụ đơn giản nhất:

>>> class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

>>> a = A() 
>>> del a 
DEL 

Đó là OK như mong đợi ... bây giờ tôi đang cố gắng để thay đổi phương pháp a() của đối tượng a và những gì xảy ra là sau khi thay đổi nó, tôi không thể xóa a nữa:

>>> a = A() 
>>> a.a = a.a 
>>> del a 

Chỉ cần làm một số kiểm tra tôi đã in a.a tham khảo trước và sau khi chuyển nhượng

>>> a = A() 
>>> print a.a 
<bound method A.a of <__main__.A object at 0xe86110>> 
>>> a.a = a.a 
>>> print a.a 
<bound method A.a of <__main__.A object at 0xe86110>> 

Cuối cùng tôi đã sử dụng objgraph module để cố gắng tìm hiểu tại sao các đối tượng không được giải phóng:

>>> b = A() 
>>> import objgraph 
>>> objgraph.show_backrefs([b], filename='pre-backref-graph.png') 

pre-backref-graph.png

>>> b.a = b.a 
>>> objgraph.show_backrefs([b], filename='post-backref-graph.png') 

post-backref-graph.png

Như bạn có thể nhìn thấy trong post-backref-graph.png hình ảnh có là một tài liệu tham khảo __self__ trong b mà không có ý nghĩa đối với tôi vì tự r eferences của phương pháp dụ nên được bỏ qua (như trước khi phân công).

Ai đó có thể giải thích lý do hành vi đó và cách tôi có thể giải quyết nó?

Trả lời

3

Khi bạn viết a.a, nó có hiệu quả chạy:

A.a.__get__(a, A) 

vì bạn không truy cập vào một phương pháp trước ràng buộc nhưng lớp phương pháp rằng đang được ràng buộc khi chạy.

Khi bạn làm

a.a = a.a 

bạn có hiệu quả "cache" hành vi ràng buộc phương pháp. Khi phương thức liên kết có một tham chiếu đến đối tượng (rõ ràng, vì nó phải vượt qua self đến hàm), điều này tạo ra một tham chiếu vòng tròn.


Vì vậy, tôi đang mô hình hóa vấn đề của bạn như:

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls(function): 
    def inner(*args, **kwargs): 
     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls(a.a) 

a.a() 

Bạn có thể sử dụng tài liệu tham khảo yếu để ràng buộc theo yêu cầu bên lof_all_calls như:

import weakref 

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls_weakmethod(method): 
    cls = method.im_class 
    func = method.im_func 
    instance_ref = weakref.ref(method.im_self) 
    del method 

    def inner(*args, **kwargs): 
     instance = instance_ref() 

     if instance is None: 
      raise ValueError("Cannot call weak decorator with dead instance") 

     function = func.__get__(instance, cls) 

     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls_weakmethod(a.a) 

a.a() 

Điều này thực sự xấu xí, vì vậy tôi thà giải nén nó ra để tạo một trang trí weakmethod:

import weakref 

def weakmethod(method): 
    cls = method.im_class 
    func = method.im_func 
    instance_ref = weakref.ref(method.im_self) 
    del method 

    def inner(*args, **kwargs): 
     instance = instance_ref() 

     if instance is None: 
      raise ValueError("Cannot call weak method with dead instance") 

     return func.__get__(instance, cls)(*args, **kwargs) 

    return inner 

class A(object): 
    def __del__(self): 
     print("DEL") 
    def a(self): 
     pass 

def log_all_calls(function): 
    def inner(*args, **kwargs): 
     print("Calling {}".format(function)) 

     try: 
      return function(*args, **kwargs) 
     finally: 
      print("Called {}".format(function)) 

    return inner 

a = A() 
a.a = log_all_calls(weakmethod(a.a)) 

a.a() 

Xong!


FWIW, không chỉ Python 3.4 không có những vấn đề này, nó cũng có WeakMethod được tạo sẵn cho bạn.

+0

Ok ... Có cách nào để tránh điều đó? Tôi nên cache một số phương thức và khôi phục lại phương thức sau: có khả thi không? –

+0

Nó phụ thuộc vào những gì bạn đang cố gắng làm. – Veedrac

+0

OK Tôi tìm thấy giải pháp: aa = types.MethodType (Aa, a, A) –

4

Câu trả lời của Veedrac về phương pháp ràng buộc giữ tham chiếu đến cá thể chỉ là một phần của câu trả lời. thu gom rác CPython của biết làm thế nào để phát hiện và xử lý tài liệu tham khảo cyclic - trừ khi một số đối tượng đó là một phần của chu kỳ có một phương pháp __del__, như đã đề cập ở đây https://docs.python.org/2/library/gc.html#gc.garbage:

Đối tượng có __del__() phương pháp và là một phần của chu kỳ tài liệu tham khảo làm cho toàn bộ chu trình tham chiếu không thể thu thập được, bao gồm các đối tượng không nhất thiết phải trong chu kỳ nhưng chỉ có thể truy cập được từ chu trình đó. Python không tự động thu thập các chu kỳ như vậy bởi vì, nói chung, không thể để Python đoán một lệnh an toàn để chạy các phương thức __del__(). (...) Thường tốt hơn để tránh vấn đề bằng cách không tạo chu kỳ chứa các đối tượng có phương thức __del__() và có thể kiểm tra thùng rác trong trường hợp đó để xác minh rằng không có chu kỳ nào là đang được tạo.

IOW: xóa phương thức __del__ của bạn và bạn sẽ ổn.

EDIT: wrt/bình luận của bạn:

tôi sử dụng nó trên các đối tượng như chức năng a.a = functor(a.a). Khi thử nghiệm được thực hiện, tôi muốn thay thế hàm functor theo phương thức gốc.

Sau đó, giải pháp là đồng bằng và đơn giản:

a = A() 
a.a = functor(a.a) 
test(a) 
del a.a 

Cho đến khi bạn explicitely ràng buộc nó, a has no 'a' dụ atribute, vì vậy nó nhìn lên trên lớp và một method dụ mới được trả về (cf https://wiki.python.org/moin/FromFunctionToMethod để biết thêm về điều này). Ví dụ method này sau đó được gọi, và (thường) bị loại bỏ.

+0

Đó không phải là giải pháp .... Tôi cần phương pháp __del__ (lý do nằm ngoài phạm vi ở đây) và vấn đề duy nhất là tôi không biết cách giải quyết cho các tham chiếu yếu khác hoạt động tốt –

+1

, nó thực sự trả lời câu hỏi của bạn "Ai đó có thể giải thích lý do tại sao hành vi đó và làm thế nào tôi có thể làm việc xung quanh nó?" - bạn đã không đề cập đến bạn cần '__del__' và đoạn trích của bạn không ngụ ý nó là của bất kỳ sử dụng thực tế nào;) Bây giờ nếu bạn nhìn vào liên kết tôi đã chỉ cho bạn, có một chút về chủ đề ... –

+0

Như viết trong liên kết bạn đăng cách tốt nhất Không tạo chu trình và những gì tôi hỏi là "Tôi không hiểu tại sao chu kỳ đó sinh ra và nếu có cách nào để không tạo chu kỳ đó" ... nhưng xóa __del__ không loại bỏ chu kỳ, nhưng chỉ các tác dụng phụ chu kỳ :) –

1

Tại sao Python thực hiện việc này. Về mặt kỹ thuật, tất cả các đối tượng chứa tham chiếu vòng tròn nếu chúng có phương thức. Tuy nhiên, việc thu gom rác sẽ mất nhiều thời gian hơn nếu bộ thu gom rác phải thực hiện kiểm tra rõ ràng trên một phương thức đối tượng để đảm bảo giải phóng đối tượng sẽ không gây ra vấn đề gì. Như vậy Python lưu trữ các phương thức riêng biệt với một đối tượng là __dict__. Vì vậy, khi bạn viết a.a = a.a, bạn đang tự đánh bóng phương thức đó trong trường a trên đối tượng. Và do đó, có một tham chiếu rõ ràng đến phương pháp ngăn chặn các đối tượng được giải phóng đúng cách.

Giải pháp cho vấn đề của bạn không bận tâm để giữ "bộ nhớ cache" của phương thức gốc và chỉ xóa biến được tô bóng khi bạn đã hoàn thành nó. Thao tác này sẽ làm mờ phương thức và làm cho nó khả dụng trở lại.

>>> class A(object): 
...  def __del__(self): 
...   print("del") 
...  def method(self): 
...   print("method") 
>>> a = A() 
>>> vars(a) 
{} 
>>> "method" in dir(a) 
True 
>>> a.method = a.method 
>>> vars(a) 
{'method': <bound method A.method of <__main__.A object at 0x0000000001F07940>>} 
>>> "method" in dir(a) 
True 
>>> a.method() 
method 
>>> del a.method 
>>> vars(a) 
{} 
>>> "method" in dir(a) 
True 
>>> a.method() 
method 
>>> del a 
del 

Ở đây vars hiển thị nội dung trong thuộc tính __dict__ của đối tượng. Lưu ý cách __dict__ không chứa tham chiếu đến chính nó mặc dù a.__dict__ hợp lệ. dir tạo danh sách tất cả các thuộc tính có thể truy cập từ đối tượng đã cho. Ở đây chúng ta có thể thấy tất cả các thuộc tính và phương thức trên một đối tượng và tất cả các phương thức và các thuộc tính của các lớp và các cơ sở của chúng.Điều này cho thấy phương pháp ràng buộc của a được lưu trữ tại vị trí riêng biệt với các thuộc tính của a được lưu trữ.

+0

Cảm ơn câu trả lời của bạn nhưng câu hỏi thực sự không phải là" tại sao đối tượng không bị hủy? " nhưng tại sao 'a.a = a.a' tạo tham chiếu vòng tròn. Nếu bạn xóa ghi đè '__del__' và cố gắng vẽ đồ thị trước và sau khi gán, bạn sẽ thấy rằng các đồ thị khác nhau và biểu đồ thứ hai có tham chiếu vòng tròn. –

+0

Hiểu sai câu hỏi. Tôi đã hoàn toàn thay đổi câu trả lời của tôi, với một đề nghị mới và học được điều gì đó bản thân mình trong quá trình này. – Dunes

+0

THX: đó là chính xác những gì tôi đã có ý nghĩa với "Tại sao thiết lập một phương pháp ràng buộc để python đối tượng tạo một tham chiếu vòng tròn?". Veedrac đã cho tôi một cách để làm việc với một số vấn đề liên quan đến điều đó, nhưng với câu trả lời của bạn tôi thực sự hiểu. –

Các vấn đề liên quan