首页 > 技术文章 > python基础之文件

yuan-qi 2021-07-29 15:19 原文

概要:

  • 文件操作
  • 文件夹和路径
  • csv格式文件
  • ini格式文件
  • xml格式文件
  • excel文件
  • 压缩文件

注意:每种格式包含很多相关操作,大家在学习的过程中只要掌握知识点的用法,参考笔记可以实现相关的练习即可,不必背会(在企业开发过程中也是边搜边实现。

1. 文件操作

  • 字符串类型str
  • 字节类型 bytes
    • 表示文字信息,本质上是utf-8 或 gbk等编码的二进制(对unicode进行压缩,方便文件存储和网络过传输)
    • 可表示原始二进制(图片、文件等信息)

1.1 读文件

  • 路径:绝对路径 和 相对路径
  • 模式:rb,read,binary,写入内容必须是bytes类型;rt:read,text,写入字符串类型。
  • 判断文件是否存在:os.path.exists(r'c:\new\file.txt')
  • f = open('file.txt', mode='rb')
  • f = open('file.txt', mode='rt', encoding='utf-8')
  • f.read()
  • f.close()
    实质上文件本身内容都是二进制形式,文本文件、图片、视频、音频等不过是编码标准不同罢了,之所以文本文件可以rt,是因为python解释器open函数做了自动解码的操作。

【坑1】

  • 1.路径:绝对路径和相对路径要分清楚;
  • 2.windows系统路径字符串问题,\ 容易导致转义字符出现,路径解析错误,解决方案:
    • 路径中反斜杠使用双反斜杠 \\
    • 使用r前缀字符串。例如:r'c:\new\file.txt'
  • 3.文件可能不存在,会报错,因此先判断:
    os.path.exists(r'c:\new\file.txt')

1.2 写文件

步骤:

  1. 使用wb模式:
  • 打开文件:f = open('file.txt', mode='wb')
  • 写入文件:f.write('中国人'.encode('utf-8')) .wb模式必须写入bytes类型,是直接写二进制值,不需要定义编码格式
  • 关闭文件:f.close()
  1. 使用wt模式:
  • 打开文件:f = open('file.txt', mode='wt', encoding='utf-8')
  • 写入文件:f.write('中国人') wt模式写入字符串类型
  • 关闭文件:f.close()
  1. 图片等二进制文件:
    直接使用wb模式写入,因为是直接写入二进制值,因此不需要定义编码格式。

注意:w模式,如果文件不存在,会自动创建一个。如果文件存在,先清空文件,再写入内容。

【小高级案例:(超前)】

利用Python向某个网址发送请求并获取结果(利用第三方的模块)

  • 下载第三方模块
  • pip install requests
  • /Library/Frameworks/Python.framework/Versions/3.9/bin/pip3 install requests
  • 使用第三方模块
import requests

res = requests.get(url="网址")
print(res)

1.3 文件打开模式

  • b、t模式实际上读取的都是二进制值,只不过t模式python会自动根据你提供的编码形式,自动转换为字符串格式。
  • r、rt是一样的,写成r,默认就是rt;

打开模式常见有:
关于文件的打开模式常见应用有:

  • 只读:rrtrb (用)

    • 存在,读
    • 不存在,报错
  • 只写:wwtwb(用)

    • 存在,清空再写
    • 不存在,创建再写
  • 只写:xxtxb

    • 存在,报错
    • 不存在,创建再写。
  • 只写:aatab【尾部追加】(用)

    • 存在,尾部追加。
    • 不存在,创建再写。
  • 读写

    • r+、rt+、rb+,默认光标位置:起始位置
    • w+、wt+、wb+,默认光标位置:起始位置(清空文件)
    • x+、xt+、xb+,默认光标位置:起始位置(新文件)
    • a+、at+、ab+,默认光标位置:末尾

【坑2】

注意每次读写完毕,光标所在的位置,否则可能出现无法预料的结果。

f.seek(0) # 将光标回到起始位置

1.4 常见功能

  • read,读 【常用,小文件一次读取】
    • 读所有 f.read() # 不加参数缺陷:如果文件过大,有可能内存不足,程序崩溃;
    • t模式读n个字符(b模式读n个字节) f.read(n),再次强调,size 表示的是一次最多可读取的字符(或字节)数,因此,即便设置的 size 大于文件中存储的字符(字节)数,read() 函数也不会报错,它只会读取文件中所有的数据。
  • readline,读一行数据 f.readline() # 相对节省内存。【由于 readline() 函数在读取文件中一行的内容时,会读取最后的换行符“\n”,再加上 print() 函数输出内容时默认会换行,所以输出结果中会看到多出了一个空行。】
  • readlines,读出所有行,每行作为列表的一个元素

【注意】当文件过大时,不知道有多少行,又想读出文件所有内容,可以使用for循环实现:

f = open('file.txt', mode='rt', encoding='utf-8')
for line in f:
    print(line)
  • write,写。
  • flush,刷到硬盘。其实write操作并不是直接写入硬盘,而是写入缓冲区,因此,write执行后,立刻查看文件内容,不一定有刚刚写入的内容,因为此时可能数据还在缓冲区。flush 的作用是发送指令让操作系统将缓冲区的内容立刻写到硬盘。 f.flush()
  • seek(n),移动光标位置(字节)。 【注意】无论是什么模式打开的文件,都是以字节为单位移动光标。 f.seek(n):光标向后移动3个字节。【注意:在a模式下,调用write在文件中写入内容时,永远只能将内容写入到尾部,不会写到光标的位置。】【如果移动光标位置不对,可能会出现乱码,因为非英文字符,utf-8是多个字节编码,如果不是编码字节数的整数倍,可能会在字符中间截断,因此出现解码乱码的情况。】

file.seek(off, whence=0):

从文件中移动off个操作标记(文件指针),off参数必须传递,正值往结束方向移动,负值往开始方向移动。如果设定了whence参数,就以whence设定的起始位为准,0代表从头开始,1代表当前位置,2代表文件最末尾位置。

【坑3】seek(off, whence)【注意,当 offset 值非 0 时,Python 要求文件必须要以二进制格式打开,否则会抛出 io.UnsupportedOperation 错误。】

使用seek()方法报错:“io.UnsupportedOperation: can't do nonzero cur-relative seeks”错误的原因:
在使用seek()函数时,有时候会报错为 “io.UnsupportedOperation: can't do nonzero cur-relative seeks”,代码如下:

>>> f=open("aaa.txt","r+")    #以读写的格式打开文件aaa.txt
>>> f.read()    #读取文件内容
'my name is liuxiang,i am come frome china'
>>> f.seek(3,0)       #从开头开始偏移三个单位(偏移到“n”)
3
>>> f.seek(5,1)     #想要从上一次偏移到的位置(即“n”)再偏移5个单位
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
io.UnsupportedOperation: can't do nonzero cur-relative seeks

照理说,按照seek()方法的格式file.seek(offset,whence),后面的1代表从当前位置开始算起进行偏移,那又为什么报错呢?
这是因为,在文本文件中,没有使用b模式选项打开的文件,只允许从文件头开始计算相对位置,从文件尾计算时就会引发异常。将 f=open("aaa.txt","r+") 改成

f = open("aaa.txt","rb") 就可以了:

>>> f = open("aaa.txt","rb")
>>> f.seek(3,0)
3
>>> f.seek(5,1)
8

【例外:当seek的offset值为0时,t、b模式打开都行,且whence非0也不报错,因为光标只相当于移动到特定位置,此时seek(0)即回到文件开头,seek(0, 2)即跳到文件末尾。】

  • tell,获取当前光标的位置。 f.tell()
  • close,关闭文件。
    【另外,在写入文件完成后,一定要调用 close() 函数将打开的文件关闭,否则写入的内容不会保存到文件中。例如,将上面程序中最后一行 f.close() 删掉,再次运行此程序并打开 a.txt,你会发现该文件是空的。这是因为,当我们在写入文件内容时,操作系统不会立刻把数据写入磁盘,而是先缓存起来,只有调用 close() 函数时,操作系统才会保证把没有写入的数据全部写入磁盘文件中。除此之外,如果向文件写入数据后,不想马上关闭文件,也可以调用文件对象提供的 flush() 函数,它可以实现将缓冲区的数据写入文件中。】

【补充:文件属性】

  • f.mode:文件打开模式
  • f.encoding:编码格式
  • f.name:文件名
  • f.closed:是否关闭

【补充:文件其他方法】

  • writelines(lines):写入多行,lines是一个列表或元组,将所有元素拼接后生成的字符串写入,元素之间不加换行符及其他字符。
  • fileno():该函数用于得到文件在进程中的编号,这是一个整数值。其中,stdin 在进程中的文件编号永远是 0,stdout 永远是 1,stderr 永远是 2,其他文件的编号都大于 2。如果该文件已经被关闭,则 fileno() 会抛出 ValueError 异常。
  • f.isatty():检测文件是否连接到一个终端设备,如果是返回 True,否则返回 False。
  • f.readable():检查文件是否可读,可读返回True,否则返回False。
  • f.writeable():检查文件是否可写,可写返回 True,否则返回 False。
  • f.seekable():如果文件是可查找的,seekable()则返回True,否则返回False。如果文件允许访问文件流,则该文件是可查找的,例如,seek()方法。
  • f.truncate(n):方法将文件大小截断为给定的字节数,后面的内容全部删除。如果未指定大小,将使用当前位置。【具体使用参看自己总结13】

【坑4】

open()的文本格式和二进制格式
使用 open() 函数以文本格式打开文件和以二进制格式打开文件,唯一的区别是对文件中换行符的处理不同。

在 Windows 系统中,文件中用 "\r\n" 作为行末标识符(即换行符),当以文本格式读取文件时,会将 "\r\n" 转换成 "\n";反之,以文本格式将数据写入文件时,会将 "\n" 转换成 "\r\n"。这种隐式转换换行符的行为,对用文本格式打开文本文件是没有问题的,但如果用文本格式打开二进制文件,就有可能改变文本中的数据(将 \r\n 隐式转换为 \n)。

而在 Unix/Linux 系统中,默认的文件换行符就是 \n,因此在 Unix/Linux 系统中文本格式和二进制格式并无本质的区别。

总的来说,为了保险起见,对于 Windows平台最好用 b 打开二进制文件;对于 Unix/Linux 平台,打开二进制文件,可以用 b,也可以不用。

【补充】同时打开多个文件写法: with open('1.txt', mode='rb') as f1, open('2.txt', mode='rb') as f2, ... :

1.5 上下文管理 with

之前对文件进行操作时,每次都要打开和关闭文件,比较繁琐且容易忘记关闭文件。

以后再进行文件操作时,推荐大家使用with上下文管理,它可以自动实现关闭文件。

with open('file.txt', mode='rb') as f:
    data = f.read()
    print(data)

python2.7 后,with支持同时打开多个文件,对多个文件的上下文管理,即:

with open("xxxx.txt", mode='rb') as f1, open("xxxx.txt", mode='rb') as f2:
    pass

2.csv格式文件

逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。

对于这种格式的数据,我们需要利用open函数来读取文件并根据逗号分隔的特点来进行处理。

3.ini格式文件

ini文件是Initialization File的缩写,平时用于存储软件的的配置文件。例如:MySQL数据库的配置文件。

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
log-bin=py-mysql-bin
character-set-server=utf8
collation-server=utf8_general_ci
log-error=/var/log/mysqld.log
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

[mysqld_safe]
log-error=/var/log/mariadb/mariadb.log
pid-file=/var/run/mariadb/mariadb.pid

[client]
default-character-set=utf8

configparser 模块

config = configparser.ConfigParser()
config.read('files/my.ini', encoding='utf-8')

  • 1.获取所有的节点 config.sections()
  • 2.获取节点下的键值 config.items("mysqld_safe")
  • 3.获取某个节点下的键对应的值 config.get("mysqld","collation-server")
  • 4.是否存在节点 config.has_section("client")
  • 5.添加一个节点
import configparser

config = configparser.ConfigParser()
config.read('files/my.ini', encoding='utf-8')
# config.read('/Users/wupeiqi/PycharmProjects/luffyCourse/day09/files/my.ini', encoding='utf-8')

# 1.获取所有的节点

result = config.sections()
print(result)  # ['mysqld', 'mysqld_safe', 'client']


# 2.获取节点下的键值

result = config.items("mysqld_safe")
print(result)  # [('log-error', '/var/log/mariadb/mariadb.log'), ('pid-file', '/var/run/mariadb/mariadb.pid')]

for key, value in config.items("mysqld_safe"):
    print(key, value)


# 3.获取某个节点下的键对应的值

result = config.get("mysqld","collation-server")
print(result)


# 4.其他

# 4.1 是否存在节点
v1 = config.has_section("client")
print(v1)

# 4.2 添加一个节点
config.add_section("group")
config.set('group','name','wupeiqi')
config.set('client','name','wupeiqi')
config.write(open('files/new.ini', mode='w', encoding='utf-8'))

# 4.3 删除
config.remove_section('client')
config.remove_option("mysqld", "datadir")
config.write(open('files/new.ini', mode='w', encoding='utf-8'))
  • 读取所有节点

    import configparser
    
    config = configparser.ConfigParser()
    config.read('/Users/wupeiqi/PycharmProjects/luffyCourse/day09/files/my.conf', encoding='utf-8')
    # config.read('my.conf', encoding='utf-8')
    ret = config.sections()
    print(ret) 
    
    >>输出
    ['mysqld', 'mysqld_safe', 'client']
    
  • 读取节点下的键值

    import configparser
    
    config = configparser.ConfigParser()
    config.read('/Users/wupeiqi/PycharmProjects/luffyCourse/day09/files/my.conf', encoding='utf-8')
    # config.read('my.conf', encoding='utf-8')
    item_list = config.items("mysqld_safe")
    print(item_list) 
    
    >>输出
    [('log-error', '/var/log/mariadb/mariadb.log'), ('pid-file', '/var/run/mariadb/mariadb.pid')]
    
  • 读取节点下值(根据 节点+键 )

    import configparser
    
    config = configparser.ConfigParser()
    config.read('/Users/wupeiqi/PycharmProjects/luffyCourse/day09/files/my.conf', encoding='utf-8')
    
    value = config.get('mysqld', 'log-bin')
    print(value)
    
    >>输出
    py-mysql-bin
    
  • 检查、删除、添加节点

    import configparser
    
    config = configparser.ConfigParser()
    config.read('/Users/wupeiqi/PycharmProjects/luffyCourse/day09/files/my.conf', encoding='utf-8')
    # config.read('my.conf', encoding='utf-8')
    
    
    # 检查
    has_sec = config.has_section('mysqld')
    print(has_sec)
    
    # 添加节点
    config.add_section("SEC_1")
    # 节点中设置键值
    config.set('SEC_1', 'k10', "123")
    config.set('SEC_1', 'name', "哈哈哈哈哈")
    
    config.add_section("SEC_2")
    config.set('SEC_2', 'k10', "123")
    # 内容写入新文件
    config.write(open('/Users/wupeiqi/PycharmProjects/luffyCourse/day09/files/xxoo.conf', 'w'))
    
    
    # 删除节点
    config.remove_section("SEC_2")
    # 删除节点中的键值
    config.remove_option('SEC_1', 'k10')
    config.write(open('/Users/wupeiqi/PycharmProjects/luffyCourse/day09/files/new.conf', 'w'))
    

4.XML格式文件

可扩展标记语言,是一种简单的数据存储语言,XML 被设计用来传输和存储数据。

  • 存储,可用来存放配置文件,例如:java的配置文件。
  • 传输,网络传输时以这种格式存在,例如:早期ajax传输的数据、soap协议等。
    java用的较多,python使用较少,不过后面微信小程序会用到。
    【看笔记,随后汇总】

4.1 读取xml文件内容

from xml.etree import ElementTree as ET

# ET去打开xml文件
tree = ET.parse("files/xo.xml")

# 获取根标签
root = tree.getroot()

print(root) # <Element 'data' at 0x7f94e02763b0>

读取xml格式字符串内容

from xml.etree import ElementTree as ET

content = """
<data>
    <country name="Liechtenstein">
        <rank updated="yes">2</rank>
        <year>2023</year>
        <gdppc>141100</gdppc>
        <neighbor direction="E" name="Austria" />
        <neighbor direction="W" name="Switzerland" />
    </country>
     <country name="Panama">
        <rank updated="yes">69</rank>
        <year>2026</year>
        <gdppc>13600</gdppc>
        <neighbor direction="W" name="Costa Rica" />
        <neighbor direction="E" name="Colombia" />
    </country>
</data>
"""

root = ET.XML(content)
print(root)  # <Element 'data' at 0x7fdaa019cea0>

4.2 读取节点数据

(1)通过find函数获取节点:

from xml.etree import ElementTree as ET

content = """
<data>
    <country name="Liechtenstein" id="999" >
        <rank>2</rank>
        <year>2023</year>
        <gdppc>141100</gdppc>
        <neighbor direction="E" name="Austria" />
        <neighbor direction="W" name="Switzerland" />
    </country>
     <country name="Panama">
        <rank>69</rank>
        <year>2026</year>
        <gdppc>13600</gdppc>
        <neighbor direction="W" name="Costa Rica" />
        <neighbor direction="E" name="Colombia" />
    </country>
</data>
"""

# 获取根标签 data
root = ET.XML(content)
# 获取第一个名字为country的标签
country_object = root.find("country")
# .tag 是标签的名称,这里是country;
# .attrib 是标签的属性,这里是  name="Liechtenstein"  
# .text  是标签里面的内容,这个标签没有   
print(country_object.tag, country_object.attrib)
gdppc_object = country_object.find("gdppc")
#  tag=gdppc,  attrib={}, text=141100
print(gdppc_object.tag,gdppc_object.attrib,gdppc_object.text)

(2)通过for in循环获取子节点:

from xml.etree import ElementTree as ET

content = """
<data>
    <country name="Liechtenstein">
        <rank>2</rank>
        <year>2023</year>
        <gdppc>141100</gdppc>
        <neighbor direction="E" name="Austria" />
        <neighbor direction="W" name="Switzerland" />
    </country>
     <country name="Panama">
        <rank>69</rank>
        <year>2026</year>
        <gdppc>13600</gdppc>
        <neighbor direction="W" name="Costa Rica" />
        <neighbor direction="E" name="Colombia" />
    </country>
</data>
"""

# 获取根标签 data
root = ET.XML(content)

# 获取data标签的孩子标签
for child in root:
    # child.tag = conntry
    # child.attrib = {"name":"Liechtenstein"}
    print(child.tag, child.attrib)
    for node in child:
        print(node.tag, node.attrib, node.text)

(3)通过生成迭代器获取子节点:

from xml.etree import ElementTree as ET

content = """
<data>
    <country name="Liechtenstein">
        <rank>2</rank>
        <year>2023</year>
        <gdppc>141100</gdppc>
        <neighbor direction="E" name="Austria" />
        <neighbor direction="W" name="Switzerland" />
    </country>
     <country name="Panama">
        <rank>69</rank>
        <year>2026</year>
        <gdppc>13600</gdppc>
        <neighbor direction="W" name="Costa Rica" />
        <neighbor direction="E" name="Colombia" />
    </country>
</data>
"""

root = ET.XML(content)
# 找到所有的year标签,生成迭代器
for child in root.iter('year'):
    print(child.tag, child.text)

(4) 通过findall、find函数获取子节点:

from xml.etree import ElementTree as ET

content = """
<data>
    <country name="Liechtenstein">
        <rank>2</rank>
        <year>2023</year>
        <gdppc>141100</gdppc>
        <neighbor direction="E" name="Austria" />
        <neighbor direction="W" name="Switzerland" />
    </country>
     <country name="Panama">
        <rank>69</rank>
        <year>2026</year>
        <gdppc>13600</gdppc>
        <neighbor direction="W" name="Costa Rica" />
        <neighbor direction="E" name="Colombia" />
    </country>
</data>
"""

root = ET.XML(content)
# findall找到所有的country标签  
v1 = root.findall('country')
print(v1)
# find 找到第一个country标签,然后再找到这个标签下面的第一个rank标签   
v2 = root.find('country').find('rank')
# 通过 .text获取rank标签的内容,这里为:2
print(v2.text)

4.3 修改和删除节点

from xml.etree import ElementTree as ET

content = """
<data>
    <country name="Liechtenstein">
        <rank>2</rank>
        <year>2023</year>
        <gdppc>141100</gdppc>
        <neighbor direction="E" name="Austria" />
        <neighbor direction="W" name="Switzerland" />
    </country>
     <country name="Panama">
        <rank>69</rank>
        <year>2026</year>
        <gdppc>13600</gdppc>
        <neighbor direction="W" name="Costa Rica" />
        <neighbor direction="E" name="Colombia" />
    </country>
</data>
"""

root = ET.XML(content)

# 修改节点内容和属性
rank = root.find('country').find('rank')
print(rank.text)
# .text= 将rank标签内容修改为‘999’
rank.text = "999"
# .set() 给rank添加一个属性update,并设置值为'2020-11-11'  
rank.set('update', '2020-11-11')
print(rank.text, rank.attrib)
############ 保存文件 ############
tree = ET.ElementTree(root)
tree.write("new.xml", encoding='utf-8')


# 删除节点,下面代码删除掉了第一个country节点及其下面所有子节点   
root.remove( root.find('country') )
print(root.findall('country'))

############ 保存文件 ############
tree = ET.ElementTree(root)
tree.write("newnew.xml", encoding='utf-8')

【注意:xml中所有属性的值和标签的内容都是字符串类型,赋值其他类型会报错。】

4.4 构建文档

方式一(ET.Element() 方法,这种方式看起来简单明了):

<home>
    <son name="儿1">
        <grandson name="儿11"></grandson>
        <grandson name="儿12"></grandson>
    </son>
    <son name="儿2"></son>
</home>
from xml.etree import ElementTree as ET

# 创建根标签
root = ET.Element("home")

# 创建节点大儿子
son1 = ET.Element('son', {'name': '儿1'})
# 创建小儿子
son2 = ET.Element('son', {"name": '儿2'})

# 在大儿子中创建两个孙子
grandson1 = ET.Element('grandson', {'name': '儿11'})
grandson2 = ET.Element('grandson', {'name': '儿12'})
son1.append(grandson1)
son1.append(grandson2)

# 把儿子添加到根节点中
root.append(son1)
root.append(son2)

tree = ET.ElementTree(root)
# short_empty_elements 是否使用短标签,如果一个标签里面text为空,
# 可以使用<son name='alex'  /> 形式保存,而不需要后面加</son>,这就是短标签,
# 相当于简写,这里设置值为False,则使用完整格式标签。  
tree.write('oooo.xml', encoding='utf-8', short_empty_elements=False)

方式二(某标签.makeelement() 方法,该方法可以使用现有的某个标签创建子标签,跟Element() 方法同等效果):

<famliy>
    <son name="儿1">
        <grandson name="儿11"></grandson>
        <grandson name="儿12"></grandson>
    </son>
    <son name="儿2"></son>
</famliy>
from xml.etree import ElementTree as ET

# 创建根节点
root = ET.Element("famliy")


# 创建大儿子
son1 = root.makeelement('son', {'name': '儿1'})
# 创建小儿子
son2 = root.makeelement('son', {"name": '儿2'})

# 在大儿子中创建两个孙子
grandson1 = son1.makeelement('grandson', {'name': '儿11'})
grandson2 = son1.makeelement('grandson', {'name': '儿12'})

son1.append(grandson1)
son1.append(grandson2)


# 把儿子添加到根节点中
root.append(son1)
root.append(son2)

tree = ET.ElementTree(root)
tree.write('oooo.xml',encoding='utf-8')

方式三(ET.SubElement() 方法,最省事的方式):

<famliy>
	<son name="儿1">
    	<age name="儿11">孙子</age>
    </son>
	<son name="儿2"></son>
</famliy>
from xml.etree import ElementTree as ET


# 创建根节点
root = ET.Element("famliy")


# 创建节点大儿子
son1 = ET.SubElement(root, "son", attrib={'name': '儿1'})
# 创建小儿子
son2 = ET.SubElement(root, "son", attrib={"name": "儿2"})

# 在大儿子中创建一个孙子
grandson1 = ET.SubElement(son1, "age", attrib={'name': '儿11'})
grandson1.text = '孙子'


et = ET.ElementTree(root)  #生成文档对象
et.write("test.xml", encoding="utf-8")

方式四(通过修改.text属性直接赋值):

<user><![CDATA[你好呀]]</user>
from xml.etree import ElementTree as ET

# 创建根节点
root = ET.Element("user")
root.text = "<![CDATA[你好呀]]"

et = ET.ElementTree(root)  # 生成文档对象
et.write("test.xml", encoding="utf-8")

【案例 解析xml文件】

content = """<xml>
    <ToUserName><![CDATA[gh_7f083739789a]]></ToUserName>
    <FromUserName><![CDATA[oia2TjuEGTNoeX76QEjQNrcURxG8]]></FromUserName>
    <CreateTime>1395658920</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[TEMPLATESENDJOBFINISH]]></Event>
    <MsgID>200163836</MsgID>
    <Status><![CDATA[success]]></Status>
</xml>"""

#####
from xml.etree import ElementTree as ET

info = {}
root = ET.XML(content)
for node in root:
    # print(node.tag,node.text)
    # 因为CDATA是xml内部一种固定搭配,所以解析时会自动把里面的数据提取出来;
    info[node.tag] = node.text
print(info)

5.Excel格式文件 【openpyxl 模块】

安装第三方模块:
pip install openpyxl

5.1 读Excel

(1)读sheet

from openpyxl import load_workbook

wb = load_workbook("files/p1.xlsx")

# sheet相关操作

# 1.获取excel文件中的所有sheet名称

print(wb.sheetnames) # ['数据导出', '用户列表', 'Sheet1', 'Sheet2']


# 2.选择sheet,基于sheet名称

sheet = wb["数据导出"]  
# .cell(1, 2) 获取第一行,第二列那个单元格的对象
cell = sheet.cell(1, 2)
# .value 获取该单元格的值  
print(cell.value)


# 3.选择sheet,基于索引位置

sheet = wb.worksheets[0]
cell = sheet.cell(1,2)
print(cell.value)


# 4.循环所有的sheet
# 第一种方式:
for name in wb.sheetnames:
    sheet = wb[name]
    cell = sheet.cell(1, 1)
    print(cell.value)

# 第二种方式:
for sheet in wb.worksheets:
    cell = sheet.cell(1, 1)
    print(cell.value)

# 第三种方式(本质上是执行的第二种方式):
for sheet in wb:
    cell = sheet.cell(1, 1)
    print(cell.value)

(2)读sheet中单元格的值:

from openpyxl import load_workbook

wb = load_workbook("files/p1.xlsx")
sheet = wb.worksheets[0]

# 1.获取第N行第N列的单元格(行、列位置是从1开始)

cell = sheet.cell(1, 1)
# .value 单元格的值
print(cell.value)
# .style  样式 
print(cell.style)
# .font  字体信息
print(cell.font)
print(cell.alignment)


# 2.获取某个单元格

c1 = sheet["A2"]
print(c1.value)

c2 = sheet['D4']
print(c2.value)


# 3.第N行所有的单元格
# sheet[n] : 获取第n行的所有单元格
for cell in sheet[1]:
    print(cell.value)


# 4.所有行的数据(获取某一列数据)
# sheet.rows,获取所有每一行的单元格
for row in sheet.rows:
    # row[0],第一列单元格的值,row[1],第二列单元格的值
    print(row[0].value, row[1].value)


# 5.获取所有列的数据

for col in sheet.columns:
    # col[n],获取第n行的素有数据
    print(col[1].value)

(3)读取合并的单元格

excel合并单元格的值是取的第一个值,水平方向取最左侧单元格的值,垂直方向取最上方的单元格的值。因此第一个单元格的类型仍为<cell...>,被合并的单元格类型则为<MergeCell ...>,注意下方输出结果中各个单元格的类型。例如下图,用户信息实际是A1的值,B1因为被合并,所以B1的值为空,所以获取的值为None。

image

from openpyxl import load_workbook

wb = load_workbook("files/p1.xlsx")
sheet = wb.worksheets[2]

# 获取第N行第N列的单元格(位置是从1开始)
c1 = sheet.cell(1, 1)
print(c1)  # <Cell 'Sheet1'.A1>
print(c1.value) # 用户信息

c2 = sheet.cell(1, 2)
print(c2)  # <MergedCell 'Sheet1'.B1>
print(c2.value) # None
from openpyxl import load_workbook

wb = load_workbook('files/p1.xlsx')
sheet = wb.worksheets[2]
for row in sheet.rows:
    print(row)
>>> 输出结果
(<Cell 'Sheet1'.A1>, <MergedCell 'Sheet1'.B1>, <Cell 'Sheet1'.C1>)
(<Cell 'Sheet1'.A2>, <Cell 'Sheet1'.B2>, <Cell 'Sheet1'.C2>)
(<Cell 'Sheet1'.A3>, <Cell 'Sheet1'.B3>, <Cell 'Sheet1'.C3>)
(<MergedCell 'Sheet1'.A4>, <Cell 'Sheet1'.B4>, <Cell 'Sheet1'.C4>)
(<Cell 'Sheet1'.A5>, <Cell 'Sheet1'.B5>, <Cell 'Sheet1'.C5>)

5.1 写Excel

(1)在原Excel文件基础上写内容。(获取cell,使用cell.value='alex'写内容。)

from openpyxl import load_workbook

wb = load_workbook('files/p1.xlsx')
sheet = wb.worksheets[0]

# 找到单元格,并修改单元格的内容
cell = sheet.cell(1, 1)
cell.value = "新的开始"

# 将excel文件保存到p2.xlsx文件中
wb.save("files/p2.xlsx")

(2)新创建Excel文件写内容。(写值方式跟上面相同。)

from openpyxl import workbook

# 创建excel且默认会创建一个sheet(名称为Sheet)
wb = workbook.Workbook()

sheet = wb.worksheets[0] # 或 sheet = wb["Sheet"]

# 找到单元格,并修改单元格的内容
cell = sheet.cell(1, 1)
cell.value = "新的开始"

# 将excel文件保存到p2.xlsx文件中
wb.save("files/p2.xlsx")

(3)其他操作:

from openpyxl import workbook

wb = workbook.Workbook() # Sheet

# 1. 修改sheet名称(sheet.title='数据集')

sheet = wb.worksheets[0]
sheet.title = "数据集"
wb.save("p2.xlsx")


# 2. 创建sheet并设置sheet颜色
# 在0位置创建一个sheet,命名为“工作计划”
sheet = wb.create_sheet("工作计划", 0)
# 给sheet设置一个颜色
sheet.sheet_properties.tabColor = "1072BA"
wb.save("p2.xlsx")


# 3. 默认打开的sheet(.active = 0)

wb.active = 0
wb.save("p2.xlsx")


# 4. 拷贝sheet(wb.copy_worksheet('alex'))

sheet = wb.create_sheet("工作计划")
sheet.sheet_properties.tabColor = "1072BA"

new_sheet = wb.copy_worksheet(wb["Sheet"])
new_sheet.title = "新的计划"
wb.save("p2.xlsx")


# 5.删除sheet

del wb["用户列表"]
wb.save('files/p2.xlsx')

from openpyxl import load_workbook
from openpyxl.styles import Alignment, Border, Side, Font, PatternFill, GradientFill


wb = load_workbook('files/p1.xlsx')

sheet = wb.worksheets[1]

# 1. 获取某个单元格,修改值(1)

cell = sheet.cell(1, 1)
cell.value = "开始"
wb.save("p2.xlsx")


# 2.  获取某个单元格,修改值(2)

sheet["B3"] = "Alex"
wb.save("p2.xlsx")


# 3. 获取某些单元格,修改值(3)
# 类似切片,但是excel中是切块。如下图,实际相当于如下的元组嵌套
(
    (B2,C2)
    (B3,C3)
)
# 下面的循环相当于上面元组的嵌套循环取值:
cell_list = sheet["B2":"C3"]
for row in cell_list:
    for cell in row:
        cell.value = "新的值"
wb.save("p2.xlsx")


# 4. 对齐方式

cell = sheet.cell(1, 1)

# horizontal,水平方向对齐方式:"general", "left", "center", "right", "fill", "justify", "centerContinuous", "distributed"
# vertical,垂直方向对齐方式:"top", "center", "bottom", "justify", "distributed"
# text_rotation,旋转角度。
# wrap_text,是否自动换行。
cell.alignment = Alignment(horizontal='center', vertical='distributed', text_rotation=45, wrap_text=True)
wb.save("p2.xlsx")


# 5. 边框(注意得事先导入模块)
# side的style有如下:dashDot','dashDotDot', 'dashed','dotted','double','hair', 'medium', 'mediumDashDot', 'mediumDashDotDot','mediumDashed', 'slantDashDot', 'thick', 'thin'

cell = sheet.cell(9, 2)
cell.border = Border(
    top=Side(style="thin", color="FFB6C1"), 
    bottom=Side(style="dashed", color="FFB6C1"),
    left=Side(style="dashed", color="FFB6C1"),
    right=Side(style="dashed", color="9932CC"),
    diagonal=Side(style="thin", color="483D8B"),  # 对角线
    diagonalUp=True,  # 左下 ~ 右上
    diagonalDown=True  # 左上 ~ 右下
)
wb.save("p2.xlsx")


# 6.字体

cell = sheet.cell(5, 1)
cell.font = Font(name="微软雅黑", size=45, color="ff0000", underline="single")
wb.save("p2.xlsx")


# 7.背景色

cell = sheet.cell(5, 3)
cell.fill = PatternFill("solid", fgColor="99ccff")
wb.save("p2.xlsx")


# 8.渐变背景色

cell = sheet.cell(5, 5)
cell.fill = GradientFill("linear", stop=("FFFFFF", "99ccff", "000000"))
wb.save("p2.xlsx")


# 9.宽高(索引从1开始)

sheet.row_dimensions[1].height = 50
sheet.column_dimensions["E"].width = 100
wb.save("p2.xlsx")


# 10.合并单元格
# 合并单元格
# B2至D8单元块合并
sheet.merge_cells("B2:D8")
# 第二种方式合并单元格
sheet.merge_cells(start_row=15, start_column=3, end_row=18, end_column=8)
wb.save("p2.xlsx")

#取消合并单元格
sheet.unmerge_cells("B2:D8")
wb.save("p2.xlsx")


# 11.写入公式

sheet = wb.worksheets[3]
sheet["D1"] = "合计"
# 写入公式
sheet["D2"] = "=B2*C2"
wb.save("p2.xlsx")


sheet = wb.worksheets[3]
# 还可以写入内置函数
sheet["D3"] = "=SUM(B3,C3)"
wb.save("p2.xlsx")


# 12.删除

# idx,要删除的索引位置
# amount,从索引位置开始要删除的个数(默认为1)
sheet.delete_rows(idx=1, amount=20)
sheet.delete_cols(idx=1, amount=3)
wb.save("p2.xlsx")


# 13.插入

sheet.insert_rows(idx=5, amount=10)
sheet.insert_cols(idx=3, amount=2)
wb.save("p2.xlsx")


# 14.循环写内容

sheet = wb["Sheet"]
cell_range = sheet['A1:C2']
for row in cell_range:
    for cell in row:
        cell.value = "xx"

for row in sheet.iter_rows(min_row=5, min_col=1, max_col=7, max_row=10):
    for cell in row:
        cell.value = "oo"
wb.save("p2.xlsx")


# 15.移动

# 将H2:J10范围的数据,向右移动15个位置、向下移动1个位置(如果负值,则向左、向上移动)
sheet.move_range("H2:J10",rows=1, cols=15)
wb.save("p2.xlsx")


sheet = wb.worksheets[3]
sheet["D1"] = "合计"
sheet["D2"] = "=B2*C2"
sheet["D3"] = "=SUM(B3,C3)"
sheet.move_range("B1:D3",cols=10, translate=True) # 移动后,原来的公式自动变更新位置坐标,自动翻译公式
wb.save("p2.xlsx")


# 16.打印区域

sheet.print_area = "A1:D200"
wb.save("p2.xlsx")


# 17.打印时,每个页面的固定表头
# 相当于保留A列到D列的第一行作为每张打印页的表头
sheet.print_title_cols = "A:D"
sheet.print_title_rows = "1:1"
wb.save("p2.xlsx")

6.压缩文件(shutil 模块)

基于python内置的shutil模块可以实现对压缩文件的操作。

import shutil

# 1. 压缩文件

# base_name,压缩后的压缩包文件
# format,压缩的格式,例如:"zip", "tar", "gztar", "bztar", or "xztar".
# root_dir,要压缩的文件夹路径

shutil.make_archive(base_name=r'datafile',format='zip',root_dir=r'files')


# 2. 解压文件

# filename,要解压的压缩包文件
# extract_dir,解压的路径
# format,压缩文件格式
# 解压缩目录不存在时会自动创建  
shutil.unpack_archive(filename=r'datafile.zip', extract_dir=r'xxxxxx/xo', format='zip')

7.路径相关

7.1 转义

windows路径使用的是\,linux路径使用的是/。

特别的,在windows系统中如果有这样的一个路径 D:\nxxx\txxx\x1,程序会报错。因为在路径中存在特殊符 \n(换行符)和\t(制表符),Python解释器无法自动区分。

所以,在windows中编写路径时,一般有两种方式:

  • 加转义符,例如:"D:\\nxxx\\txxx\\x1"
  • 路径前加r,例如:r"D:\\nxxx\\txxx\\x1"

7.2 程序当前路径

项目中如果使用了相对路径,那么一定要注意当前所在的位置。

例如:在/Users/wupeiqi/PycharmProjects/CodeRepository/路径下编写 demo.py文件

with open("a1.txt", mode='w', encoding='utf-8') as f:
    f.write("你好呀")

用以下两种方式去运行:

  • 方式1,文件会创建在 /Users/wupeiqi/PycharmProjects/CodeRepository/ 目录下。

    cd /Users/wupeiqi/PycharmProjects/CodeRepository/
    python demo.py
    
  • 方式2,文件会创建在 /Users/wupeiqi目录下。

    cd /Users/wupeiqi
    python /Users/wupeiqi/PycharmProjects/CodeRepository/demo.py
    

【解决程序运行路径问题】

相对路径和绝对路径是一个要命的问题,解决思路:
首先导入os模块,获取当前运行文件所在绝对路径,然后获取当前文件的上级目录绝对路径,使用os.path.join()将目录和文件拼接成最终的绝对路径,这样的好处是,针对不同 平台路径分隔符的不同,它会自动生成对应的路径,而且也没有写死,固定写法如下:

import os

"""
# 1.获取当前运行的py脚本所在路径
abs = os.path.abspath(__file__)
print(abs) # /Users/wupeiqi/PycharmProjects/luffyCourse/day09/20.路径相关.py
path = os.path.dirname(abs)
print(path) # /Users/wupeiqi/PycharmProjects/luffyCourse/day09
"""
base_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(base_dir, 'files', 'info.txt')
print(file_path)
if os.path.exists(file_path):
    file_object = open(file_path, mode='r', encoding='utf-8')
    data = file_object.read()
    file_object.close()

    print(data)
else:
    print('文件路径不存在')

7.3 文件和路径相关

import shutil
import os

# 1. 获取当前脚本绝对路径

abs_path = os.path.abspath(__file__)
print(abs_path)


# 2. 获取当前文件的上级目录

base_path = os.path.dirname( os.path.dirname(路径) )
print(base_path)


# 3. 路径拼接

p1 = os.path.join(base_path, 'xx')
print(p1)

p2 = os.path.join(base_path, 'xx', 'oo', 'a1.png')
print(p2)


# 4. 判断路径是否存在

exists = os.path.exists(p1)
print(exists)


# 5. 创建文件夹

os.makedirs(路径)


path = os.path.join(base_path, 'xx', 'oo', 'uuuu')
if not os.path.exists(path):
    os.makedirs(path)


# 6. 是否是文件夹

file_path = os.path.join(base_path, 'xx', 'oo', 'uuuu.png')
is_dir = os.path.isdir(file_path)
print(is_dir) # False

folder_path = os.path.join(base_path, 'xx', 'oo', 'uuuu')
is_dir = os.path.isdir(folder_path)
print(is_dir) # True


# 7. 删除文件或文件夹

os.remove("文件路径")  # 文件和文件夹都可以删除


path = os.path.join(base_path, 'xx')
shutil.rmtree(path)


# 8. 拷贝文件夹

shutil.copytree("/Users/wupeiqi/Desktop/图/csdn/","/Users/wupeiqi/PycharmProjects/CodeRepository/files")
# 将csdn拷贝到files  

# 9.拷贝文件

shutil.copy("/Users/wupeiqi/Desktop/图/csdn/WX20201123-112406@2x.png","/Users/wupeiqi/PycharmProjects/CodeRepository/")
shutil.copy("/Users/wupeiqi/Desktop/图/csdn/WX20201123-112406@2x.png","/Users/wupeiqi/PycharmProjects/CodeRepository/x.png")
# 将第一个参数的png拷贝到第二个参数的目录

# 10.文件或文件夹重命名

shutil.move("/Users/wupeiqi/PycharmProjects/CodeRepository/x.png","/Users/wupeiqi/PycharmProjects/CodeRepository/xxxx.png")
shutil.move("/Users/wupeiqi/PycharmProjects/CodeRepository/files","/Users/wupeiqi/PycharmProjects/CodeRepository/images")
# 如果两个参数位于同一个目录,则是重命名,不同目录则是剪切。

【补充知识点】 enumerate 函数(自动生成序号)

data_list = [11, 22, 33, 44, 55]
for i, item in enumerate(data_list, 1):
    print(i, item)
    
# 输出结果:
1 11
2 22
3 33
4 44
5 55

推荐阅读