プログラミング/python の変更点
更新- 追加された行はこの色です。
- 削除された行はこの色です。
- プログラミング/python へ行く。
- プログラミング/python の差分を削除
#author("2026-01-16T04:10:58+00:00","default:administrator","administrator") #author("2026-01-16T04:11:29+00:00","default:administrator","administrator") [[公開メモ]] * import の仕組み [#x9091d6d] python では - module.py みたいな個々のファイルがモジュールと呼ばれ、 - モジュールを複数入れたフォルダに _ _init_ _.py という空のファイルを置くと、 そのフォルダはパッケージとして扱われる(pukiwikiで表示が崩れないよう _ と _ の間にスペースを入れてます。悪しからず。) - import 時には package/module.py を package.module として参照可能 というのが基本なんだけど、驚くべきはここからで、 LANG:python from some_package.some_module import xxx と書くとこれは感覚的には LANG:python from root.**.some_package.some_module import xxx と書いたのと同じことになる。 つまり sys.path にある検索パスの配下のどこかで some_package.some_module が見つかれば、 「最初に見つかったもの」が import される(_ _init_ _.py によるパッケージ化を前提)。 これはどこで import したかにかかわらないので、~ package1/package2/package3/module.py から、~ package1/module.py を import pakcage1.module したりできる。 とにかく自分がどこにいるかは関係なく、つねに sys.path からの探索になる。 一方、パッケージ内では LANG:python from .some_file import xxx みたいな相対 import もできる。 ただしこれは自身がパッケージ名付きで import されていないとダメで、単体ファイルとして import された場合には「現在のモジュール」が空になってしまうため相対 import が ImportError: attempted relative import with no known parent package という分かりにくいエラーで落ちます。 フォルダ構造とモジュール構造がほぼ1:1なのに完全に1:1じゃないのでとてもややこしい。 _ _init_ _.py が置かれているファイルの単体 import なんて需要ゼロだろうに、 どうしてこのまま放置されているのか全く理解できない。 しかもこういうあやうい仕様なのにベストプラクティスが周知されていないのもとても不思議。 * python import に関する覚書 [#w1d9191c] 初心者向けに、「罠」がそこらじゅうに敷き詰められてる。ほんと、えげつない。 - パッケージの独立性を保つにはパッケージ内の内部参照に相対 import を使うことが推奨される。 - 相対 import はパッケージ階層を前提にするので各フォルダに _ _init_ _.py を置いてパッケージとして明示する。 - パッケージ内のファイルをパッケージ名も相対指定もなしで import すると独立ファイル扱いになってしまうので、そのファイルからの相対 import が壊れてエラーになる。 -- 起動時も、python -m package.module としてパッケージ名付きで起動しないとダメ --- __main__.py を置けば python -m package だけで __main__.py を起動できる -- notebook の実行にはパッケージ名を指定できないので、notebookから相対 import はできない。 --- notebook から同階層にあるモジュールをパッケージ名付きで import するには、その **親フォルダ** が sys.path の下になければならない。 --- 大抵親フォルダは参照されないので、手動でこれを追加しないと import できなくてエラーになる コメント: - 相対 import じゃないと、どこかに同名のモジュールがあればバッティングする - がちがちにパッケージ名付けるとポータビリティが失われる - _ _init_ _.py 置かなくてもいい、と書かれた記事をよく見るけど、これも罠なので無視する - パッケージ内のモジュールをパッケージ名なしで import すると破綻する。理由が分からずとても困る。 - notebook からの import も非常にややこしい * プロファイリング [#t0ad56a0] python は普通に書くと思った以上に遅いことになる場合が多いので、 プロファイリングの結果を基に重い処理を numpy 側に移したりするのが役に立つ いろいろ試した結果、特に並列処理を使ったバッチ処理が走るライブラリを使う場合には jupyter 上でのプロファイリングするとライブラリ関数の実行中に UI スレッドが動いて プロファイル結果が無茶苦茶になるようです。 したがって面倒でもプロファイルは Jupyter 外で行った方が間違いない。 ** 前処理結果を dump する [#j33e19a7] jupyter 上で計算した結果を引き継ぎつつ jupyter 外で処理を回すには、前処理で得られたデータ(プロファイリングしたい処理への入力)を pickle.dump しておき、外部で動かす処理ではそれを呼んで動作を行う Jupyter セル内で実行 LANG:python import pickle payload = { "param1": param1, "param2": param2, "param3": param3, } with open("payload.pkl", "wb") as f: pickle.dump(payload, f, protocol=pickle.HIGHEST?PROTOCOL) で(通常の python データであれば)書き出せる ** 読み込んで動かす .py ファイル [#gd0caee2] do_profile.py LANG:python import pickle import cProfile import pstats from my_module import func_to_profile def main(): with open("payload.pkl", "rb") as f: payload = pickle.load(f) prof = cProfile.Profile() prof.enable() refined = func_to_profile(**payload) prof.disable() prof.dump_stats("prof.out") if __name__ == "__main__": main() ** 実行する [#d467ea3d] LANG:console $ python do_profile.py ** 解析する [#s2942399] LANG:console $ python -m pstats prof.out Welcome to the profile statistics browser. prof.out% strip # strip directory names prof.out% sort cumtime # sort by cumtime prof.out% stats 40 # show first 40 items これで、 my_module.py:1234(my_function) みたいな関数ごとに LANG:console ncalls tottime percall cumtime percall filename:lineno(function) 呼び出し 自前処理 呼び出し 子も含む 呼び出し ファイル名:行番号(関数名) 回数 時間 あたり 全時間 あたり のリストが出ます 特定の関数内から呼ばれている分だけを調べたければ、 LANG:console prof.out% sort time prof.out% callees my_module.py:1234(my_function) などとします。 * ipynb 向けのスニペット [#n2126fb8] ** 必要なモジュールを確実に見つけられるように検索パスを広げる [#yf8e132f] LANG:python from pathlib import Path import sys # 必要なモジュールを確実に見つけられるように検索パスを広げる # # from some_module.some_file import xxx は実質的に # from root.**.some_module.some_file import xxx のように働くので、 # root の配下のどこかに some_module があれば確実に import できる # しかし some_module と同列に置かれた ipynb では root が # some_module になって、その下で some_module を探してエラーになる # ここは root を十分上に持っていくための処理 # どれかが見つかった場所をルート扱いにする MARKERS = ("pyproject.toml", ".git", ".vscode") p = Path.cwd().resolve() root = None for parent in (p, *p.parents): if any((parent / m).exists() for m in MARKERS): root = parent break if root is None: # 見つからなければさしあたり親ディレクトリにしておく root = Path.cwd().resolve().parents[0] # 先頭がrootでなければ、既存のrootエントリを全部消してから先頭に入れる print(f"Adding this folder to sys.path: {root}") root_s = str(root) if not sys.path or sys.path[0] != root_s: sys.path[:] = [p for p in sys.path if p != root_s] sys.path.insert(0, root_s) ** オートリロード [#pef8f02b] ソースファイルが日本語を含む可能性があるならこれをまず実行 LANG: python import builtins # デフォルトでファイルは UTF-8 で読むように open を置き換える # 日本語コメント入りの .py ファイルを autoreload しようとして落ちるのを避けるため if "_original_open" not in globals(): _original_open = builtins.open def open_utf8_default(path, mode="r", *args, **kwargs): # binary モードや明示指定がある場合は素通し if "b" not in mode and "encoding" not in kwargs: kwargs["encoding"] = "utf-8" return _original_open(path, mode, *args, **kwargs) builtins.open = open_utf8_default で、これが自動リロードの設定。 LANG: python # 自動リロードを設定 %reload_ext autoreload %autoreload 2 ** 必要に応じて依存モジュールを pip install する [#g496960a] LANG:python ensure_package("trimesh") みたいにしてモジュールを pip install できるようになる。 みたいにしてモジュールがない場合に限って pip install できるようになる。 古いモジュールは __version LANG:python # 必要に応じて必要なモジュールをインストールする import sys import subprocess import importlib.util print(sys.executable) print(f"sys.version: {sys.version}") def _get_installed_version(dist_name: str) -> str | None: """ Return installed distribution version (e.g., 'numpy' -> '1.26.4'). Prefer importlib.metadata; fallback to pkg_resources for older Pythons. """ try: # Python 3.8+ from importlib.metadata import version, PackageNotFoundError # type: ignore try: return version(dist_name) except PackageNotFoundError: return None except Exception: # Older Python or environment without importlib.metadata try: import pkg_resources # type: ignore try: return pkg_resources.get_distribution(dist_name).version except Exception: return None except Exception: return None def _get_module_dunder_version(import_name: str) -> str | None: """Try module.__version__ as a last resort.""" try: import importlib m = importlib.import_module(import_name) v = getattr(m, "__version__", None) return v if isinstance(v, str) and v else None except Exception: return None def ensure_package(import_name: str, dist_name: str | None = None, pip_args: list[str] | None = None) -> None: """ Ensure a module is importable. If not, install it via pip. - import_name: the name used in `import ...` - dist_name : the name used in `pip install ...` / metadata lookup (often same) """ dist = dist_name or import_name if importlib.util.find_spec(import_name) is None: print(f"{import_name} not found. Installing {dist}...") args = [sys.executable, "-m", "pip", "install", dist] if pip_args: args.extend(pip_args) subprocess.check_call(args) # install後の表示(取れなければunknown) ver = _get_installed_version(dist) or _get_module_dunder_version(import_name) or "unknown" print(f"{import_name} installed. version={ver}") else: ver = _get_installed_version(dist) or _get_module_dunder_version(import_name) or "unknown" print(f"{import_name} already importable. version={ver}") # ここに必要なパッケージを並べる ensure_package("scipy") ensure_package("trimesh") ensure_package("embreex") # for raycast ensure_package("fast_simplification", dist_name = "fast-simplification")
Counter: 456 (from 2010/06/03),
today: 2,
yesterday: 4