open()如何使用和不使用`with`?

时间:2014-04-03 11:23:12

标签: python

我想写一个类似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'

3 个答案:

答案 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()

两者都可以完美地运行:)