我想写一个类似open
的函数。我希望能够使用with
来呼叫它,但也可以不使用with
。
当我使用contextlib.contextmanager
时,它会使我的功能与with
完美配合使用:
@contextmanager
def versioned(file_path, mode):
version = calculate_version(file_path, mode)
versioned_file = open(file_path, mode)
yield versioned_file
versioned_file.close()
所以,我这样使用它:
with versioned('file.txt', 'r') as versioned_file:
versioned_file.write(...)
如何在没有with
的情况下使用它:
versioned_file = versioned('file.txt', 'r')
versioned_file.write(...)
versioned_file.close()
它抱怨道:
AttributeError: 'GeneratorContextManager' object has no attribute 'write'
答案 0 :(得分:12)
问题是contextmanager
只提供了这个;要在with
语句中使用的上下文管理器。调用该函数不返回文件对象,而是一个特殊的上下文生成器,它提供__enter__
和__exit__
函数。如果你想让with
语句和“普通”赋值都工作,那么你必须有一些对象作为你的函数的返回值,它是完全可用的和也提供了上下文功能
您可以通过创建自己的类型并手动提供上下文功能来轻松完成此任务:
class MyOpener:
def __init__ (self, filename):
print('Opening {}'.format(filename))
def close (self):
print('Closing file.')
def write (self, text):
print('Writing "{}"'.format(text))
def __enter__ (self):
return self
def __exit__ (self, exc_type, exc_value, traceback):
self.close()
>>> f = MyOpener('file')
Opening file
>>> f.write('foo')
Writing "foo"
>>> f.close()
Closing file.
>>> with MyOpener('file') as f:
f.write('foo')
Opening file
Writing "foo"
Closing file.
答案 1 :(得分:6)
你有这个:
@contextmanager
def versioned(file_path, mode):
# some setup code
yield versioned_file
# some teardown code
您的基本问题当然是来自上下文管理器的yield
来自with
语句来自as
,但不是您的函数返回的对象。您需要一个返回与对象open()
返回的行为类似的函数。也就是说,一个自我产生的上下文管理器对象。
您是否可以这样做取决于您对versioned_file
类型的处理方式。如果你不能改变它,那么你基本上没有运气。如果您可以更改它,则需要实现PEP 343中指定的__enter__
和__exit__
函数。
在您的示例代码中,它已经拥有它,并且您的拆卸代码与它在上下文退出时已经执行的操作相同。因此,根本不需要使用contextlib,只需返回open()
的结果。
对于需要__enter__
和__exit__
的其他示例,如果您喜欢contextlib样式(以及谁不喜欢?),您可以将这两种情况联系起来。写一个用context
修饰的函数@contextmanager
并生成self
。然后执行:
def __enter__(self):
self.context = context() # if context() is a method use a different name!
return self.context.__enter__()
def __exit__(self, *args):
return self.context.__exit__(*args)
除了将设置代码分成__enter__
而将拆解代码分成__exit__
之外,您是否更好或更差,这基本取决于您。我通常觉得它更好。
答案 2 :(得分:4)
你真的需要使用contextlib.contextmanager吗? 如果您有自定义流,则需要使用Poke的解决方案。
但是因为你刚刚返回一个文件对象,为什么要经历所有麻烦:
def versioned(file_path, mode):
version = calculate_version(file_path, mode)
return open(file_path, mode)
with versioned('test.conf', 'r') as stream:
print stream.read()
f = versioned('test.conf', 'r')
print f.read()
f.close()
两者都可以完美地运行:)