最近在寫一個自動產出 Excel 報表的腳本,用到 openpyxl 的 copy_worksheet() 來複製上個月的 sheet 當模板,改一改數字就輸出新的當月 sheet。
功能是跑通了,但打開 Excel 之後發現一件很奇怪的事:
兩個 sheet tab 同時被選取,都是反白的狀態。
不是只有當月,連上個月的 sheet 也被選起來,看起來像使用者按著 Ctrl 手動多選了兩個 sheet。
一開始以為是我的 wb.active 設錯了,但改來改去都沒用。後來才發現這個問題根本不在 active,而在另一個我從來沒注意過的屬性。
問題:copy_worksheet() 會把 tabSelected 一起複製過來
openpyxl 的 Worksheet 物件有一個屬性叫 sheet_view.tabSelected,這個值控制的是「這個 sheet 在 Excel 介面上有沒有被選取(反白)」。
當你對一個已被選取的 sheet 呼叫 copy_worksheet(),新複製出來的 sheet 也會繼承 tabSelected=True。
更慘的是,舊的 sheet 的 tabSelected 也還是 True。
所以兩個都是 True,Excel 就忠實地幫你「選取」了兩個 sheet,就像你按著 Ctrl 點了它們一樣。
為什麼這個問題很難找
copy_worksheet() 本來就是設計來複製 sheet 的所有屬性,tabSelected 是 sheet view 的一部分,被複製過去是 by design 的行為,不算 bug。
但問題是「選取狀態」這個屬性在平常的 Excel 操作情境下根本不會在意——你手動新增 sheet 的時候,Excel 會自動幫你清掉其他 sheet 的選取狀態。可是透過程式操作的時候,沒有人幫你做這件事。
所以這是一個「只有在特定操作流程下才會出現、而且症狀看起來很怪的問題」,一時間不知道從哪下手很正常。
修法:在複製前清掉所有 sheet 的 tabSelected
解法其實很直觀,就是在做任何 copy 或 active 操作之前,把所有 worksheet 的 tabSelected 全部設成 False,然後再設定你要的 active sheet。
1 | import openpyxl |
就這樣,打開 Excel 之後只有 2026-04 是反白的,其他 sheet 都正常。
順手搞定「預設開啟哪個 sheet」
說到這裡,順便講一個相關的東西:wb.active。
wb.active 決定的是「用 Excel 打開這個檔案時,預設停在哪個 sheet」。如果你不設,openpyxl 會用 index 0,也就是第一個 sheet。
但要注意一件事:**wb.active = ws 接受的是 worksheet 物件,不是 sheet 名稱字串。**
1 | # ✅ 正確 |
最後這個錯我自己就犯過,而且不會報錯所以很難發現,只是開啟的 sheet 沒有照預期——記一下。
完整範例:每月自動複製 sheet 並正確設定開啟狀態
把上面的概念整合起來,假設你有一個每月跑的腳本,要從模板複製當月的 sheet:
1 | import openpyxl |
延伸:日期計算的小技巧
既然在聊自動化腳本,順便記一個我最近用到的 date utility。
很多定期付款或截止日的情境需要「找出下一個還沒到的 N 號」——比如每月 5 號是結算日,你今天是 4 月 1 號,那下一個 5 號是 4/5;但如果今天已經是 4 月 10 號,那下一個 5 號就是 5/5 了。
這個邏輯很直白但每次都要想一下,不如直接包成函式:
1 | from datetime import date |
邏輯很簡單:先算本月的那天,如果已經過了就跳到下個月。邊界情況(2 月、月底天數不同)用 monthrange 處理。
小結
今天這兩個坑都是「不報錯、但行為不對」的類型,最麻煩。
copy_worksheet()複製tabSelected狀態 → 複製後記得清掉所有 sheet 的 tabSelectedwb.active只接受 worksheet 物件,不接受字串 → 設定前先把 ws 物件拿好- 日期計算「下一個 N 號」→ 包成
next_nth_day(n)函式,一次寫好以後就不用再想了
openpyxl 整體而言是個很好用的套件,但它的 sheet view 相關屬性文件比較稀疏,很多細節要踩過才會知道。希望這篇能幫你少踩幾個坑。😂