我很困惑如何安排Python上下文管理器可以在适当的位置做的所有事情。
据我了解,可能用于构建上下文管理器的元素包括:
as
使用)我认为我粗略地了解了这些元素在上下文管理器函数中的位置,但是完全不知道如何在类中安排它们。
上下文管理器函数和类是否有模板显示这些元素中的每一个都在函数和(特别是)类中?我在这里和其他地方看了很多例子,但是没有找到任何全面的例子,而且很多都使用我不能总是映射到上面每个构建块的实际代码。
我认为我基本上了解上下文管理器在通过函数实现时的行为:
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
但我不确定这是对的,我很困惑它如何映射到类中使用__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
答案 0 :(得分: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__
。
另请注意,finally
优于except
,除非您计划禁止客户端代码中的异常,这很少有用。所以__exit__
只是:
def __exit__(self, type, value, traceback):
... # F, H: cleanup
return False # don't suppress any exception
答案 1 :(得分: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”在Web上找到大量资源,这通常是您参考此模式的方式(尽管上下文管理器确实更正确)