HN 日本語サマリー

← 一覧へ戻る
プログラミング

Pythonの事前宣言済み定数は少々奇妙である

Python's pre-declared constants are kinda weird (sebsite.pw)

158 pointsby rbanffy149 コメント

要約

PythonにはTrue、False、None、__debug__、Ellipsis、NotImplementedという6つの事前宣言済み定数が存在しますが、それぞれ挙動が異なります。True、False、Noneはキーワードであり、他の定数とは異なり、lexical tokenとして扱われるため、代入しようとするとSyntaxErrorが発生します。__debug__は通常Trueですが、-Oオプションで実行するとFalseになり、代入できない唯一の識別子です。EllipsisとNotImplementedは通常の組み込み定数として扱われ、グローバル変数で上書き可能です。

全文翻訳

Pythonには6つの事前宣言済み「定数」があります: True、False、None、__debug__、Ellipsis(または同等の...)、そしてNotImplementedです。しかし、それらはすべて、なぜか少しずつ異なる挙動をします。 True、False、None True、False、Noneはキーワードです。これらは識別子ではなく、純粋にそれ自体のlexical tokenです。これは非常に奇妙です。Pythonでは他にこのようなものはありません。通常、名前解決中に解決されるものが、lexer自体で解決されます。このことの興味深い副作用として、x.Trueのような式はSyntaxErrorを発生させます。この決定の根拠(もしあれば)に興味があります。これらの定数にはさらに興味深い点がありますが、それは他の定数と関連するため、後で説明します。 __debug__ __debug__はブール定数です。通常はTrueですが、-Oで実行するとFalseになります。このアイデアは、非デバッグビルドでassertが無効になるのと似ています。チェックが高価すぎる場合などに、最適化されたビルドでは、if __debug__でコードをラップできます。 __debug__は非常に興味深いですが、通常の識別子(True、False、Noneとは異なり)であるにもかかわらず、言語内で代入できない唯一の識別子です。 >>> __debug__ = 67 File "", line 1 SyntaxError: cannot assign to __debug__ 属性として代入することもできません。 >>> x.__debug__ = 67 File "", line 1 SyntaxError: cannot assign to __debug__ 繰り返しになりますが、他の識別子はこのような挙動をしません。これは真の特殊ケースです。しかし、キーワードではないため、True、False、Noneとは少し異なる挙動をします。x.__debug__はAttributeError(SyntaxErrorではなく)を発生させます。これは構文的には有効であり、単に存在しない属性を参照しているだけです。 興味深いことに、__debug__を削除しようとした場合にも特別なエラーメッセージがあります(特別なケースでなければ、いずれにしてもNameErrorが発生するにもかかわらず)。しかし、これは__debug__という名前の属性を削除する場合には適用されません。 >>> del __debug__ File "", line 1 SyntaxError: cannot delete __debug__ >>> del x.__debug__ Traceback (most recent call last): File "", line 1, in NameError: name 'x' is not defined xが定義されていれば、代わりにAttributeErrorが発生します。いずれの場合も、代入の場合とは異なり、なぜかSyntaxErrorではありません。 脱線:SyntaxErrorは嘘 エラーの話をすると、__debug__への代入は、実際には無効な構文ではないにもかかわらずSyntaxErrorが発生する数少ないケースの1つです。ここで自分で確認できます。 >>> assert (__debug__ := 67) debugビルドでこのassertを実行するとSyntaxErrorが発生しますが、-Oを使用すると、assertはコンパイルされず、例外は発生しません。この種の例は他にも2つあり、関数外でのyieldまたはawaitの使用です。 >>> assert (yield) >>> assert (await 67) EllipsisとNotImplemented EllipsisとNotImplementedは、リファレンスの「定数」セクションで文書化されていますが、他の4つの定数とは異なり、「本物の」定数ではありません。これらは通常の組み込み定数なので、グローバル変数でシャドウイングできます。 >>> NotImplemented = 67 >>> NotImplemented 67 ここでも、なぜこれらの定数が特別ではなく、他の定数が特別なのか、その根拠に興味があります。 定数の上書き 興味深い点があります。True、False、Noneはlexical tokenであるにもかかわらず、通常の組み込み定数としても存在します。 >>> import builtins >>> getattr(builtins, 'True') True >>> getattr(builtins, 'False') False >>> getattr(builtins, 'None') is None True getattrを使用しない限り、これらの値に直接アクセスする方法はありません。しかし、ここで本当に興味深いことが起こります。setattrも機能します! >>> setattr(builtins, 'True', 67) >>> getattr(builtins, 'True') 67 しかし、これはlexical tokenとしてアクセスした場合の値を変えません。 >>> True True __debug__も同様の挙動をします! >>> setattr(builtins, '__debug__', 67) >>> builtins.__debug__ 67 >>> __debug__ True したがって、__debug__はある意味で代入可能ですが、lexical tokenではないにもかかわらず、True、False、Noneと同様に特殊ケースとして扱われ、組み込みモジュールの変更による値の影響を受けません。なので、これは本当に定数なのです! EllipsisとNotImplementedは、再び、実際には定数ではありません。 >>> setattr(builtins, 'Ellipsis', 67) >>> Ellipsis 67 しかし、これは...の値を変えません。 >>> ... Ellipsis したがって、ある意味では...は本物の定数ですが、Ellipsisはそうではありません。奇妙ですよね?