2016-02-11 56 views
9

在python中,有很多函數既可以作爲標準函數又可以作爲上下文管理器。例如open()可以被稱爲無論是作爲:Python:標準函數和上下文管理器?

my_file=open(filename,'w') 

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

都讓你可以用來做任何你需要一個my_file對象。一般來說,後者是可取的,但有時候也可能想要做前者。

我已經能夠找出如何寫一個上下文管理器,通過創建與__enter____exit__函數的類或使用上的功能的@contextlib.contextmanager裝飾和yield而非return。但是,當我這樣做時,我不能再直接使用函數 - 例如,使用裝飾器,我得到一個_GeneratorContextManager對象,而不是想要的結果。當然,如果我把它作爲一個類來做,我只會得到一個生成器類的實例,我認爲它基本上是一樣的。

那麼我怎麼能設計一個函數(或類),作爲一個函數,返回一個對象,或上下文管理器,返回_GeneratorContextManager或類似的?

編輯:

例如,說我有如下內容(這是高度簡化的)功能:

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

所以函數需要一系列的參數,確實的東西與他們,和使用這些東西的結果初始化一個類,然後它返回。最終結果是我有一個my_class的實例,就像我有一個file對象,如果我叫open。如果我想能夠使用此功能作爲一個上下文管理器,我可以修改它,如下所示:

@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 

的上下文管理器調用時,以下哪工作得很好,但我不再得到my_class當實例稱爲直接功能。也許我只是做錯了什麼?

編輯2:

請注意,我有完全控制權my_class,包括增加功能的能力。根據以下公認的答案,我可以推斷出我的困難源於一個基本的誤解:我想我所說的任何東西(上面的例子中的my_func)都需要具有__exit____enter__函數。這是不正確的。實際上,它只是功能返回my_class在上面的示例中)需要函數才能用作上下文管理器。

+2

'open'是一個返回類實例的函數。無論你使用'myfile = open(filename)'還是'以open(filename)作爲myfile',它仍然是同一類的一個實例。沒有什麼改變。 – zondo

+0

@zondo真的,我正在努力寫的功能。但是,當我將函數包裝在'@ contextlib.contextmanager'裝飾器中,並將其作爲標準函數調用時,我返回的類不是函數中的類「yield」。只有當我將其稱爲上下文管理器時,我才能獲得該類。我會添加一個簡單的例子。 – ibrewster

+1

在你的課堂上定義一個'__call__'方法。 –

回答

1

你要碰到的困難是,對於一個功能被同時用作上下文管理器(with foo() as x)和常規功能(x = foo()),返回的對象從功能需要有兩個__enter____exit__方法......並且在一般情況下沒有一種好方法來將方法添加到現有對象。

一種方法可能是創建一個使用__getattr__傳遞方法和屬性原始對象的包裝類:

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) 

但是,這將導致潛在的問題,因爲它不是正是一樣由原始函數返回的對象(例如,isinstance測試將失敗,某些內置函數如iter(obj)將無法​​按預期方式工作等)。

你也可以動態地繼承返回的對象如下所示: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__ ... 

但這種方法有問題,太(如鏈接文章中指出),這是一個神奇的水平,我個人止跌」不要輕易介紹的理由。

我一般喜歡要麼添加明確__enter____exit__方法,或者使用類似contextlib.closing一個幫手:

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

啊,我錯過的關鍵是對象*返回*需要'__enter__'和'__exit__'函數 - 不一定是對象(在本例中爲函數)*調用*。因此,通過將這些函數添加到函數返回的類中,使用'__enter__'函數簡單地返回'self',它就可以工作! – ibrewster

1

只是爲了清晰:如果你能夠改變my_class,你當然會的__enter__/__exit__描述符添加到那個班。

如果你無法改變my_class(這是我從你的問題推斷),這是我指的是解決方案:

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) 

作爲裝飾:

@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

我很能夠改變my_class。我的困惑源於這樣的事實:我沒有直接調用(或實例化)my_class,而是調用返回my_class實例的函數,我希望能夠將該函數用作函數或上下文經理 - 就像你可以用open()函數一樣。我沒有意識到它是需要作爲上下文管理器運行的*返回*對象 - 我認爲我需要作爲上下文管理器的函數。 – ibrewster