首页 资讯 业界 查看内容

Python的档案导向API

2016-5-3 20:40 1993 0

摘要: Python的档案导向API无论看哪个文件,无论翻开哪本书籍,谈到Python的档案处理,一律就是介绍open函式的使用,再搭配一个开档模式列表。承认吧!每隔一段时间没用,你就总是得再次确认那些r、w、x、a、+等模式,到底 ...

Python的档案导向API


无论看哪个文件,无论翻开哪本书籍,谈到Python的档案处理,一律就是介绍open函式的使用,再搭配一个开档模式列表。

承认吧!每隔一段时间没用,你就总是得再次确认那些r、w、x、a、+等模式,到底代表哪些操作。

若厌倦了老是重复查阅、记忆相关文件的这个过程,何妨来探索一下open函式的背后到底是怎么一回事呢?

初看Python的open函式

档案处理在程式设计中是很基本的需求,在Python中无论要读取、写入、更新档案或处理二进位资料,基本上只要运用open函式,其中最常使用的就是file与mode两个参数,最多就是在处理文字档案,而档案编码与作业系统预设编码(locale.getpreferredencoding()传回值)不同时,多指定个encoding参数。

看了一篇又一篇的文件之后,发现里面都是如此介绍就结束了,这让我觉得有些不安,档案处理真的只有这么简单吗?

回头看看我熟悉的Java,单是古老的I/O串流设计,除了InputStream、OutputStream各自的子类别外,还有着各种的装饰器(Decorator),像是BufferedInputStream、BufferedOutputStream等,较新的I /O处理,则有NIO、NIO2等,那么Python中有类似的东西吗?

当搜寻到的文件或可查阅的书籍无法满足疑问时,耐着性子查阅无趣的官方文件总是正确的决定。实际上在Python 3中,open函式共有file、mode、buffering、encoding、errors、newline、closefd、opener八个参数,虽然除了file是必要的参数之外,其他都有预设值,然而对照Python的简明风格来说,一个函式会有这么多的参数,依旧显得很不寻常。

如果曾经试着使用type函式,测试在不同参数下open函式的传回物件型态,那么就会稍微有点答案。使用r、w、a时,预设会以文字模式读取,open函式传回的是_io.TextIOWrapper;若加上b模式,会以二进位方式读取,open函式会传回_ io.BufferedReader(rb模式),或者_io.BufferedWriter(wb模式);如果再加上+模式的话,传回的会是_io.BufferedRandom;如果将buffering设为0,那么会传回_io. FileIO。

显然地,open函式是作为一个工厂(Factory)函式,在指定不同的参数值下,隐藏了不同型态的实例建构的细节,直接传回建构好的物件。由于Python中可以指定预设引数,然而,作用不见得是真的要指定什么预设值,有时是要在动态定型语言中模拟出重载的特性,例如,对于预设引数被设定为None的情况,经常是此作用。

若将指定不同模式或参数值的open函式,看成像是静态定型语言中重载出来的不同open函式,那么open函式会有这么多参数,基本上,就可以理解为数个工厂函式重载的集合体了。

档案物件继承架构

无论open传回的物件是何种型态,在Python的官方文件中给了它们一个名称:file object。因为,无论底层实际上是连接至磁碟档案、记忆体,或者是网路上某个资源,都可以透过file object公开的档案导向(file-oriented)API,像是使用read、write等方法进行存取。

因此对于常见的需求,使用open工厂函式搭配file object,开发者读写档案的程式码流程也就几乎大同小异,无怪乎许多文件或书籍都只介绍open函式怎么使用。然而,在需要进一步细部操作档案时,就得知道file object在Python中,基本上分为三个大类:文字档案、缓冲二进位(buffered binary)档案与原始二进位档案(raw binary)。

这三个大类的对应型态,分别是_io模组中的TextIOBase、BufferedIOBase与RawIOBase,它们都继承了IOBase,而函式readline、readlines、writelines、close等,就是定义在IOBase中。不过IOBase并没有定义read、write,而是定义在各自的子类别之中,因为实际上这要依TextIOBase、BufferedIOBase与RawIOBase不同的存取模式,而有不同的签署定义。

方才看到的TextIOWrapper,实际上是TextIOBase的子类别,FileIO则是RawIOBase的子类别,BufferedReader、BufferedWriter都是BufferedIOBase的子类别,而BufferedRandom同时继承了BufferedReader、BufferedWriter。

虽然_io是Python的内建模组,然而,可以察看io模组的原始码,会发现它只是在做名称空间管理,从内建模组_io中from import(汇入)了TextIOWrapper、BufferedReader等名称,因此,相关的类别说明,也就可以在io模组的说明文件中一探究竟。

回到open工厂函式

知道了open传回的file object,实际上会是什么样的继承架构之后,对于open函式的魔法,就比较能知道其底细了。例如说,open(r'c:\workspace\test.py')的话,底层大概就是执行f = io.TextIOWrapper(io.FileIO(r'c:\workspace\test.py'))并将f参考的实例传回,若直接执行f.readlines(),也就可以读取test.py的内容。

然而,相对于方才的程式码来说,使用open函式还是简明多了,这让我想起在Stack Overflow上有人问到,Java中有没有python-like的IO程式库(http://goo. gl/kqGrYu),其实,可以有类似以下的风格:

File f = Open('file.txt', 'w')
for(String line:f){
    //do something with the line from file
}
当时Java 7还没有出现,不然的话,Java 7的NIO2中,确实有个Files类别提供了类似的功能,例如搭配lambda语法的话,可以撰写成这样的形式:

Files.lines(Paths.get(args[0])).forEach(line -> {
    //do something with the line from file
});
探索工厂的复杂度

实际上,在Java的NIO2中,也还有像Files.newBufferedReader这类的方法存在,用以取代过去面临的一种情况——想取得一个BufferedReader实例时,我们往往必须使用new BufferedReader(new InputStreamSReader(new FileInputStream("..."))。而现在,当你习惯了又臭又长的语法,看到这类需求被封装为工厂方法,并列入标准API,无疑是件令人高兴的事。

然而,工厂毕竟是工厂,充其量只是代劳一些常见的物件制作过程,直接给你最后的成品,当你必须要掌握更多细节的时候,就有必要探索工厂背后的运作机制,了解到建立各个物件时的复杂度。

那么,暂时要你忘了open函式,情况会如何呢?你有办法在不使用open函式下,自行建立open指定了r、w、x、a、+等模式下,原本会传回的物件吗?

这会是个有趣的尝试,过程中,你除了能够更加了解Python的IO设计方式之外,也能反过来更加认识open函式上各参数之作用,不用只是死背那些r、w、x、a、 +等模式,对于工厂交给你的物件,也就不会再觉得疑虑而不安了。
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部