Jaybanuan's Blog

どうせまた調べるハメになることをメモしていくブログ

Python 3のコレクションの抽象基底クラス

時々、dict風の動作をする自前クラスを作りたくなることがあるが、何を満たせばdict風になるのか毎回調べていたので、ここにメモしておく。 と言っても、ドキュメントに全てまとまっているので、以下を参照するだけ。

docs.python.org

抽象基底クラスについては、PEP 3119で提案がある。

peps.python.org

ここで、以下のサンプルコードを実行してdictMutableMappingのクラス階層を表示してみる。

import collections.abc

from typing import *


def print_class_hierarchy(t: Type, depth: int = 0):
    print(''.join('    ' for _ in range(depth)) + t.__name__ + ' (' + t.__class__.__name__ + ')')

    if t != object:
        for base_type in t.__bases__:
            print_class_hierarchy(base_type, depth+1)


print_class_hierarchy(dict)
print()
print_class_hierarchy(collections.abc.MutableMapping)
print()
print(issubclass(dict, collections.abc.MutableMapping))

出力結果は以下のようになる。

dict (type)
    object (type)

MutableMapping (ABCMeta)
    Mapping (ABCMeta)
        Collection (ABCMeta)
            Sized (ABCMeta)
                object (type)
            Iterable (ABCMeta)
                object (type)
            Container (ABCMeta)
                object (type)

True

出力結果より、dictMutableMappingはクラス階層的には継承関係にはないことが読み取れるが、issubclass(dict, MutableMapping)Trueになる。 これは、PEP 3119の提案によると、issubclass()をオーバーライドして、あたかもdictMutableMappingのサブクラスであるかのように見せかけているためである。 言語のコアな部分を変更せずに、コレクションの性質を抽象化する工夫と思われる。

最後に蛇足だが、もうちょっと広くPythonのデータモデル (というより型モデル) はこちらを参照。

docs.python.org

2013/10/15追記。 各クラスのメソッドも表示するようにして、継承関係のツリーの罫線も引いてみた。

import inspect
import collections.abc

from typing import *


def print_class_hierarchy(node: Union[Type, str], indent_types: list[str] = []) -> None:
    indents = ''
    for index, indent_type in enumerate(indent_types):
        indent = '??????'   # this value must be never used
        if index != len(indent_types) - 1:
            if indent_type == 'TYPE':
                indent = '  |   '
            elif indent_type == 'TYPE_LAST':
                indent = '      '
        else:
            if indent_type == 'METHOD':
                indent = '  |   '
            elif indent_type == 'METHOD_LAST':
                indent = '      '
            elif indent_type == 'TYPE':
                indent = '  +-- '
            elif indent_type == 'TYPE_LAST':
                indent = '  `-- '
        
        indents = indents + indent

    node_text = (node.__name__ + ' (' + node.__class__.__name__ + ')') if isinstance(node, Type) else node
    print(indents + node_text)

    if isinstance(node, Type):
        base_types = list(node.__bases__)

        methods = {member for member, _ in inspect.getmembers(node, inspect.isfunction)}
        base_methods = {member for base_class in node.__bases__ for member, _ in inspect.getmembers(base_class, inspect.isfunction)}
        indent_type = 'METHOD' if len(base_types) > 0 else 'METHOD_LAST'

        for method in list(methods - base_methods):
            print_class_hierarchy(method, indent_types + [indent_type])

        if node != object:
            for index, base_type in enumerate(base_types):
                indent_type = 'TYPE' if index != len(base_types) - 1 else 'TYPE_LAST'
                print_class_hierarchy(base_type, indent_types + [indent_type])


print_class_hierarchy(collections.abc.MutableMapping)

出力結果は以下。

MutableMapping (ABCMeta)
  |   setdefault
  |   __setitem__
  |   pop
  |   clear
  |   popitem
  |   __delitem__
  |   update
  `-- Mapping (ABCMeta)
        |   items
        |   get
        |   values
        |   keys
        |   __eq__
        |   __getitem__
        `-- Collection (ABCMeta)
              +-- Sized (ABCMeta)
              |     |   __len__
              |     `-- object (type)
              +-- Iterable (ABCMeta)
              |     |   __iter__
              |     `-- object (type)
              `-- Container (ABCMeta)
                    |   __contains__
                    `-- object (type)

Power BIで「データをモデルに読み込み中」で止まる現象の解決方法

問題

クリーンインストールしたWindows 11に、ここからPower BIをダウンロードしてインストール。 しかし、Excelファイルに接続してデータを読み込もうとすると、以下のポップアップが表示されて処理が進まない。

解決方法

キャッシュをクリアすると解決する可能性がある。 メニューから「ファイル」=>「オプションと設定」=>「オプション」を選択する。

そして以下のように、一通りボタン「キャッシュをクリア」を押下する。

参考

community.powerbi.com

Shift JIS (というかCP932)のCSVを扱うPythonプログラムを、VS Codeで開発するための作業テンプレ

はじめに

レガシーな業務系の基幹システムとデータ連携する場合は、まだまだCSVが活躍している。 今後もCSV処理ツールを作り続けそうなので、作業のテンプレを残しておく。

特に注意が必要なのが、日本語版のWindowsで作成されたCSVは、往々にして文字コードがCP932であるところ。

環境

$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Ubuntu 22.04.2 LTS"

$ uname -srvm
Linux 5.19.0-40-generic #41~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 31 16:00:14 UTC 2 x86_64

$ python3 --version
Python 3.10.6

$ code --version
1.77.3
704ed70d4fd1c6bd6342c436f1ede30d1cff4710
x64

VS Codeには以下のPython用の拡張を導入済み。

Pythonで利用するツールやライブラリ

今回のPythonプログラムの開発では、以下を利用する。

  • Poetry

    • Pythonのパッケージングと依存関係の管理を行うツール
  • Pandas

    • CSVやDBなどの表形式のデータを加工したり分析するツール

(1) Poetryのインストール

Poetryの公式サイトの手順に沿って、以下のコマンドを実行する。

$ curl -sSL https://install.python-poetry.org | python3 -

以下のように、Poetryのバージョンを確認してみる。

$ poetry --version
Poetry (version 1.4.2)

(2) プロジェクトの作成

ここでは、作成するプロジェクトの名前をcsvsampleとする。 以下のように、ターミナルでプロジェクトを作成するディレクトリ(例えば~/src)に移動した後、プロジェクトを作成する。

$ cd ~/src
$ poetry new csvsample

その結果、以下のようなディレクトリ構成が作成される。

~/src
└── csvsample
    ├── README.md
    ├── csvsample
    │   └── __init__.py
    ├── pyproject.toml
    └── tests
        └── __init__.py

(3) コードの作成

まず、~/src/csvsampleをワーキングディレクトリとしてVS Codeを起動した後、Explorercsvsample/tool.pyを作成し、以下の内容で保存する。

import sys
import pandas


def main(csv_file_path):
    data_frame = pandas.read_csv(csv_file_path, encoding='cp932')
    print(data_frame)


if __name__ == '__main__':
    main(sys.argv[1])

pandas.read_csv()に引き渡す文字コードだが、「WindowsはShift JIS」という思い込みがあるので思わず'shit_jis'を指定したくなるものの、これだと機種依存文字に対応できない場合がある。 よって、Shift JISのMicrosoftの拡張である'cp932'が正解。

この時点では、まだPandasがインストールされていないので、コードエディタ上でimport pandasあたりがエラーになっているはず。

次に、以下の内容でdata.csvを作成しておく。

"id","name","age"
"1000","John","20"
"1001","Jane","30"

(4) Pandasのインストール

ライブラリPandasをインストールするために、Terminalを開いて以下のコマンドを実行する。

$ poetry add pandas

Pandasをインストールしても、まだコード中のエラーは消えないはず。

(5) VS Codeで仮想環境を指定

コード中のpandasが見つからないというエラーを解消するには、VS CodeにPoetryが作成した仮想環境を指定する必要がある。 Poetryは、デフォルトでは~/.cache/pypoetry/virtualenvsに仮想環境のディレクトリを作成する。

VS Codeのcommand palletpython: select interpreterと入力するか、ステータスバーの以下の部分をクリックして、Pythonの仮想環境を指定するポップアップを開く。

そして、以下のように右端にPoetryと書いてあるエントリを選択する。

これで、コードエディタ上のエラーは解消する。

(6) 動作確認

VS Code上のTerminalを開き直すと、上記で設定した仮想環境に自動的に切り替わる。 そのTerminalで以下のように作成したPythonプログラムを実行する。

$ python3 csvsample/tool.py data.csv 
     id  name  age
0  1000  John   20
1  1001  Jane   30

Ubuntu 22.04LTSでゲームパッドが認識されない場合の対処

はじめに

ThundeRobot社製のゲームパッドを利用しているが、PCを買い替えたら認識されなくなった。 対処方法を調べたので、メモしておく。

環境

$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Ubuntu 22.04.2 LTS"

$ uname -srvm
Linux 5.19.0-40-generic #41~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 31 16:00:14 UTC 2 x86_64

対処方法

ググってなんとか対処方法が書いてある以下に辿り着いた。

github.com

投稿を信じるなら、カーネル 5.16以降で発生する事象のようなので、自分の環境は該当する。 コメントで言及されているworkaround通りに/etc/modprobe.d/nonintendo.confを以下の内容で作成してリブートすると、認識された。

blacklist hid_nintendo

対処前後のsudo lshwの結果を比較すると、差分は以下のように出た。 まずは対処前。

                   詳細: ヒューマンインターフェースデバイス(HID)
                   製品: THUNDEROBOT G30
                   ベンダー: Nintendo Co., Ltd
                   物理ID: 1
                   バス情報: usb@1:1
                   バージョン: 1.15
                   性能: usb-2.00
                   設定: driver=usbhid maxpower=500mA speed=12Mbit/s
              *-usb:1

次に対処後。

                   詳細: 汎用USBデバイス
                   製品: Microsoft X-Box 360 pad
                   ベンダー: Microsoft Corp.
                   物理ID: 1
                   バス情報: usb@1:1
                   論理名: input44
                   論理名: /dev/input/event22
                   論理名: /dev/input/js0
                   バージョン: 1.15
                   シリアル: 12340000
                   性能: usb-2.00 usb
                   設定: driver=xpad maxpower=500mA speed=12Mbit/s
              *-usb:1

対処前はNintendoのプロダクトとして、対処後はMicrosoftのプロダクトとして認識されている。 対処前は/dev/input/マッピングされずに困っていたが、対処後はマッピングできていることが分かる。

参考

wiki.archlinux.jp

snapでインストールしたThunderbirdのメッセージフィルタのコピー

はじめに

Thunderbirdでメールフィルタを別のアカウントにコピーしたかったが、画面上からコピーするメニューが見当たらなかった。 調べたところ、メールフィルタの設定はテキストファイルとしてThunderbirdのプロファイルのディレクトリの中に配置されており、単なるファイルコピーで別のアカウントにコピーできるようだ。

しかし、snapでインストールしたThunderbirdだとsnap固有の場所にプロファイルがあり、その場所はググっても見つけにくかったので、ここでメモしておく。

プロファイルの場所

snapでインストールしたThunderbirdのプロファイルは以下の場所にある。

~/snap/thunderbird/common/.thunderbird/[ランダム文字列].default

[ランダム文字列]は以下で確認できる。

$ cat ~/snap/thunderbird/common/.thunderbird/profiles.ini
[Profile0]
Name=default
IsRelative=1
Path=[ランダム文字列].default
Default=1

[General]
StartWithLastProfile=1
Version=2

メールフィルタのコピー

以下のように、アカウント1にメールフィルタの設定ファイルmsgFilterRules.datがあるとして、

/path/to/[ランダム文字列].default
└── Mail
    ├── アカウント1
    │   └── msgFilterRules.dat
    └── アカウント2

単にこの設定ファイルをアカウント2にコピーして、以下のようにすればよい。

/path/to/[ランダム文字列].default
└── Mail
    ├── アカウント1
    │   └── msgFilterRules.dat
    └── アカウント2
         └── msgFilterRules.dat

MySQLのroot@localhostのパスワード設定

はじめに

とあるサンプルプログラムを実行することになったのだが、MySQLへの接続にアカウントがroot@localhostを利用し、なおかつabc123のようなパスワードで接続するようになっていた。 サンプルのあちこちにMySQLへのログインのコードがあるので、サンプルに合わせてMySQLのroot@localhostのパスワードを変更することにした。 そこで少し詰まったので、作業メモを残しておく。

作業メモ

初期状態のrootは、パスワードでのログインではなく、UNIXソケットを利用したログインになっているようだ。 普通にクライアントであるmysqlを実行すると、ソケットへのアクセス権がないためかログインに失敗するので、sudoで実行する。

$ sudo mysql -u root

ログインできたら、MySQLのユーザのログインの方式を確認してみる。

mysql> select user, host, plugin from mysql.user;
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| debian-sys-maint | localhost | caching_sha2_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | auth_socket           |
+------------------+-----------+-----------------------+
5 rows in set (0.00 sec)

確かに、rootはUNIXソケット経由でのログインになっていそうだ。 そのため、他のユーザと同様にパスワードでのログインに変更する。

mysql> update mysql.user set plugin = 'caching_sha2_password' where user = 'root';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> flush privileges;

そして、rootのパスワードを設定しておく。

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'abc123';
Query OK, 0 rows affected (0.01 sec)

mysql> flush privileges;

mysqlコマンドを終了して、次はパスワード認証を実施してみる。

mysql> quit;

$ mysql -u root -p

これでログインできていれば成功となる。

補足

ユーザ権限を特定の方法で変更した場合にflush privilegesを実行する必要があるそうだ。 実行タイミングまで調べきれていないので、とりあえず上記では認証方式の変更後とパスワード変更後に呼び出している。