假设我想在open
和close
时间使用额外的操作扩展内置文件抽象。在Python 2.7中,这可以工作:
class ExtFile(file):
def __init__(self, *args):
file.__init__(self, *args)
# extra stuff here
def close(self):
file.close(self)
# extra stuff here
现在我正在考虑将程序更新为Python 3,其中open
是一个工厂函数,可能会从io
模块中返回任意几个不同类的实例,具体取决于它是如何实现的调用。我原则上可以将它们全部子类化,但这很乏味,我必须重新实现open
所做的调度。 (在Python 3中,二进制文件和文本文件之间的区别比2.x更重要,我需要两者。)这些对象将被传递给库代码,这些代码可能与它们做任何事情,所以这些成语制作一个“类文件”的鸭子类来包装open
的返回值并转发必要的方法将是最冗长的。
有人建议使用3.x方法,除了显示的2.x代码之外,尽可能少的附加样板吗?
答案 0 :(得分:15)
您可以改为使用上下文管理器。例如这一个:
class SpecialFileOpener:
def __init__ (self, fileName, someOtherParameter):
self.f = open(fileName)
# do more stuff
print(someOtherParameter)
def __enter__ (self):
return self.f
def __exit__ (self, exc_type, exc_value, traceback):
self.f.close()
# do more stuff
print('Everything is over.')
然后你可以像这样使用它:
>>> with SpecialFileOpener('C:\\test.txt', 'Hello world!') as f:
print(f.read())
Hello world!
foo bar
Everything is over.
无论如何,对文件对象(和其他资源)首选使用带有with
的上下文块。
答案 1 :(得分:11)
tl; dr 使用上下文管理器。请参阅本答案的底部,了解有关它们的重要注意事项。
Python 3中的文件变得更加复杂。虽然有些方法可以在普通用户类上使用,但这些方法不适用于内置类。一种方法是在实现它之前混合一个所需的类,但这需要知道混合类首先应该是什么:
class MyFileType(???):
def __init__(...)
# stuff here
def close(self):
# more stuff here
因为有这么多类型,未来可能会添加更多类型(不太可能,但可能),我们不确定哪些将在之后返回致电open
,此方法无效。
另一种方法是更改我们的自定义类型以使返回的文件___bases__
,并将返回的实例的__class__
属性修改为我们的自定义类型:
class MyFileType:
def close(self):
# stuff here
some_file = open(path_to_file, '...') # ... = desired options
MyFileType.__bases__ = (some_file.__class__,) + MyFile.__bases__
但这会产生
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __bases__ assignment: '_io.TextIOWrapper' deallocator differs from 'object'
另一种可以与纯用户类一起使用的方法是直接从返回的实例的类中动态创建自定义文件类型,然后更新返回的实例的类:
some_file = open(path_to_file, '...') # ... = desired options
class MyFile(some_file.__class__):
def close(self):
super().close()
print("that's all, folks!")
some_file.__class__ = MyFile
但又一次:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __class__ assignment: only for heap types
所以,看起来它是Python 3中最好的方法,幸运的是它也适用于Python 2(如果你想在两个版本上使用相同的代码库,那么它很有用)就是拥有一个自定义的上下文管理器:
class Open(object):
def __init__(self, *args, **kwds):
# do custom stuff here
self.args = args
self.kwds = kwds
def __enter__(self):
# or do custom stuff here :)
self.file_obj = open(*self.args, **self.kwds)
# return actual file object so we don't have to worry
# about proxying
return self.file_obj
def __exit__(self, *args):
# and still more custom stuff here
self.file_obj.close()
# or here
并使用它:
with Open('some_file') as data:
# custom stuff just happened
for line in data:
print(line)
# data is now closed, and more custom stuff
# just happened
需要注意的一点是:__init__
或__enter__
中的任何未处理的异常都会阻止__exit__
运行,因此在这两个位置您仍需要使用{{1} } / try
和/或except
/ try
成语,以确保您不会泄漏资源。
答案 2 :(得分:7)
我遇到了类似的问题,并且要求支持Python 2.x和3.x.我所做的与以下(current full version)类似:
class _file_obj(object):
"""Check if `f` is a file name and open the file in `mode`.
A context manager."""
def __init__(self, f, mode):
if isinstance(f, str):
self.file = open(f, mode)
else:
self.file = f
self.close_file = (self.file is not f)
def __enter__(self):
return self
def __exit__(self, *args, **kwargs):
if (not self.close_file):
return # do nothing
# clean up
exit = getattr(self.file, '__exit__', None)
if exit is not None:
return exit(*args, **kwargs)
else:
exit = getattr(self.file, 'close', None)
if exit is not None:
exit()
def __getattr__(self, attr):
return getattr(self.file, attr)
def __iter__(self):
return iter(self.file)
它将所有调用传递给基础文件对象,并可以从打开的文件或文件名初始化。也可用作上下文管理器。灵感来自this answer。