🐍

Pythonで定数を返す方法

2024/03/28に公開

結論

Final + 静的解析を使おう!

議題

「Pythonで使うならFinalをアノテーションして静的解析する方法がある!」「でもFinalは実行時に検証されない!」「実行時にでも定数が欲しい!」

↓Finalを使うとPythonの静的解析で再代入・再定義・上書きが禁止される

↓静的解析なので実行時は反応しない

https://typing.readthedocs.io/en/latest/spec/qualifiers.html#syntax

https://mypy.readthedocs.io/en/stable/index.html

定数関数を返せばいいのでは?という発想と現実

よくある発想

「これなら実行時にoverideされないだろう」「勝ったな, 風呂食ってくる」

def hello() -> str:
    return "Hello, World!"

変数に詰めた場合上書きすることが可能

私自身しっかり調べるまで定数を返す関数を使う方法が良いのかなと思っていましたがどうやら穴があったようです.
「オブジェクトに詰めて使う人がいたら関数オブジェクトになるだろうから, 更新できるのでは?」「うっ」

def hello() -> str:
    return "Hello, World!"


if __name__ == "__main__":
    print(hello())  # Hello, World!

    h = hello()  # Hello, World!
    h = lambda: print("Overwritten!")

    h()  # Overwritten!

Python公式を参照する

公式ドキュメントをよく見るとFunction自体がPyFunctionObjectというCの構造体をしている. 悪いことができそうですね.

type PyFunctionObject
関数に使われるCの構造体。

https://docs.python.org/ja/3/c-api/function.html

実は関数自体上書きできる

オブジェクトなら関数だろうと更新できるよなと思った皆様, 正解です.

def hello() -> str:
    return "Hello, World!"


def hello() -> str:
    return "Overwritten!"

if __name__ == "__main__":
    print(hello())  # Overwritten!

まとめ

lambda関数を関数型オブジェクトに代入する形を取ればFinalを使うことができます.
...が, これなら素直に Final[str]と対して変わらないのではというのが正直な感想です.

from typing import Final, Callable

hello_world: Final[Callable[[], str]] = lambda: "Hello, World!"


if __name__ == "__main__":
    print(hello_world())  # Output: Hello, World!

typing Final使おう --☆

Discussion