接收文件对象或文件路径
我知道在静态语言中,接收一个文件对象总是比接收一个表示路径的字符串要好,这样从软件设计的角度来看更合理。不过在像Python这样的动态语言中,你看不到变量的类型,那么传递文件的“正确”方式是什么呢?
传递函数对象是不是有问题呢?因为你需要记得在之后关闭它(而且你可能不会记得,因为你看不到类型)?
4 个回答
你可以在Python中使用文件对象。当这些文件对象被自动清理时,文件会自动关闭。
文件对象是通过C语言的标准输入输出包来实现的,可以使用内置的open()函数来创建。
像其他类型一样传递文件对象。
def f(myfile):
myfile.write('asdf')
ofs = open(filepath, 'w') # ofs is file object
f(ofs) # passes file object
ofs.close()
也可以使用闭包。
def f():
return open(filepath, 'w') # returns file object
ofs = f()
ofs.write('something')
ofs.close()
不过,在像Python这样的动态语言中,你看不到变量的类型,传递文件的“正确”方式是什么呢?
简单来说,就是你不需要特别去考虑这个问题。
在大多数面向对象的编程语言中,有一个对象契约,保证如果一个对象有一个叫做quack
的方法,那它就知道怎么“呱呱叫”。有些语言在执行这个契约时非常严格(比如Java),而有些则没有那么严格。
最终,这个问题可以归结为Python的一个原则EAFP
:
Easier to ask for forgiveness than permission。这种常见的Python编码风格假设存在有效的键或属性,并在假设不成立时捕获异常。这种干净快速的风格通常会有很多try和except语句。这个方法与许多其他语言(如C)常用的LBYL风格形成对比。
LBYL = 先看后跳
这意味着,如果你的方法期待一个“文件”(而你在文档中说明了这一点),那么就假设你接收到的是一个“类似文件的对象”。尝试在这个对象上执行文件操作(比如read()
或close()
),如果出现异常再去捕获它。
EAFP方法的一个主要观点是,你可能接收到的是一个像文件一样工作的对象,换句话说,调用者知道他们在做什么。所以如果你花时间去检查具体的类型,你的代码可能在应该工作的情况下却不工作。现在,责任在于调用者去满足你的“对象契约”;但如果他们处理的不是文件,而是一个内存中的缓冲区(它有与文件相同的方法)呢?或者是一个请求对象(同样,它也有类似文件的方法)。你不可能在代码中检查所有这些变种。
这就是推荐的方法——而不是先进行类型检查的LBYL方法。
所以,如果你的方法文档说明它期待一个文件对象,它应该能与任何“类似文件”的对象一起工作,但当有人传递一个文件路径的字符串时,你的方法应该抛出一个合适的异常。
另外,更重要的是——你应该避免在你的方法中关闭这个对象;因为它可能不是一个“文件”,就像之前解释的那样。不过,如果你确实必须关闭,请确保你的方法文档中明确说明这一点。
下面是一个例子:
def my_method(fobj):
''' Writes to fobj, which is any file-like object,
and returns the object '''
try:
fobj.write('The answer is: {}\n'.format(42))
except (AttributeError, TypeError):
raise TypeError('Expected file-like object')
return fobj
理想情况下,每次打开文件时都应该使用with语句,这样文件会自动关闭。
with open('filepath', 'r') as f:
myfunc(f)
otherstuff() # f is now closed
来自文档的说明:
在处理文件对象时,使用with关键字是个好习惯。这样做的好处是,即使在执行过程中出现了错误,文件也会在操作完成后被正确关闭。