2026-01-24

シンプルな PDF Viewer 〜 PySide6

PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。Linux, macOS および Microsoft Windows をサポートしています。配布ライセンスは LGPL で公開されています。

PySide6 の GUI アプリで PDF ファイルを閲覧するサンプルを Microsoft Copilot に手伝ってもらいながら作成しました。

ここからカスタマイズしたい処理を加えていく予定なのですが、閲覧のための最低限の機能をコンパクトに実装できたので、一旦はサンプルとしてまとめました。

下記の OS 環境で動作確認をしています。

Fedora Linux 43
Workstation Edition x86_64
Python 3.13.11
PySide6 6.10.1
qt_pdfview.py

以下に実行例を示しました。

ツールバーのアイコンは、Qt にビルトイン・アイコンを利用しました。そのため、ダークモードではアイコンが見えなくなってしまう場合があります。

qt_pdfview.py の実行例

参考サイト

  1. PySide6.QtPdf.QPdfDocument - Qt for Python
  2. PySide6.QtPdfWidgets.QPdfView - Qt for Python

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2026-01-22

【備忘録】xlwings と QThread

xlwings は、Python を使って Excel を直接操作したり、Excel から Python コードを呼び出したりできるライブラリです[BSD ライセンス]。VBA マクロの代替として Python で Excel の自動化(スクリプト作成、マクロ、ユーザー定義関数(UDF))を実現でき、Excel と Python のデータをシームレスに連携させ、双方の得意な部分を組み合わせて強力な自動化・データ処理を可能にします。

今回の目的
  • PySide6 の GUI アプリから Excel を操作しようと、Excel とやり取りをする部分をスレッド化したところ、うまくいかずにハマってしまいました。
  • 生成 AI から「PySide6 × xlwings を使う人が必ず通る“最大の罠”なんだよ」なんて言われていたのに、二度も同じ間違いをしてしまったので、備忘録にまとめました。

下記の OS 環境で動作確認をしています。

Windows 11 Pro 25H2
Excel 2024 MSO
Python 3.13.9
PySide6 6.10.1
xlwings 0.33.20

簡単なサンプル(GUI 無し)

まずは、xlwings を利用した、ごく簡単な GUI 無しのサンプルです。

import xlwings as xw

if __name__ == "__main__":
    wb = xw.Book()  # 新しいワークブックを開く
    sheet = wb.sheets["Sheet1"]  # シート・オブジェクトをインスタンス化
    sheet["A1"].value = "Python から書き込みました。"  # 値の書き込み
「簡単なサンプル」の実行例

GUI サンプル

GUI のプッシュボタンをクリックして、簡単なサンプル(GUI 無し)と同じ処理を実行するサンプルです。

このサンプルは Excel へ文字列を書き込むだけですが、複雑な処理を加えても GUI のイベントループに影響を与えないように、「別スレッド」で Excel へアクセスする処理をするようにしています。

import sys

import xlwings as xw
from PySide6.QtCore import (
    QObject,
    QThread,
    Signal,
    Slot,
)
from PySide6.QtGui import QCloseEvent
from PySide6.QtWidgets import (
    QApplication,
    QPushButton,
    QVBoxLayout,
    QWidget,
)


class ExcelWorker(QObject):
    def __init__(self):
        super().__init__()
        self.sheet = None

    @Slot()
    def initWorker(self):
        wb = xw.Book()  # 新しいワークブックを開く
        self.sheet = wb.sheets["Sheet1"]  # シート・オブジェクトをインスタンス化

    @Slot(str)
    def excel_write(self, msg: str):
        self.sheet["A1"].value = msg  # 値の書き込み


class SampleXlwings(QWidget):
    requestWorkerInit = Signal()
    requestWrite = Signal(str)

    def __init__(self):
        super().__init__()
        # xlwings用スレッド
        self.thread = thread = QThread()
        self.worker = worker = ExcelWorker()
        worker.moveToThread(thread)
        thread.started.connect(self.requestWorkerInit.emit)
        self.requestWorkerInit.connect(worker.initWorker)
        self.requestWrite.connect(worker.excel_write)
        thread.start()
        # GUI
        layout = QVBoxLayout()
        self.setLayout(layout)
        but = QPushButton("新規 Excel へ文字列を書き込む")
        but.clicked.connect(self.request_write)
        layout.addWidget(but)

    def closeEvent(self, event: QCloseEvent):
        if self.thread is not None:
            self.thread.quit()
            self.thread.wait()
        if self.worker is not None:
            self.worker.deleteLater()
            self.worker = None
        event.accept()

    @Slot()
    def request_write(self):
        msg = "Python から書き込みました。"
        self.requestWrite.emit(msg)


def main():
    app = QApplication(sys.argv)
    win = SampleXlwings()
    win.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

なお、PySide6 では @Slot のデコレータを省略しても動作します。

「GUI サンプル」の実行例

GUI サンプルを実行すると、Excel の新しいワークブックが開きます。

新規 Excel へ文字列を書き込む ボタンをクリックすると、「簡単なサンプル」と同じように、文字列が Excel シートに書き込まれます。

注意点

ExcelWorker クラスのコンストラクタ __init__ メソッドで、以下のように xlwings のインスタンスを定義すると動作しません。

class ExcelWorker(QObject):
    def __init__(self):
        super().__init__()
        wb = xw.Book()  # 新しいワークブックを開く
        self.sheet = wb.sheets["Sheet1"]  # シート・オブジェクトをインスタンス化

この場合、下記のようなエラーが出ます。

pywintypes.com_error: (-2147417842, 'アプリケーションは、別のスレッドにマーシャリングされたインターフェイスを呼び出しました。', None, None)

メインスレッドで ExcelWorker のインスタンスを定義するので、ExcelWorker.__init__ も当然、「メインスレッド」で実行されます。

「別スレッド」で Excel とやり取りをしたければ、「別スレッド」を thread.start() で開始した後に、その「別スレッド」内で xlwings のインスタンスを定義する必要があります。

そういうわけで、「別スレッド」が開始されたときに初期化用のメソッド ExcelWorker.initWorker を実行するようにしています。

self.thread = thread = QThread()
self.worker = worker = ExcelWorker()
worker.moveToThread(thread)
thread.started.connect(self.requestWorkerInit.emit)  # 「別スレッド」が開始されればシグナルを発行
self.requestWorkerInit.connect(worker.initWorker)
self.requestWrite.connect(worker.excel_write)
thread.start()  # 「別スレッド」開始
    @Slot()
    def initWorker(self):
        wb = xw.Book()  # 新しいワークブックを開く
        self.sheet = wb.sheets["Sheet1"]  # シート・オブジェクトをインスタンス化

参考サイト

  1. xlwings Documentation
  2. ASCII.jp:COM(Component Object Model)は古い技術だが、いまだに現役 あらためて解説する (1/2) [2023-04-23]

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2026-01-14

【備忘録】UNIX秒とタイムゾーン 〜 Pandas

Pandas は、Python プログラミング言語向けに開発されたデータ操作・分析用ソフトウェアライブラリです(三条項 BSD ライセンス)。特に数値テーブルや時系列データの操作のためのデータ構造と演算を提供しています。Pandas という名称は、経済計量学における「パネルデータ」という用語に由来しています。また「Pythonデータ分析」という語句をもじったものともされています 。Pandas により、R 言語で利用可能な DataFrame 操作の多くの同等の機能が Python でも利用できるようになりました。Pandas は NumPy を基盤として構築されています。

Wikipedia より引用、翻訳・編集

タイムゾーンにいつも苦しむ UNIX秒

UNIX 時間(エポック秒)は、UTC(協定世界時)の 1970 年 1 月 1 日 0 時 0 分 0 秒からの経過秒数です。本記事では「UNIX秒」と呼ぶことにします。

リアルタイムのデータを扱う時、取得したデータのタイムスタンプを UNIX秒 に直して保持するようにしています。あとになって分析などをする際に表示用に時刻フォーマットをするのですがタイムゾーンが合っていません。そんなときにいつも、その場しのぎの対処をしてしまっています。

分析やチャート作成時にタイムゾーンの情報は必要ないのですが、UNIX秒をタイムゾーンが付いていないローカル時刻表記に戻す時には、一旦はタイムゾーン付きの処理、変換をしてから、タイムゾーンの情報を削除する、という流れが確実です。

Pandas の Series で .dt アクセサを利用すれば一括処理が可能なので、サンプルでは一つの時刻(UNIX秒)しか扱いませんが、わざわざ Series にしてローカル時刻へ変換する処理の流れをまとめました。丁寧に言葉でくどくど説明しても解りにくいので、コードの簡単なコメントで済ませてしまっています。

import time

import pandas as pd

print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print("UNIX秒からローカル時刻へ変換する流れ")
print("UNIX秒 → UTC (tz-aware) → JST (tz-aware) → tz-naive")
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")

print("\nUNIX秒: 現在のUNIX秒を取得")
ts_epoch = time.time()
print(ts_epoch)

print("\nUNIX秒 → tz-naive: Pandas の Timestamp(utc=True 指定無し)→ Series へ ☹")
dt_no_utc = pd.to_datetime(ts_epoch, unit="s")
ser_no_utc = pd.Series([dt_no_utc])
print(ser_no_utc)

print("\n*** .dt アクセサを使った変換[備忘録] ***")
print("UNIX秒 → UTC (tz-aware): Pandas の Timestamp(タイムゾーン付き)→ Series へ")
dt_utc = pd.to_datetime(ts_epoch, unit="s", utc=True)
ser_utc = pd.Series([dt_utc])
print(ser_utc)

print("\nUTC (tz-aware) → JST (tz-aware): タイムゾーンを日本時間に変更")
ser_jst = ser_utc.dt.tz_convert("Asia/Tokyo")
print(ser_jst)

print("\nJST (tz-aware) → tz-naive: タイムゾーン情報を削除")
ser_no_tz = ser_jst.dt.tz_localize(None)
print(ser_no_tz)
実行例
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
UNIX秒からローカル時刻へ変換する流れ
UNIX秒 → UTC (tz-aware) → JST (tz-aware) → tz-naive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

UNIX秒: 現在のUNIX秒を取得
1768363958.6145868

UNIX秒 → tz-naive: Pandas の Timestamp(utc=True 指定無し)→ Series へ ☹
0   2026-01-14 04:12:38.614586830
dtype: datetime64[ns]

*** .dt アクセサを使った変換[備忘録] ***
UNIX秒 → UTC (tz-aware): Pandas の Timestamp(タイムゾーン付き)→ Series へ
0   2026-01-14 04:12:38.614586830+00:00
dtype: datetime64[ns, UTC]

UTC (tz-aware) → JST (tz-aware): タイムゾーンを日本時間に変更
0   2026-01-14 13:12:38.614586830+09:00
dtype: datetime64[ns, Asia/Tokyo]

JST (tz-aware) → tz-naive: タイムゾーン情報を削除
0   2026-01-14 13:12:38.614586830
dtype: datetime64[ns]

参考サイト

  1. pandasで日付・時間の列を処理(文字列変換、年月日抽出など) | note.nkmk.me

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加