プログラミング言語を定義する規則のシステムは複雑であり、間違っていなくても非常に奇妙で予測不可能なコードになる可能性があります。この章では、理解しにくい Python 言語の癖について詳しく説明します。実際のコーディングでこのような状況に遭遇することはほとんどありませんが、Python の構文の興味深い使い方 (見方によっては悪用) です。
この章の例に取り組むことで、Python がどのように機能するかをよりよく理解できるようになります。楽しみながら、いくつかの難解な質問を探りましょう。
256 が 256 で 257 が 257 でない理由
==
operator は 2 つのオブジェクトが等しいかどうかを比較し、is
operator はそれらが等しいかどうかを比較します。整数値42
と浮動小数点値は42.0
同じ値ですが、コンピューター メモリ内の異なる場所に保持される 2 つの異なるオブジェクトです。id()
関数を使用して異なる ID をチェックすることで、これを確認できます。
>>> a = 42
>>> b = 42.0
>>> a == b
True
>>> a is b
False
>>> id(a), id(b)
(140718571382896, 2526629638888)
Python が新しい整数オブジェクトを作成してメモリに格納する場合、オブジェクトの作成にはほとんど時間がかかりません。小さな最適化として、CPython (Python インタープリターは からダウンロードできます) はpython.org
、各プログラムの開始時に-5
y の256
整数オブジェクトを作成します。これらの整数はpreallocated integerと呼ばれ、CPython はそれらのオブジェクトを自動的に作成します。これは、それらがかなり一般的であるためです。プログラム0
は2
、1729
. メモリ内に新しい整数オブジェクトを作成するとき、CPython は最初にそれが ~ の間にあるかどうかをチェックし-5
ます256
。その場合、CPython は新しい整数オブジェクトを作成する代わりに既存の整数オブジェクトを返すだけで時間を節約します。この動作は、図 9-1 に示すように、繰り返される小さな整数を格納しないことでメモリを節約します。
図 9-1: Python は、単一の整数オブジェクトへの複数の参照を使用することでメモリを節約します (左)。参照ごとに別個の重複した整数オブジェクトを使用する (右)。
この最適化により、不自然な状況によって奇妙な結果が生じることがあります。例を表示するには、インタラクティブ シェルで次のように入力します。
>>> a = 256
>>> b = 256
>>> a is b # 1
True
>>> c = 257
>>> d = 257
>>> c is d # 2
False
256 個のオブジェクトはすべて実際には同じオブジェクトであるため、a
合計b
演算子は1is
を返しますTrue
。しかし、Python はc
とに対してそれぞれd
257 個のオブジェクトを作成するため、is
演算子はFalse
2 を返します。
式は257 is 257
に評価されますTrue
が、CPython は同じステートメントで同じリテラル値に対して作成された整数オブジェクトを再利用します。
>>> 257 is 257
True
もちろん、実際のプログラムは通常、整数の単位ではなく、整数の値のみを使用します。is
整数、浮動小数点数、文字列、ブール値、またはその他の単純なデータ型の値を比較するために演算子を使用することはありません。例外は、 96 ページの「比較の代わりに使用する」で説明されているように、is None
代わりに使用する場合です。そうしないと、この問題に遭遇することはめったにありません。== None
is None
==
文字列の内部化
同様に、Python はオブジェクトを再利用して、同じ文字列の個別のコピーを作成するのではなく、コード内で同じ文字列リテラルを表します。これを実際に確認するには、対話型シェルで次のように入力します。
>>> spam = 'cat'
>>> eggs = 'cat'
>>> spam is eggs
True
>>> id(spam), id(eggs)
(1285806577904, 1285806577904)
Python は、 に割り当てられた文字列が に割り当てられた文字列と同じでeggs
あることを認識し、2 つ目の冗長な文字列オブジェクトを作成する代わりに、使用されたのと同じ文字列オブジェクトへの参照を割り当てます。これは、文字列 ID が同じである理由を説明しています。'cat'
spam
'cat'
eggs
spam
この最適化は文字列予約と呼ばれ、整数の事前割り当てと同様に、CPython 実装の単なる詳細です。それに依存するコードを書くべきではありません。また、この最適化は、考えられるすべての同一文字列をキャッチするわけではありません。最適化を使用できるすべてのインスタンスを特定しようとすると、最適化によって節約できるよりも多くの時間がかかることがよくあります。たとえば、インタラクティブ シェルから文字列を作成してみる'c'
と、作成された文字列オブジェクトを再利用するのではなく、CPython が最終的な文字列を新しい文字列オブジェクトとして作成することがわかります。'at'
'cat'
'cat'
spam
>>> bacon = 'c'
>>> bacon += 'at'
>>> spam is bacon
False
>>> id(spam), id(bacon)
(1285806577904, 1285808207384)
文字列の内部化は、さまざまな言語のインタープリターとコンパイラーによって使用される最適化手法です。en.wikipedia.org/wiki/String_interning
詳細については、こちらをご覧ください。
Python の疑似インクリメントおよびデクリメント演算子
Python では、インクリメント代入演算子を使用して変数の値をインクリメント1
またはデクリメントできます1
。コードspam += 1
と は、それぞれ の値をインクリメントおよびデクリメントspam -= 1
します。spam
1
C++ や JavaScript などの他の言語には、インクリメントおよびデクリメント用の++
AND--
演算子があります。(「C++」という名前自体がそれを物語っています。それが C 言語の拡張形式であることを示唆するのは冗談です。) C++ と JavaScript の両方のコードには、 または のような操作を含めることができます++spam
。spam++
これらの演算子は微妙なバグの影響を受けやすいため、Python は賢明にもこれらの演算子を含めません (softwareengineering.stackexchange.com/q/59880
投稿で説明されているように)。
しかし、次の Python コードを使用することは完全に合法です。
>>> spam = --spam
>>> spam
42
最初に注意すべき点は、 Python の++
および--
「演算子」は、実際にspam
は の値をインクリメントまたはデクリメントしないということです。代わりに、主なものは-
Python の単項否定演算子です。次のようなコードを記述できます。
>>> spam = 42
>>> -spam
-42
複数の単項否定演算子を値の前に置くことは正当です。それらのうちの 2 つを使用すると、負の値が得られます。整数値の場合は、元の値を計算するだけです。
>>> spam = 42
>>> -(-spam)
42
これは非常にばかげた操作であり、単項否定演算子が実際のコードで 2 回使用されることはおそらくないでしょう。(しかし、もしそうなら、プログラマーが別の言語でプログラミングすることを学び、間違った Python コードを書いた可能性があります!)
単項演算子もあります+
。整数値を元の値と同じ符号に評価します。つまり、何もしません。
>>> spam = 42
>>> +spam
42
>>> spam = -42
>>> +spam
-42
+42
(または++42
)を書くのは--42
と同じくらいばかげているように見えるのに、なぜ Python にはこの単項演算子があるのでしょうか? 独自のクラスで演算子をオーバーロードする必要がある場合にのみ、演算子を補完するために存在します-
。(これは、あなたがよく知らない用語がたくさんあるかもしれません! 演算子のオーバーロードについては、第 17 章で詳しく学びます。)
+
および-
単項演算子は、Python 値の前でのみ有効であり、その後では有効ではありません。spam++
C++ または JavaScript では正当なコードである可能性がありますがspam--
、Python では構文エラーが発生します。
>>> spam++
File "<stdin>", line 1
spam++
^
SyntaxError: invalid syntax
Python にはインクリメント演算子とデクリメント演算子がありません。言語の構文の癖は、そのように見えるだけです。
全部かゼロか
all()
組み込み関数は、リストなどの値のシーケンスを取り、そのシーケンス内のすべての値が「true」の場合に戻り、1 つ以上の値が「false」の場合に「false」を返すと考えることができTrue
ますFalse
。all([False, True, True])
式と同等であるとしての関数呼び出しのFalse and True and True
。
これをリスト内包表記と組み合わせて使用するとall()
、最初に別のリストに基づいてブール値のリストを作成し、次にそれらの設定値を評価できます。たとえば、対話型シェルで次のように入力します。
>>> spam = [67, 39, 20, 55, 13, 45, 44]
>>> [i > 42 for i in spam]
[True, False, False, True, False, True, True]
>>> all([i > 42 for i in spam])
False
>>> eggs = [43, 44, 45, 46]
>>> all([i > 42 for i in eggs])
True
spam
またはeggs
内のすべての数値が42 より大きい場合、all()
ユーティリティは戻りますTrue
。
しかし、空のシーケンスを渡すとall()
、常に戻りますTrue
。インタラクティブ シェルに次のように入力します。
>>> all([])
True
all([])
「リスト内のすべての項目が真である」ではなく、「リスト内のすべての項目が真である」と評価すると理解するのが一番ですTrue
。そうしないと、奇妙な結果が得られる可能性があります。たとえば、対話型シェルで次のように入力します。
>>> spam = []
>>> all([i > 42 for i in spam])
True
>>> all([i < 42 for i in spam])
True
>>> all([i == 42 for i in spam])
True
spam
このコードは、(空のリスト) 内のすべての値が より大きいだけでなく42
、 よりも小さく42
、正確に42
!に等しいことを示しているようです。これは論理的に不可能に思えます。ただし、これら 3 つのリスト内包表記はそれぞれ空のリストに評価されることに注意してください。そのため、それらの項目のいずれも false に評価されず、関数は をall()
返しますTrue
。
ブール値は整数値です
42.0
Python が浮動小数点値を整数値と等しいと見なすのと同様に42
、ブール値のTrue
andをそれぞれ およびとFalse
同等であると見なします。Python では、データ型はデータ型のサブクラスです。(クラスとサブクラスについては第 16 章で説明します。) 以下を使用してブール値を整数に変換できます。1
0
bool
int
int()
>>> int(False)
0
>>> int(True)
1
>>> True == 1
True
>>> False == 0
True
isinstance()
ブール値が整数と見なされることも確認できます。
>>> isinstance(True, bool)
True
>>> isinstance(True, int)
True
値はデータ型True
ですbool
。しかし、これも のサブクラスでbool
あるためです。これは、整数を使用できる場所ならどこでも合計を使用できることを意味します。これにより、奇妙なコードが発生する可能性があります。int
True
int
True
False
>>> True + False + True + True # Same as 1 + 0 + 1 + 1
3
>>> -True # Same as -1.
-1
>>> 42 * True # Same as 42 * 1 mathematical multiplication.
42
>>> 'hello' * False # Same as 'hello' * 0 string replication.
' '
>>> 'hello'[False] # Same as 'hello'[0]
'h'
>>> 'hello'[True] # Same as 'hello'[1]
'e'
>>> 'hello'[-True] # Same as 'hello'[-1]
'o'
もちろん、bool
値を数値として使用できるからといって、そうすべきであるとは限りません。前の例はすべて判読不能であり、実際のコードでは使用しないでください。もともと Python にはbool
データ型がありませんでした。Boolean は Python 2.3 まで追加されませんでした。その時点で、実装を簡素化するためにbool
サブクラス化されます。PEP 285 でデータ型の履歴を読むint
ことができます。www.python.org/dev/peps/pep-0285
bool
ちなみに、True
and はFalse
Python3 の単なるキーワードです。True
これは、Python2 では変数名としてandを使用できることを意味しFalse
、次のような一見矛盾したコードにつながります。
Python2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> True is False
False
>>> True = False
>>> True is False
True
True
幸いなことに、Python3 ではこの種の紛らわしいコードは使用できず、キーワードやFalse
変数名を使用しようとすると構文エラーがスローされます。
複数の演算子をチェーンする
同じ式で異なる種類の演算子をチェーンすると、予期しないエラーが発生する場合があります。たとえば、次の例では、式で==
AND演算子を使用しています。in
>>> False == False in [False]
True
このTrue
結果は驚くべきものです。
(False == False) in [False]
、つまりですFalse
。False == (False in [False])
、つまりですFalse
。
しかし、False == False in [False]
これらの 2 つの式と同等ではありません。むしろ、同等(False == False) and (False in [False])
であり、42 < spam < 99
同等です(42 < spam) and (spam < 99)
。式は、次の図に従って計算されます。
False == False in [False]
式は興味深い Python のなぞなぞですが、実際のコードにはほとんど出てきません。
Python の反重力機能
Python の反重力機能を有効にするには、インタラクティブ シェルで次のように入力します。
>>> import antigravity
この行は楽しいイースターエッグで、Web ブラウザーを開いて Python に関する古典的な XKCD コミックをxkcd.com/353
. Python が Web ブラウザーを開くことができることに驚かれるかもしれませんが、これはwebbrowser
モジュールによって提供される組み込み機能です。Pythonwebbrowser
モジュールには、オペレーティング システムの既定の Web ブラウザーを検出し、特定の URL へのブラウザー ウィンドウを開く機能がありますopen()
。インタラクティブ シェルに次のように入力します。
>>> import webbrowser
>>> webbrowser.open('https://xkcd.com/353/')
webbrowser
モジュールは限られていますが、ユーザーがインターネットでより多くの情報を見つけるのに役立ちます。
要約する
コンピューターやプログラミング言語は人間が設計したものであり、独自の制限があることを忘れがちです。非常に多くのソフトウェアが、言語設計者やハードウェア エンジニアの作成物に基づいて構築され、依存しています。彼らは、あなたのプログラムに問題がある場合、それはあなたのプログラムが原因であり、それを実行するインタープリター ソフトウェアや CPU ハードウェアが原因ではないことを確認するために非常に懸命に働いています。私たちはこれらのツールを当然のことと考えてしまいます。
しかし、だからこそ、コンピューターとソフトウェアの隅々まで学ぶことが価値があるのです。コードにバグやクラッシュが発生した場合 (または単に奇妙な動作をして、「これはおかしい」と思わせるような場合)、これらの問題をデバッグする際の一般的な落とし穴を理解する必要があります。
この章で言及されている問題に遭遇することはほとんどありませんが、これらの小さな詳細を認識していれば、経験豊富な Python プログラマーになることができます。