openpyxl で複数範囲セルの外枠に罫線を設定する
Python から Excel を操作するライブラリは幾つか存在しますが、特に openpyxl が有名のように思います。 但し、現状の openpyxls には「複数範囲に対して、一括で外枠を定義する」ようなメソッドが無いようなので、サンプル実装をメモしておきます。 今回は以下を利用しました。
- python 3.9.6
- openpyxl 3.1.2
尚、罫線に限らない「書式全般の操作」については openpyxl 公式ドキュメントの Working with styles で言及されています。
前提となるコード
今回、幾つかのサンプルコードをメモしますが、いずれも以下のようなコードを前提とします (「サンプルコード」部分に処理を記載するイメージです)。 openpyxl で計算を引く場合、「罫線の書式は Side」で定義、「罫線そのものは Border」で定義することで計算を引きます。 その為、openpyxl.styles
名前空間にある Border と Side を import しています。
| #!/usr/bin/env python3
from openpyxl import Workbook
from openpyxl.styles import Border, Side
wb = Workbook()
ws = wb["Sheet"]
(サンプルコード)
wb.save("sample.xlsx")
|
罫線を引く
「指定セルの上側 (top) に罫線を引く」場合、サンプルコードは以下の通りです。
| side = Side(style="thin", color="000000")
border = Border(top=side)
ws.cell(column=2, row=2).border = border
|
罫線の種類
上記の例では thin
を指定していますが、罫線には以下の種類を指定可能です。
Excel 上での、実際の罫線設定画面に「openpyxl で設定可能な罫線の種類」を追記すると以下です。
複数箇所に罫線を引く
「指定セルの上側 (top) と左側 (left) に罫線を引く」場合、サンプルコードは以下の通りです。
| side = Side(style="thin", color="000000")
border = Border(top=side, left=side)
ws.cell(column=2, row=2).border = border
|
同じセルに複数回、罫線を引く (失敗例)
但し、「同じセルに対して・複数回に分けて罫線を設定する」と、実行結果は以下のようになり、うまくいきません。
| side = Side(style="thin", color="000000")
border1 = Border(top=side)
border2 = Border(left=side)
ws.cell(column=2, row=2).border = border1
ws.cell(column=2, row=2).border = border2
|
同じセルに対して再度、「罫線を設定」しようとすると top
や left
などの位置を 指定していない 辺の罫線は解除されてしまいます。 その為、下記の例では (先に設定した) top
側の罫線が解除されてしまい、意図した結果になりません。
同じセルに複数回、罫線を引く (失敗例 2)
「既に設定済みの罫線」は cell.border.top.border_style
で取得することが可能です。 その為、ひとつ前の「失敗例」を動作するように書き直す場合、例えば下記のように書けます。
| side1 = Side(style="thin", color="000000")
border1 = Border(top=side1)
cell.border = border1
side2_top = Side(cell.border.top.border_style)
side2_left = Side(style="thin", color="000000")
side2_right = Side(cell.border.right.border_style)
side2_bottom = Side(cell.border.bottom.border_style)
border2 = Border(top=side2_top, left=side2_left, right=side2_right, bottom=side2_bottom)
cell.border = border2
|
但し、このコードは下記のエラーになってしまいます。 このエラーをよく見ると「side2_right」(セルの右側に設定済みであろう、罫線の情報) を取得する際にエラーが発生しているようです。
| Traceback (most recent call last):
File "/Users/enbutsu/Desktop/border_sample/./5.py", line 17, in <module>
side2_right = Side(cell.border.right.border_style)
AttributeError: 'NoneType' object has no attribute 'border_style'
|
上記のサンプルコードの動作を噛み砕くと、以下の部分でエラーになっています。
- セルの上側に罫線 (side1 と border1) を設定する
- セルの上側 (side2_top) の "設定済み罫線" を取得しようとする。 ひとつ前の手順で「設定済み」である為、値を取得出来る
- セルの左側 (side2_left) に「新規の罫線」(
style="thin", color="000000"
) を設定する
- セルの右側 (side2_right) の "設定済み罫線" を取得しようとする。 しかし、右側には罫線が未設定である為、
border_style
が存在しない為、エラーになってしまう
複数範囲セルの外枠に罫線を引く
上記の点 (未設定の罫線を取得するとエラーになる) を解決し、「複数範囲セルの外枠に罫線を引く」処理をメソッド化すると、例えば以下のように実装出来ます。 「設定済みの罫線が存在するか? しないか?」という点は if hasattr(style, "border_style"):
部分で判定しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 | #!/usr/bin/env python3
from openpyxl import Workbook
from openpyxl.cell.cell import Cell
from openpyxl.styles import Border, Side
from openpyxl.worksheet.worksheet import Worksheet
BORDER_TOP = 1
BORDER_BOTTOM = 2
BORDER_LEFT = 3
BORDER_RIGHT = 4
def get_border_style(cell: Cell, position: int) -> Side:
if position == BORDER_TOP:
style = cell.border.top
elif position == BORDER_BOTTOM:
style = cell.border.bottom
elif position == BORDER_LEFT:
style = cell.border.left
elif position == BORDER_RIGHT:
style = cell.border.right
if hasattr(style, "border_style"):
return Side(style.border_style)
else:
return None
def set_border(
ws: Worksheet,
min_col: int,
min_row: int,
max_col: int,
max_row: int,
style="thin",
color="000000",
):
side = Side(border_style=style, color=color)
# Top & Bottom
for index in range(min_col, max_col + 1):
top = ws.cell(column=index, row=min_row)
top.border = Border(
top=side,
bottom=get_border_style(top, BORDER_BOTTOM),
left=get_border_style(top, BORDER_LEFT),
right=get_border_style(top, BORDER_RIGHT),
)
bottom = ws.cell(column=index, row=max_row)
bottom.border = Border(
top=get_border_style(bottom, BORDER_TOP),
bottom=side,
left=get_border_style(bottom, BORDER_LEFT),
right=get_border_style(bottom, BORDER_RIGHT),
)
# Left & Right
for index in range(min_row, max_row + 1):
left = ws.cell(column=min_col, row=index)
left.border = Border(
top=get_border_style(left, BORDER_TOP),
bottom=get_border_style(left, BORDER_BOTTOM),
left=side,
right=get_border_style(left, BORDER_RIGHT),
)
right = ws.cell(column=max_col, row=index)
right.border = Border(
top=get_border_style(right, BORDER_TOP),
bottom=get_border_style(right, BORDER_BOTTOM),
left=get_border_style(right, BORDER_LEFT),
right=side,
)
wb = Workbook()
ws = wb["Sheet"]
cell = ws.cell(column=2, row=3)
side1 = Side(style="thin", color="000000")
border1 = Border(top=side1)
cell.border = border1
set_border(ws, 2, 3, 4, 5)
wb.save("sample.xlsx")
|