2013-03-15 36 views
7

Vì nó là kiến ​​thức phổ biến của python __del__ phương pháp không nên được sử dụng để làm sạch những điều quan trọng, vì nó không được đảm bảo phương pháp này được gọi là. Cách khác là sử dụng trình quản lý ngữ cảnh, như được mô tả trong một số chủ đề.Cách sử dụng trình quản lý ngữ cảnh để tránh sử dụng __del__ trong python?

Nhưng tôi không hoàn toàn hiểu cách viết lại một lớp để sử dụng trình quản lý ngữ cảnh. Để xây dựng, tôi có một ví dụ đơn giản (không làm việc) trong đó một lớp trình bao bọc sẽ mở và đóng một thiết bị, và sẽ đóng thiết bị trong mọi trường hợp thể hiện của lớp nằm ngoài phạm vi của nó (ngoại trừ vv).

Các tập tin đầu tiên mydevice.py là một lớp wrapper tiêu chuẩn để mở và đóng một thiết bị:

class MyWrapper(object): 
    def __init__(self, device): 
     self.device = device 

    def open(self): 
     self.device.open() 

    def close(self): 
     self.device.close() 

    def __del__(self): 
     self.close() 

lớp này được sử dụng bởi một lớp myclass.py:

import mydevice 


class MyClass(object): 

    def __init__(self, device): 

     # calls open in mydevice 
     self.mydevice = mydevice.MyWrapper(device) 
     self.mydevice.open() 

    def processing(self, value): 
     if not value: 
      self.mydevice.close() 
     else: 
      something_else() 

Câu hỏi của tôi: Khi tôi thực hiện quản lý ngữ cảnh trong mydevice.py với các phương thức __enter____exit__, cách lớp này có thể được xử lý trong myclass.py? Tôi cần phải làm điều gì đó như

def __init__(self, device): 
    with mydevice.MyWrapper(device): 
     ??? 

nhưng cách xử lý? Có lẽ tôi đã bỏ qua một cái gì đó quan trọng? Hoặc tôi có thể sử dụng một trình quản lý ngữ cảnh chỉ trong một hàm và không phải là một biến bên trong một phạm vi lớp?

Trả lời

4

Vấn đề không phải là bạn đang sử dụng nó trong một lớp học, đó là bạn muốn rời khỏi thiết bị theo cách "mở": bạn mở nó và sau đó chỉ để nó mở. Trình quản lý ngữ cảnh cung cấp một cách để mở một số tài nguyên và sử dụng nó theo một cách tương đối ngắn, chắc chắn, đảm bảo nó được đóng lại ở cuối. Mã hiện tại của bạn đã không an toàn, bởi vì nếu xảy ra sự cố, bạn không thể đảm bảo rằng __del__ sẽ được gọi, vì vậy thiết bị có thể được mở.

Không biết chính xác thiết bị là gì và cách hoạt động, khó nói nhiều hơn, nhưng ý tưởng cơ bản là, nếu có thể, tốt hơn là chỉ mở thiết bị ngay khi bạn cần sử dụng và sau đó đóng nó ngay lập tức sau đó. Vì vậy, processing của bạn là gì có thể cần phải thay đổi, một cái gì đó giống như hơn:

def processing(self, value): 
    with self.device: 
     if value: 
      something_else() 

Nếu self.device là một người quản lý bối cảnh một cách thích hợp bằng văn bản, cần mở thiết bị trong __enter__ và đóng nó trong __exit__. Điều này đảm bảo rằng thiết bị sẽ được đóng ở cuối khối with.

Tất nhiên, đối với một số loại tài nguyên, không thể thực hiện điều này (ví dụ: do mở và đóng thiết bị mất trạng thái quan trọng hoặc hoạt động chậm). Nếu đó là trường hợp của bạn, bạn đang bị mắc kẹt với việc sử dụng __del__ và sống với những cạm bẫy của nó. Vấn đề cơ bản là không có cách nào dễ dàng để rời khỏi thiết bị "mở kết thúc" nhưng vẫn đảm bảo nó sẽ bị đóng ngay cả trong trường hợp có một số lỗi chương trình bất thường.

+0

Nhận xét giống như trước: Tôi không biết trước điều gì sẽ xảy ra với thiết bị nhưng nó sẽ được mở và sử dụng trong một số phương thức, lớp học khác ... Đó có phải là trường hợp tôi vẫn nên sử dụng '__del__' thay thế không? – Alex

+0

Tôi nghĩ rằng '__del __()' là của bạn chỉ mở cho hành vi mở này kết thúc. Hãy nhớ rằng '__del __()' chỉ không được gọi nếu bộ thu gom rác tìm thấy một chu trình tham chiếu liên quan đến lớp của bạn. Bạn thậm chí có thể phát hiện và giải quyết điều này theo cách thủ công trong mã của mình bằng cách kiểm tra định kỳ ['gc.garbage'] (http://docs.python.org/2/library/gc.html#gc.garbage) và phá vỡ các chu kỳ tham chiếu đang gây ra sự cố. – Cartroo

0

Tôi không hoàn toàn chắc chắn những gì bạn đang yêu cầu. Một cá thể quản lý ngữ cảnh có thể là một thành viên lớp - bạn có thể sử dụng lại nó trong nhiều mệnh đề with như bạn muốn và các phương thức __enter__()__exit__() sẽ được gọi mỗi lần.

Vì vậy, khi bạn đã thêm các phương thức đó vào MyWrapper, bạn có thể tạo nó trong MyClass giống như bạn ở trên.Và sau đó bạn muốn làm điều gì đó như:

def my_method(self): 
    with self.mydevice: 
     # Do stuff here 

Đó sẽ gọi __enter__()__exit__() phương pháp trên dụ bạn đã tạo trong các nhà xây dựng.

Tuy nhiên, mệnh đề with chỉ có thể mở rộng một hàm - nếu bạn sử dụng mệnh đề with trong hàm dựng thì nó sẽ gọi __exit__() trước khi thoát khỏi hàm tạo. Nếu bạn muốn làm điều đó, cách duy nhất là sử dụng __del__(), trong đó có những vấn đề riêng của nó như bạn đã đề cập. Bạn có thể mở và đóng thiết bị chỉ khi bạn cần nó bằng cách sử dụng with nhưng tôi không biết nếu điều này đáp ứng yêu cầu của bạn.

+0

Giải pháp của bạn cũng chỉ hoạt động trong một chức năng! Điều gì xảy ra nếu tôi muốn mở một cái gì đó trong phương thức 'my_method()', hãy làm điều gì đó khác trong một hàm khác? Đề xuất của bạn sẽ không hoạt động. – Alex

+0

Như tôi đã chỉ ra ở phần cuối của câu trả lời, mệnh đề 'with' sẽ chỉ hoạt động trong hàm mà nó được sử dụng - đó chỉ là cách nó được định nghĩa. Bạn có thể sử dụng 'with' trong định nghĩa lớp nhưng nó sẽ chỉ áp dụng trong khi định nghĩa đã được xử lý. Nếu bạn muốn cùng một hiệu ứng trong suốt vòng đời của một lớp thì bạn sẽ cần phải sử dụng '__del __()', mặc dù nó có vấn đề. Giải pháp tốt hơn có lẽ là chỉ mở và đóng thiết bị trong mỗi chức năng. Xin lỗi nếu tôi không làm rõ điều đó. – Cartroo

14

Tôi khuyên bạn nên sử dụng lớp contextlib.contextmanager thay vì viết một lớp thực hiện __enter____exit__. Dưới đây là làm thế nào nó sẽ làm việc:

class MyWrapper(object): 
    def __init__(self, device): 
     self.device = device 

    def open(self): 
     self.device.open() 

    def close(self): 
     self.device.close() 

    # I assume your device has a blink command 
    def blink(self): 
     # do something useful with self.device 
     self.device.send_command(CMD_BLINK, 100) 

    # there is no __del__ method, as long as you conscientiously use the wrapper 

import contextlib 

@contextlib.contextmanager 
def open_device(device): 
    wrapper_object = MyWrapper(device) 
    wrapper_object.open() 
    try: 
     yield wrapper_object 
    finally: 
     wrapper_object.close() 
    return 

with open_device(device) as wrapper_object: 
    # do something useful with wrapper_object 
    wrapper_object.blink() 

Các dòng bắt đầu với một dấu hiệu được gọi là một trang trí. Nó sửa đổi khai báo hàm trên dòng tiếp theo.

Khi gặp phải tuyên bố with, hàm open_device() sẽ thực thi lên đến câu lệnh yield. Giá trị trong câu lệnh yield được trả lại trong biến là mục tiêu của mệnh đề as tùy chọn, trong trường hợp này là wrapper_object. Bạn có thể sử dụng giá trị đó giống như một đối tượng Python bình thường sau đó. Khi điều khiển thoát khỏi khối theo bất kỳ đường nào – bao gồm trường hợp ném ngoại lệ –, phần còn lại của hàm open_device sẽ thực thi.

Tôi không chắc chắn nếu (a) lớp trình bao bọc của bạn thêm chức năng vào API cấp thấp hơn hoặc (b) nếu đó chỉ là thứ bạn đang đưa vào để bạn có thể có trình quản lý ngữ cảnh. Nếu (b), sau đó bạn có thể có thể phân phối với nó hoàn toàn, kể từ khi contextlib chăm sóc đó cho bạn. Dưới đây là những gì mã của bạn có thể trông giống như sau:

import contextlib 

@contextlib.contextmanager 
def open_device(device): 
    device.open() 
    try: 
     yield device 
    finally: 
     device.close() 
    return 

with open_device(device) as device: 
    # do something useful with device 
    device.send_command(CMD_BLINK, 100) 

99% việc sử dụng trình quản lý ngữ cảnh có thể được thực hiện với contextlib.contextmanager. Nó là một lớp API cực kỳ hữu ích (và cách nó được triển khai cũng là một cách sử dụng sáng tạo của hệ thống ống nước Python cấp thấp hơn, nếu bạn quan tâm đến những thứ như vậy).

+0

Sự trở lại có cần thiết trong 'def open_device' không? –

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