はじめに
openpyxlでブックをReadOnlyで開くとセルをインデックスアクセスしたときに異常に遅くなることから、iter_rowsを使えてなおかつVBAのCellsっぽく使えるクラス作りたくなったので作った。
from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.utils import column_index_from_string
from openpyxl.utils import get_column_letter
from openpyxl.cell.cell import Cell
import re
from dataclasses import dataclass
MAX_ROW = 1048576 # Excelの最大行数
@dataclass
class CellRange:
"""セルの上端、下端、左端、右端の行列番号を管理するデータクラス"""
sheet: Worksheet
top: int
bottom: int
left: int
right: int
@classmethod
def from_string(cls, sheet: Worksheet, range_str: str) -> "CellRange":
"""
Excelのセル範囲文字列(例: 'B2:P8')を CellRange に変換する。
:param sheet: ワークシートオブジェクト
:param range_str: セル範囲文字列
:return: `CellRange` のインスタンス
"""
match = re.fullmatch(r"([A-Z]{1,3})(\d+):([A-Z]{1,3})(\d+)", range_str, re.I)
if not match:
raise ValueError(f"無効な範囲指定: {range_str}")
col1, row1, col2, row2 = match.groups()
try:
top = int(row1)
bottom = int(row2)
if not (1 <= top <= MAX_ROW) or not (1 <= bottom <= MAX_ROW):
raise ValueError(f"行番号が範囲外: {top} または {bottom} (許可範囲: 1~{MAX_ROW})")
left = column_index_from_string(col1.upper())
right = column_index_from_string(col2.upper())
except ValueError:
raise ValueError(f"無効な列名または行番号を検出: {col1}{row1} または {col2}{row2}")
return cls(sheet=sheet, top=top, bottom=bottom, left=left, right=right)
def get_cells(self) -> list[tuple[Cell | None, ...]]:
"""
このセル範囲のセルデータを `list[tuple[Cell | None]]` で返す。
:return: セル範囲のリスト(各行が `tuple` になっている)
"""
return [
tuple(cell if cell.value is not None else None for cell in row)
for row in self.sheet.iter_rows(
min_row=self.top, max_row=self.bottom,
min_col=self.left, max_col=self.right
)
]
def cells(self, row: int, col: int) -> Cell | None:
"""
Excelの絶対行列番号でセルを取得する(シート全体の座標基準)。
VBAの `Cells(row, col)` に相当。
:param row: Excelの行番号(1始まり)
:param col: Excelの列番号(1始まり)
:return: `Cell` オブジェクトまたは `None`
"""
if not (self.top <= row <= self.bottom and self.left <= col <= self.right):
return None # 範囲外なら None を返す
return self.sheet.cell(row=row, column=col)
def rows_count(self) -> int:
"""
行数を返す
VBAの `Rows.Count` に相当。
:return: 行数
"""
return self.bottom - self.top + 1
def columns_count(self) -> int:
"""
列数を返す
VBAの `Columns.Count` に相当。
:return: 列数
"""
return self.right - self.left + 1
def address(self) -> str:
"""
セル範囲のExcelアドレス表記を返す(例: 'A123:AB345')
:return: セル範囲の文字列
"""
left_col = get_column_letter(self.left)
right_col = get_column_letter(self.right)
return f"{left_col}{self.top}:{right_col}{self.bottom}"