Python 学习笔记(十)结构化的文本文件

999 阅读5分钟
原文链接: www.jianshu.com

对于简单的文件,唯一的结构层次时间隔的行。然而有时需要更加结构化的文本,用于后续使用的程序保存数据或者向另一个程序传送数据。
结构化的文本有很多格式,区别他们的方法如下所示:

  • 分隔符,比如tab('\t'),逗号(',')或者竖线('|')。逗号分割值(CSV)就是这样的栗子
  • '<' 和 '>' 标签,例如 XML 和 HTML。
  • 标点符号,例如 JavaScript Object Notation(JSON)
  • 缩进,例如 YAML(即 YAML Ain't Markup Language 的缩写)
  • 混合的,例如各种配置文件

每一种结构化文件格式都能够至少被一种Python模块读写。


CSV:Comma-Separated Values

带分隔符的文件一般用作数据交换格式或者数据库。我们可以人工读入CSV文件,每一次读取一行,在逗号分隔符处将每行分开,并添加结果到数据结构中,例如列表或者字典。但是,最好使用标准的csv模块,因为这样切分会得到更加复杂的信息。

  • 除了逗号,还有其他可代替的分隔符:'|' 和 '\t' 很常见。
  • 有些数据会有转义字符序列,如果分隔符出现在一块区域内,则整块都要加上引号或者 在它之前加上转义字符。
  • 文件可能有不同的换行符,Unix 系统的文件使用 '\n',Microsoft 使用 '\r\n',Apple 之前使用 '\r' 而现在使用 '\n'。
  • 在第一行可以加上列名

首先读和写一个列表的行,每一行有很多列:

In [1]: import csv 
In [2]: villains = [
   ...: ['Doctor', 'No'],
   ...: ['Rosa', 'Klebb'], 
   ...: ['Mister', 'Big'],
   ...: ['Auric', 'Goldfinger'],
   ...: ['Ernst', 'Blofeld'],
   ...: ]
In [3]: with open('villains','wt') as fout:
   ...:     csvout = csv.writer(fout)
   ...:     csvout.writerows(villains)
   ...:

于是创建了包含以下几行的文件:
Doctor,No
Rosa,Klebb
Mister,Big
Auric,Goldfinger
Ernst,Blofeld
现在,我们重新读这个文件:

In [4]: import csv 
In [5]: with open('villains','rt') as fin: 
   ...:     cin = csv.reader(fin) 
   ...:     villains = [row for row in cin]
   ...:      
In [6]: print(villains)
[['Doctor', 'No'], ['Rosa', 'Klebb'], ['Mister', 'Big'], ['Auric', 'Goldfinger'], ['Ernst', 'Blofeld']]

我们利用函数reader()创建的结构,它在通过for循环提取到的cin对象中创建每一行。
使用 reader() 和 writer() 的默认操作。每一列用逗号分开;每一行用换行符分开。
数据可以是字典的集合(a list of dictionary),不仅仅是列表的集合(a list of list)。这次使 用新函数 DictReader() 读取文件 villains,并且指定每一列的名字:

In [7]: import csv 
In [8]: with open('villains','rt') as fin:
   ...:     cin = csv.DictReader(fin,fieldnames=['first','last'])
   ...:     villains = [row for row in cin] 
   ...:      
In [9]: print(villains)
[{'last': 'No', 'first': 'Doctor'}, {'last': 'Klebb', 'first': 'Rosa'}, {'last': 'Big', 'first': 'Mister'}, {'last': 'Goldfinger', 'first': 'Auric'}, {'last': 'Blofeld', 'first': 'Ernst'}]

下面使用新函数DictWriter()重写CSV文件,同事调用writeheader()向CSV文件中第一行写入每一列的名字:

In [10]: import csv 
In [11]: villains 
Out[11]: 
[{'first': 'Doctor', 'last': 'No'},
 {'first': 'Rosa', 'last': 'Klebb'},
 {'first': 'Mister', 'last': 'Big'},
 {'first': 'Auric', 'last': 'Goldfinger'},
 {'first': 'Ernst', 'last': 'Blofeld'}]
In [13]: with open('villains','wt') as fout:
    ...:     cout = csv.DictWriter(fout,['first','last'])
    ...:     cout.writeheader()
    ...:     cout.writerows(villains) 
    ...:

于是创建了具有标题行的新文件villains:
first,last
Doctor,No
Rosa,Klebb
Mister,Big
Auric,Goldfinger
Ernst,Blofeld
回过来再读取写入的文件,忽略函数 DictReader() 调用的参数 fieldnames,把第一行的值 (first,last)作为列标签,和字典的键做匹配:

In [14]: import csv 
In [15]: with open('villains','rt') as fin:
    ...:     cin = csv.DictReader(fin)
    ...:     villains = [row for row in cin] 
    ...:      
In [16]: print(villains) 
[{'last': 'No', 'first': 'Doctor'}, {'last': 'Klebb', 'first': 'Rosa'}, {'last': 'Big', 'first': 'Mister'}, {'last': 'Goldfinger', 'first': 'Auric'}, {'last': 'Blofeld', 'first': 'Ernst'}]

XML

带分隔符的文件仅有二维的数据:行和列。如果你想在程序之间交换数据结构,需要一种 方法把层次结构、序列、集合和其他的结构编码成文本。
XML 是最突出的处理这种转换的标记(markup)格式,它使用标签(tag)分隔数据,如 下面的示例文件 menu.xml 所示:

<?xml version="1.0"?> 
<menu>
   <breakfast hours="7-11">
    <item price="$6.00">breakfast burritos</item>
    <item price="$4.00">pancakes</item>   
   </breakfast>   
   <lunch hours="11-3">
    <item price="$5.00">hamburger</item>
   </lunch> 
   <dinner hours="3-10">
    <item price="8.00">spaghetti</item>
   </dinner>
</menu>

下面是XML的一些重要特性:

  • 标签以一个 < 字符开头,例如示例中的标签 menu、breakfast、lunch、dinner 和 item;
  • 忽略空格;
  • 通常一个开始标签(例如 <menu>)跟一段其他的内容,然后是最后相匹配的结束标签, 例如 </menu>;
  • 标签之间是可以存在多级嵌套的,在本例中,标签 item 是标签 breakfast、lunch 和 dinner 的子标签,反过来,它们也是标签 menu 的子标签;
  • 可选属性(attribute)可以出现在开始标签里,例如 price 是 item 的一个属性;
  • 标签中可以包含值(value),本例中每个 item 都会有一个值,比如第二个 breakfast item 的 pancakes;
  • 如果一个命名为 thing 的标签没有内容或者子标签,它可以用一个在右尖括号的前面添 加斜杠的简单标签所表示,例如 <thing/> 代替开始和结束都存在的标签 <thing> 和 </ thing>;
  • 存放数据的位置可以是任意的——属性、值或者子标签。例如也可以把最后一个 item 标签写作 <item price ="$8.00" food ="spaghetti"/>。

XML通常用于数据传送和消息,XML的灵活性导致出现了很多方法和性能各异的Python库。
在Python中解析XML最简单的方法是使用ElementTree,下面的代码用来解析menu.xml文件以及输出一些标签和属性:

import xml.etree.ElementTree as et
tree = et.ElementTree(file='menu.xml')
root = tree.getroot()
print(root.tag)
for child in root:
    print('tag:',child.tag,'attributes:',child.attrib)
    for grandchild in child:
        print('\ttag:',grandchild.tag,'attributes:',grandchild.attrib)
print(len(root)) # 菜单选择的数目 
3
print(root[0])    # 早餐项的数目 
2
结果:
menu
tag: breakfast attributes: {'hours': '7-11'}
    tag: item attributes: {'price': '$6.00'}
    tag: item attributes: {'price': '$4.00'}
tag: lunch attributes: {'hours': '11-3'}
    tag: item attributes: {'price': '$5.00'}
tag: dinner attributes: {'hours': '3-10'}
    tag: item attributes: {'price': '8.00'}

对于嵌套列表中的每一个元素,tag 是标签字符串,attrib 是它属性的一个字典。 ElementTree 有许多查找 XML 导出数据、修改数据乃至写入 XML 文件的方法,它的文档 :docs.python.org/3.3/library…

其他标准的 Python XML 库如下。

  • xml.dom JavaScript 开发者比较熟悉的文档对象模型(DOM)将 Web 文档表示成层次结构,它 会把整个 XML 文件载入到内存中,同样允许你获取所有的内容。
  • xml.sax 简单的XML API或者SAX都是通过在线解析XML,不需要一次载入所有内容到内存中, 因此对于处理巨大的 XML 文件流是一个很好的选择。

JSON

JavaScript Object Notation(JSON,www.json.org)是源于 JavaScript 的当今很流行的 数据交换格式,它是 JavaScript 语言的一个子集,也是 Python 合法可支持的语法。对于 Python 的兼容性使得它成为程序间数据交换的较好选择。