2014-01-06 22 views
3

我很困惑如何安排Python上下文管理器可以做的所有事情到适当的位置。放在一起的Python上下文管理器:一个谜题

据我了解,这有可能进入全面建设上下文管理的要素包括:

  • 答:东西总是发生
  • B:需要对C
  • C语言带来的准备:创建并建立在上下文中使用的对象X
  • D:在上下文开始之前使用成功建立的X执行一些事情
  • E:将X返回到上下文(供使用)
  • F:结束语与X时一切正常在上下文
  • G的端部:进入上下文
  • 小时,然后应对故障的在C和后果B:应对故障的后果上下文

我想我大致得到这些元素在上下文管理器函数中的每个元素的位置,但对于如何将它们排列在类中完全不知所措。

是否有一个上下文管理器函数和类的模板,显示这些元素中的每一个都在函数和(尤其是)类中?我在这里和其他地方查看了很多示例,但没有发现任何全面的示例,并且许多示例使用实际的代码,我无法总是映射到上面的每个构建块。


认为我基本上明白通过函数来​​实现,当一个上下文管理器的行为:

from contextlib import contextmanager  
@contextmanager 
def log_file_open(oec_data, build_description, log_dir): 
    # A: Something that always happens 
    try: 
     # B: Some stuff needed to make a_thing 
     a_thing = establish_thing_in_a_way_that_might_fail() # C 
     # D: Some things that happen using a_thing at context start 
     yield a_thing # E 
     # F: Wrap up with a_thing when all is well 
    except: 
     # G: Deal the consequences of failure in try or... 
     # H: Deal the consequences of failure in context 
    finally: 
     # Could F go here instead? 

例如,打开的东西应该在成功打开并写入到一个文件关闭,但应该清理如果有问题,我可以写

from contextlib import contextmanager  
@contextmanager 
def log_file_open(oec_data, build_description, log_dir): 
    print('Entering context...') 
    try: 
     usable_file_name = get_some_name() 
     a_thing = open(usable_file_name, mode='w') 
     a_thing.write('Logging context started.') 
     yield a_thing 
     a_thing.write('Logging context ended.') 
    except: 
     a_thing.close() 
     os.remove(a_thing.name) 
     raise 

但我不确定这是对的, nd我很困惑它如何映射到__enter()____exit()__在类中的使用。是(示意性):

def __init__(self): 
    # A: Something that always happens 

def __enter__(self): 
    try: 
     # B: Some stuff needed to make a_thing 
     a_thing = establish_thing_in_a_way_that_might_fail() # C 
     # D: Some things that happen using a_thing at context start 
    except: 
     # G: Deal the consequences of failure in try 
     a_thing = some_appropriate_blank_value 
    finally: 
     return a_thing # E 

def __exit__(self, type, value, traceback): 
     if type is None: 
      # F: Wrap up with a_thing when all is well 
      return True 
     else: 
      # H: Deal the consequences of failure in context 
      return False 

回答

2

你正在混合错误处理在上下文本身中生成上下文值和错误处理。这是再好不过的写:

@contextmanager 
def fn(...): 
    value = ...  # A, B, C, D: setup 
    try: 
     yield value # E: pass value to client 
    except:   # or better, finally: 
     ...   # F, H: cleanup 

你知道你只处理源自客户端代码异常这种方式,和您简化清理代码,你知道,安装成功。试图在设置代码中处理异常通常没有意义;您不希望客户端代码必须处理None上下文值。这意味着,__enter__很简单:

def __enter__(self): 
    self.value = ... # A, B, C, D: setup 
    return self.value # E: pass value to client 

如果__enter__抛出一个异常,那么__exit__将不会被调用。

还要注意finallyexcept要好,除非你打算从客户端代码中排除异常,这是很少用的。所以__exit__只不过是:

def __exit__(self, type, value, traceback): 
    ...    # F, H: cleanup 
    return False  # don't suppress any exception 
1

我觉得你的理解大都是正确的。上下文管理器是对象,其通过其__enter____exit__方法来管理上下文。所以在__init__中会发生什么事情在对象的生命中保持真实。 让我们看一个具体的例子:

class CMan(object): 
    def __init__(self, *parameters): 
     "Creates a new context manager" 
     print "Creating object..." 

    def __enter__(self): 
     "Enters the manager (opening the file)" 
     print "Entering context..." 
     a_thing = self # Or any other relevant value to be used in this context 
     print "Returning %s" % a_thing 
     return a_thing 

    def __exit__(self, type, value, traceback): 
     "Exits the context" 
     if type is None: 
      print "Exiting with no exception -> Wrapping up" 
      return 
     print "Exiting with exception %s" % type 

这将被用作此:

>>> with CMan(1,2,3) as x: 
...  print 1 + 1 
Creating object... 
Entering context... 
Returning <__main__.CMan object at 0x02514F70> 
2 
Exiting with no exception -> Wrapping up 

需要注意的是在飞行中创建对象不是强制性的:

>>> mgr = CMan(1,2,3) 
Creating object... 
>>> with mgr as x: 
...  print 1 + 1 
Entering context... 
Returning <__main__.CMan object at 0x02514F70> 
2 
Exiting with no exception -> Wrapping up 

最后, __exit__的返回值决定是否应该提出异常。如果该值评估为False(例如False,0,None ...),则会引发任何异常。否则,这意味着上下文管理器已经处理了该异常,并且不需要提出。例如:

>>> class Arithmetic(object): 
...  def __enter__(self): 
...   return self 
...  def __exit__(self, type, value, traceback): 
...   if type == ZeroDivisionError: 
...    print "I dont care -> Ignoring" 
...    return True 
...   else: 
...    print "Unknown error: Panicking !" 
...    return False 

>>> with Arithmetic() as a: 
...  print 1/0 # Divide by 0 
I dont care -> Ignoring 

>>> with Arithmetic() as a: 
...  print 1 + "2" # Type error 
Unknown error: Panicking ! 
Traceback (most recent call last): 
    File "<stdin>", line 2, in <module> 
TypeError: unsupported operand type(s) for +: 'int' and 'str' 

需要注意的是在由0错误的鸿沟的情况下,作为__exit__返回True,误差不传播。在其他情况下,它退出上下文管理器后引发。你可以把一个呼叫到一个上下文管理器:

>>> with X as x: 
...  f(x) 

等价于:

>>> x = X.__enter__() 
>>> try: 
...  exc = None 
...  f(x)  
... except Exception as e: 
...  exc = e 
... finally: 
...  handled = X.__exit__(exc) 
...  if exc and not handled: 
...   raise exc 

当然,如果将引发异常你的方法__enter____exit__,它应该是处理得当,例如如果生成a_thing可能会失败。通过查找“Python with statement”,您可以在网上找到大量资源,这通常是您如何引用此模式(尽管上下文管理器确实更加正确)

相关问题