「Pythonには定数が無い」と思われがちだが、実はいくつかの定数が存在する。
今回は、Built-in の名前空間の中にある組み込み定数を全て紹介してみる。
言わずと知れた真偽値だが、実は True
と False
は定数である。
試しに False
に値を代入してみよう。
False = 5
Cell In[1], line 1
False = 5
^
SyntaxError: cannot assign to False
しっかり SyntaxError
が出力された。True
も同様に再代入は不可能である。
値の非存在を表すオブジェクトである NoneType
型唯一のインスタンス。
よく使うが、実は定数で代入ができない。
None = "Hello World"
Cell In[2], line 1
None = "Hello World"
^
SyntaxError: cannot assign to None
特殊メソッドの二項演算系で、他の型に対して演算ができないときに返される値。
試しに状況を再現してみる。
print(int(5).__gt__(10)) # like 5 > 10
print(int(5).__gt__("Tanaka")) # like 5 > "Tanaka"
False
NotImplemented
整数と文字列型で比較は実装されていないので NotImplemented
と出力された。
NotImplemented
があると何が便利なのか?という疑問に答えるために、以下の2つのクラスを実装してみる。
class A:
def __eq__(self, obj):
print("exec A.__eq__")
if isinstance(obj, A) or isinstance(obj, B):
return True
else:
return False
class B:
def __eq__(self, obj):
print("exec B.__eq__")
return NotImplemented
__eq__
は二項演算 ==
の特殊メソッドなので、それらを使用して確認してみる。
a = A()
b = B()
print("--- a == a ---")
print(a == a)
print("--- a == b ---")
print(a == b)
print("--- b == a ---")
print(b == a)
--- a == a ---
exec A.__eq__
True
--- a == b ---
exec A.__eq__
True
--- b == a ---
exec B.__eq__
exec A.__eq__
True
出力を見ると、最後の b == a
では B.__eq__
が呼ばれた後に A.__eq__
を呼び出していることが分かる。これが NotImplemented
の効果である。
この仕組みのメリットを、自作クラス MyInt
を用いて説明する。
class MyInt:
def __init__(self, num):
self.num = num
def __eq__(self, obj):
if isinstance(obj, MyInt):
return self.num == obj.num
elif isinstance(obj, int):
return self.num == obj
else:
return NotImplemented
int
型には MyInt
型が来た際専用の処理は用意されていないが、出力はどうなるだろう?
print("--- MyInt(5) == int(5) ---")
print(MyInt(5) == int(5))
print("--- int(5) == MyInt(5) ---")
print(int(5) == MyInt(5))
--- MyInt(5) == int(5) ---
True
--- int(5) == MyInt(5) ---
True
見事に同じ結果が出ている。この理由が NotImplemented
である。
int
型の実装で、知らないクラスと二項演算する際には NotImplemented
とすることで、左項と右項を入れ替えた特殊メソッドが呼ばれる。これにより「左項と右項を入れ替えても結果が同じ」になるのだ!
Ellipsis については、別記事 で詳しく説明しているため、ここでは簡単に紹介する。
Ellipsis
型は定数であり、リテラル記法 ...
で記述できる。
print(type(...))
print(type(Ellipsis))
print(... is Ellipsis)
<class 'ellipsis'>
<class 'ellipsis'>
True
ただし、名前 Ellipsis
自体は再代入できてしまう。
Ellipsis = "World" # エラーにならない
print(Ellipsis)
World
最後に、あまり知られていない __debug__
という定数を紹介する。
print(__debug__)
print(type(__debug__))
True
<class 'bool'>
__debug__
は Python を実行する際のオプションによって値が変わる定数である。
True
になる-O
-OO
オプション付きで実行:False
になる# 通常の実行
$ python -c "print(__debug__)"
True
# -Oオプション付きで実行
$ python -O -c "print(__debug__)"
False
-O
オプションは「基本的な最適化」を行うオプションで、主に以下の処理を行う。
__debug__
を False
に設定assert
文を除去if __debug__:
ブロック内コードを除去if __debug__:
print("debug mode")
assert 1 != 1, "1 must not be 1"
通常実行では両方のコードが実行されるが、-O
オプション付きでは両方とも完全に無視される。
ただし、この最適化は主にデバッグコードの除去が目的で、実行速度の大幅な向上は期待できない。むしろ本番環境でデバッグ用のコードを確実に無効化することが主な用途である。
また、通常実行時 __debug__
は True
であることから、普段何気なく実行している python xxx.py
のようなコマンドはデバッグモードで実行していることが分かる。
公式ドキュメントでは、以下のように述べられている。
名前
None
、False
、True
、__debug__
は再代入できない (これらに対する代入は、たとえ属性名としてであってもSyntaxError
が送出されます) ので、これらは「真の」定数であると考えられます。
つまり、Python における「真の定数」は以下の 4 つだけである。
None
False
True
__debug__
NotImplemented
や Ellipsis
は、値としては定数的な性質を持つが、名前自体は再代入可能なため「真の定数」ではない。
公式ドキュメント
https://docs.python.org/ja/3/library/constants.html
Real Python
https://realpython.com/python-assert-statement/