Skip to content

Python note assortment 2025 #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Mar 23, 2025
Merged
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7afb31c
python-miniconda: rewrite (#284)
showa-yojyo Dec 5, 2024
d0347f9
python-miniconda: rename an anchor to miniconda-anchor-pip (#284)
showa-yojyo Dec 8, 2024
deebe8a
python-miniconda: mention mamba and micromamba (#284)
showa-yojyo Dec 9, 2024
b4da74a
index: move python-miniconda up (close #284)
showa-yojyo Dec 9, 2024
5020464
_include/python-refs-core: add a link target pytest
showa-yojyo Dec 13, 2024
829369d
python-pytest: start to write (#286)
showa-yojyo Dec 13, 2024
3e76374
python-XXXX: WIP
showa-yojyo Dec 15, 2024
425416c
python-mypy: WIP (#287)
showa-yojyo Dec 17, 2024
424f1ba
python-XXXX: WIP
showa-yojyo Dec 19, 2024
2d0e004
python-click: WIP
showa-yojyo Dec 21, 2024
8c1e4df
python-click: start writing tips WIP
showa-yojyo Jan 9, 2025
ec93ebc
python-mypy: fix a link
showa-yojyo Jan 9, 2025
f73401e
python-click: tips WIP
showa-yojyo Jan 10, 2025
4e32c83
python-click: stop writing (close #288)
showa-yojyo Jan 11, 2025
5d6846c
python-ruff: WIP (#291)
showa-yojyo Jan 13, 2025
20b4c61
python-ruff: close #291
showa-yojyo Feb 2, 2025
9d063fc
python-mypy: WIP
showa-yojyo Feb 2, 2025
2f7a45c
python-ruff: remove tab characters
showa-yojyo Feb 3, 2025
a4f8c4b
python-mypy: fill the content (#287)
showa-yojyo Feb 3, 2025
c43dcc1
python-mypy: static typing is difficult (#287)
showa-yojyo Feb 15, 2025
a437350
python-mypy: RC (close #287)
showa-yojyo Feb 19, 2025
f3deddb
python-pytest: WIP (#286)
showa-yojyo Mar 5, 2025
d55276f
python-pytest: learn some CLI options (#286)
showa-yojyo Mar 6, 2025
992e913
python-pytest: RC (close #286)
showa-yojyo Mar 8, 2025
399f2da
python-hatch: WIP (#290) [skip ci]
showa-yojyo Mar 11, 2025
6cfdc33
python-hatch: hatch-static-analysis WIP (#290) [skip ci]
showa-yojyo Mar 13, 2025
9aa48ca
python-hatch: RC (#290)
showa-yojyo Mar 15, 2025
06d74fc
python-mkdocs: WIP (#289)[skip ci]
showa-yojyo Mar 17, 2025
ee263dd
python-mkdocs: WIP (#289)[skip ci]
showa-yojyo Mar 20, 2025
c5ab283
python-mkdocs: RC (#289)
showa-yojyo Mar 21, 2025
686b082
python-mkdocs: RC2 (#289)
showa-yojyo Mar 22, 2025
9e70597
python-index: remove temporary index
showa-yojyo Mar 23, 2025
803f693
python-mkdocs: extra-javascript (#289)
showa-yojyo Mar 23, 2025
017decb
python-pytest: finish (#286)
showa-yojyo Mar 23, 2025
ffd7b92
python-hatch: remove TODO
showa-yojyo Mar 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
python-click: tips WIP
  • Loading branch information
showa-yojyo committed Jan 10, 2025
commit f73401ef035c5f510ad41ea182ea2356597f9ab4
194 changes: 176 additions & 18 deletions doc/source/python-click.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ Python で |CLI| を書くときに Click_ はたいへん便利なパッケー
単一コマンドしかないような単純なスクリプトを作成する場合ですらよく使う手筋を記
す。

画面への出力には関数 ``click.echo`` を使え
----------------------------------------------------------------------

公式文書によれば、関数 ``click.echo`` を Python 標準 ``print()`` の代わりとして
なるべく使えとある。さまざまなデータ、ファイル、環境に対してより良く働く。

個人的に好きな性質を挙げる:

* 色や単純な書式付きでテキストを出力可能。例えば赤い太字とか。
* 出力が対話型端末でなさそうな場合、ANSI 色とスタイルコードを削る。
* 出力をつねにフラッシュする。

.. todo::

Python 標準 ``logging`` との棲み分けは?

Python 関数をコマンドに仕立てる
----------------------------------------------------------------------

Expand All @@ -83,9 +99,14 @@ Click_ を用いるもっとも単純なスクリプトは次のようなコー
このスクリプトの名前を :file:`helloworld.py` とすると、これだけで次のコマンドラ
インが有効だ:

* ``helloworld.py``
* 当然ながら ``helloworld.py`` のみ。
* ``helloworld.py --help``: オプション ``--help`` も自動的に組み込まれる。

このオプションを付けてスクリプトを走らせると、ヘルプを表示して終了する。そのとき
の本文はコマンド関数の docstring から構成される。Click_ はこのテキストを端末画面
の幅に合わせて折り返し表示する。エディターに入力したとおりに画面に出力させるには
制御文字 ``\b`` を用いる。解除は ``\f`` だ。

オプション ``--help`` を調整する
----------------------------------------------------------------------

Expand Down Expand Up @@ -118,6 +139,8 @@ Click_ が用意している ``@click.version_option`` を再利用するのが

import click

__version__ = "1.0.0"

@command()
@click.version_option(__version__, help="show the version and exit")
def main(): ...
Expand All @@ -128,6 +151,18 @@ Click_ が用意している ``@click.version_option`` を再利用するのが
このスクリプトの名前を :file:`myapp.py` とすると、これだけでコマンドライン
``myapp.py --version`` が有効となる。

フラグ名を ``--version`` だけではなく ``-V`` にも対応するには、バージョン実引数
のすぐ次からキーワード実引数すべての直前までにフラグ名を列挙すればいい:

.. sourcecode:: lang
:caption: "-V" と "--version" の両方をバージョンフラグとする
:force:

@command()
@click.version_option(__version__, "-V", "--version")
def main(): ...


より詳細なバージョン出力を備えたい場合には ``version_option`` ではなく、汎用の
``option`` を用いる。さらにコールバックで実装する:

Expand Down Expand Up @@ -170,26 +205,149 @@ Click_ が用意している ``@click.version_option`` を再利用するのが

参考にした Stack Overflow のスレを引用する。

コマンドライン引数をファイルパスとする
----------------------------------------------------------------------

ファイルパスを引数にとるスクリプトを作成する機会は頻繁にある。コマンドライン引数
として複数のパス文字列を取るコマンドを作る場合には、次のようにするのがよい。

.. sourcecode:: python
:caption: ``type=click.Path`` の適用例
:force:

import pathlib
import click

@click.command()
@click.argument(
"file",
nargs=-1,
type=click.Path(
exists=True,
path_type=pathlib.Path,
),
)
def main(file): ...

Python コードではコマンド関数 ``main`` の最初の仮引数名が ``@click.argument`` の
最初の実引数値と同じになる。

* ``myapp.py file1 file2`` のようなコマンドが許される。
* ``nargs=-1`` のおかげでパスを全く指定しないコマンドも許される。この手のイン
ターフェイスはそう設計するのが鉄則だ。
* 急所は ``type=click.Path(...)`` だ。これは引数 ``file`` がファイルシステムの有
効なパス文字列であることを保証する。コンストラクターに渡す値により、そのパス文
字列の条件を柔軟に指定することが可能だ。例えば、

* ``exists=True`` により、存在するファイルパスしか指定を許さない。
* ``path_type=pathlib.Path`` により、関数 ``main`` の引数としての ``file`` の
型を ``pathlib.Path`` に変換させる。後続のパス操作に便利であるがゆえ、このパ
ス型指定を与えたい。

``@click.option`` 系デコレーターでよく使うキーワード引数
----------------------------------------------------------------------

``@click.option`` 系デコレーターでよく使うキーワード引数はクラス ``Option`` のコ
ンストラクターが取る引数とだいたい一致する。よく使用するものを下に載せる:

``help``
ヘルプ文字列。自作オプションに対しては必ず指定しろ。
``type``
オプション値の型。これを適切に指定しておくと、Click_ がコマンドラインからの入
力値を検証してから、値を所望の型に変換するか、エラーで終了する。Python 組み込
み型を渡す場合もあるが、パスや日付など、土台は文字列だが特別な書式をとる値に
対して機能する専用型も Click_ は備えている。後ほど個別に記すが、例を挙げる:

* ``click.Choice``
* ``click.DateTime``
* ``click.File``
* ``click.Path``

``is_flag``
オプションをフラグとして機能させたい場合には値を ``True`` に明示的に指示しろ。
指定しない場合には Click_ がオプション型を自動的に判断する。
``show_default``
コマンドヘルプ表示において、当該オプションの既定値をヘルプ画面に出すかどうか
を指定する。Click_ は ``False`` を既定とするが、一律 ``True`` でいいと思う。
``show_envvar``
当該オプションが環境変数に対応している場合、コマンドヘルプ表示にその変数名を
示すかどうかを指定する。一律 ``True`` でいいと思う。

構成ファイル実装
----------------------------------------------------------------------

Git でいうところのファイル :file:`.gitconfig` のような機能を実現する手順を記す。

まず、コマンドラインオプション ``-c FILE`` または ``--config FILE`` で構成ファイ
ルを指定するインターフェイスを定義する:

.. sourcecode:: python
:caption: オプション ``--config`` 搭載例
:force:

import pathlib
import click

@click.command()
@click.option(
"-c",
"--config",
type=click.Path(exists=True, path_type=pathlib.Path),
default=None,
metavar="PATH",
callback=configure,
is_eager=True,
expose_value=False,
help="path to config file",
)
def main(): ...

キーワード引数を指定する狙いは次のとおり:

* ``type=click.Path(...)`` の行の目的は先述のとおり、既存のファイルパスを与えら
れることを保証したい。
* ``is_eager=True`` であるオプション値は、そうでない値のものより先に処理される。
構成ファイル処理を急いているのだ。これを利用して構成ファイルを先に読み込むのが
目的だ。
* ``expose_value=False`` を指定して、関数 ``main`` の引数リストに対応する引数を
与えなくて済むようにする。構成ファイル処理を ``main`` で行うわけではないのだ。
* ``callback=configure`` を指定して、構成ファイルを読み込む関数を呼び出すように
指示する。ここでは関数 ``configure`` を呼び出させる。

別途コールバック関数 ``configure`` を実装する。次の例のコードは構成ファイルの書
式を YAML であるとしている。

.. sourcecode:: python
:caption: 構成ファイル読み込み例
:force:

import yaml

def configure(ctx, param, value):
if value:
assert isinstance(value, pathlib.Path)
with open(value, mode="r") as fin:
ctx.default_map = yaml.safe_load(fin)
return value

辞書 ``ctx.default_map`` とはコマンドラインオプションの既定値を含むデータであり、
YAML ファイル内容の値を使ってそれを初期化するという理解でいい。

.. todo::

参考にした Stack Overflow のスレのリンクを掲載する。

関数 ``click.get_app_dir``
----------------------------------------------------------------------

構成ファイル読み込み処理にも関連するのだが、その既定パスを決定するのに関数
``get_app_dir`` が有用だ。アプリケーションやパッケージの名前を指定すると、その構
成ディレクトリーパス文字列を得られる。

既定動作では OS に最適のパスを返す。例えば WSL を含む Linux では:

.. * docstring での改行
.. * ``@click.command``
.. * ``@click.argument``
.. * ``@click.option`` でよく使うキーワード引数
..
.. * ``metavar``
..
.. * `metavar` に使えない文字がある?
.. * ``type``
.. * ``default``
.. * ``is_flag``
.. * ``click.echo``
.. * ``click.get_app_dir``
.. *
.. * 構成ファイル実装
..
.. * ``@click.pass_context``
.. * ユーザー名とパスワード ``@click.password_option``
.. * ``type=click.Path`` で ``path_type`` を指定する
.. * ``type=click.Choice``
.. * 日付型オプション (`type=click.DateTime(...)`)

Expand Down
Loading