作为互联网从业人员,无论是产品、运营、数据分析,都经常会与Excel打交道。自然,我也是如此。但是以往用Excel最大的问题就是缺少像程序这样的灵活性。处理简单的数据当然是戳戳有余,但是想针对一些复杂的内容,包括数据清洗,还是Python更好用一点(起码我是这么觉得的)。

本章内容其实比较简单,主要是Excel的读取、写入。但是因为忙于就业的事情(是的,总在家坐吃山空会被饿死的)所以已经一个月没碰Python。

这章也算是复健内容吧。

13.1 Excel文档

首先,让我们来看一些基本定义。一个Excel电子表格文档称为一个“工作簿”。一个工作簿保存在扩展名为.xlsx的文件中。每个工作簿可以包含多个“表”(也称之为“工作表”)。用户当前查看的表(或关闭Excel前最后查看的表)称为“活动表”。

每个表都有一些“列”(地址是从A开始的字母)和一些“行”(地址是从1开始的数字)。处于特定行和列的方格称为“单元格”。每个单元格包含一个数字或文本值,或者是空白。单元格形成的网格和数据构成了表。

13.2 安装openpyxl 模块

Python没有自带的openpyxl,所以必须安装。模块的名称是openpyxl。

本书使用的是openpyxl的2.6.2版本。重要的是,你必须通过运营pip install –user –Uopenpyxl==2.6.2 来安装这个版本,因为交心版本的openpyxl与本书中的信息不兼容。要测试是否安装正确,就在交互式环境中输入代码:

import openpyxl

如果该模块正确安装,就不会产生错误信息。

这里我想吐槽一下,如果当前的最新模块版本与书中的模块版本不同会导致书中的内容大部分失效,那学这部分还有什么意义,不如自己去看最新模块的内容就是了。

所以,我决定倔强一把。在本文里使用最新的openpyxl,看看到底会有多大差别。截止本文撰写的2021年7月8日,openpyxl的版本为3.0.5。那么,咱们就开始吧。

13.3.1 用openpyxl 模块打开Excel文档

在导入openpyxl 模块后,就可以使用openpyxl.load_workbook()函数了。在交互式环境中输入以下代码:

In [1]: import openpyxl

In [2]: wb = openpyxl.load_workbook('example.xlsx')

In [3]: type(wb)
Out[3]: openpyxl.workbook.workbook.Workbook

open.load_workbook()函数接收文件名,并返回一个workbook 数据类型的值。这个workbook对象代表这个excel文件,这有点类似于file对象代表一个打开的文本文件。

要记住,example.xlsx 必须在当前工作目录中,你才能处理它。可以导入os,使用函数os.getcwd()弄清楚当前工作目录是什么,并使用os.chdir()改变当前工作目录。

13.3.2 从工作簿中取得工作表

访问sheetnames属性可以取得工作簿中所有表名的列表。在交互式环境中输入以下代码:

In [1]: import openpyxl

In [2]: wb = openpyxl.load_workbook('example.xlsx')

In [3]: type(wb)
Out[3]: openpyxl.workbook.workbook.Workbook

In [4]: wb.sheetnames
Out[4]: ['Sheet1', 'Sheet2', 'Sheet3']

In [6]: sheet = wb['Sheet3']

In [7]: sheet
Out[7]: <Worksheet "Sheet3">

In [8]: type(sheet)
Out[8]: openpyxl.worksheet.worksheet.Worksheet

In [9]: sheet.title
Out[9]: 'Sheet3'

In [10]: anotherSheet = wb.active

In [11]: anotherSheet
Out[11]: <Worksheet "Sheet1">

每个表由一个Worksheet对象表示,取得它的方法是使用带方括号的工作表名称字符串,折合取得字典的键一样。最后,可以使用Workbook对象的active属性来取得工作簿的活动表。在取得worksheet对象后,可以通过title属性取得它的名称。

13.3.3 从表中取得单元格

有了worksheet对象后,就可以按名字访问cell对象。在交互式环境中输入以下代码:

In [12]: import openpyxl

In [13]: wb = openpyxl.load_workbook('example.xlsx')

In [15]: sheet = wb['Sheet1']

In [16]: sheet['A1']
Out[16]: <Cell 'Sheet1'.A1>

In [17]: sheet['A1'].value
Out[17]: datetime.datetime(2015, 4, 5, 13, 34, 2)

In [18]: c = sheet['B1']

In [19]: c.value
Out[19]: 'Apples'

In [20]: 'Row %s,Column %s is %s' % (c.row,c.column,c.value)
Out[20]: 'Row 1,Column 2 is Apples'

In [21]: 'Cell %s is %s' % (c.coordinate,c.value)
Out[21]: 'Cell B1 is Apples'

In [22]: sheet['c1'].value
Out[22]: 73

Cell 对象有一个value属性,它包含这个单元格中保存的值。Cell对象也有row、column和coordinate 属性,可以提供该单元格的位置信息。

row = 行

column = 列

coordinate = 位置信息(比如A1、B2)

cell = 单元格

这里,访问单元格B1的Cell对象的value属性,我们得到字符串‘Apples’。row属性给出的是整数1,column属性给出的是‘B’,coordinate属性给出的是‘B1’。

openpyxl 将自动解释列A中的日期,将它们返回为datetime值,而不是字符串。datetime 数据类型将在第17章进一步解释。

用字母来指定列,这在程序中可能有点奇怪,特别是在Z列之后,列开始使用两个字母:AA、AB、AC来表示。作为替代,在调用表的cell()方法时,传入整数作为该方法的row和column关键字参数,这样也可以得到一个单元格。第一行或第一列的整数是1,不是0.输入以下代码,继续演示交互式环境的例子:

In [23]: sheet.cell(row=1,column=2)
Out[23]: <Cell 'Sheet1'.B1>

In [24]: sheet.cell(row=1,column=2).value
Out[24]: 'Apples'

In [26]: for i in range(1,8,2):
    ...:     print(i,sheet.cell(row=i,column=2).value)
    ...: 
1 Apples
3 Pears
5 Apples
7 Strawberries

可以看到,使用表的cell()方法,并传入row=1 和 column=2,将得到单元格B1的cell对象,就像指定sheet['B1']一样。然后,利用cell()方法和它的关键字参数,就可以编写for循环以输出一些列单元格的值。

假定你想顺着B列输出所有奇数单元格的值。通过传入2作为range()函数的‘步长’参数,可以取得每隔一行的单元格(在这里就是所有奇数行)。for循环的i变量被传递作为cell()方法的row关键字参数,而column 关键字参数总是取2.请注意传入的是整数2,而不是字符串‘B’。

可以通过Worksheet对象的max_row和max_column属性来确定表的大小。在交互式环境中输入以下代码:

In [29]: import openpyxl

In [30]: wb = openpyxl.load_workbook('example.xlsx')

In [32]: sheet = wb['Sheet1']

In [33]: sheet.max_row
Out[33]: 7

In [34]: sheet.max_column
Out[34]: 3

请注意,max_column 属性是一个整数,而不是Excel中出现的字母。

13.3.4 列字母和数字之间的转换

要从字母转换到数字,就调用openpyxl.utils.column_index_from_string()函数。要从数字转换到字母,就调用openpyxl.utils.get_column_letter()函数。在交互式环境中输入以下代码:

In [36]: from openpyxl.utils import get_column_letter,column_index_from_string

In [37]: get_column_letter(1)
Out[37]: 'A'

In [38]: get_column_letter(2)
Out[38]: 'B'

In [39]: get_column_letter(27)
Out[39]: 'AA'

In [40]: get_column_letter(900)
Out[40]: 'AHP'

In [41]: wb = openpyxl.load_workbook('example.xlsx')

In [42]: sheet = wb['Sheet1']

In [7]: get_column_letter(sheet.max_column)
Out[7]: 'C'

In [8]: column_index_from_string('A')
Out[8]: 1

In [9]: column_index_from_string('AA')
Out[9]: 27

在从openpyxl.utils 模块引入这两个函数后,那就可以调用get_column_letter()了。传入像27这样的整数,弄清楚第27列的字母是什么。函数column_index_from_string()做的事情相反:传入一列的字母名称,它告诉你该列是第几列。要使用这些函数,不必加载一个工作簿。也可以加载一个工作簿,取得worksheet对象,并使用worksheet对象的属性。如max_column,来取得一个整数。然后,将该整数传递给get_column_letter()。

13.3.5 从表中取得行和列

可以将worksheet对象切片,取得电子表格中一行、一列或一个矩形区域中所有cell对象。然后可以循环遍历这个切片中的所有单元格。在交互式环境中输入以下代码:

In [10]: import openpyxl

In [11]: wb = openpyxl.load_workbook('example.xlsx')

In [12]: sheet = wb['Sheet1']

In [13]: tuple(sheet['A1':'C3'])
Out[13]: 
((<Cell 'Sheet1'.A1>, <Cell 'Sheet1'.B1>, <Cell 'Sheet1'.C1>),
 (<Cell 'Sheet1'.A2>, <Cell 'Sheet1'.B2>, <Cell 'Sheet1'.C2>),
 (<Cell 'Sheet1'.A3>, <Cell 'Sheet1'.B3>, <Cell 'Sheet1'.C3>))

In [15]: for row0fCell0bjects in sheet['A1':'C3']:
    ...:     for cell0bj in row0fCell0bjects:
    ...:         print(cell0bj.coordinate,cell0bj.value)
    ...:     print('--- end of row ---')
    ...: 
A1 2015-04-05 13:34:02
B1 Apples
C1 73
--- end of row ---
A2 2015-04-05 03:41:23
B2 Cherries
C2 85
--- end of row ---
A3 2015-04-06 12:46:51
B3 Pears
C3 14
--- end of row ---

这里,我们指明需要从A1到C3的矩形区域的Cell对象,我们还得到了一个Generator对象,它包含该区域中的Cell对象。为了弄清楚这个Generator对象,可以对它使用tuple()方法以在一个元组中列出它的Cell对象。

这个元组包含3个元组:每个元组代表1行,从指定区域的顶部到底部。这3个内部元组中的每一个包含指定区域中的一行Cell对象,从最左边的单元格到最右边的单元格。总的来说,工作表的这个切片包含了从A1到C3区域的所有Cell对象,从左上角的单元格开始,到右下角的单元格结束。

要输出这个区域所有单元格的值,我们使用两个for循环。外层for循环遍历这个切片中的每一行。然后针对每一行,内层for循环遍历该行中的每个单元格。

要访问特定行或列的单元格的值,也可以利用worksheet对象的rows 和columns属性。这些属性必须被list()函数转换为列表,才能使用方括号和索引。在交互环境中输入以下代码:

In [18]: import openpyxl

In [19]: wb = openpyxl.load_workbook('example.xlsx')

In [20]: sheet = wb.active

In [21]: list(sheet.columns)[1]
Out[21]: 
(<Cell 'Sheet1'.B1>,
 <Cell 'Sheet1'.B2>,
 <Cell 'Sheet1'.B3>,
 <Cell 'Sheet1'.B4>,
 <Cell 'Sheet1'.B5>,
 <Cell 'Sheet1'.B6>,
 <Cell 'Sheet1'.B7>)

In [23]: for cell0bj in list(sheet.columns)[1]:
    ...:     print(cell0bj.value)
    ...: 
Apples
Cherries
Pears
Oranges
Apples
Bananas
Strawberries

利用worksheet对象的rows属性,可以得到一个元组构成的元组。内部的每个元组都代表1行,包含该行中的cell对象。columns属性也会给你一个元组构成的元组,内部的每个元组都包含1列中的cell对象。对于example.xlsx,因为有7行3列,所以rows给出由7个元组构成的一个元组(每个内部元组包含3个cell对象),columns给出由3个元组构成一个元组(每个内部元组包含7个cell对象)。

要访问一个特定的元组,可以利用它在大元组中的索引。例如,要得到B列的元组,可以用sheet.columns[1];要得到代表A列的元组,可以用sheet.columns[0]。在得到了代表行或列的元组后,可以循环遍历它的对象,并输出它们的值。

13.3.6 工作簿、工作表、单元格

作为快速复习的内容,下面是从电子表格文件中读取单元格涉及的所有函数、方法和数据类型。

1.导入openpyxl 模块。

2.调用openpyxl.load_workbook()函数。

3、取得workbook对象。

4、取得active 或 sheetnames属性。

5、取得worksheet对象。

6、使用索引或工作表的cell()方法,带上row和column关键字参数。

7、取得cell对象。

8、读取cell对象的value属性。

13.4 项目:从电子表格中读取数据

程序需要以下任务:

1.从excel电子表格中读取数据

2.计算每个县中普查区的数目

3.计算每个县的总人口

4.输出结果

这意味着代码需要执行以下操作。

1.用openpyxl 模块打开excel文档并读取单元格。

2.计算所有普查区和人口数据,并将它保存到一个数据结构中。

3.利用pprint模块,将该数据结构写入一个扩展名为.py的文本文件

第一步:读取电子表格数据

cen.xlsx电子表格中只有一张表,每一行都保存了一个普查区的数据。列分别是普查区的编号(A)、州的简称(B)、县的名称(C)、普查区的人口(D)。

打开一个新的文件编辑器窗口,输入以下代码,将文件保存为read.py。

#! python3
import openpyxl,pprint
print('正在打开中,莫要急......')
wb = openpyxl.load_workbook('cen.xlsx')
sheet = wb['Sheet1']
data = {}
print('读取行数中......')
for row in range(2,sheet.max_row +1):
# 电子表格中的每一行都有一个普查区的数据
    state = sheet['B'+str(row)].value
    county = sheet['C' + str(row)].value
    pop = sheet['D' + str(row)].value

这段代码导入了openpyxl 模块,也导入了pprint模块,用pprint模块来输出最终县的数据。然后代码打开了cen.xlsx文件。取得了包含人口普查数据的工作表,并开始迭代它的行。

请注意,你也创建了一个data的变量,它将包含你计算的每个县的人口和普查区数目。在它里面存储任何数据之前,你应该确定它内部的数据结构。

第2步:填充数据结构

保存在data中的数据结构将是一个字典,以轴的简称作为键。每个州的简称将映射到另一个字典,其中的键是该州的县的名称。每个县的名称又映射到一个字典,该字典只有两个键:‘tracts’和‘pop’。这些键映射到普查区数目和该县的人口。例如,该字典可能类似于:

{'AK':{'Aleutians East':{'pop':3141,'tracts':1},
       'Aleutians West':{'pop':5561,'tracts':2}
}}

如果前面的字典保存在data中,下面的表达式求值结果如下:
>>> data['AK']['Anchorage']['pop']
291826
>>> data['AK']['Anchorage']['tracts']
55

一般来说,data字典中的键看起来像这样:
data[state abbrev][county]['tracts']
data[state abbrev][county]['pop']

既然知道了data的结构,那就可以编写代码,并用县的数据填充它了。将下面的代码添加到程序的末尾:
#确保该状态的键存在
data.setdefault(state,{})
#确保这个州的这个县的键存在
data[state].setdefault(county,{'tracts':0,'pop':0})
#每一行代表一个人口普查区,所以加一 
data[state][county]['tracts'] += 1
#用该普查区的人口数增加县城人口数
data[state][county]['pop'] +=int(pop)

最后的两行代码执行实际的计算工作,在for循环的每次迭代中,针对当前的县,增加tracts的值,并增加pop的值。

其他代码存在是因为只有data中存在键,你才能添加县字典作为州缩写键的值。(也就是说,如果‘AK’键不存在,data[state][county]['tracts'] += 1 将导致一个错误。)为了确保州缩写的键存在,你需要调用setdefault()方法,在state还不存在时设置一个默认值。

正如data字典需要一个字典作为每个周缩写的值一样,这样的字典又需要一个字典作为每个县的键的值。这样每个字典又需要键‘tracts’和‘pop’,它们的初始值为整数0(如果这个字典的结构令你混淆,回去看看本节开始处的例子)。

如果键已经存在,那么setdefault()不会做任何事情,因此for循环的每次迭代中调用它不会有问题。

你还知道令人混淆啊!摔!本来我觉得我懂了,我再看看你的解释,我发现我又没懂,再看看代码,啊!这就是混淆的滋味。

总结下从这里为止不懂的点,以后万一精进了,或者大彻大悟了或许就能够理解吧。

1.为什么在for循环中sheet.max_row要+1?后面的内容要取值,但是max_row已经是最后一行了呀。再+1,那行应该是空的才对啊。那就是没有值。既然最后一行是没有值的,为什么要+1呢?这个可能在我完成本节学习后试一试两种情况下代码输出内容的不同才能判断了。

2.这个是我看完随书的资料才发现的,书中仅表述data.setdefault(state,{})添加到程序的末尾,但是没说其实是添加到for循环的末尾。所以这部分应该是循环的一部分。(算是解决了)

但是这部分我并不是很懂,因为注释竟然是英文!英文!大哥你翻译一下啊。

得了,还是自己去谷歌翻一下吧。

增加上注释了,发现还是不懂,得了,再见吧,我的青春。

第3步:将结果写入文件
for循环结束后,data字典将包含所有的人口和普查区信息,以县和州为键。这时,你可以编写更多代码,将数据写入文本文件或另一个excel电子表格。目前,我们只是使用pprint.pformat()函数,将变量字典的值作为一个巨大的字符串写入文件sea.py。在程序的末尾加上以下代码:

print('录入结果......')
res = open('sea.py','w')
res.write('allData = ' + pprint.pformat(data))
res.close()
print('结束')

pprint.pformat()函数产生一个字符串,它本身就是格式化好的、有效的Python代码。将它输出到文本文件sea.py中,你就通过Python程序生成了一个Python程序。这看起来有点复杂,但好处是你现在可以导入sea.py,就像导入其他Python模块一样。在交互式环境中,将当前工作目录变更到sea.py所在的文件夹,然后导入它。

In [34]: import os

In [35]: import sea

In [36]: sea.allData['AK']['Anchorage']
Out[36]: {'pop': 291826, 'tracts': 55}

read.py程序是可以扔掉的代码:当你把它的结果保存为sea.py之后,就不需要再次运行该程序了。任何时候,只要需要县的数据,就可以执行import sea 来获得。

手动计算这些数据可能需要数小时,而使用这些程序只要几秒。利用openpyxl,可以毫无苦难地提取保存在excel电子表格中的信息,并对它进行计算。

第4步:类似程序的思想
许多公司使用excel来保存各种类型的数据,这使得电子表格会变得庞大,这并不少见。解析excel电子表格的程序都有类似的结构:加载电子表格文件,准备一些变量或数据结构,然后循环遍历电子表格的每一行。这个程序还可以做下列事情:
1.比较一个电子表格中多行的数据
2.打开多个excel文件,酷啊电子表格比较数据
3.检查电子表格是否有空行或无效的表格,如果有就发出警告
4.从电子表格中读取数据,将它作为Python程序的输入

13.5 写入Excel文档

openpyxl 也提供了一些方法写入数据,这意味着你的程序可以创建和编辑电子表格文件。利用Python创建一个包含几千行数据的电子表格是非常简单的。

13.5.1 创建并保存Excel文档

调用openpyxl.workbook()函数以创建一个新的workbook对象。在交互式环境中输入以下代码:

In [1]: import openpyxl

In [2]: wb = openpyxl.Workbook()

In [3]: wb.sheetnames
Out[3]: ['Sheet']

In [5]: sheet = wb.active

In [6]: sheet.title
Out[6]: 'Sheet'

In [7]: sheet.title = '全孝盛'

In [8]: wb.sheetnames
Out[8]: ['全孝盛']

工作簿将从一个名为Sheet的工作表开始。你可以将新的字符串保存在它的title属性中,从而改变工作表的名字。

当修改workbook 对象或它的工作表和单元格时,电子表格文件不会保存,除非你调用save()工作簿方法。在交互式环境中输入以下代码(让xiaosheng.xlsx)处于当前工作目录:

In [9]: wb.save('xiaosheng.xlsx')

这里,我们改变了工作表的名称。为了保存变更,我们将文件名作为字符串传递给save()方法。

当你编辑从文件加载一个电子表格时,总是应该将新的、编辑过的电子表格保存到不同的文件名中。这样,如果代码有bug,导致新的保存到文件的数据不对,那么还有最初的电子表格文件可以处理。

13.5.2 创建和删除工作表

利用create_sheet()方法和del操作符可以在工作簿中添加或删除工作表。在交互式环境中输入以下代码:

In [10]: import openpyxl

In [11]: wb = openpyxl.Workbook()

In [12]: wb.sheetnames
Out[12]: ['Sheet']

In [13]: wb.create_sheet()
Out[13]: <Worksheet "Sheet1">

In [14]: wb.sheetnames
Out[14]: ['Sheet', 'Sheet1']

In [15]: wb.create_sheet(index=0,title='全孝盛')
Out[15]: <Worksheet "全孝盛">

In [16]: wb.sheetnames
Out[16]: ['全孝盛', 'Sheet', 'Sheet1']

In [17]: wb.create_sheet(index=2,title='朴孝敏')
Out[17]: <Worksheet "朴孝敏">

In [18]: wb.sheetnames
Out[18]: ['全孝盛', 'Sheet', '朴孝敏', 'Sheet1']

create_sheet()方法返回一个新的Worksheet 对象,其名为Sheetx,它默认是工作簿的最后一个工作表。或者,可以利用index和title关键字参数指定新工作表的索引和名称。

继续前面的例子,输入以下代码:
In [21]: del wb['Sheet']

In [22]: del wb['Sheet1']

In [23]: wb.sheetnames
Out[23]: ['全孝盛', '朴孝敏']

可以使用del操作符,从工作簿中删除一个工作表,就像用它从字典中删除一个键值对一样。在工作簿中添加或删除工作表之后,记得调用save()方法来保存变更。

13.5.3 将值写入单元格

将值写入单元格,很像将值写入字典中的键。在交互式环境中输入以下代码:

In [26]: import openpyxl

In [27]: wb = openpyxl.Workbook()

In [28]: sheet = wb['Sheet']

In [29]: sheet['A1'] = '撒浪嘿哟,全孝盛'

In [30]: sheet['A1'].value
Out[30]: '撒浪嘿哟,全孝盛'

如果有单元格坐标的字符串,那么可以像字典的键一样,将它用于Worksheet 对象,并制定要写入的单元格。

13.7 设置单元格的字体风格

为了定义单元格的字体风格,需要从openpyxl.styles 模块导入Font()函数:

from openpyxl.style import Font

这让你能输入Font(),代替openpyxl.styles.Font()。

这里有一个例子,它创建了一个新的工作簿,将A1单元格设置为24点、斜体。在交互式环境中输入以下代码:

In [32]: import openpyxl

In [33]: from openpyxl.styles import Font

In [34]: wb = openpyxl.Workbook()

In [35]: sheet = wb['Sheet']

In [36]: dachui = Font(size=24,italic=True)

In [37]: sheet['A1'].font = dachui

In [38]: sheet['A1'] = '全孝盛❤'

In [40]: wb.save('xiaosheng.xlsx')

在这个例子中,Font(size=24,italic=True)返回一个Font对象,它保存在dachui中。Font()的关键字参数size和italic配置了该Font对象的风格信息。当sheet['A1'].font被赋值为dachui对象时,所有字体风格的信息将应用于单元格A1。

13.8 Font 对象

要设置font属性,就像Font()函数传入关键字参数。下图所示为Font()函数可能的关键字参数。

关键字参数 数据类型 描述

name 字符串 字体名称,如‘Calibri’或‘Times New Roman’

size 整型 大小点数

bold 布尔型 True表示粗体

italic 布尔型 True表示斜体

可以调用Font()来创建一个Font对象,并将这个Font对象保存在一个变量中,然后将该变量赋给Cell对象的font属性。例如,下面的代码创建了各种字体风格:

In [45]: from openpyxl.styles import Font

In [46]: import openpyxl

In [47]: from openpyxl.styles import Font

In [48]: wb = openpyxl.Workbook()

In [49]: sheet = wb['Sheet']

In [50]: font0=Font(name='Calibri',bold=True)

In [51]: sheet['A1'].font = font0

In [52]: sheet['A1'] = 'Bold Calibri'

In [53]: font1 = Font(size=24,italic=True)

In [54]: sheet['B3'].font = font1

In [55]: sheet['B3'] = '24 pt quanxiaosheng'

In [56]: wb.save('xiaosheng.xlsx')

这里,我们将一个Font对象保存在font0中,然后将A1的Cell对象的font属性设置为font0.我们对另一个Font对象重复这个过程,以设置第二个单元格的字体。运行这段代码后,电子表格中的A1和B3单元格的字体风格将被设置为自定义的字体风格。

对于单元格A1,我们将字体名称设置为‘Calibri’,并将bold 设置为True。这样我们的文本将以粗体Calibri的方式显示。我们没有指定大小,因此使用openpyxl默认值11。在单元格B3中,我们的文本是斜体,大小是24.我们没有指定字体的名称,因此使用openpyxl的默认值是Calibri。

13.9 公式

公式以一个等号开始,可以配置单元格来让它包含通过其他单元格计算得到的值。在本节中,你将利用openpyxl模块,用变成的方式在单元格中添加公式,就像添加普通的值一样。

例如:sheet[‘B9’] = ‘SUM(B1:B8)’

为单元格设置公式就像设置其他文本值一样。

In [58]: import openpyxl

In [59]: wb = openpyxl.Workbook()

In [60]: sheet = wb.active

In [61]: sheet['A1'] = 200

In [62]: sheet['A2'] = 300

In [63]: sheet['A3'] = 'SUM(A1:A2)'

In [64]: wb.save('gongshi.xlsx')

单元格A1和A2分别设置为200和300.单元格A3设置为一个公式,求出A1和A2的和。如果在Excel中打开这个电子表格,那么A3 的值显示为500。

Excel 公式为电子表格提供了一定程度的变成能力,但对于复杂的任务,它很快就会无能为力。例如,即使你非常熟悉Excel的公式。想要弄清楚=IFERROR(TRIM(IF(LEN(VLOOKUP(F7,Sheet2!$A$1:$B$10000,2,FALSE))>0,SUBSTITUTE(VLOOKUP(f7,heet2!$A$1:$B$10000,2,FALSE),"",""),"")),"")实际上做了什么,也是非常头痛的事。Python代码的可读性要好的多。

13.10 调整行和列

在Excel中,调整行和列的大小非常容易,只需单击并拖动行的边缘或列的表即可。但如果你需要根据单元格的内容来设置行或列的大小,或者希望一次性设置大量电子表格文件中的行列大小,那么编写Python程序就来做的要快得多。

13.10.1 设置行高和列宽

Worksheet 对象有row_dimensions 和 column_dimensions属性,分别用于控制行高和列宽。在交互式环境中输入以下代码:

In [65]: import openpyxl

In [66]: wb = openpyxl.Workbook()

In [67]: sheet = wb.active

In [68]: sheet['A1'] = '全孝盛'

In [69]: sheet['B2'] = '朴孝敏'

In [70]: sheet.row_dimensions[1].height = 70

In [71]: sheet.column_dimensions['B'].width = 20

In [72]: wb.save('xiaosheng.xlsx')

工作表的row_dimensions 和 column_dimensions 是像字典一样的值,row_dimensions 包含RowDimension对象,column_dimensions 包含columnDimensions。在row_dimensions 中,可以用行的编号来访问一个对象。在column_dimensions中,可以用列的字母来访问一个对象。

一旦有了RowDimension对象,就可以设置它的高度。一旦有了columnDimensions对象,就可以设置它的宽度。行的高度可以设置为0到409之间的整数或浮点值,这个值表示高度的点数。一点等于0.35mm(1/72英寸)。默认的行高是12.75。列宽可以设置0到255之间的整数或浮点数,这个值表示默认字体大小时(11点),单元格可以显示的字符数。默认的列宽是8.43个字符。列宽为零或行高为零将使单元格隐藏。

13.10.2 合并和拆分单元格

利用merge_cells()工作表方法,可以将一个矩形区域中的单元格合并为一个单元格。

In [73]: import openpyxl

In [74]: wb = openpyxl.Workbook()

In [76]: sheet = wb.active

In [77]: sheet.merge_cells('A1:D3')

In [78]: sheet['A1'] = '全孝盛'

In [79]: sheet.merge_cells('C5:D5')

In [80]: sheet['C5'] = '朴智妍'

In [81]: wb.save('xiaosheng.xlsx')

merge_cells()的参数是一个字符串,表示要合并的矩形区域左上角和右上角的单元格:‘A1:D3’将12个单元格合并为一个单元格。要设置合并后单元格的值,只需要设置这一组合并单元格左上角的单元格的值。

如果运行这段代码,merged.xlsx 看起来如下图:
要拆分单元格,就调用unmerge_cells()工作表方法。

In [84]: wb = openpyxl.load_workbook('xiaosheng.xlsx')

In [85]: sheet = wb.active

In [86]: sheet.unmerge_cells('A1:D3')

In [87]: sheet.unmerge_cells('C5:D5')

In [88]: wb.save('xiaosheng.xlsx')

如果保存变更,然后查看这个点子表格,就会看到合并的单元格恢复成了独立的单元格。

13.10.3 冻结窗格

对于太大而不能一屏显示的电子表格,“冻结”顶部的几行或最左边的几列是很有帮助的。例如,就算用户滚动电子表格,冻结的列或行表头也是始终可见的。这称为“冻结窗格”。在openpyxl中,每个Worksheet对象都有一个freeze_panes属性,该属性可以设置为一个Cell对象或一个单元格坐标的字符串。请注意,单元格上边的所有行和左边的所有列都会冻结,但单元格所在的行和列不会冻结。

要解冻所有的单元格,就像freeze_panes设置为None或‘A1’。下文所示为freeze_panes设置的一些例子,其中的哪些行或列会冻结。

freeze_panes 的设置 冻结的行和列

sheet.freeze_panes=’A2′ 行1

sheet.freeze_panes=’B1′ 列A

sheet.freeze_panes=’C1′ 列A和列B

sheet.freeze_panes=’C2′ 行1、列A和列B

sheet.freeze_panes = ‘A1’ 或

sheet.freeze_panes = None 没有冻结窗格

确保你有产品销售电子表格。然后在交互式环境中输入以下代码:

In [89]: import openpyxl

In [90]: wb = openpyxl.load_workbook('produceSales.xlsx')

In [91]: sheet = wb.active

In [92]: sheet.freeze_panes = 'A2'

In [93]: wb.save('freezeExample.xlsx')

如果将freeze_panes属性设置为‘A2’,那么无论用户将电子表格滚动到何处,行1将永远可见。

13.11 图表

openpyxl 支持利用工作表中单元格的数据来创建条形图、折线图、散点图和饼图。要创建图表,需要做下列事情。

1.从一个矩形区域选择单元格来创建一个Reference对象。

2.通过传入Reference 对象来创建Series对象。

3.创建一个Chart 对象。

4.将Series 对象添加Chart 对象。

5.可选地设置Chart 对象drawing.top、drawing.left\drawing.width和drawing.height属性。

6.将Chart 对象添加到Worksheet对象。

Reference 对象需要一些解释。Reference 对象是通过调用openpyxl.charts.Reference()函数并传入以下3个参数创建。

1.包含图标数据的Worksheet对象。

2.两个整数的元组,代表矩形选择区域的左上角单元格,该区域包含图表数据:元组中第一个整数是行,第二个整数是列。

3.两个整数的元组,代表矩形选择区域的右下角单元格,该区域包含图标数据:元组中第一个整数是行,第二个整数是列。

import openpyxl
wb = openpyxl.Workbook()
sheet = wb.active
for i in range(1,11):
    sheet['A' + str(i)] = 10
ref0import openpyxl
wb = openpyxl.Workbook()
sheet = wb.active
for i in range(1,11):
    sheet['A' + str(i)] = i
ref0 = openpyxl.chart.Reference(sheet,min_col=1,min_row=1,max_col=1,max_row=10)
series0 = openpyxl.chart.Series(ref0,title='First series')
chart0 = openpyxl.chart.BarChart()
chart0.title = 'my Chart'
chart0.append(series0)
sheet.add_chart(chart0,'c5')
wb.save('test.xlsx')

整章学习完后发现没有出现openpyxl版本导致代码出错的问题。大家可以尽管尝试新版本。

差不多一个月不写代码,果然无论是写还是阅读都产生了一点的小困难。果然逆水行舟,不进则退啊。

胭惜雨

2021年07月10日

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据