前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python指南:文件处理

Python指南:文件处理

作者头像
王强
发布2018-08-09 18:07:56
1.3K0
发布2018-08-09 18:07:56
举报
文章被收录于专栏:Python爬虫实战

大多数程序都需要向文件中存储或从文件中加载信息,比如数据或状态信息。本文将深入全面地介绍文件处理的相关知识与方法。

哪种文件格式最适合用于存储整个数据集——二进制、文本还是XML?这严重依赖于具体的上下文。

  • 二进制格式的存储与加载通常是非常快的,并且也是非常紧凑的。但二进制数据不是那种适合阅读或可编辑的数据格式。
  • 文本格式适合阅读,并且是可编辑的,这使得单独的工具对文本文件处理变得容易,也很容易对其进行修改。
  • XML格式适合阅读,并且是可编辑的,可以使用单独的工具进行处理。XML文件格式的分析是直接的,XML分析器速度可能会较慢,因此,读入很大的XML文件回避读入同样大小的二进制文件或文本文件耗费更多的时间资源。

1、文件操作函数

1.1

open()

提到文件操作,那就必须提到 open 函数,因为无论是读取还是写入,都需要先把文件打开,然后才能进行读写操作。 open 函数的作用是打开一个文件,返回一个 file 对象,相关的方法才可以调用它进行读写。其语法如下:

file_object = open(file_name, [,access_mode][, buffering])

  • file_name:字符串类型的文件名称
  • access_mode:打开文件的模式,下面会详细介绍可取值
  • buffering:如果该值为0,这不会有寄存;如果其值为1,访问文件时会寄存行;如果其值大于1,表明了这就是寄存区的缓冲大小;如果为负值,寄存去的缓冲大小为系统默认。

测试一下 open() 函数:

代码语言:javascript
复制
file_object = open('test.txt')
print(file_object)
file_object.close()

[out]
<_io.TextIOWrapper name='test.txt' mode='r' encoding='cp936'>

从输出结果可以看出,默认打开模式为 'r' ,下面来详细介绍文件打开模式:

模式

描述

r

以只读方式打开文件。文件指针将会放在文件的开头。这是默认模式。

w

打开一个文件只用于写入。如果该文件存在,则将其覆盖;不存在则创建。

a

打开一个文件用于追加。如果该文件存在,文件指针将放在文件的结尾;不存在则创建。

r+

打开一个文件用于读写。文件指针将会放在文件的开头。

rb

以二进制形式打开一个文件用于只读。文件指针将会放在文件的开头,一般用于非文本文件。

rb+

以二进制形式打开一个文件用于读写。文件指针将会放在文件的开头。

w+

打开一个文件用于读写。文件指针将会放在文件的开头。

wb

以二进制形式打开一个文件只用于写入。文件存在则覆盖,不存在则创建。

wb+

以二进制形式打开一个文件读写。文件存在则覆盖,不存在则创建。

a+

打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。

ab

以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件进行写入 。

ab+

以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

1.2

write()

write()方法可将任何字符串写入一个打开的文件。需要重点注意的是,Python字符串可以是二进制数据,而不是仅仅是文字。

write()方法不会在字符串的结尾添加换行符('\n'):

代码语言:javascript
复制
fo = open("test.txt", "w")
fo.write( "This is a test.\nwrite function.\n")
fo.close()

打开 test.txt 文件,可以看到,文件中有两行文字,正是刚刚写入的。

02.write()函数测试结果

1.3

read()

read()方 法从一个打开的文件中读取一个字符串。需要重点注意的是,Python字符串可以是二进制数据,而不是仅仅是文字。 read() 在未指定参数的情况下读取整个文件,如果传入一个参数,则读取指定个数的字节。

代码语言:javascript
复制
# read()
fo = open("test.txt", "r")
txt = fo.read()
print(txt)
fo.close()

[out]
This is a test.
write function.

注意:read() 在到达文件末尾时返回一个空的字符串,这个空字符串显示出来就是一个空行,所以上面的输出最后有一个空行。

1.4

close()

文件对象的 close(0 方法关闭一个已经打开的文件,关闭后不能再对该文件对象进行读写操作。

当一个文件对象的引用被重新指定给另一个文件时,Python 会关闭之前的文件。用 close() 方法关闭文件是一个很好的习惯。

2、二进制数据的读写

即便在没有进行压缩处理的情况下,二进制格式通常也是占据磁盘空间最小、保存与加载速度最快的数据格式。最简单的方法是使用 pickles,尽管对二进制数据进行手动处理应该会产生更小的文件。

2.1

带可选压缩的Pickle

Pickle模块实现了基本的数据序列反序列化。Python中几乎所有的数据类型(列表,字典,集合,类等)都可以用Pickle来序列化, 通过Pickle模块的序列化操作我们能够将程序中运行的对象信息保存到文件中去,永久存储;通过Pickle模块的反序列化操作,我们能够从文件中创建上一次程序保存的对象。

基本接口:

  • pickle.dump(obj, file, [,protocol]) 序列化对象,并将结果数据流写入到文件对象中。参数protocol是序列化模式,有三个值可选:0 为ASCII,1为旧式二进制,2为新式二进制,默认值为0。
  • pickle.load(file) 反序列化对象。将文件中的数据解析为一个Python对象。

2.1.1 序列化

下面代码用来演示如何将数据保存到pickle中:

代码语言:javascript
复制
import pickle
import gzip

def export_pickle(data, filename, compress=False):
    fh = None
    try:
        if compress:
            fh = gzip.open(filename, 'wb')
        else:
            fh = open(filename, 'wb')
        pickle.dump(data, fh, pickle.HIGHEST_PROTOCOL)
        return True

    except (EnvironmentError, pickle.PicklingError) as err:
        print(err)
        return False
    finally:
        if fh is not None:
            fh.close()

如果要求进行压缩,我们可以使用 gzip 模块的 gzip.open() 函数来打开文件,否则就是用内置的 open() 函数。在以二进制模式 picking 数据时,我们必须使用“二进制写”模式(“wb”)。其中 pickle.HIGHEST_PROTOCOL表示protocol 3。

下面把一个简单的字典{'hello': 'world'}序列化保存到文件pickle_test.txt中:

代码语言:javascript
复制
export_pickle({'hello': 'world'}, './pickle_test.txt')

使用notepad++打开该 txt 文件,可以看到如下结果:

01.pickle_test结果

显然已经进行序列化,从中我们可以看到"hello"和"world"两个单词,而其他部分并不可读。

2.1.2 反序列化

要读回 pickled 的数据,我们需要区分开压缩的与未压缩的 pickle。使用 gzip 压缩的任意文件都以一个特定的魔数引导,魔数是一个或多个字节组成的序列,位于文件的起始处,用于指明文件的类型。对 gzip 文件, 其魔数为两个字节的 0x1F 0x8B,并存放在一个 bytes 变量中:GZIP_MAGIC = b'\x1F\x8B'

下面代码用于读取 pickle 文件:

代码语言:javascript
复制
def import_pickle(filename):
    fh = None
    try:
        fh = open(filename, 'rb')
        magic = fh.read(len(GZIP_MAGIC))
        if magic == GZIP_MAGIC:
            fh.close()
            fh = gzip.open(filename, 'rb')
        else:
            fh.seek(0)

        print(pickle.load(fh))
        return True
    except (EnvironmentError, pickle.PicklingError) as err:
        print(err)
        return False
    finally:
        if fh is not None:
            fh.close()

使用下面代码进行测试:

代码语言:javascript
复制
import_pickle('./pickle_test.txt')

执行完之后可以看到输出如下:

代码语言:javascript
复制
{'hello': 'world'}

正是之前写入的内容。

2.2

带可选压缩的原始二进制数据

如果编写自己的代码来处理原始二进制数据,就可以对文件格式进行完全控制,这比 pickle 更具安全性,因为恶意的无效数据将由我们自己的代码控制,而不是由解释器执行。

Python提供了两种数据类型用于处理原始字节:固定的数据类型 bytes ,可变的数据类型 bytearray。这两种数据类型都用于存放0个或多个8位的无符号整数(字节),每个字节所代表的值范围在0到255之间。

2.2.1 写入二进制文件

创建自定义的二进制文件时,创建一个用于标识文件类型的魔数以及用于标识文件版本的版本号是有意义的:

代码语言:javascript
复制
MAGIC = b'AIB\x00'
FORMAT_VERSION = b'\x00\x01'

我们使用4个字节表示魔数,2个字节表示版本号。字节序不是问题,因为数据是以单独的字节形式写入。

下面演示如何将字符串保存成二进制:

代码语言:javascript
复制
import struct
import gzip

def export_binary(string, filename, compress=False):
    data = string.encode('utf-8')
    format = '<H{0}s'.format(len(data))
    fh = None
    try:
        if compress:
            fh = gzip.open(filename, 'wb')
        else:
            fh = open(filename, 'wb')

        fh.write(MAGIC)
        fh.write(FORMAT_VERSION)
        bytearr = bytearray()
        bytearr.extend(struct.pack(format, len(data), data))
        fh.write(bytearr)
        return True
    except (EnvironmentError, pickle.PicklingError) as err:
        print(err)
        return False
    finally:
        if fh is not None:
            fh.close()

用下面这行代码进行测试:

代码语言:javascript
复制
export_binary('I love Python.', './binary_test.txt')

2.2.2 读取二进制文件

数据的读回不像写入那么直接,首先,我们需要更多的错误检查操作。并且读回可变长度的字符串也是棘手的。下面代码实现数据读回功能:

代码语言:javascript
复制
def import_binary(filename):
    def unpack_string(fh, eof_is_error=True):
        uint16 = struct.Struct('<H')
        length_data = fh.read(uint16.size)
        if not length_data:
            if eof_is_error:
                raise ValueError('missing or corrupt string size')
            return None
        length = uint16.unpack(length_data)[0]

        if length == 0:
            return ''
        data = fh.read(length)
        if not data or len(data) != length:
            raise ValueError('missing or corrupt string')
        format = '<{0}s'.format(length)
        return struct.unpack(format, data)[0].decode('utf-8')

    fh = None
    try:
        fh = open(filename, 'rb')
        magic = fh.read(len(GZIP_MAGIC))
        if magic == GZIP_MAGIC:
            fh.close()
            fh = gzip.open(filename, 'rb')
        else:
            fh.seek(0)
        magic = fh.read(len(MAGIC))
        if magic != MAGIC:
            raise ValueError('invalid .aib file format')
        version = fh.read(len(FORMAT_VERSION))
        if version > FORMAT_VERSION:
            raise ValueError('unrecognized .aib file version')

        string = unpack_string(fh)
        if string is not None:
            print(string)
    except (EnvironmentError, pickle.PicklingError) as err:
        print(err)
        return False
    finally:
        if fh is not None:
            fh.close()

使用下面一行代码进行测试:

代码语言:javascript
复制
import_binary('./binary_test.txt')

正常输出I love Python.则成功。

3、文本文件的读写

第一小节已经伴随着 文件操作函数进行了文本文件操作的演示,此处不再赘述。

4、XML文件的读写

本节参考了 Python 官方文档 , https://docs.python.org/3.6/library/xml.etree.elementtree.html 。

Python提供了 3 种写入 XML 文件的方法:手动写入 XML;创建元素树并使用其 write() 方法;创建 DOM 并使用其 write() 方法。XML 文件的读入与分析则有 4 中方法:人工读入并分析;使用元素树;DOM;SAX(Simple API for XML)分析器。

下面这段 XML 是上述参考链接里的内容,下面的写入和解析都采用这段 XML。

代码语言:javascript
复制
<data>
    <country name="Liechtenstein">
        <rank>1</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank>4</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank>68</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>

4.1

元素树

使用元素树写入 XML 数据分为两个阶段:首先,要创建用于表示 XML 数据的元素树;然后将元素写入到文件中。如果数据本身就是 XML 格式的,那就省去了第一阶段。

代码语言:javascript
复制
from xml.etree import ElementTree as ET

countries = [
    {
        'name': 'Liechtenstein',
        'rank': 1,
        'year': 2008,
        'gdppc': 141100,
        'neighbor': [{
            'name': 'Austria',
            'direction': 'E'
            },{
            'name': 'Switzerland',
            'direction': 'W'
            }
        ]
    },{
        'name': 'Singapore',
        'rank': 4,
        'year': 2011,
        'gdppc': 59900,
        'neighbor': [{
            'name': 'Malaysia',
            'direction': 'N'
            }
        ]
    },{
        'name': 'Panama',
        'rank': 68,
        'year': 2011,
        'gdppc': 13600,
        'neighbor': [{
            'name': 'Costa Rica',
            'direction': 'W'
            },{
            'name': 'Colombia',
            'direction': 'E'
            }
        ]
    },
]

def export_xml_etree(filename):        
    root=ET.Element('data')  

    for country in countries:  
        country_tag = ET.SubElement(root,'country',name=(country['name']))
        rank_tag = ET.SubElement(country_tag,'rank')  #子元素  
        rank_tag.text = '%i'%country['rank']          #节点内容  
        year_tag = ET.SubElement(country_tag, 'year')
        year_tag.text = '%i'%country['year']
        gdppc_tag = ET.SubElement(country_tag, 'gdppc')
        gdppc_tag.text = '%i'%country['gdppc']
        for neighbor in country['neighbor']:
            neighbor_tag = ET.SubElement(country_tag, 'neighbor')
            neighbor_tag.attrib['name'] = neighbor['name']
            neighbor_tag.attrib['direction'] = neighbor['direction']

        tree=ET.ElementTree(root)  

    try:
        tree.write(filename, 'UTF-8')
    except EnvironmentError as err:
        print(err)
        return False

    return True

export_xml_etree("xml_test_etree.xml")

我们从创建根元素(\)开始,之后对所有的城市进行迭代。所有的属性必须是文本,因此,我们需要对日期、数值型数据、布尔型数据进行相应转换。

下面展示利用元素树对 XML 文件进行解析:

代码语言:javascript
复制
from xml.etree import ElementTree as ET
from xml.parsers import expat

def import_xml_etree(filename):
    countries = []
    try:
        tree = ET.parse(filename)
    except (EnvironmentError, expat.ExpatError) as err:
        print(err)
        return False

    for country in tree.findall('country'):
        data = {}
        data['name'] = country.get('name')
        for child_tag in ('rank', 'year', 'gdppc'):
            data[child_tag] = int(country.find(child_tag).text)

        data['neighbor'] = []
        for child in country.findall('neighbor'):
            neighbor = {}
            for child_tag in ('name', 'direction'):
                neighbor[child_tag] = child.get(child_tag)
            data['neighbor'].append(neighbor)
        countries.append(data)

    print(countries)
    return True

import_xml_etree("xml_test_etree.xml")

输出内容如下:

代码语言:javascript
复制
[{'name': 'Liechtenstein', 'rank': 1, 'year': 2008, 'gdppc': 141100, 'neighbor': [{'name': 'Austria', 'direction': 'E'}, {'name': 'Switzerland', 'direction': 'W'}]}, {'name': 'Singapore', 'rank': 4, 'year': 2011, 'gdppc': 59900, 'neighbor': [{'name': 'Malaysia', 'direction': 'N'}]}, {'name': 'Panama', 'rank': 68, 'year': 2011, 'gdppc': 13600, 'neighbor': [{'name': 'Costa Rica', 'direction': 'W'}, {'name': 'Colombia', 'direction': 'E'}]}]

除了格式不完美外,基本还原了 countries 的内容。

上述代码用到了几个方法:

  • find(match, namespaces=None ) :寻找匹配的第一个子元素。
  • findall(match, namespaces=None ):寻找所有匹配的子元素。
  • get(key, default=None ):获取元素的属性值。

4.2

DOM

DOM 是一种用于表示操纵内存中 XML 文档的标准 API。用于创建 DOM 并将其写入到文件的的代码,以及使用 DOM 对 XML 文件进行分析的代码,在结构上与元素树代码非常相似。

代码语言:javascript
复制
# DOM
from xml.dom.minidom import Document

def export_xml_dom(filename):   
    fh = None
    # 创建dom文档
    doc = Document()

    # 创建根节点     
    root=doc.createElement('data')  

    for country in countries:  
        # 创建节点<country>,然后插入到父节点<data>下
        country_tag = doc.createElement('country')
        country_tag.setAttribute('name', country['name'])
        root.appendChild(country_tag)

        # 将<rank>插入<country>
        rank_tag = doc.createElement('rank')
        rank_tag_text = doc.createTextNode('%i'%country['rank'])
        rank_tag.appendChild(rank_tag_text)
        country_tag.appendChild(rank_tag)

        # 将<year>插入<country>
        year_tag = doc.createElement('year')
        year_tag_text = doc.createTextNode('%i'%country['year'])
        year_tag.appendChild(year_tag_text)
        country_tag.appendChild(year_tag)

        # 将<gdppc>插入<country>
        gdppc_tag = doc.createElement('gdppc')
        gdppc_tag_text = doc.createTextNode('%i'%country['gdppc'])
        gdppc_tag.appendChild(gdppc_tag_text)
        country_tag.appendChild(gdppc_tag)

        # 将<neighbor>插入<country>
        for neighbor in country['neighbor']:
            neighbor_tag = doc.createElement('neighbor')
            neighbor_tag.setAttribute('name', neighbor['name'])
            neighbor_tag.setAttribute('direction', neighbor['direction'])
            country_tag.appendChild(neighbor_tag)
    try:
        fh = open(filename, 'wb')
        fh.write(root.toprettyxml(indent='\t', encoding='utf-8'))
    except EnvironmentError as err:
        print(err)
        return False

    return True

export_xml_dom('xml_test_dom.xml')

我们打开生成的 xml_test_dom.xml,发现已经进行了格式化、排版。

03.xml_dom

下面展示使用 DOM 解析 XML的代码:

代码语言:javascript
复制
from xml.dom import minidom

def import_xml_dom(filename):
    countries = []

    try:
        tree = minidom.parse(filename)
    except (EnvironmentError, expat.ExpatError) as err:
        print(err)
        return False

    for country in tree.getElementsByTagName('country'):
        data = {}
        # 解析<country>的‘name’属性
        data['name'] = country.getAttribute('name')

        # 解析<country>的子标签:<rank>、<year>、<gdppc>
        for child_tag in ('rank', 'year', 'gdppc'):
            data[child_tag] = int(country.getElementsByTagName(child_tag)[0].firstChild.data)

        # 解析<country>的子标签<neighbor>
        data['neighbor'] = []
        for child in country.getElementsByTagName('neighbor'):
            neighbor = {}
            for child_tag in ('name', 'direction'):
                neighbor[child_tag] = child.getAttribute(child_tag)
            data['neighbor'].append(neighbor)
        countries.append(data)

    print(countries)
    return True

import_xml_dom("xml_test_dom.xml")

输出结果

代码语言:javascript
复制
[{'name': 'Liechtenstein', 'rank': 1, 'year': 2008, 'gdppc': 141100, 'neighbor': [{'name': 'Austria', 'direction': 'E'}, {'name': 'Switzerland', 'direction': 'W'}]}, {'name': 'Singapore', 'rank': 4, 'year': 2011, 'gdppc': 59900, 'neighbor': [{'name': 'Malaysia', 'direction': 'N'}]}, {'name': 'Panama', 'rank': 68, 'year': 2011, 'gdppc': 13600, 'neighbor': [{'name': 'Costa Rica', 'direction': 'W'}, {'name': 'Colombia', 'direction': 'E'}]}]

可以看出,和使用 xtree 进行解析的结果一致。

4.3

手动写入XML

将预存的元素树或 DOM 写成 XML 文档可以使用单独的方法调用完成。如果数据本身不是以这两种形式存在,我们就必须先创建元素树或 DOM ,之后直接写出数据更佳方便。手动写入的主要工作是字符串的拼接和格式化,这里不做详细解释。

插播一条通知:本公众号上次的抽奖活动已结束数天,中奖者“江小白要喝江小白”还没有填写地址信息,请尽快填写!

推荐阅读

Recommended reading

点击下列标题 阅读Python指南系列往期文章

| 精彩文章回顾

| Python指南:Python的8个关键要素

| Python指南:数据类型

| Python指南:组合数据类型

| Python指南:控制结构与函数

| Python指南:面向对象程序设计

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-05-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 C与Python实战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2.1.1 序列化
  • 2.1.2 反序列化
  • 2.2.1 写入二进制文件
  • 2.2.2 读取二进制文件
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档