2016-02-11 28 views
9

Trong python, có nhiều chức năng hoạt động như cả chức năng chuẩn và trình quản lý ngữ cảnh. Ví dụ open() có thể được gọi là một trong hai như:Python: chức năng chuẩn và trình quản lý ngữ cảnh?

my_file=open(filename,'w') 

hoặc

with open(filename,'w') as my_file: 

Cả hai đem lại cho bạn một đối tượng my_file có thể được sử dụng để làm bất cứ điều gì bạn cần. Nói chung sau này là thích hợp hơn, nhưng có những lúc người ta có thể muốn làm như cũ là tốt.

tôi đã có thể tìm ra cách để viết một trình quản lý nội dung, hoặc bằng cách tạo ra một lớp học với __enter____exit__ chức năng hoặc bằng @contextlib.contextmanager trang trí trên một hàm và yield hơn return. Tuy nhiên, khi tôi làm điều này tôi không còn có thể sử dụng chức năng thẳng - bằng cách sử dụng trang trí, ví dụ, tôi nhận được một đối tượng _GeneratorContextManager trở lại chứ không phải là kết quả mong muốn. Tất nhiên, nếu tôi làm nó như một lớp, tôi sẽ chỉ nhận được một thể hiện của lớp máy phát điện, mà tôi cho rằng cơ bản là giống nhau.

Vậy làm cách nào tôi có thể thiết kế một hàm (hoặc lớp) hoạt động như một hàm, trả về một đối tượng hoặc trình quản lý ngữ cảnh, trả lại _GeneratorContextManager hoặc tương tự?

chỉnh sửa:

Ví dụ, nói rằng tôi có một chức năng như sau (đây là RẤT đơn giản):

def my_func(arg_1,arg_2): 
    result=arg_1+arg_2 
    return my_class(result) 

Vì vậy, các chức năng có một số tranh cãi, không thứ với họ, và sử dụng kết quả của công cụ đó để khởi tạo một lớp, sau đó nó sẽ trả về. Kết quả cuối cùng là tôi có một thể hiện của my_class, giống như tôi sẽ có một đối tượng file nếu tôi đã gọi là open. Nếu tôi muốn để có thể sử dụng chức năng này như một người quản lý bối cảnh, tôi có thể sửa đổi nó như vậy:

@contextlib.contextmanager 
def my_func(arg_1,arg_2): 
    result=arg_1+arg_2 # This is roughly equivalent to the __enter__ function 
    yield my_class(result) 
    <do some other stuff here> # This is roughly equivalent to the __exit__function 

nào chỉ hoạt động tốt khi gọi như một người quản lý bối cảnh, nhưng tôi không còn nhận được một thể hiện của my_class khi gọi là hàm thẳng. Có lẽ tôi chỉ đang làm điều gì đó sai?

Chỉnh sửa 2:

Lưu ý rằng tôi có toàn quyền kiểm soát my_class, bao gồm cả khả năng thêm chức năng với nó. Từ câu trả lời được chấp nhận dưới đây, tôi có thể suy ra rằng khó khăn của tôi bắt nguồn từ một sự hiểu lầm cơ bản: Tôi đã nghĩ rằng bất cứ điều gì tôi gọi (my_func trong ví dụ trên) cần có các chức năng __exit____enter__. Điều này LAF không đúng. Trên thực tế, chỉ có chức năng trả về (my_class trong ví dụ trên) cần có các chức năng để hoạt động như một trình quản lý ngữ cảnh.

+2

'open' là một hàm trả về một cá thể của một lớp. Cho dù bạn sử dụng 'myfile = open (tên tệp)' hay 'với tên tệp mở (open) như myfile', nó vẫn sẽ là một cá thể của cùng một lớp. Không có gì thay đổi. – zondo

+0

@zondo Đúng, như hàm tôi đang cố viết. Tuy nhiên, khi tôi bọc hàm trong trình trang trí '@ contextlib.contextmanager', và gọi nó là hàm chuẩn, lớp tôi lấy lại không phải là lớp tôi" sinh ra "từ hàm. Chỉ khi tôi gọi nó là người quản lý ngữ cảnh thì tôi mới có được lớp đó. Tôi sẽ thêm một ví dụ đơn giản. – ibrewster

+1

Xác định phương thức '__call__' trong lớp học của bạn. –

Trả lời

1

Khó khăn bạn sẽ chạy vào là đối với một chức năng để được sử dụng như một nhà quản lý cả hai bối cảnh (with foo() as x) và một chức năng thường xuyên (x = foo()), đối tượng trở về từ chức năng cần phải có cả hai __enter____exit__ các phương thức ... và không có cách nào tuyệt vời - trong trường hợp chung - để thêm các phương thức vào một đối tượng hiện có.

Một cách tiếp cận có thể là để tạo ra một lớp wrapper mà sử dụng __getattr__ để vượt qua các phương pháp và các thuộc tính cho đối tượng ban đầu:

class ContextWrapper(object): 
    def __init__(self, obj): 
     self.__obj = obj 

    def __enter__(self): 
     return self 

    def __exit__(self, *exc): 
     ... handle __exit__ ... 

    def __getattr__(self, attr): 
     return getattr(self.__obj, attr) 

Nhưng điều này sẽ gây ra những vấn đề tế nhị vì nó không phải là chính xác giống như đối tượng được trả về bởi hàm ban đầu (ví dụ: isinstance kiểm tra sẽ không thành công, một số nội trang dựng sẵn như iter(obj) sẽ không hoạt động như mong đợi, v.v.).

Bạn cũng có thể tự động phân lớp các đối tượng quay trở lại như đã chứng minh ở đây: https://stackoverflow.com/a/1445289/71522:

class ObjectWrapper(BaseClass): 
    def __init__(self, obj): 
     self.__class__ = type(
      obj.__class__.__name__, 
      (self.__class__, obj.__class__), 
      {}, 
     ) 
     self.__dict__ = obj.__dict__ 

    def __enter__(self): 
     return self 

    def __exit__(self, *exc): 
     ... handle __exit__ ... 

Nhưng phương pháp này có vấn đề quá (như đã nêu trong bài viết liên kết), và đó là một mức độ kỳ diệu Cá nhân tôi wouldn' t được giới thiệu thoải mái mà không cần mạnh mẽ biện minh.

Tôi thường thích hoặc thêm rõ ràng __enter____exit__ phương pháp, hoặc sử dụng một helper như contextlib.closing:

with closing(my_func()) as my_obj: 
    … do stuff … 
+0

Ah, khóa mà tôi bị thiếu là đối tượng * trả về * cần các hàm '__enter__' và' __exit__' - không nhất thiết là đối tượng (hàm trong trường hợp này) * được gọi là *.Vì vậy, bằng cách thêm các hàm đó vào lớp được hàm trả về, với hàm '__enter__' chỉ đơn giản trả về' self', nó hoạt động! – ibrewster

1

Chỉ cần cho rõ ràng: nếu bạn có thể thay đổi my_class, bạn sẽ tất nhiên thêm __enter__/__exit__ mô tả để lớp đó.

Nếu bạn không thể thay đổi my_class (mà tôi suy ra từ câu hỏi của bạn), đây là giải pháp tôi đã đề cập đến:

class my_class(object): 

    def __init__(self, result): 
     print("this works", result) 

class manage_me(object): 

    def __init__(self, callback): 
     self.callback = callback 

    def __enter__(self): 
     return self 

    def __exit__(self, ex_typ, ex_val, traceback): 
     return True 

    def __call__(self, *args, **kwargs): 
     return self.callback(*args, **kwargs) 


def my_func(arg_1,arg_2): 
    result=arg_1+arg_2 
    return my_class(result) 


my_func_object = manage_me(my_func) 

my_func_object(1, 1) 
with my_func_object as mf: 
    mf(1, 2) 

Là một trang trí:

@manage_me 
def my_decorated_func(arg_1, arg_2): 
    result = arg_1 + arg_2 
    return my_class(result) 

my_decorated_func(1, 3) 
with my_decorated_func as mf: 
    mf(1, 4) 
+1

Tôi hoàn toàn có thể thay đổi my_class. Sự nhầm lẫn của tôi xuất phát từ thực tế là tôi không gọi (hoặc instantiating) my_class trực tiếp - thay vào đó, tôi gọi một hàm trả về một thể hiện của my_class và tôi muốn có thể sử dụng hàm đó làm chức năng hoặc ngữ cảnh quản lý - giống như bạn có thể làm với hàm open(). Tôi đã không nhận ra rằng đó là đối tượng * trả về * cần thiết để hoạt động như một người quản lý ngữ cảnh - tôi nghĩ rằng chức năng tôi đã * gọi * cần thiết để trở thành một người quản lý ngữ cảnh. – ibrewster

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