我最近在一个项目中使用了 openpyxl 3.0.6,想要在 Excel 2016 中自动绘制一些复杂的表格并调整其大小。一切都很顺利,直到我尝试勾勒出一系列单元格的轮廓,并且之前的边框被覆盖。我的问题与覆盖先前的边框有关。比如说
import openpyxl
workbook = openpyxl.load_workbook("Borders Test.xlsx")
sheet = workbook.active
cell = sheet["B6"]
border = openpyxl.styles.borders.Side(style = "thick")
# Draw a border on top of cell B6 #
cell.border = openpyxl.styles.Border(top = border)
# Draw a border on the bottom of cell B6, overwrites the above border #
cell.border = openpyxl.styles.Border(bottom = border)
workbook.save("Borders Test.xlsx")
workbook.close()
只会在“B6”的底部设置边框,因为这是最后一个,但我想要的输出是单元格 B6,顶部和底部都有边框。注意,在围绕某个范围绘制边框的情况下,我们可以在外侧绘制边框,因此将 B5 的下边框和 B7 的上边框设置为粗即可得到所需的结果,但这并不能解决覆盖问题问题。我也尝试修改“cell.border”这样
cell.border.left = openpyxl.styles.Border(left = border)
但我收到错误消息:
'AttributeError:样式对象是不可变的,无法更改。 用副本重新指定样式'
虽然这是一个选项,但有点糟糕,我想看看是否有更好的方法。
我在 Windows 10 中使用 64 位 python,如果这很重要,则使用 python 3.7.8。
提前感谢您的帮助,希望我的第一篇文章不会太糟糕!
我设法复制当前边框。
例如,如果您只想更改单元格 H10 的左边框:
cell = worksheet['H10']
# copy other cell borders
right_s = Side(**d.border.right.__dict__)
top_s = Side(**d.border.top.__dict__)
bottom_s = Side(**d.border.bottom.__dict__)
# set style of left border
left_s = Side(border_style="thin", color="000000")
# reassign border
border = Border(left=left_s, right=right_s, top=top_s, bottom=bottom_s)
cell.border = border
这是我对这个问题的解决方案:
获取现有边框属性
仅在属性中定义的侧面替换它们。
def add_border(c, b, overwrite=True):
"""Add a border to a cell.
:param c: the cell to apply the new border
:param b: the new border of type openpyxl.styles.borders.Border
:param overwrite: (OPTIONAL) remove existing borders on sides not defined in b (default True)
"""
def get_border_attr(b):
return {
'left': getattr(b, 'left'),
'right': getattr(b, 'right'),
'top': getattr(b, 'top'),
'bottom': getattr(b, 'bottom'),
}
if overwrite:
c.border = b
else:
saved_border = get_border_attr(c.border)
new_border = get_border_attr(b)
c.border = Border(
left = new_border['left'] if new_border['left'] else saved_border['left'],
right = new_border['right'] if new_border['right'] else saved_border['right'],
top = new_border['top'] if new_border['top'] else saved_border['top'],
bottom = new_border['bottom'] if new_border['bottom'] else saved_border['bottom'],
)
然后,您可以在代码中使用此函数在一系列单元格周围添加一条粗线,并在顶部单元格的底部添加一条细线:
import openpyxl
from openpyxl.styles import Side, Border
wb = openpyxl.Workbook()
ws = wb.create_sheet("My sheet")
thin = Side(border_style="thin", color="000000")
thick = Side(border_style="thick", color="000000")
# Not directly related to the question but nice to have
def set_cell_range_border(ws, cell_range, border_style, overwrite=True):
rows = ws[cell_range]
for row in rows:
add_border(row[0], Border(left=border_style), overwrite)
add_border(row[-1], Border(right=border_style), overwrite)
for c in rows[0]:
add_border(c, Border(top=border_style), overwrite)
for c in rows[-1]:
add_border(c, Border(bottom=border_style), overwrite)
add_border(rows[0][0], Border(left=border_style, top=border_style), overwrite)
add_border(rows[0][-1], Border(right=border_style, top=border_style), overwrite)
add_border(rows[-1][0], Border(left=border_style, bottom=border_style), overwrite)
add_border(rows[-1][-1], Border(right=border_style, bottom=border_style), overwrite)
# Apply thick border around A1:E5
set_cell_range_border(ws, 'A1:E5', thick)
# Add a thin line under the top row cells without removing the thick borders previously set
for c in ws['A1:E1'][0]
add_border(c, Border(bottom=thin), overwrite=False)
对边界的两项更改应同时进行。 Border 可以采用一个元组,这样可以避免设置边框然后覆盖它。
代替:
# Draw a border on top of cell B6 #
cell.border = openpyxl.styles.Border(top = border)
# Draw a border on the bottom of cell B6, overwrites the above border #
cell.border = openpyxl.styles.Border(bottom = border)
替换为:
cell.border = openpyxl.styles.Border(top = border, bottom = border)
虽然艾伦说可以这样做是正确的,但我的目标(我表达得不好)是能够在第一次作业后回来进行编辑,而不丢失之前的边框。这是我提出的一个丑陋的解决方案:
__elements__ = ('left', 'right', 'top', 'bottom')
def drawSafely(cell, __side = Thick_side, _where = "left"):
### Overwrites only the value at _where
kwargs = {element:getattr(cell.border,element) for element in __elements__}
kwargs[_where] = __side
border = openpyxl.styles.Border(**kwargs)
cell.border = border
其中单元格与问题陈述中相同,“Thick_side”的类型为“openpyxl.styles.borders.Side”。这只是将旧数据保存在字典中,更新字典,重新制作边框,并将该值分配给 cell.border。另外,“_where”必须位于
openpyxl.styles.borders.Side.__elements__
(而不是我的解决方案中的那个,它只是一个子集)。再说一次,可能会更好,但它确实有效。
我没有找到@Romn 的工作答案。这是一个对我有用的代码片段。它确实使用了OP确实提到的“会很糟糕”的“复制”,但是它很短并且可以完成工作。
def add_border(cell: Cell, left: Union[Side,None]=None, right: Union[Side,None]=None, top: Union[Side,None]=None, bottom: Union[Side,None]=None) -> None:
border_copy: Border = copy(cell.border)
if left:
border_copy.left = left
if right:
border_copy.right = right
if top:
border_copy.top = top
if bottom:
border_copy.bottom = bottom
cell.border = border_copy
它只会覆盖给定的面。