WTF Python!😱 Python、たじかよ前線

 
0
この゚ントリヌをはおなブックマヌクに远加
Daichi Takayama
Daichi Takayama (高山 倧地)

以䞋の文章はこちらのペヌゞを翻蚳したものです。

https://github.com/satwikkansal/wtfpython

こちらのGitHubプロゞェクト「What the f*ck Python! 😱」は、予想倖のコヌドスニペットを通じおPythonを探求し、理解するこずを目指しおいたす。倚くの蚀語に翻蚳されおおり、りェブサむトやノヌトブック、コマンドラむンツヌルを通じおむンタラクティブに孊ぶこずもできたす。

他のモヌド: Interactive Website | Interactive Notebook | CLI

Pythonは高氎準な解釈型のプログラミング蚀語であり、開発者が快適に利甚するために倚くの機胜を搭茉しおいたす。しかし、䞀芋しただけでは理解し難いPythonスニペットの結果ずいうのもありたす。

こちらのワクワクするプロゞェクトでは、盎感に反するスニペットやPythonのあたり知られおいない特城の背埌にある正確なメカニズムの解説を詊みたす。そこたで驚くものではないような䟋も取り䞊げられおいるかもしれたせんが、Pythonの面癜いずころを理解しやすくしおくれたす。このプロゞェクトはプログラミング蚀語の奥深さを孊ぶ良い手段です。読者であるあなたにずっおも興味深いものになるでしょう。

経隓豊富なPythonプログラマヌの方は、䞀発で正解が分かるか、お詊しください。いく぀かの事象は以前に経隓されおいるかもしれたせん。叀い蚘憶を呌び戻すこずができるでしょうか

P.S. この蚘事を読むのが二床目以降だずいう方は、最新版぀いおはこちらをご芧ください最新の改蚂で远加された䟋にはアスタリスクが付いおいたす。

それでは、始めたしょう...


目次

  • 䟋の構造
  • 䜿い方
  • 実䟋
    • セクション: 脳トレチャレンゞ
      • たずは倧事なこずから
      • やっかいな文字列
      • 連鎖した操䜜には泚意が必芁
      • **is**挔算子の萜ずし穎
      • ハッシュブラりニヌ
      • 本質的にはみんな同じ
      • 秩序の䞭の無秩序
      • トラむし続けよう
      • 䜕のためなのか
      • 評䟡時間の䞍䞀臎
      • is notはis notではない
      • 䞀発でXが勝぀䞉目䞊べ
      • シュレヌディンガヌの倉数
      • 鶏が先か卵が先か
      • サブクラスのリレヌション
      • メ゜ッドの等䟡性ず同䞀性
      • すべお真である
      • 驚きのコンマ
      • 文字列ずバックスラッシュ
      • notの結び目
      • 半分のトリプルクォヌト文字列
      • ブヌル倀の意倖な挙動
      • クラス属性ずむンスタンス属性の違い
      • Noneの生成
      • returnからyield
      • Nan再垰性
      • 䞍倉の倉異
      • 倖郚スコヌプから消える倉数
      • 謎のキヌ型倉換
      • これは分かるかな
      • 敎数の文字列倉換が限界を超えるずき
    • セクション: 滑りやすい斜面
      • むテレヌト䞭の蟞曞倉曎問題
      • し぀こいdel操䜜
      • スコヌプ倖の倉数
      • むテレヌト䞭のリスト項目削陀
      • むテレヌタのロッシヌ圧瞮
      • ルヌプ倉数が挏れる
      • デフォルトの可倉匕数に泚意
      • 䟋倖凊理の萜ずし穎
      • 同じオペランド、異なる結果
      • クラススコヌプを無芖する名前解決
      • 「銀行家の䞞め」
      • 藁の䞭の針
      • Splitsies
      • ワむルドむンポヌト
      • すべおsorted
      • 真倜䞭は存圚しない
    • セクション: 隠された宝物たち
      • Python、空を飛びたいんだけど 
      • なぜgotoなのか
      • カッコ぀けよう
      • barryおじさん
      • Pythonでも知っおいるこず愛は耇雑
      • 実は存圚するんです
      • Ellipsis
      • むンピニティ
      • マングリングしよう
    • セクション: 芋た目に惑わされるな
      • 行をスキップ
      • テレポヌトテヌション
      • 䜕かがおかしい...
    • セクション: さたざたなトピック
      • +=の方が早い
      • 巚倧な文字列の䜜成
      • dict 怜玢の遅延
      • むンスタンスdict の膚匵
      • その他の小さな発芋

䟋の構造

党おの䟋は以䞋のような構造で衚瀺されおいたす:

▶ 面癜いタむトル

#コヌド
#面癜いこずが起きたすよ...

出力Pythonのバヌゞョン

>>> トリガヌずなる文
䜕らかの予枬できない出力

堎合によっおはここで䞀蚀

💡 解説:

䜕が起きおいるのか、なぜ起きおいるのかの簡単な説明。

#コヌド
#必芁あれば、解説のために䟋を远加

出力Pythonのバヌゞョン

>>> トリガヌ # 䜕が起きおいるのかを簡単に理解するための䟋
# 正しい出力

泚意党おの䟋はPython 3.5.2の察話型むンタヌプリタでテストされおおり、出力の前に特に指定がない限り、党おのPythonバヌゞョンで動䜜するはずです。


䜿い方

䜜者の考える、それぞれの䟋を最倧限に掻甚するための方法です。

  • 䟋の最初のコヌドを泚意深く読んでください。経隓豊富なPythonプログラマヌであれば、ほずんどの堎合、次に䜕が起こるかを予枬できるでしょう。

  • 出力スニペットを読み、

    • あなたが期埅するものず同じかどうかをご確認ください。
    • そのような状態になっおいる理由を正確に理解できおいたすか
      • 答えが「いいえ」であれば党く問題ありたせん、深呌吞をしお、解説を読んでください。
      • 答えが「はい」であれば、自分のこずを耒めた䞊で、次の䟋に進んでください。

    P.S.コマンドラむンを䜿甚しおWTFPythonを読むこずもできたす。pypi packageを䜿甚しお、

    $ pip install wtfpython -U
    $ wtfpython
    

👀 䟋

セクション: 脳トレチャレンゞ

▶ たずは倧事なこずから

Python 3.8の「セむりチ」挔算子:=がなぜか人気を博しおいたす。これに぀いお芋おみたしょう。

# Pythonバヌゞョン3.8以降

>>> a = "wtf_walrus"
>>> a
'wtf_walrus'

>>> a := "wtf_walrus"
File "<stdin>", line 1
    a := "wtf_walrus"
      ^
SyntaxError: invalid syntax

>>> (a := "wtf_walrus") # これは動䜜したす
'wtf_walrus'
>>> a
'wtf_walrus'

# Pythonバヌゞョン3.8以降

>>> a = 6, 9>>> a
(6, 9)

>>> (a := 6, 9)
(6, 9)
>>> a
6

>>> a, b = 6, 9 # 通垞のアンパック>>> a, b
(6, 9)
>>> (a, b = 16, 19) # おっず
  File "<stdin>", line 1
    (a, b = 16, 19)
          ^
SyntaxError: invalid syntax

>>> (a, b := 16, 19) # 倉な3぀組が出力されたす
(6, 16, 19)

>>> a # aはただ倉わっおいたせん
6

>>> b
16

💡 解説

セむりチ挔算子に぀いおの軜い埩習

セむりチ挔算子:=はPython 3.8で導入されたした。匏の䞭で倉数に倀を割り圓おたい堎合に圹立ちたす。

def some_func():
        # ここで高コストの蚈算が行われるずしたす
        # time.sleep(1000)
        return 5

# なので、
if some_func():
        print(some_func()) # 蚈算が2回行われるのは良くないですね

# もしくは
a = some_func()
if a:
    print(a)

# これで簡朔にできたす
if a := some_func():
        print(a)

出力 (> 3.8):

5
5
5

これにより、1行のコヌドが省略され、some_funcを2回呌び出すこずが防がれたした。

  • 括匧を぀けない「代入匏」セむりチ挔算子の䜿甚はトップレベルでは制限されおいるため、最初の䟋のa := "wtf_walrus"文でSyntaxErrorが発生したした。括匧を぀けるず期埅通りに機胜し、aに割り圓おられたした。
  • 通垞、=挔算子を含む匏の括匧付けは蚱可されおいたせん。したがっお、(a, b = 6, 9)での構文゚ラヌが発生したす。
  • セむりチ挔算子の構文は NAME:= expr の圢匏で、NAMEは有効な識別子であり、exprは有効な匏です。したがっお、iterableのパッキングずアンパッキングはサポヌトされおいたせん。぀たり、
    • (a := 6, 9) は ((a := 6), 9) に盞圓し、最終的には (a, 9) になりたすここで a の倀は6です。
>>> (a := 6, 9) == ((a := 6), 9)
True
>>> x = (a := 696, 9)
>>> x
(696, 9)
>>> x[0] is a # どちらもメモリ䞊の同じ箇所を参照
True

同様に、(a, b := 16, 19)は(a, (b := 16), 19)に盞圓し、これはただの3぀組です


▶ やっかいな文字列

>>> a = "some_string">>> id(a)
140420665652016
>>> id("some" + "_" + "string") # 䞡方のidが同じであるこずに泚意しおください。
140420665652016

>>> a = "wtf"
>>> b = "wtf"
>>> a is b
True

>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False
>>> a, b = "wtf!", "wtf!">>> a is b # 3.7.xを陀く党おのバヌゞョンで
True

>>> a = "wtf!"; b = "wtf!">>> a is b # これは、どこで実行するかによっおTrueたたはFalseを出力したすPython shell / ipython / スクリプトずしお
False

# 今床はsome_file.pyファむル内で
a = "wtf!"
b = "wtf!"
print(a is b)

# モゞュヌルを呌び出すずTrueが出力されたす

出力 (< Python3.7 )

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False

理にかなっおいたすよね

💡 解説:

  • 最初ず2番目のスニペットの動䜜は、CPythonの最適化文字列むンタヌニングず呌ばれるによるものです。いく぀かの堎合に新しいオブゞェクトを䜜成するのではなく、既存の䞍倉オブゞェクトを䜿甚しようずしたす。

  • 「むンタヌニング」された埌、倚くの倉数がメモリ内の同じ文字列オブゞェクトを参照するこずがありたすこれによりメモリを節玄できたす。

  • 䞊蚘のスニペットでは、文字列が暗黙的にむンタヌニングされおいたす。文字列が暗黙的にむンタヌニングされるかどうかの刀断は実装によりたす。いく぀かのルヌルがあるので、文字列がむンタヌニングされるかどうかを掚枬するのにお䜿いください

    • 長さ0ず長さ1のすべおの文字列はむンタヌニングされる
    • 文字列はコンパむル時にむンタヌニングされる'wtf'はむンタヌニングされるが、''.join(['w', 't', 'f'])はむンタヌニングされない
    • ASCIIの文字、数字、アンダヌスコアで構成されおいない文字列はむンタヌニングされない。これが'wtf!'がむンタヌニングされなかった理由!のため。このルヌルのCPython実装はこちらをご芧ください
      やっかいな文字列
  • aずbが同じ行で"wtf!"に蚭定されるず、Pythonむンタヌプリタは新しいオブゞェクトを䜜成し、その埌すぐに2番目の倉数を参照する。別々の行で行うず、"wtf!"が既にオブゞェクトずしお存圚しおいるこずを「知らない」䞊蚘の事実によれば"wtf!"は暗黙的にむンタヌニングされないため。これがコンパむル時の最適化。この最適化はCPythonの3.7.xバヌゞョンには適甚されたせん。詳现な議論に぀いおはこのissueをご芧ください。

  • 察話型環境IPythonなどではコンパむル単䜍は単䞀のステヌトメントであり、モゞュヌルの堎合はモゞュヌル党䜓。a, b = "wtf!", "wtf!"は単䞀のステヌトメントであり、a = "wtf!"; b = "wtf!"は単䞀行の2぀のステヌトメント。これこそがa = "wtf!"; b = "wtf!"で識別子が異なる理由であり、some_file.pyで呌び出された堎合に同じである理由にもなる。

  • 4番目のスニペットの出力が突然倉わるのは、ピヌプホヌル最適化ずしお知られる定数折りたたみによるもの。぀たり、匏'a'*20はコンパむル䞭に'aaaaaaaaaaaaaaaaaaaa'に眮き換えられ、ランタむム䞭にいく぀かのクロックサむクルを節玄する。定数折りたたみは、長さが21未満の文字列に察しおのみ行われる。なぜでしょうか匏'a'*10**10の結果ずしお生成される.pycファむルのサむズを想像しおみおください。実装゜ヌスはこちら。

  • 泚意Python 3.7では、定数折りたたみはピヌプホヌル最適化から新たなAST最適化に移され、ロゞックも少し倉曎されたので、4番目のスニペットはPython 3.7では機胜したせん。倉曎に぀いおの詳现はこちら。


▶ 連鎖した操䜜には泚意が必芁

>>> (False == False) in [False] # わかりたす
False
>>> False == (False in [False]) # わかりたす
False
>>> False == False in [False] # お
True

>>> True is False == False
False
>>> False is False is False
True

>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False

💡 解説:

https://docs.python.org/3/reference/expressions.html#comparisons によるず

圢匏的には、a, b, c, ..., y, zが匏であり、op1, op2, ..., opNが比范挔算子である堎合、a op1 b op2 c ... y opN zは、a op1 bか぀b op2 cか぀...y opN zに盞圓したすが、各匏は最倧で䞀床しか評䟡されたせん。

䞊蚘の䟋ではこのような動䜜がばかげおいるず感じたかもしれたせんが、a == b == cや0 <= x <= 100のようなものに察しおは本圓に玠晎らしいです。

  • False is False is Falseは(False is False) and (False is False)に盞圓したす。

  • True is False == Falseは(True is False) and (False == False)に盞圓し、文の最初の郚分True is FalseがFalseに評䟡されるため、党䜓の匏がFalseに評䟡されたす。

  • 1 > 0 < 1は(1 > 0) and (0 < 1)に盞圓し、Trueに評䟡されたす。

  • 匏(1 > 0) < 1はTrue < 1に盞圓し、

    >>> int(True)
    1
    >>> True + 1 # この䟋には関係ありたせんが、面癜いです。
    2
    
  • なので、1 < 1はFalseに評䟡されたす。


▶ is 挔算子の萜ずし穎

以䞋はネット䞊でも非垞に有名な䟋です。

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False
>>> a = []
>>> b = []
>>> a is b
False

>>> a = tuple()
>>> b = tuple()
>>> a is b
True

出力

>>> a, b = 257, 257
>>> a is b
True

出力 (Python 3.7.x限定)

>>> a, b = 257, 257
>>> a is b
False

💡 解説:

is ず == の違い

  • is 挔算子は、䞡方のオペランドが同じオブゞェクトを参照しおいるかどうかをチェックしたす぀たり、オペランドのIDが䞀臎するかどうかをチェックしたす。

  • == 挔算子は、䞡方のオペランドの倀を比范しお、それらが同じであるかチェックしたす。

  • ぀たり is は参照の等䟡性のためのもので、== は倀の等䟡性のためのものです。わかりやすい䟋を挙げたす。

    >>> class A: pass
    >>> A() is A() # それぞれ異なるメモリ䜍眮の空のオブゞェクトです。
    False
    
    

256 は既存のオブゞェクトですが 257 はそうではありたせん

Pythonを起動するず、-5 から 256 たでの数が割り圓おられたす。これらの数は頻繁に䜿甚されるので、準備しおおくのは理にかなっおいたす。

https://docs.python.org/3/c-api/long.html からの匕甚

珟圚の実装では、-5 から 256 たでの党おの敎数に぀いお、敎数オブゞェクトの配列が保持されおいたす。その範囲で int を䜜成するず、既存のオブゞェクトぞの参照を返すだけです。そのため、1 の倀を倉曎するこずは理論的に可胜です。私は、この堎合のPythonの動䜜は未定矩だず考えおいたす。:-)

>>> id(256)
10922528
>>> a = 256
>>> b = 256
>>> id(a)
10922528
>>> id(b)
10922528
>>> id(257)
140084850247312
>>> x = 257
>>> y = 257
>>> id(x)
140084850247440
>>> id(y)
140084850247344

むンタヌプリタが y = 257 を実行する際に、すでに 257 の倀の敎数が䜜成されおいるこずを認識できるほど賢くありたせん。そのため、メモリ内に別のオブゞェクトを䜜成しおしたいたす。

同様の最適化は、空のタプルのような他の䞍倉オブゞェクトにも適甚されたす。リストは倉曎可胜なので、[] is [] は False を返し、() is () は True を返したす。これが第2のスニペットの解説です。第3のスニペットに進みたしょう。

同じ行で同じ倀で初期化された堎合、a ず b は同じオブゞェクトを参照したす。

出力

>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488

  • a ず b が同じ行で 257 に蚭定されるず、Pythonむンタヌプリタは新しいオブゞェクトを䜜成し、その埌すぐに2番目の倉数を参照したす。別々の行で行うず、すでに 257 がオブゞェクトずしお存圚するこずを「知りたせん」。

  • これはコンパむラの最適化であり、特に察話型環境に適甚されたす。ラむブむンタヌプリタで2行入力するず、個別にコンパむルされるため、個別に最適化されたす。この䟋を .py ファむルで詊すず、ファむルが䞀床にコンパむルされるため、同じ動䜜は芋られたせん。この最適化は敎数に限らず、他の䞍倉デヌタ型にも適甚されたす。䟋えば、「文字列はやっかいな時もある」で取り䞊げた䟋ず同じく、文字列や浮動小数点数でも動䜜したす。

    >>> a, b = 257.0, 257.0
    >>> a is b
    True
    
    
  • なぜ Python 3.7 ではこれが機胜しないのでしょうか抜象的な理由ずしおは、そのようなコンパむラの最適化が実装に䟝存するためです぀たり、バヌゞョン、OSなどによっお倉曎される可胜性があるから。具䜓的にどのような実装の倉曎が問題を匕き起こしたのかはただ調査䞭ですが、最新情報はこちらのissueで確認できたす。


▶ ハッシュブラりニヌ

some_dict = {}
some_dict[5.5] = "JavaScript"
some_dict[5.0] = "Ruby"
some_dict[5] = "Python"

出力:

>>> some_dict[5.5]
"JavaScript"
>>> some_dict[5.0] # "Ruby"の存圚が"Python"によっお砎壊された
"Python"
>>> some_dict[5]
"Python"

>>> complex_five = 5 + 0j>>> type(complex_five)
complex
>>> some_dict[complex_five]
"Python"

なぜ"Python"だらけなのでしょうか

💡 解説:

  • Pythonの蟞曞におけるキヌの䞀意性は、同䞀性ではなく等䟡性に基づいおいたす。したがっお、5、5.0、5 + 0jは異なる型の別々のオブゞェクトですが、これらは等しいため、同時に同じdictたたはsetに存圚するこずはできたせん。いずれか䞀぀を挿入した埌に他の等䟡ではあるけど異なるキヌで怜玢を詊みおも、KeyErrorで倱敗する代わりに、元のマップされた倀が成功するようになりたす。

    >>> 5 == 5.0 == 5 + 0j
    True
    >>> 5 is not 5.0 is not 5 + 0j
    True
    >>> some_dict = {}
    >>> some_dict[5.0] = "Ruby">>> 5.0 in some_dict
    True
    >>> (5 in some_dict) and (5 + 0j in some_dict)
    True
    
    

これはアむテムを蚭定する堎合にも適甚されたす。したがっお、some_dict[5] = "Python" を行うず、Pythonは等䟡なキヌ 5.0 -> "Ruby" で既存のアむテムを芋぀け、その堎で倀を䞊曞きし、元のキヌはそのたたにしたす。

>>> some_dict
{5.0: 'Ruby'}
>>> some_dict[5] = "Python"
>>> some_dict
{5.0: 'Python'}
  • では、キヌを 5.0 から 5 に曎新するにはどうすればいいのでしょうか実際にはその堎での曎新はできたせんが、たずキヌを削陀しdel some_dict[5.0]、それから蚭定するsome_dict[5]こずで、浮動小数点の 5.0 ではなく敎数 5 をキヌずしお取埗するこずができたす。ただし、これが必芁ずなるケヌスは皀でしょう。

  • Pythonはどうやっお 5.0 を含む蟞曞で 5 を芋぀けたのでしょうかハッシュ関数を䜿甚しお、すべおのアむテムをスキャンするこずなく定数時間でこれを行いたす。Pythonが蟞曞でキヌ foo を探すずき、たず hash(foo) を蚈算したすこれは定数時間で実行されたす。等しいオブゞェクトは同じハッシュ倀を持぀こずが芁求されおいるためこちらのドキュメント参照、5、5.0、5 + 0j は同じハッシュ倀を持ちたす。

    >>> 5 == 5.0 == 5 + 0j
    True
    >>> hash(5) == hash(5.0) == hash(5 + 0j)
    True
    

泚: 逆は必ずしも真ではありたせん。ハッシュ倀が等しいオブゞェクトでも、それ自䜓が等しくない堎合がありたす。これはハッシュ衝突ずしお知られ、通垞ハッシュが提䟛する定数時間のパフォヌマンスを䜎䞋させたす。


▶ 本質的にはみんな同じ

class WTF:
  pass

出力:

>>> WTF() == WTF() # 二぀の異なるむンスタンスは等しくない
False
>>> WTF() is WTF() # IDも異なる
False
>>> hash(WTF()) == hash(WTF()) # ハッシュ倀も異なるべき
True
>>> id(WTF()) == id(WTF())
True

💡 解説:

  • id が呌び出されたずき、Pythonは WTF クラスのオブゞェクトを䜜成し、id 関数に枡したした。id 関数はその idメモリ䜍眮を取埗し、オブゞェクトを砎棄したす。オブゞェクトは砎棄されたす。

  • これを連続しお二回行うず、Pythonは二番目のオブゞェクトにも同じメモリ䜍眮を割り圓おたす。CPythonにおいお id はオブゞェクトのIDずしおメモリ䜍眮を䜿甚するので、二぀のオブゞェクトのIDは同じになりたす。

  • したがっお、オブゞェクトのIDはオブゞェクトの生存期間に察しおのみ䞀意です。オブゞェクトが砎棄された埌、たたは䜜成される前であれば、他の䜕かが同じIDを持぀こずがありたす。

  • では、なぜ is 挔算子は False ず評䟡されたのでしょうかこちらのスニペットで確認したしょう。

    class WTF(object):
      def __init__(self): print("I")
      def __del__(self): print("D")
    

出力:

>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF()) == id(WTF())
I
D
I
D
True

  • ご芧の通り、オブゞェクトが砎棄される順番が、ここでの違いを生んでいたす。

▶ 秩序の䞭の無秩序

from collections import OrderedDict

dictionary = dict()
dictionary[1] = 'a'; dictionary[2] = 'b';

ordered_dict = OrderedDict()
ordered_dict[1] = 'a'; ordered_dict[2] = 'b';

another_ordered_dict = OrderedDict()
another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';

class DictWithHash(dict):
    """
    A dict that also implements __hash__ magic.
    """
    __hash__ = lambda self: 0

class OrderedDictWithHash(OrderedDict):
    """
    An OrderedDict that also implements __hash__ magic.
    """
    __hash__ = lambda self: 0

出力

>>> dictionary == ordered_dict # a == b であり、
True
>>> dictionary == another_ordered_dict # b == c であれば、
True
>>> ordered_dict == another_ordered_dict # なぜ c == a ではないのか
False

# setはナニヌクな芁玠のみから構成されるこずを知っおいたすが、
# これらの蟞曞を䜿っおを䜜っおみるずどうなるでしょう...

>>> len({dictionary, ordered_dict, another_ordered_dict})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

# 蟞曞には __hash__ が実装されおいないので理にかなっおいたすが、
# ラッパヌクラスを䜿いたしょう。
>>> dictionary = DictWithHash()
>>> dictionary[1] = 'a'; dictionary[2] = 'b';
>>> ordered_dict = OrderedDictWithHash()
>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
>>> another_ordered_dict = OrderedDictWithHash()
>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
>>> len({dictionary, ordered_dict, another_ordered_dict})
1
>>> len({ordered_dict, another_ordered_dict, dictionary}) # 順番を倉えおいたす
2

ここでは䜕が起きおいるのでしょうか

💡 解説:

  • dictionary、ordered_dict、another_ordered_dict の間で掚移的等䟡性が保持されなかった理由は、OrderedDict クラスで __eq__ メ゜ッドがどのように実装されおいるかにありたす。ドキュメントからの匕甚です。

    OrderedDictオブゞェクト間の等䟡性テストは順序に敏感であり、list(od1.items())==list(od2.items()) ずしお実装されおいたす。OrderedDictオブゞェクトず他のマッピングオブゞェクト間の等䟡性テストは、通垞の蟞曞のように順序に敏感ではありたせん。

  • この動䜜の等䟡性の理由は、OrderedDict オブゞェクトが通垞の蟞曞が䜿甚される堎所で盎接代甚できるようにするためです。

  • では、なぜ順序を倉えるず生成された set オブゞェクトの長さに圱響が出たのでしょうか答えは掚移的等䟡性の欠劂に他なりたせん。setはナニヌクな芁玠の「順序なし」コレクションなので、芁玠が挿入される順序は関係ないはずです。しかし、この堎合は関係ありたす。もう少し詳しく分解しおみたしょう。

>>> some_set = set()
>>> some_set.add(dictionary) # これは䞊蚘スニペットからのマッピングオブゞェクトです
>>> ordered_dict in some_set
True
>>> some_set.add(ordered_dict)
>>> len(some_set)
1
>>> another_ordered_dict in some_set
True
>>> some_set.add(another_ordered_dict)
>>> len(some_set)
1

>>> another_set = set()
>>> another_set.add(ordered_dict)
>>> another_ordered_dict in another_set
False
>>> another_set.add(another_ordered_dict)
>>> len(another_set)
2
>>> dictionary in another_set
True
>>> another_set.add(another_ordered_dict)
>>> len(another_set)
2
  • ぀たり、䞍䞀臎は another_ordered_dict in another_set が False ずなるこずによるものなのです。なぜなら、another_set には既に ordered_dict が存圚しおおり、先ほど芋たように ordered_dict == another_ordered_dict は False なのです。

▶ トラむし続けよう

def some_func():
    try:
        return 'from_try'
    finally:
        return 'from_finally'

def another_func():
    for _ in range(3):
        try:
            continue
        finally:
            print("Finally!")

def one_more_func(): # よし
    try:
        for i in range(3):
            try:
                1 / i
            except ZeroDivisionError:
                # ここで䟋倖を投げお、forルヌプの倖で凊理したしょう
                raise ZeroDivisionError("A trivial divide by zero error")
            finally:
                print("Iteration", i)
                break
    except ZeroDivisionError as e:
        print("Zero division error occurred", e)

出力:

>>> some_func()
'from_finally'

>>> another_func()
Finally!
Finally!
Finally!

>>> 1 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

>>> one_more_func()
Iteration 0

💡 解説:

  • tryブロック内でreturn, break, continue文が実行された堎合、finally節も出るずきに実行されたす。
  • 関数の戻り倀は、最埌に実行されたreturn文によっお決定されたす。finally節は垞に実行されるので、finally節内で実行されるreturn文が垞に最埌に実行されるものになりたす。
  • ここでの泚意点は、finally節がreturnたたはbreak文を実行するず、䞀時的に保存されおいた䟋倖が砎棄されるこずです。

▶ 䜕のためなのか

some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
    i = 10

出力:

>>> some_dict # むンデックス付きのdictが珟れる。
{0: 'w', 1: 't', 2: 'f'}

💡 解説:

  • Python文法におけるfor文は次のように定矩されおいたす

    for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
    
    

ここでexprlistは代入のタヌゲットです。぀たり、{exprlist} = {next_value}が反埩可胜な各アむテムに察しお実行されるこずを意味したす。分かりやすくするために、興味深い䟋を芋おみたしょう

for i in range(4):
    print(i)
    i = 10

出力:

0
1
2
3

ルヌプは䞀床しか実行されないず思いたしたか

💡 解説:

  • i = 10の代入文がルヌプの反埩に圱響を䞎えないのは、Pythonのforルヌプの動䜜方法によるものです。各反埩の開始前に、むテレヌタによっお提䟛された次のアむテムこの堎合はrange(4)がアンパックされ、タヌゲットリスト倉数に割り圓おられたすこの堎合はi。

  • enumerate(some_string)関数は、各反埩で新しい倀i増加するカりンタずsome_stringからの文字を生成したす。それから、蟞曞some_dictのiキヌをその文字に蚭定したす。ルヌプの展開を単玔化するず以䞋のようになりたす

    >>> i, some_dict[i] = (0, 'w')
    >>> i, some_dict[i] = (1, 't')
    >>> i, some_dict[i] = (2, 'f')
    >>> some_dict
    
    

▶ 評䟡時間の䞍䞀臎

array = [1, 8, 15]
# 兞型的なゞェネレヌタ匏
gen = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]

出力:

>>> print(list(gen)) # 他の倀はどこぞ
[8]

array_1 = [1,2,3,4]
gen_1 = (x for x in array_1)
array_1 = [1,2,3,4,5]

array_2 = [1,2,3,4]
gen_2 = (x for x in array_2)
array_2[:] = [1,2,3,4,5]

出力:

>>> print(list(gen_1))
[1, 2, 3, 4]

>>> print(list(gen_2))
[1, 2, 3, 4, 5]

array_3 = [1, 2, 3]
array_4 = [10, 20, 30]
gen = (i + j for i in array_3 for j in array_4)

array_3 = [4, 5, 6]
array_4 = [400, 500, 600]

出力:

>>> print(list(gen))
[401, 501, 601, 402, 502, 602, 403, 503, 603]

💡 解説:

  • ゞェネレヌタ匏においお、in句は宣蚀時に評䟡されたすが、条件句は実行時に評䟡されたす。

  • したがっお、実行時になる前にarrayはリスト[2, 8, 22]に再床割り圓おられ、1, 8, 15のうち8のカりントだけが0より倧きいので、ゞェネレヌタは8のみを生成したす。

  • gen_1ずgen_2の出力の違いは、倉数array_1ずarray_2に倀が再割り圓おされる方法によっお生じるものです。

  • 最初のケヌスでは、array_1は新しいオブゞェクト[1,2,3,4,5]にバむンドされ、in句は宣蚀時に評䟡されるので、叀いオブゞェクト[1,2,3,4]砎棄されおいないを指し続けたす。

  • 二぀目のケヌスでは、array_2ぞのスラむス代入が叀いオブゞェクト[1,2,3,4]を[1,2,3,4,5]に曎新したす。したがっお、gen_2ずarray_2の䞡方が同じオブゞェクト珟圚は[1,2,3,4,5]に曎新されおいるを匕き続き参照しおいたす。

  • では、ここたでのロゞックに基づき、第3スニペットのlist(gen)の倀は[11, 21, 31, 12, 22, 32, 13, 23, 33]であるべきではないでしょうかなぜならarray_3ずarray_4はarray_1のように振る舞うはずです。なぜarray_4の倀のみが曎新されたのかは、PEP-289で解説されおいたす。

    最も倖偎のfor匏だけがすぐに評䟡され、他の匏はゞェネレヌタが実行されるたで遅延される。

💡 解説:

ゞェネレヌタ匏においお、in句は宣蚀時に評䟡されたすが、条件句は実行時に評䟡されたす。

したがっお、実行時になる前にarrayはリスト[2, 8, 22]に再床割り圓おられ、1, 8, 15のうち8のカりントだけが0より倧きいので、ゞェネレヌタは8のみを生成したす。

gen_1ずgen_2の出力の違いは、倉数array_1ずarray_2に倀が再割り圓おされる方法によっお生じるものです。

最初のケヌスでは、array_1は新しいオブゞェクト[1,2,3,4,5]にバむンドされ、in句は宣蚀時に評䟡されるので、叀いオブゞェクト[1,2,3,4]砎棄されおいないを指し続けたす。

二぀目のケヌスでは、array_2ぞのスラむス代入が叀いオブゞェクト[1,2,3,4]を[1,2,3,4,5]に曎新したす。したがっお、gen_2ずarray_2の䞡方が同じオブゞェクト珟圚は[1,2,3,4,5]に曎新されおいるを匕き続き参照しおいたす。

では、ここたでのロゞックに基づき、第3スニペットのlist(gen)の倀は[11, 21, 31, 12, 22, 32, 13, 23, 33]であるべきではないでしょうかなぜならarray_3ずarray_4はarray_1のように振る舞うはずです。なぜarray_4の倀のみが曎新されたのかは、PEP-289で解説されおいたす。

    最も倖偎のfor匏だけがすぐに評䟡され、他の匏はゞェネレヌタが実行されるたで遅延される。

▶ is not ... は is (not ...) ではない

>>> 'something' is not None
True
>>> 'something' is (not None)
False

💡 解説:

is notは䞀぀の二項挔算子で、isずnotを分けお䜿うずきずは動䜜が異なりたす。
is notは、オペレヌタの䞡偎の倉数が同じオブゞェクトを指しおいる堎合はFalseに、そうでない堎合はTrueに評䟡されたす。
䟋では、(not None)はTrueに評䟡されたす。なぜならNoneの倀はブヌルコンテキストでFalseになるので、匏は'something' is Trueになりたす。

▶ 䞀発でXが勝぀䞉目䞊べ

# 行を初期化
row = [""] * 3 #行 i['', '', '']
# 盀面を䜜る
board = [row] * 3

出力:

>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

"X" を3぀割り圓おたわけではありたせんよね

💡 解説:

row倉数を初期化するず、メモリ内で以䞋のようになりたす。

row倉数

そしお、rowを乗算しおboardが初期化されるず、メモリ内で以䞋のようになりたすboard[0], board[1], board[2]の各芁玠は、rowが参照しおいる同じリストを参照。

row倉数2

このシナリオを避けるには、row倉数を䜿っおboardを生成しないようにしたすこのissueで尋ねられたした。

>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]

▶ シュレヌディンガヌの倉数

funcs = []
results = []
for x in range(7):
    def some_func():
        return x
    funcs.append(some_func)
    results.append(some_func())  # ここで関数を呌び出しおいるこずに泚意

funcs_results = [func() for func in funcs]

出力 (Pythonバヌゞョン):

>>> results
[0, 1, 2, 3, 4, 5, 6]
>>> funcs_results
[6, 6, 6, 6, 6, 6, 6]

ルヌプ䞭にsome_funcをfuncsに远加する前にxの倀が毎回異なっおいたしたが、ルヌプが完了した埌に評䟡されるずすべおの関数が6を返したす。

>>> powers_of_x = [lambda x: x**i for i in range(10)]
>>> [f(2) for f in powers_of_x]
[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

💡 解説

  • ルヌプ内で関数を定矩し、その関数の本䜓でルヌプ倉数を䜿甚する堎合、ルヌプ関数のクロヌゞャは倉数にバむンドされたす。その倀にはバむンドされたせん。関数はその倀を䜿甚するのではなく、呚囲のコンテキストからxを探したす。そのため、すべおの関数は蚈算のために倉数に割り圓おられた最新の倀を䜿甚したす。以䞋、呚囲のコンテキストからのxを䜿甚しおいるこず぀たり、ロヌカル倉数ではないこずをご確認ください。
>>> import inspect
>>> inspect.getclosurevars(funcs[0])
ClosureVars(nonlocals={}, globals={'x': 6}, builtins={}, unbound=set())

xはグロヌバルな倀なので、xを曎新するこずでfuncsが参照しお返す倀を倉曎するこずができたす。

>>> x = 42>>> [func() for func in funcs]
[42, 42, 42, 42, 42, 42, 42]

  • 望たしい動䜜を埗るためには、ルヌプ倉数を名前付き倉数ずしお関数に枡したしょう。なぜこれが機胜するのか それは、倉数を関数のスコヌプ内で定矩するからです。もはや呚囲のグロヌバルスコヌプを探しお倉数の倀を探すのではなく、その時点でのxの倀を栌玍するロヌカル倉数を䜜成したす。
funcs = []
for x in range(7):
    def some_func(x=x):
        return x
    funcs.append(some_func)

出力:

>>> funcs_results = [func() for func in funcs]
>>> funcs_results
[0, 1, 2, 3, 4, 5, 6]

もうグロヌバルスコヌプのxは䜿甚しおいたせん。

>>> inspect.getclosurevars(funcs[0])
ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())


▶ 鶏が先か卵が先か

>>> isinstance(3, int)
True
>>> isinstance(type, object)
True
>>> isinstance(object, type)
True

では、"究極"の基底クラスはどれでしょうかそれに...

>>> class A: pass>>> isinstance(A, A)
False
>>> isinstance(type, type)
True
>>> isinstance(object, object)
True

>>> issubclass(int, object)
True
>>> issubclass(type, object)
True
>>> issubclass(object, type)
False

💡 解説

  • Pythonにおいおtypeはメタクラスです。
  • すべおのものがPythonではオブゞェクトです。クラスずそのオブゞェクトむンスタンスも含たれたす。
  • typeクラスはobjectクラスのメタクラスであり、typeを含むすべおのクラスは盎接的にも間接的にもobjectから継承しおいたす。
  • objectずtypeの間に実際の基底クラスは存圚したせん。䞊蚘のスニペットでの混乱は、Pythonのクラスずいう芳点からこれらの関係issubclassやisinstanceを考えおいるからです。objectずtypeの関係は玔粋なPythonでは再珟できたせん。぀たり、以䞋のような関係は玔粋なPythonでは再珟できたせん。
    • クラスAはクラスBのむンスタンスであり、クラスBはクラスAのむンスタンスである。
    • クラスAは自身のむンスタンスである。
  • objectずtypeのそのような関係お互いのむンスタンスでありながら自身もむンスタンスであるは、実装レベルでの"ごたかし"によっおPythonに存圚しおいたす。

▶ サブクラスのリレヌション

出力:

>>> from collections.abc import Hashable
>>> issubclass(list, object)
True
>>> issubclass(object, Hashable)
True
>>> issubclass(list, Hashable)
False

サブクラスのリレヌションは掚移的であるず予想されおいたしたよねAがBのサブクラスで、BがCのサブクラスならば、AもCのサブクラスであるはずです

💡 解説:

  • Pythonにおけるサブクラスのリレヌションは必ずしも掚移的ではありたせん。メタクラスで独自の任意の__subclasscheck__を定矩するこずが蚱されおいたす。
  • issubclass(cls, Hashable)が呌び出されるず、単にclsたたはそれが継承するものの䞭で停でない"__hash__"メ゜ッドを探したす。
  • objectはハッシュ可胜ですが、listはハッシュ䞍可胜なので、掚移的関係が厩れたす。
  • より詳しい説明はこちら

▶ メ゜ッドの等䟡性ず同䞀性

class SomeClass:
    def method(self):
        pass

    @classmethod
    def classm(cls):
        pass

    @staticmethod
    def staticm():
        pass

出力:

>>> print(SomeClass.method is SomeClass.method)
True
>>> print(SomeClass.classm is SomeClass.classm)
False
>>> print(SomeClass.classm == SomeClass.classm)
True
>>> print(SomeClass.staticm is SomeClass.staticm)
True

classmに二回アクセスするず、同䞀ではない等䟡なオブゞェクトが埗られたす。SomeClassのむンスタンスでは䜕が起こるか芋おみたしょう

o1 = SomeClass()
o2 = SomeClass()

出力:

>>> print(o1.method == o2.method)
False
>>> print(o1.method == o1.method)
True
>>> print(o1.method is o1.method)
False
>>> print(o1.classm is o1.classm)
False
>>> print(o1.classm == o1.classm == o2.classm == SomeClass.classm)
True
>>> print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm)
True

SomeClassの同じむンスタンスに察しおclassmやmethodに二回アクセスするず、等䟡ではあるが同䞀ではないオブゞェクトが生成されたす。

💡 解説:

  • 関数はデスクリプタです。属性ずしお関数にアクセスするずきはい぀でも、デスクリプタが呌び出され、属性を所有するオブゞェクトず関数を「バむンド」するメ゜ッドオブゞェクトが䜜成されたす。呌び出された堎合、メ゜ッドは関数を呌び出し、暗黙のうちにバむンドされたオブゞェクトを最初の匕数ずしお枡したすこれが、明瀺的に枡さなくおもselfが最初の匕数ずしお埗られる方法です。
>>> o1.method
<bound method SomeClass.method of <__main__.SomeClass object at ...>>

  • 属性に耇数回アクセスするず、毎回メ゜ッドオブゞェクトが䜜成されたすしたがっお、o1.method is o1.method が真になるこずはありたせん。むンスタンスではなくクラス属性ずしお関数にアクセスするずメ゜ッドは䜜成されないので、SomeClass.method is SomeClass.method は真です。
>>> SomeClass.method
<function SomeClass.method at ...>
  • classmethod は関数をクラスメ゜ッドに倉換したす。クラスメ゜ッドはデスクリプタであり、アクセスされるず、オブゞェクト自䜓ではなくオブゞェクトのクラス型をバむンドするメ゜ッドオブゞェクトを䜜成したす。
>>> o1.classm
<bound method SomeClass.classm of <class '__main__.SomeClass'>>
  • 関数ずは異なり、classmethodはクラス属性ずしおアクセスされた堎合にもメ゜ッドを䜜成したすこの堎合、それはクラスにバむンドされ、型にはバむンドされたせん。したがっおSomeClass.classm is SomeClass.classmは停です。
>>> SomeClass.classm
<bound method SomeClass.classm of <class '__main__.SomeClass'>>
  • メ゜ッドオブゞェクトは、関数が等しく、バむンドされたオブゞェクトが同じである堎合に等しくなりたす。したがっお、o1.method == o1.methodは真ですが、メモリ䞊で同じオブゞェクトではありたせん。
  • staticmethodは関数をそのたた返す「ノヌオペレヌション」のディスクリプタに倉換したす。メ゜ッドオブゞェクトは䞀切䜜成されないので、isでの比范は真ずなりたす。
>>> o1.staticm
<function SomeClass.staticm at ...>
>>> SomeClass.staticm
<function SomeClass.staticm at ...>
  • むンスタンスメ゜ッドを呌び出すたびに新しい「メ゜ッド」オブゞェクトを䜜成し、selfを挿入するために毎回匕数を倉曎する必芁があるため、パフォヌマンスに悪圱響を及がしたした。
    CPython 3.7は、䞀時的なメ゜ッドオブゞェクトを䜜成せずにメ゜ッドを呌び出す新しいオペコヌドを導入するこずで、これを解決したした。これはアクセスされた関数が実際に呌び出されるずきのみ䜿甚されるので、この䟋のスニペットは圱響を受けず、䟝然ずしおメ゜ッドを生成したす :)

▶ すべお真である

>>> all([True, True, True])
True
>>> all([True, True, False])
False

>>> all([])
True
>>> all([[]])
False
>>> all([[[]]])
True

このTrue-Falseはなぜ亀互になるのでしょうか

💡 解説:

  • all関数の実装は以䞋のように等䟡です。

    def all(iterable): for element in iterable: if not element: return False return True
    
    
  • all([])はiterableが空であるためTrueを返したす。

  • all([[]])はFalseを返したす。なぜなら枡された配列には[]ずいう䞀぀の芁玠があり、Pythonでは空のリストは停ず評䟡されるからです。

  • all([[[]]])ずそれ以䞊の再垰的なバリアントは垞にTrueです。これは枡された配列の単䞀の芁玠[[...]]がもはや空ではなく、倀があるリストは真ず評䟡されるからです。


▶ 驚きのコンマ

出力 (< 3.6):

>>> def f(x, y,):
...     print(x, y)
...
>>> def g(x=4, y=5,):
...     print(x, y)
...
>>> def h(x, **kwargs,):
  File "<stdin>", line 1
    def h(x, **kwargs,):
                     ^
SyntaxError: invalid syntax

>>> def h(*args,):
  File "<stdin>", line 1
    def h(*args,):
                ^
SyntaxError: invalid syntax

💡 説明

  • トレヌリングコンマ末尟のコンマは、Python関数の圢匏パラメヌタリストにおいお垞に䜿えるわけではありたせん。
  • Pythonでは、匕数リストは先行するコンマず埌続するコンマの䞀郚で定矩されおいたす。この察立が原因で、コンマが䞭間に挟たれ、どのルヌルにも適合しない状況が生じたす。
  • 泚意: トレヌリングコンマの問題はPython 3.6で修正されおいたす。この投皿のコメント欄でPythonにおけるトレヌリングコンマのさたざたな䜿甚法に぀いおも簡単な議論がされおいたす。

▶ 文字列ずバックスラッシュ

出力:

>>> print("\"")
"

>>> print(r"\"")
\"

>>> print(r"\")
File "<stdin>", line 1
    print(r"\")
              ^
SyntaxError: 文字列リテラル䞭での行末

>>> r'\'' == "\\'"
True

💡 解説:

  • 通垞のPython文字列では、バックスラッシュは特別な意味を持぀文字シングルクォヌト、ダブルクォヌト、バックスラッシュ自䜓などを゚スケヌプするために䜿甚されたす。

    >>> "wt\"f"
    'wt"f'
    

バックスラッシュに続く文字を゚スケヌプするずいう動䜜ずずもに、バックスラッシュ自䜓もそのたた枡されたす。

>>> r'wt\"f' == 'wt\\"f'
True
>>> print(repr(r'wt\"f')
'wt\\"f'

>>> print("\n")

>>> print(r"\\n")
'\\n'
  • パヌサヌが生の文字列リテラルrプレフィックスで瀺されるでバックスラッシュ\に遭遇するず、䜕らかの文字を続けるこずを期埅したす。そしお、このケヌスprint(r"\")では、バックスラッシュが末尟の匕甚笊を゚スケヌプし、パヌサヌを終了匕甚笊なしにしおしたったため、SyntaxErrorが発生しおいたす。そのため、raw文字列の末尟にバックスラッシュを䜿甚するこずはできたせん。

▶ notの結び目

x = True
y = False

出力:

>>> not x == y
True
>>> x == not y
  File "<input>", line 1
    x == not y
           ^
SyntaxError: invalid syntax

💡 解説:

  • 挔算子の優先順䜍は匏がどのように評䟡されるかに圱響を䞎えたす。Pythonでは、== 挔算子は not 挔算子よりも優先順䜍が高いです。
  • したがっお not x == y は not (x == y) ず同等であり、これは最終的に not (True == False) ず評䟡され True になりたす。
  • しかし x == not y は SyntaxError を匕き起こしたす。これは (x == not) y ず同等であり、最初に予想された x == (not y) ではないからです。
  • パヌサヌは not トヌクンを not in 挔算子の䞀郚ずしお期埅しおいたしたなぜなら == ず not in 挔算子は同じ優先順䜍を持っおいるため。しかし、not トヌクンの埌に in トヌクンを芋぀けられなかったため、SyntaxError を発生させたした。

▶ 半分のトリプルクォヌト文字列

出力:

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # 以䞋のステヌトメントは `SyntaxError` を発生させる
>>> # print('''wtfpython')
>>> # print("""wtfpython")
  File "<input>", line 3
    print("""wtfpython")
                        ^
SyntaxError: EOF while scanning triple-quoted string literal

💡 解説:

  • Pythonは暗黙的な文字列リテラルの連結をサポヌトしおいたす。䟋えば、

    >>> print("wtf" "python")
    wtfpython
    >>> print("wtf" "") # たたは "wtf"""
    wtf
    
  • ''' ず """ もPythonで文字列区切りずしお䜿甚されるため、Pythonむンタプリタは珟圚出䌚っおいるトリプルクォヌト文字列リテラルの終わりずしおトリプルクォヌトの区切りを期埅しおいるため、SyntaxError が発生したす。


▶ ブヌル倀の意倖な挙動

# iterableに含たれるブヌル倀ず敎数の数を数えるシンプルな䟋。
mixed_list = [False, 1.0, "some_string", 3, True, [], False]
integers_found_so_far = 0
booleans_found_so_far = 0

for item in mixed_list:
    if isinstance(item, int):
        integers_found_so_far += 1
    elif isinstance(item, bool):
        booleans_found_so_far += 1

出力:

>>> integers_found_so_far
4
>>> booleans_found_so_far
0

>>> some_bool = True
>>> "wtf" * some_bool
'wtf'
>>> some_bool = False
>>> "wtf" * some_bool
''
def tell_truth():
    True = False
    if True == False:
        print("I have lost faith in truth!")

出力 (< 3.x):

>>> tell_truth()
I have lost faith in truth!

💡 説明:

  • Pythonにおける bool は int のサブクラスです

    >>> issubclass(bool, int)
    True
    >>> issubclass(int, bool)
    False
    

そのため、True ず False は int のむンスタンスです。

>>> isinstance(True, int)
True
>>> isinstance(False, int)
True

True の敎数倀は 1 で、False の敎数倀は 0 です。

>>> int(True)
1
>>> int(False)
0
  • 詳现な理由に぀いおは、このStackOverflowの回答をご参照ください。
  • 元々Pythonには bool 型がなく停を衚すには 0 を、真を衚すには 1 のような非れロ倀を䜿甚しおいたした、True、False、そしお bool 型が 2.x バヌゞョンで远加されたしたが、埌方互換性のために True ず False を定数にするこずはできたせんでした。それらは単なるビルトむン倉数で、再割り圓おが可胜でした。
  • Python 3 は埌方互換性がなかったので、぀いにこの問題が修正され、したがっお最埌のスニペットは Python 3.x では動䜜したせん

▶ クラス属性ずむンスタンス属性

class A:
    x = 1

class B(A):
    pass

class C(A):
    pass

出力:

>>> A.x, B.x, C.x
(1, 1, 1)
>>> B.x = 2
>>> A.x, B.x, C.x
(1, 2, 1)
>>> A.x = 3
>>> A.x, B.x, C.x # C.xは倉わったが、B.xは倉わらなかった
(3, 2, 3)
>>> a = A()
>>> a.x, A.x
(3, 3)
>>> a.x += 1
>>> a.x, A.x
(4, 3)

class SomeClass:
    some_var = 15
    some_list = [5]
    another_list = [5]
    def __init__(self, x):
        self.some_var = x + 1
        self.some_list = self.some_list + [x]
        self.another_list += [x]

出力:

>>> some_obj = SomeClass(420)
>>> some_obj.some_list
[5, 420]
>>> some_obj.another_list
[5, 420]
>>> another_obj = SomeClass(111)
>>> another_obj.some_list
[5, 111]
>>> another_obj.another_list
[5, 420, 111]
>>> another_obj.another_list is SomeClass.another_list
True
>>> another_obj.another_list is some_obj.another_list
True

💡 解説

  • クラス倉数ずクラスむンスタンスの倉数は、クラスオブゞェクトの蟞曞ずしお内郚的に扱われたす。倉数名が珟圚のクラスの蟞曞に芋぀からない堎合は、芪クラスが怜玢されたす。
  • += 挔算子は、新しいオブゞェクトを䜜成せずに倉曎可胜なオブゞェクトをその堎で倉曎したす。したがっお、あるむンスタンスの属性を倉曎するず、他のむンスタンスずクラス属性にも圱響が及びたす。

▶ Noneの生成

some_iterable = ('a', 'b')

def some_func(val):
    return "something

出力 (<= 3.7.x):

>>> [x for x in some_iterable]
['a', 'b']
>>> [(yield x) for x in some_iterable]
<generator object <listcomp> at 0x7f70b0a4ad58>
>>> list([(yield x) for x in some_iterable])
['a', 'b']
>>> list((yield x) for x in some_iterable)
['a', None, 'b', None]
>>> list(some_func((yield x)) for x in some_iterable)
['a', 'something', 'b', 'something']

💡 解説:

  • これは、ゞェネレヌタず包括的な理解での yield のCPythonの扱いに関するバグです。
  • ゜ヌスず詳しい解説はこちら
  • 関連するバグレポヌト: https://bugs.python.org/issue10544
  • Python 3.8+ では、リスト内包衚蚘で yield を䜿甚するこずは蚱可されおおらず、SyntaxError が投げられたす。

▶ returnからyield

def some_func(x):
    if x == 3:
        return ["wtf"]
    else:
        yield from range(x)

出力 (> 3.3):

>>> list(some_func(3))
[]

"wtf"はどこに消えたのでしょうかこれはyield fromの特別な効果によるものでしょうか怜蚌しおみたしょう。

def some_func(x):
    if x == 3:
        return ["wtf"]
    else:
        for i in range(x):
          yield i

出力:

>>> list(some_func(3))
[]

同じ結果です。機胜したせんでした。

💡 解説:

  • Python 3.3以降、ゞェネレヌタ内で倀を䌎うreturnステヌトメントを䜿甚するこずが可胜になりたしたPEP380を参照。公匏ドキュメントでは、

"... ゞェネレヌタの䞭でreturn exprを䜿甚するず、ゞェネレヌタの終了時にStopIteration(expr)が発生する。"

ずありたす。

  • some_func(3)のケヌスでは、returnステヌトメントのためにStopIterationが発生し、最初にリストが空になりたす。StopIteration䟋倖は、list(...)ラッパヌやforルヌプ内で自動的にキャッチされたす。したがっお、䞊蚘の2぀のスニペットは空のリストになりたす。
  • ゞェネレヌタsome_funcから["wtf"]を取埗するには、StopIteration䟋倖をキャッチする必芁がありたす。
try:
    next(some_func(3))
except StopIteration as e:
    some_string = e.valu
  • >>> some_string ["wtf"]

▶ Nanの再垰性

a = float('inf')
b = float('nan')
c = float('-iNf')  # 倧文字小文字は区別したせん
d = float('nan')

出力:

>>> a
inf
>>> b
nan
>>> c
-inf
>>> float('some_other_string')
ValueError: could not convert string to float: some_other_string
>>> a == -c # inf==inf
True
>>> None == None # None == None
True
>>> b == d # but nan!=nan
False
>>> 50 / a
0.0
>>> a / a
nan
>>> 23 + b
nan

>>> x = float('nan')
>>> y = x / x
>>> y is y # 同䞀性は保持
True
>>> y == y # yの等䟡性は倱敗
False
>>> [y] == [y] # だがyを含むリストの等䟡性は成功
True

💡 解説:

  • 'inf'ず'nan'は特別な文字列倧文字小文字を区別しないで、明瀺的にfloat型にキャストするず、それぞれ数孊的な"無限"ず"非数"を衚すために䜿甚されたす。
  • IEEE暙準によるずNaN != NaNなので、このルヌルに埓うず、Pythonのコレクション芁玠の反射性の仮定が砎られたす。぀たり、xがlistのようなコレクションの䞀郚である堎合、比范のような実装はx == xずいう仮定に基づいおいたす。この仮定により、たず同䞀性が比范されたす速床が速いため。倀は同䞀性が䞍䞀臎の堎合にのみ比范されたす。以䞋のスニペットでより明確に理解できるでしょう。
>>> x = float('nan')
>>> x == x, [x] == [x]
(False, True)
>>> y = float('nan')
>>> y == y, [y] == [y]
(False, True)
>>> x == y, [x] == [y]
(False, False
  • xずyの同䞀性が異なるため、倀が考慮され、これらも異なるため、比范は今回Falseを返したす。
  • 参考: Reflexivity, and other pillars of civilization

▶ 䞍倉の倉異

Pythonの参照の仕組みを知っおいれば、これは些现なこずかもしれたせん。

some_tuple = ("A", "tuple", "with", "values")
another_tuple = ([1, 2], [3, 4], [5, 6])

出力:

>>> some_tuple[2] = "change this"
TypeError: 'tuple' object does not support item assignment
>>> another_tuple[2].append(1000) #これぱラヌを投げない
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000])
>>> another_tuple[2] += [99, 999]
TypeError: 'tuple' object does not support item assignment
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000, 99, 999])

あれtupleは䞍倉だったのでは

💡 解説:

  • https://docs.python.org/3/reference/datamodel.html からの匕甚

    䞍倉のシヌケンス
    䞍倉のシヌケンス型のオブゞェクトは、䞀床䜜成されるず倉曎するこずはできたせん。オブゞェクトが他のオブゞェクトぞの参照を含んでいる堎合、これらの他のオブゞェクトは倉曎可胜であり倉曎されるこずがありたす。しかし、䞍倉オブゞェクトが盎接参照するオブゞェクトの集合は倉曎するこずができたせん。

  • += 挔算子は新しいオブゞェクトを䜜成せずに、倉曎可胜なオブゞェクトをその堎で倉曎したす。アむテムの代入は機胜したせんが、䟋倖が発生したずきには、アむテムはすでにその堎で倉曎されおいたす。

  • Pythonの公匏 FAQにも説明がありたす。


▶ 倖郚スコヌプから消える倉数

e = 7
try:
    raise Exception()
except Exception as e:
    pass

出力 (Python 2.x):

>>> print(e)
# 䜕も衚瀺されない

出力 (Python 3.x):

>>> print(e)
NameError: name 'e' is not defined

💡 解説:

  • 出兞: https://docs.python.org/3/reference/compound_stmts.html#except

    as タヌゲットを䜿っお䟋倖が割り圓おられた堎合、except 節の終わりでクリアされたす。たるで

    except E as N:
        foo
    

が以䞋に倉換されたかのようです。

except E as N:
    try:
        foo
    finally:
        del N
  • これは、䟋倖をexcept節の埌でも参照するためには、䟋倖を別の名前に割り圓おる必芁があるこずを意味したす。䟋倖は、トレヌスバックが添付されおいるため、スタックフレヌムず参照サむクルを圢成し、次のガベヌゞコレクションが行われるたでそのフレヌム内のすべおのロヌカル倉数を生き続けさせたす。
  • Pythonでは節にスコヌプがありたせん。䟋の䞭の党おが同じスコヌプ内に存圚し、倉数eはexcept節の実行によっお陀去されたした。これは、それぞれの内郚スコヌプを持぀関数では圓おはたりたせん。以䞋の䟋がこれを分かりやすく衚しおいたす
def f(x):
    del(x)
    print(x)

x = 5
y = [5, 4, 3]

出力:

>>> f(x)
UnboundLocalError: local variable 'x' referenced before assignment
>>> f(y)
UnboundLocalError: local variable 'x' referenced before assignment
>>> x
5
>>> y
[5, 4, 3]

Python 2.xでは、倉数名eはException()むンスタンスに割り圓おられるので、printしようずしおも䜕も衚瀺されたせん。

出力 (Python 2.x):

>>> e
Exception()
>>> print e
# 䜕も衚瀺されない


▶ 謎のキヌ型倉換

class SomeClass(str):
    pass

some_dict = {'s': 42}

出力:

>>> type(list(some_dict.keys())[0])
str
>>> s = SomeClass('s')
>>> some_dict[s] = 40>>> some_dict # 2぀の異なるキヌ倀ペアが期埅される
{'s': 40}
>>> type(list(some_dict.keys())[0])
str

💡 解説:

  • オブゞェクト s ず文字列 "s" は同じ倀にハッシュされたす。なぜなら SomeClass は str クラスの __hash__ メ゜ッドを継承しおいるからです。

  • SomeClass("s") == "s" は True に評䟡されたす。なぜなら SomeClass は str クラスから __eq__ メ゜ッドも継承しおいるからです。

  • 䞡方のオブゞェクトが同じ倀にハッシュされ、等しいため、蟞曞内で同じキヌで衚されたす。

  • 望たしい動䜜を埗るためには、SomeClass 内で __eq__ メ゜ッドを再定矩するこずができたす。

    class SomeClass(str):
      def __eq__(self, other):
          return (
              type(self) is SomeClass
              and type(other) is SomeClass
              and super().__eq__(other)
          )
    
    # カスタムの __eq__ を定矩するず、Pythonは自動的に __hash__ メ゜ッドを継承しなくなるため、 # それも定矩する必芁がある __hash__ = str.__hash__
    some_dict = {'s':42}
    

出力:

>>> s = SomeClass('s')
>>> some_dict[s] = 40>>> some_dict
{'s': 40, 's': 42}
>>> keys = list(some_dict.keys())
>>> type(keys[0]), type(keys[1])
(__main__.SomeClass, str)

▶ これは分かるかな

a, b = a[b] = {}, 5

出力:

>>> a
{5: ({...}, 5)}

💡 解説

Python language referenceによるず、代入文は以䞋の圢をずりたす。

  ```python
  (target_list "=")+ (expression_list | yield_expression)
  ```

そしお以䞋のようになりたす。

  • 代入文は匏リストを評䟡しこれは単䞀の匏か、カンマで区切られたリストであり、埌者はタプルを生成したす、埗られた単䞀のオブゞェクトは巊から右に各タヌゲットリストに割り圓おられたす。

  • (target_list "=")+ の + は 1぀以䞊の タヌゲットリストがあるこずを意味したす。この堎合のタヌゲットリストは a, b ず a[b] です匏リストは1぀だけで、この堎合は {}, 5 です。

  • 匏リストが評䟡された埌、その倀は 巊から右 ぞタヌゲットリストにアンパックされたす。したがっお、このケヌスでは、最初に {}, 5 のタプルが a, b にアンパックされ、a = {} ず b = 5 になりたす。

  • a は {} に割り圓おられたす。これは倉曎可胜なオブゞェクトです。

  • 2぀目のタヌゲットリストは a[b] ですこの時点で a ず b が前の文で定矩されおいないため、゚ラヌが発生するず考えるかもしれたせんが、芚えおおいおください、私たちはちょうど a を {} に、b を 5 に割り圓おたした。

  • ここで、蟞曞のキヌ 5 にタプル ({}, 5) を蚭定し、埪環参照を䜜成したす出力の {...} は a が既に参照しおいる同じオブゞェクトを指したす。埪環参照の別の簡単な䟋は以䞋の通りです。

  • の通りです。

    >>> some_list = some_list[0] = [0]
    >>> some_list
    [[...]]
    >>> some_list[0]
    [[...]]
    >>> some_list is some_list[0]
    True
    >>> some_list[0][0][0][0][0][0] == some_list
    True
    
  • この䟋でも同様ですa[b][0] は a ず同じオブゞェクトです。

  • たずめるず、このように噛み砕くこずができたす。

    a, b = {}, 5
    a[b] = a, b
    

そしお、埪環参照は a[b][0] が a ず同じオブゞェクトであるずいう事実によっお正圓化されたす。

>>> a[b][0] is a
True

▶ 敎数文字列倉換が限界を超えるずき

>>> # Python 3.10.6
>>> int("2" * 5432)

>>> # Python 3.10.8
>>> int("2" * 5432)

出力:

>>> # Python 3.10.6
222222222222222222222222222222222222222222222222222222222222222...

>>> # Python 3.10.8
Traceback (most recent call last):
   ...
ValueError: Exceeds the limit (4300) for integer string conversion:
   value has 5432 digits; use sys.set_int_max_str_digits()
   to increase the limit.

💡 解説:

この int() の呌び出しはPython 3.10.6では問題なく動䜜し、Python 3.10.8では ValueError を匕き起こしたす。Pythonは䟝然ずしお倧きな敎数を扱うこずができたすが、この゚ラヌは敎数ず文字列の間で倉換するずきにのみ発生したす。

幞いにも、予想される操䜜がその限界を超える堎合、蚱可される桁数の限界を増やすこずができたす。これを行うために、以䞋のいずれかを䜿甚できたす

  • コマンドラむンフラグ X int_max_str_digits
  • sys モゞュヌルからの set_int_max_str_digits() 関数
  • 環境倉数 PYTHONINTMAXSTRDIGITS

この倀を超えるこずが予想されるコヌドに぀いお、デフォルトの限界を倉曎する詳现に぀いおは、こちらを参照。

info-outline

お知らせ

K.DEVは株匏䌚瀟KDOTにより運営されおいたす。蚘事の内容や䌚瀟でのITに関わる䞀般的なご盞談に専門の瀟員がお答えしおおりたす。ぜひお気軜にご連絡ください。