社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

推荐两个高效处理 Excel 的 Python 开源库

Python开发者 • 3 年前 • 354 次点击  

【导语】:openpyxl 和 formulas 是两个成熟的开源库,在Python中借助这两个库,处理Excel电子表格,可以实现自动访问、处理表格中数据的功能,省时高效,不易出错,是处理Excel表格的一种好办法。

简介

Excel在工作中很常见,许多公司的软件项目都会用到它。对于应用程序开发者而言,我们经常需要将Excel文件转换为应用程序。大多数情况下我们都把Excel作为数据的导出格式,有时也将其作为数据的输入格式。

虽然Excel并不是一个软件领域的专用语言,但一些软件领域的专家常常需要用其处理问题。这就导致会“说”Excel的软件更受用户的青睐。即使从长远来看,我们的目标是把Excel变成一个应用程序或者软件领域的一种语言(DSL),但能自动处理Excel中的数据也是一个重要方向。

在以下情景中,自动处理Excel数据将非常有用:

例如在一个项目中,许多用户需要使用某些相同的Excel文件,而应用程序通过处理这些Excel文件,让用户使用起来更加方便。或者当一些用户想用Excel中的数据生成带有附加图表和formulas的报告时,应用程序可以生成一个电子表格,用来输出一些查询或计算结果,方便用户使用这些数据。

在本文中,我们将探索如何使用openpyxl库和其他工具,结合Python处理Excel电子表格。主要分为以下几点:

  • 处理Excel文件,用不同的方法访问其中的数据;
  • 使用formulas;
  • 输出Excel文件。

注意:本文中我们处理的只是“现代”(2010)基于xml的Excel文件,文件扩展名为.xlsx。Openpyxl不支持之前的二进制Excel文件(.xls),针对这类文件有其他的解决方案,在本文中不做讨论。本文中的所有示例代码均可在GitHub仓库中找到。

项目地址:

https://github.com/Strumenta/article-python-excel

安装

下面,我们设定一个类似unix的环境-Linux、OSX或者WSL。第一步,创建一个目录来存放我们的项目:

mkdir python-excel
cd python-excel

如果想要把代码放到Github上,我们可以在Github上创建一个仓库,然后把它克隆到本地:

git clone  python-excel
cd python-excel

这将使我们能在GitHub为Python生成一个.gitignore文件,为我们生成一个README文件和一个license文件。在创建了项目根目录之后,我们可以使用pip安装来openpyxl。我们需要设置一个虚拟环境,这样就不会影响到系统的Python安装:

python -m venv .venv
source .venv/bin/activate

激活环境后,我们应该可以在shell prompt中看到:

(.venv) python-excel (main*) »

随后我们写一个requirements.txt文件,列出我们的依赖包:

echo openpyxl > requirements.txt

即使当前只有一个依赖包,也要将它列在一个可以被机器解析的文件中,这样做会使其他开发人员更容易地使用我们的项目-即使在之后的版本中我们已经忘记了这个依赖包。之后用pip安装所需的依赖包:

python -m pip install -r requirements.txt

简单使用

1.加载一个Excel文件

在Openpyxl中,我们把Excel文件称为"workbook",用openpyxl.workbook.Workbook类的实例来表示,打开一个Excel文件非常简单:

wb = load_workbook(path)
调用load_workbook的结果

Openpyxl打开文件后,一般可以同时进行读取和写入工作,除非我们给load_workbook设置一个read_only=True参数,表示只读取文件,当我们使用完一个Excel文件后,必须关闭它:

wb.close()

不幸的是,Workbook不是一个“文件管理者”,所以不能用Python中的语句来自动关闭它。即使在一些exceptions的情况中,也必须得手动关闭文件:

wb = load_workbook(path)
try:
    # Use wb...
finally:
    wb.close()

2.处理一个Excel文件 - 通用案例

通常,Workbooks中可能有几个表,我们需要选择Excel文件中的一个表,访问其中的数据。随后,我们再学习如何处理多个表。现在,假设我们对active工作表中的数据比较感兴趣——当用户在他们的应用中打开文件就会看到的工作表:

sheet = wb.active

这实际上是文档中最常用的表。我们可以用不同的方法访问表格中的数据。我们可以使用Pythonic生成器每次处理一行数据

(1)对行进行遍历:

for row in sheet.rows():
    # Do something with the row

rows()产生的行,本身就是一个生成器,我们可以遍历它们:

for row in sheet.rows():
    for cell in row:
        # Do something with the cell

还可以根据索引访问数据:

for row in sheet.rows():
    header = row[2]

实际上,表格本身就是可以按行进行迭代的,所以我们可以忽略所有行:

for row in sheet:
    pass

(2)使用cols方法对列进行遍历。

for col in sheet.cols():
    # Use the column

遍历列与遍历行的操作基本相同:它们本身都是可迭代的,并且可以通过索引寻址。

(3)通过地址访问单元格。

如果我们需要某个单元格中的数据,那么并不需要遍历整个表格去找;可以使用excel样式的坐标来访问这个单元格:

cell = sheet['C5']

在随后的章节中,将展示如何从一行,一列,或者一些单元格中获取一个生成器。

3.处理单元格

在任何情况下,想要处理电子表格中的数据,就必须访问每个单元格。在Openpyxl中,单元格有一个值和许多仅用于编写的其他信息,比如样式信息。更方便地是,我们可以把单元格中的值作为Python对象(数字、日期、字符串等),用Openpyxl将它们转换为Excel类型。因此,单元格内容就不一定要是字符串。例如,我们以数字的形式读取单元格的内容:

tax_percentage = sheet['H16'].value
tax_amount = taxable_amount * tax_percentage

然而,我们并不能保证用户一定在单元格中输入了数字;如果它包含字符串"bug",如果比较幸运的话,在运行上面的代码后,我们会得到一个运行错误:

TypeError: can't multiply sequence by non-int of type 'float'

然而,在不那么幸运的情况下,例如,当taxable_amount是一个整数时——因为我们在示例中处理的数据是钱,所以应该是整数——我们将得到一个重复taxable_amount次的“bug”。这是因为Python把*操作符当成了字符串,整数就意味着“重复字符串n次”。这可能会导致进一步的类型错误,或者在Python无法放置如此大的字符串时出现内存错误。因此,我们应该验证程序的输入,包括Excel文件。在这个特殊的例子中,我们可以使用Python的isinstance函数来检查单元格中值的类型:

if isinstance(cell.value, numbers.Number):
    # Use the value

我们还可以询问单元格它存储的数据类型是什么:

if cell.data_type == TYPE_NUMERIC:
    # Use the numeric value

4.单元格高级寻址

到目前为止,我们已经使用了访问单元格最简单、最直接的方法。然而,这并不是所有方法;让我们来看看更复杂的方案。

(1)除了active之外的其他表。

我们可以通过在workbook中通过名称来访问它们:

sheet = wb['2020 Report']

然后我们就可以像之前看到的那样访问单元格了。

(2)单元格范围

我们不一定要一个一个的寻址单元格-还可以设定范围来访问单元格:

  • sheet['D']是指一整行(本例中是D这一行)
  • sheet[7]是指一整列(本例中是第7列)
  • sheet['B:F']代表许多行
  • sheet['4:10'] 代表许多列
  • sheet['C3:H5']是最通用的选择,代表任意范围的单元格

以上任何一种情况,结果都是一个按行迭代所有单元格(除非迭代的范围以列为标准,在这种情况下,单元格按列顺序进行迭代):

for cell in sheet['B2:F10']:
    # B2, B3, ..., F1, F2, ..., F10
for cell in sheet['4:10']:
    # A4, B4, ..., A10, B10, ... 
sheet['B2:F10']中的单元格
sheet['4:10']中的单元格

5.单元格迭代器

如果上述寻址方案解决不了问题,那我们可以考虑一些简单的方法iter_rows和iter_columns,它们分别按行和列返回单元格生成器。需要指出,这些方法都需要5个参数:

  • min_row - 起始行的编号(1就是A,2就是B,以此类推)
  • min_col - 起始列的编号
  • max_row - 最后一行的编号
  • max_col - 最后一列的编号
  • values_only - 生成器将只显示每个单元格的值,而不是整个单元格对象。所以,我们不需要用cell.value,而只要value。另一方面,我们不能访问单元格的其他属性,比如data_type。例如,如果我们想按列在B2:F10的范围上进行迭代,可以这样写:
for cell in sheet.iter_columns(min_row=2, min_col=2, max_row=6, max_col=10):
    # Use the cell

6.编写一个Excel文件

要写一个Excel文件,我们只需在workbook上调用save方法:

wb.save('someFile.xlsx')

知道如何保存一个workbook后,让我们看看如何修改它,这将会很有趣。我们可以修改文件中的workbook,也可以修改在Python中创建的workbook。

7.修改单个单元格

我们可以用指定的方式来改变一个单元格中的值:

cell.value = 42

这会自动更新单元格的数据类型以存储新的值。除了基本类型(整数、浮点数、字符串)之外,还包括datetime模块中的各种类,如果你安装了NumPy,那么NumPy数字类型也可以使用。不仅可以设置值和类型,我们还可以设置单元格的其他属性,特别是样式信息(字体、颜色等),这对做一个好看的报告很有用。

Openpyxl的文档中有许多关于调整样式的详细信息,我们可以在这里查询:

https://openpyxl.readthedocs.io/en/stable/styles.html

8.添加或移除表格

到目前为止,我们已经看到了如何处理一些对象,特别是workbooks和worksheets——就像处理字典一样,访问其中的细节:工作表、行、列、单个单元格、单元格范围。现在,我们将学习如何添加新信息,以及如何更改现有信息。我们先从表格开始。

使用 create_sheet方法来创建worksheet:

new_sheet = wb.create_sheet()

这样就可以在workbook中的其他表格之后添加一个新表,我们可以给这个新表一个标题:

new_sheet = wb.create_sheet(title = 'My new sheet')

如果我们想把这个表格放在其他位置,我们可以指定它的索引(从0开始):

# The new sheet will be inserted as the third sheet
new_sheet = wb.create_sheet(index = 2)

要删除一个表格的话有两种方法。可以根据名字进行删除:

del wb['My sheet']

我们可以使用in操作符来查看给出的表格名称是否在workbook中:

name = 'My sheet'
if name in workbook:
    del workbook[name]

或者还能调用remove方法来删除表格:

wb.remove(sheet)

9.增加或移除行、列、单元格

看看下面这些例子。首先通过访问一个单元格,可以为创建行和列腾出空间:

wb = Workbook()
# Initially, an empty worksheet has a single row and column, A and 1
self.assertEqual(wb.active.max_row, 1)
self.assertEqual(wb.active.max_column, 1)
# We set the value of the cell at C3; 
# openpyxl creates rows B, C and columns 2, 3 automatically
wb.active['C3'].value = 12
# Now the sheet has 3 rows and columns
self.assertEqual(wb.active.max_row, 3)
self.assertEqual(wb.active.max_column, 3)
wb.close()

此外,我们还可以用insert_rows和insert_cols方法在表格中添加行或列。当新创建一行或 一列时,单元格会自动调整:

wb = Workbook()
self.assertEqual(wb.active.max_row, 1)
wb.active['A1'].value = 11
# Insert 3 rows, starting at index 0 (i.e. row 1)
wb.active.insert_rows(0, 3)
self.assertEqual(wb.active.max_row, 4)
# Note how the cell, A1, has automatically moved by 3 rows to A4
self.assertEqual(wb.active['A4'].value, 11)

与此相对应,还可以使用delete_rows和delete_cols来删除行或列:

# Delete 2 columns, starting from index 1, i.e. column B
sheet.delete_columns(1, 2)

10.使用formulas

电子表格非常强大,因为它还支持用formulas来计算单元格中的值。当其他单元格发生变化时,通过计算取得值的单元格也会自动更新。让我们看看如何使用在Openpyxl中使用formulas吧。首先,如果我们只想读取一个Excel文件,我们可以完全忽略formulas。

此时,以“data only”模式打开它,这种模式将会隐藏formulas,所有单元格中的值都是固定的-也就是上次Excel文件计算后的结果:

wb = load_workbook(filename, data_only=True)

只有在修改Excel文件时,才需要重新使用formulas。虽然openpyxl有一些对解析 formulas的支持(例如,检查是否只调用了已知函数),但openpyxl自身不能产生formulas。因此,如果我们想要使用formulas,就必须求助于第三方库。进入一个叫做“formulas”的库。让我们把它添加到requirements.txt文件中并安装它:




    
$ cat requirements.txt
openpyxl
formulas
pip install -r requirements.txt

我们有两种方法来使用formulas库:

(1)像Excel那样,使用所有formulas,计算workbook中的值;

(2)将单独的formulas写入Python函数,这样就可以放入不同的参数,来使用这些formulas。

11.计算所有formulas中的值:

第一个示例比较枯燥,因为功能与前面的data_only方法作用相似。事实上,在那 种模式下,我们加载不了workbook,修改不了它,也不能重新使用其中的formulas。我们必须:

  • 把修改后的workbook保存到一个文件中;
  • 用formulas重新加载文件;
  • 调用API使用formulas;
  • 把计算后的值保存到文件中;
  • 在data_only模式下用openpyxl打开文件,查看计算后的结果。

这简直是在浪费时间!

当然,formulas库的这个特性也有价值,因为它支持一些高级的用法:

  • 同时在多个workbook中使用formulas

Excel中,可以在另一个文件中引用其他文件中的formulas。formulas可以在同一个集合中加载多个workbook,以解决这种跨文件引用问题,这是Excel中很少使用的特性。

  • 将整个Excel工作簿编译为Python函数

我们可以将某些单元格定义为输入单元格,把剩下的定义为输出单元格,得到一个函数,在给定输入后使用formulas,并在计算后把值返回给输出单元格。然而为了保持本文的简洁性,就不做详述。

12.把单独的formulas编译为Python函数:

让我们将重点放在单个formulas上,以便更好的使用openpyxl。操作如下:

func = formulas.Parser().ast(value)[1].compile()

由于某些原因,ast方法返回一个由2个对象组成的元组,其中第二个对象builder是最有用的。尽管这是内部APl,但也应该被包装在一个更友好的用户界面中。无论如何,当我们对上面的代码求值时,得到的func将是一个带有许多参数的函数,这些参数与formulas的输入一样:

func = formulas.Parser().ast('=A1+B1')[1].compile()
func(1, 2) == 3 # True

13.处理formulas的依赖项

因此,我们可以将单个单元格的formulas编译成一个函数。但是,当formulas依赖于其他单元格本身的formulas时,会发生什么呢?formulas库这时候就失去了作用;我们必须计算所有的输入。让我们来看看怎么做。

首先,如何区分含formulas的单元格和含常规值的单元格?Openpyxl没有提供这样的区分方法,所以我们必须检查单元格的值是否以等号字符开头:

def has_formula(cell: Cell)
   return isinstance(cell.value, str) and cell.value.startswith('=')

这样我们就知道了如何处理不包含formulas的单元格了:

def compute_cell_value(cell: Cell):
   if not has_formula(cell):
       return cell.value

现在需要处理的是含有formulas的单元格:

func = formulas.Parser().ast(cell.value)[1].compile()
args = []
TODO: compute function arguments
return func(*args)

我们将formulas编译成一个Python函数,然后调用它。因为输入是对单元格的 值,所以我们递归地调用compute_cell_value来获取它们的值:

sheet = cell.parent
for key in func.inputs.keys():
   args.append(compute_cell_value(sheet[key]))

我们利用这样一个关系:每个单元格都与包含它的工作表有关。我们还使用formulas保留的信息,这样就可以检查函数的输入——包含单元格的字典。注意,它不支持跨表或跨文件的使用。

14.基于单元格范围使用formulas

到目前为止,compute_cell_value函数使用基于其他单元格的formulas,成功地计算了单元格的值。然而,对于那些不依赖于单个单元格,而是依赖于许多单元格的formulas,又该如何计算呢? 在这种情况下,函数的输入是一个范围表达式,例如=SUM(A1:21)中的A1:Z1。我们给compute_cell_ value传入以下信息:

sheet[key]

当key是单个单元格的地址时,我们将得到一个单元格对象;但是当key是许多单元格时,我们将得到一个元组。compute_cell_value不知道如何处理这样的输入,所以我们必须修改它,来应对这种情况:

if isinstance(input, Tuple):
   return tuple(map(compute_cell_value, input))

函数的完整版本如下:

def compute_cell_value(input: Union[Cell, Tuple]):
   if isinstance(input, Tuple):
       return tuple(map(compute_cell_value, input))
   if not has_formula(input):
       return input.value
   func = formulas.Parser().ast(input.value)[1].compile()
   args = []
   sheet = input.parent
   for key in func.inputs.keys():
       args.append(compute_cell_value(sheet[key]))
   return func(*args)

15.添加新的formula函数

formulas支持许多内置的Excel函数,但不包括所有函数。当然,它也不支持VBA中的自定义函数。但是,我们可以添加一些新的Python函数,这样就可以在formulas中调用这些函数:

def is_number(number):
   ... # This is actually defined in formulas, but strangely not exposed as the Excel function

FUNCTIONS = formulas.get_functions()
FUNCTIONS['ISNUMBER'] = is_number

函数的输入值就是Python中的值,比如字符串、数字、日期等,而不是cell类中的值。此外,与普通Python函数相比,我们需要防止XIError,它表示计算中的错误,例如#DIV/0!或#REF! (当我们在输入formulas中犯了一些错误时,通常会在Excel中看到这些):

def is_number(number):
    if isinstance(number, XlError):
        return False
    ...

结论

通过使用openpyxl和formulas这两个成熟的开源库,我们可以更高效地用Python处理Excel。对于那些经常使用Excel的用户来说,能够处理复杂的Excel文件是一个非常有用的功能。

在本文中,我们学习了如何读写带有formulas的Excel文件。你还可以在样式,图表,合并单元格中学到其他相关的知识。


- EOF -

推荐阅读  点击标题可跳转

1、如何使用 python 提取 PDF 表格及文本,并保存到 Excel

2、可能是全网最完整的 Python 操作 Excel库总结!

3、再见 VBA!神器工具统一 Excel 和 Python


觉得本文对你有帮助?请分享给更多人

推荐关注「Python开发者」,提升Python技能

点赞和在看就是最大的支持❤️

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/111222
 
354 次点击