Skip to content

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 しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/usr/bin/env python3

from openpyxl import Workbook
from openpyxl.styles import Border, Side

wb = Workbook()
ws = wb["Sheet"]

(サンプルコード)

wb.save("sample.xlsx")

罫線を引く

「指定セルの上側 (top) に罫線を引く」場合、サンプルコードは以下の通りです。

1
2
3
side = Side(style="thin", color="000000")
border = Border(top=side)
ws.cell(column=2, row=2).border = border

file

罫線の種類

上記の例では thin を指定していますが、罫線には以下の種類を指定可能です。

file

Excel 上での、実際の罫線設定画面に「openpyxl で設定可能な罫線の種類」を追記すると以下です。

file

複数箇所に罫線を引く

「指定セルの上側 (top) と左側 (left) に罫線を引く」場合、サンプルコードは以下の通りです。

1
2
3
side = Side(style="thin", color="000000")
border = Border(top=side, left=side)
ws.cell(column=2, row=2).border = border

file

同じセルに複数回、罫線を引く (失敗例)

但し、「同じセルに対して・複数回に分けて罫線を設定する」と、実行結果は以下のようになり、うまくいきません。

1
2
3
4
5
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

同じセルに対して再度、「罫線を設定」しようとすると topleft などの位置を 指定していない 辺の罫線は解除されてしまいます。 その為、下記の例では (先に設定した) top 側の罫線が解除されてしまい、意図した結果になりません。

file

同じセルに複数回、罫線を引く (失敗例 2)

「既に設定済みの罫線」は cell.border.top.border_style で取得することが可能です。 その為、ひとつ前の「失敗例」を動作するように書き直す場合、例えば下記のように書けます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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」(セルの右側に設定済みであろう、罫線の情報) を取得する際にエラーが発生しているようです。

1
2
3
4
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'

上記のサンプルコードの動作を噛み砕くと、以下の部分でエラーになっています。

  1. セルの上側に罫線 (side1 と border1) を設定する
  2. セルの上側 (side2_top) の "設定済み罫線" を取得しようとする。 ひとつ前の手順で「設定済み」である為、値を取得出来る
  3. セルの左側 (side2_left) に「新規の罫線」(style="thin", color="000000") を設定する
  4. セルの右側 (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")

file