GitHubじゃ!Pythonじゃ!

GitHubからPython関係の優良リポジトリを探したかったのじゃー、でも英語は出来ないから日本語で読むのじゃー、英語社会世知辛いのじゃー

satwikkansal

wtfpython – 驚くべきPythonスニペットとあまり知られていない機能のコレクション

投稿日:

驚くべきPythonスニペットとあまり知られていない機能のコレクション。

どのようなf * ckのPython! 🐍

驚くべきスニペットとあまり知られていないPythonの機能の興味深いコレクションです。

Pythonは、きれいに設計された高水準のインタープリタベースのプログラミング言語であり、プログラマの快適さのための多くの機能を提供します。 しかし、時には、Pythonスニペットの結果は、一目ぼれに通常のユーザーにとっては明らかではないように見えるかもしれません。

ここでは、そのような難解で反抗的な例やPythonのあまり知られていない機能を収集し、フードの中で何が起こっているのかを議論しようとする楽しいプロジェクトです!

以下に示す例のいくつかは、実際の意味ではWTFではないかもしれませんが、あなたが気付かないかもしれないPythonの興味深い部分を明らかにします。 私はそれがプログラミング言語の内部を学ぶうまい方法だと思っています。あなたはそれらも面白いと思います!

あなたが経験豊富なPythonプログラマーであれば、最初の試みでそれらの大半を手に入れることができます。 あなたはすでにこれらの例のいくつかに精通しているかもしれませんが、私はあなたの甘い思い出を復活させて、これらの悪夢に噛まれているかもしれません 😅

あなたが帰ってきた読者なら、あなたはここでの新しい変更について知ることができます

だからここに行く…

目次

例の構造

すべての例は以下のように構成されています。

▶いくつかの豪華なタイトル*

タイトル末尾のアスタリスクは、この例題が最初のリリースには存在せず、最近追加されたことを示しています。

# Setting up the code.
# Preparation for the magic...

出力(Python版):

>>> triggering_statement
Probably unexpected output

(オプション):予期しない出力を説明する1行。

💡 説明:

  • 何が起こっているのか、なぜそれが起こっているのかを簡単に説明します。
    Setting up examples for clarification (if necessary)

    出力:

    >>> trigger # some example that makes it easy to unveil the magic
    # some justified output

注:すべてのサンプルはPython 3.5.2インタラクティブなインタプリタでテストされており、記述で明示的に指定されていない限り、すべてのPythonバージョンで動作するはずです。

使用法

私の意見では、これらの例を最大限に活用するうまい方法は、時系列的に例を読み、すべての例を読むだけです。

  • この例を設定するための初期コードを慎重に読んでください。 あなたが経験豊富なPythonプログラマーであれば、ほとんどの場合、次に何が起こるかをうまく予測できます。
  • 出力スニペットを読み、
    • 出力が期待どおりであるかどうか確認してください。
    • 出力の背後にある正確な理由が正しいかどうかを確認してください。
      • いいえ、深呼吸をして説明を読んでください(まだ理解していない場合は、叫んでください!)
      • はいの場合は、背中に穏やかに撫でてください。次の例にスキップしてください。

PS:コマンドラインでWTFpythonを読むこともできます。 pypiパッケージとnpmパッケージ(色付き書式をサポート)があります。

npmパッケージをインストールするにはwtfpython

$ npm install -g wtfpython

あるいは、pypiパッケージwtfpythonをインストールするには

$ pip install wtfpython -U

今度は、選択した$PAGERでこのコレクションを開くコマンドラインでwtfpythonを実行してください。


👀

セクション:あなたの脳に緊張!

▶ストリングは時々トリッキーになります*

1。

>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Notice that both the ids are same.
140420665652016

2。

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

>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False

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

3。

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

理にかなっているよね?

💡 説明:

  • このような動作は、新しいオブジェクトを毎回作成するのではなく、既存の不変オブジェクトを使用しようとするCPythonの最適化(文字列インターンリングと呼ばれる)によるものです。
  • 格納された後、多くの変数がメモリ内の同じ文字列オブジェクトをポイントすることがあり(メモリを節約します)。
  • 上記のスニペットでは、文字列は暗黙のうちにインターンされています。 暗黙のうちに文字列をインターンするときの決定は、実装に依存します。 文字列がインターンされるかどうかを推測するために使用できるいくつかの事実があります:
    • すべての長さ0と長さ1の文字列はインターンされます。
    • 文字''.join(['w', 't', 'f']はコンパイル時にインターンされます( 'wtf'はインターンされますが''.join(['w', 't', 'f']はインターンされません)
    • ASCII文字、数字またはアンダースコアで構成されていない文字列はインターンされません。 これはなぜ'wtf!' のために拘禁されていませんでした!

  • ab"wtf!"設定されている場合 同じ行にPythonインタプリタが新しいオブジェクトを作成し、同時に2番目の変数を参照します。 あなたが別々の行でそれを行うなら、それはすでにwtf!があることを “知っていません” wtf! "wtf!"は上記の事実に基づいて暗黙のうちに保留されていないため"wtf!" )。 これはコンパイラの最適化であり、特に対話型の環境に適用されます。
  • 定数フォールディングは、Pythonにおけるピープホール最適化の手法です。 これは、コンパイル時に'a'*20という表現が'aaaaaaaaaaaaaaaaaaaa'に置き換えられて'a'*20ことを意味し、ランタイム中に数クロックサイクルを短縮します。 しかし、コンパイル後に生成されたPythonバイトコードは.pycファイルに格納されるため、長さが20以上の文字列は穴の最適化のために破棄されます(なぜですか?式'a'*10**10結果として生成される.pycファイルのサイズを想像してください) 'a'*10**10

▶ハッシュ・ブラウニーの時間!

1。

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

出力:

>>> some_dict[5.5]
"Ruby"
>>> some_dict[5.0]
"Python"
>>> some_dict[5]
"Python"

“Python”は “JavaScript”の存在を破壊しましたか?

💡 説明

  • Python辞書は等価性をチェックし、ハッシュ値を比較して2つのキーが同じかどうかを判断します。
  • 同じ値を持つ不変オブジェクトは、常にPythonで同じハッシュを持ちます。
    >>> 5 == 5.0
    True
    >>> hash(5) == hash(5.0)
    True

    注:異なる値を持つオブジェクトは、同じハッシュ(ハッシュコリジョンと呼ばれます)を持つこともできます。

  • some_dict[5] = "Python"というステートメントが実行されると、Pythonは55.0を辞書some_dict同じキーとして認識するため、既存の値 “JavaScript”を “Python”で上書きします。
  • このStackOverflowの答えは、その背後にある根拠を美しく説明しています。

▶どこにでも戻りましょう!

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

出力:

>>> some_func()
'from_finally'

💡 説明:

  • “try … finally”文のtryスイートでreturnbreakまたはcontinue文が実行されるtryfinally節も途中で実行されます。
  • 関数の戻り値は、最後に実行されたreturn文によって決まります。 finally節は常に実行されるため、 finally節で実行されるreturn文は常にfinally実行される文になります。

▶深く、私たちはすべて同じです。 *

class WTF:
  pass

出力:

>>> WTF() == WTF() # two different instances can't be equal
False
>>> WTF() is WTF() # identities are also different
False
>>> hash(WTF()) == hash(WTF()) # hashes _should_ be different as well
True
>>> id(WTF()) == id(WTF())
True

💡 説明:

  • idが呼び出されると、PythonはWTFクラスオブジェクトを作成し、それをid関数に渡しました。 id関数はid (そのメモリ位置)をとり、オブジェクトをスローします。 オブジェクトは破壊されます。

  • これを2回続けて実行すると、Pythonは同じメモリ位置をこの2番目のオブジェクトにも割り当てます。 (CPythonでは) idはメモリの場所をオブジェクトIDとして使用するため、2つのオブジェクトのIDは同じです。

  • したがって、オブジェクトのIDは、オブジェクトの存続期間中にのみ一意です。 オブジェクトが破棄された後、またはオブジェクトが作成される前に、別のものが同じIDを持つことがあります。

  • しかし、なぜ演算子はFalse評価されたのですか? このスニペットを見てみましょう。

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

    出力:

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

    ご覧のとおり、オブジェクトが破壊される順序は、ここですべての違いをもたらしたものです。


▶何のため?

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

出力:

>>> some_dict # An indexed dict is created.
{0: 'w', 1: 't', 2: 'f'}

💡 説明:

  • for文はPython文法で次のように定義されています:

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

    exprlistは割り当て対象です。 これは、 {exprlist} = {next_value}相当するものが反復可能{exprlist} = {next_value} 各項目に対して実行されることを意味します。 これを説明する興味深い例:

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

    出力:

    0
    1
    2
    3
    

    ループが一回実行されると思いますか?

    💡 説明:

    • 代入文i = 10は、Pythonでループが動作するため、ループの反復には決して影響しません。 すべての反復の開始前に、イテレータ(この場合はrange(4)によって提供される次の項目が展開され、ターゲットリスト変数(この場合はi )が割り当てられます。
  • enumerate(some_string)関数は、各反復で新しい値i (カウンタが上がる)とsome_string文字を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

▶評価時間の差異

1。

array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]

出力:

>>> print(list(g))
[8]

2。

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

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

出力:

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

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

💡 説明

  • ジェネレータ式では、 in節は宣言時に評価されますが、条件節は実行時に評価されます。
  • したがって、実行時の前に、 arrayはリスト[2, 8, 22]に再割り当てされ、1,8、および15うち8のカウントだけが0より大きいため、ジェネレータは8ます。
  • 2番目の部分のg1g2出力の違いは、変数array_1array_2が再割り当てされた値のためです。
  • 最初のケースでは、 array_1は新しいオブジェクト[1,2,3,4,5]array_1され、 in節は宣言時に評価されるため、古いオブジェクト[1,2,3,4]参照します[1,2,3,4] (これは破壊されない)。
  • 2番目のケースでは、 array_2へのスライスの割り当ては、同じ古いオブジェクト[1,2,3,4][1,2,3,4,5]更新します。 したがって、 g2array_2両方には同じオブジェクト( [1,2,3,4,5]更新されています)への参照がまだあります。

▶それはそれじゃない!

以下は、インターネット上の非常に有名な例です。

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

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

>>> a = 257; b = 257
>>> a is b
True

💡 説明:

is==の違い

  • 演算子は、両方のオペランドが同じオブジェクトを参照しているかどうかをチェックします(つまり、オペランドの識別情報が一致するかどうかを確認します)。
  • ==演算子は、両方のオペランドの値を比較し、それらが等しいかどうかをチェックします。
  • 参照平等であり、 ==は値の平等である。 物事をクリアする例は、
    >>> [] == []
    True
    >>> [] is [] # These are two empty lists at two different memory locations.
    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,整数を作成したことを認識し、メモリに別のオブジェクトを作成します。

abは、同じ行で同じ値で初期化されたときに同じオブジェクトを参照します。

>>> 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ファイルで試してみると、ファイルは一度にコンパイルされるので、同じ動作は見られません。

▶最初の試行でXが勝つティックタック・トゥ!

# Let's initialize a row
row = [""]*3 #row i['', '', '']
# Let's make a board
board = [row]*3

出力:

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

我々は3 “X”を割り当てなかったか?

💡 説明:

row変数を初期化すると、この視覚化はメモリ内で何が起こるかを説明します

そして、 board row掛けて初期化すると、メモリ内で何が起こるのですか(各要素のboard[0]board[1] 、およびboard[2]は、 rowによって参照される同じリストへの参照です)

boardを生成するためにrow変数を使用しないことで、このシナリオを避けることができます。 今回の質問)。

>>> 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]

出力:

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

some_funcfuncsに追加する前にxの値がすべての反復で異なる場合でも、すべての関数は6を返します。

// OR

>>> 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]

💡 説明

  • ループの内部でループ変数を使用する関数を定義するとき、ループ関数のクロージャは、その値ではなく変数にバインドされます。 したがって、すべての関数は計算に変数に割り当てられた最新の値を使用します。

  • 目的の動作を得るには、ループ変数を関数の名前付き変数として渡すことができます。 これはなぜ機能するのですか? これは、関数のスコープ内で再び変数を定義するためです。

    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]

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

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

💡 説明

  • 1つのバイナリ演算子でis not isを使用する動作とは異なる動作を持ち、分離されてnot
  • 演算子のいずれかの側の変数が同じオブジェクトを指す場合はFalse評価されis not 、そうでない場合はTrue評価されます。

▶驚くべきカンマ

出力:

>>> 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("\\ C:\\")
\ C:\
>>> print(r"\ C:")
\ C:
>>> print(r"\ C:\")

    File "<stdin>", line 1
      print(r"\ C:\")
                     ^
SyntaxError: EOL while scanning string literal

💡 説明

  • 接頭辞rで示される生の文字列リテラルでは、バックスラッシュは特別な意味を持ちません。
    >>> print(repr(r"wt\"f"))
    'wt\\"f'
  • しかし、通訳者が実際に行っていることは、バックスラッシュの動作を単純に変更するだけなので、自分自身と次の文字を通します。 そのため、生の文字列の最後ではバックスラッシュが機能しません。

▶ノットではない!

x = True
y = False

出力:

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

💡 説明:

  • 演算子の優先順位は、式の評価方法に影響を及ぼし、 ==演算子はPythonの演算子よりも高い優先順位を持ちます。
  • したがってnot x == ynot (x == y)と等価で、 not (True == False)と等しくなり、最終的にTrue評価されTrue
  • しかし、 x == not yではx == not y(x == not) yと同じであると考えられるので、 SyntaxErrorを発生させx == not y
  • パーサーはnotトークンがnot in演算子の一部であることを期待してnot in (演算子ではnot in ==と同じ優先順位を持つため)。しかしinトークンの後ろにあるinトークンを見つけることができnotと、 SyntaxErrorが発生しSyntaxError

▶半引用符で囲まれた文字列

出力:

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # The following statements raise `SyntaxError`
>>> # print('''wtfpython')
>>> # print("""wtfpython")

💡 説明:

  • Pythonは暗黙の文字列リテラル連結をサポートしています
    >>> print("wtf" "python")
    wtfpython
    >>> print("wtf" "") # or "wtf"""
    wtf
    
  • ''''''はPythonの文字列区切り文字でもあり、Pythonインタプリタは現在遭遇している三重引用符付きの文字列リテラルをスキャンしている間に、区切り文字として終わりの三重引用符を期待していたためSyntaxErrorを引き起こします。

▶真夜中の時間は存在しない?

from datetime import datetime

midnight = datetime(2018, 1, 1, 0, 0)
midnight_time = midnight.time()

noon = datetime(2018, 1, 1, 12, 0)
noon_time = noon.time()

if midnight_time:
    print("Time at midnight is", midnight_time)

if noon_time:
    print("Time at noon is", noon_time)

出力:

('Time at noon is', datetime.time(12, 0))

真夜中の時間は印刷されません。

💡 説明:

Python 3.5より前では、 datetime.timeオブジェクトのブール値はUTCで深夜を表す場合はFalseと見なされました。 if obj:構文を使用して、 objがnullか、または「空」と同等かどうかをチェックすると、エラーが発生しやすくなります。


▶ブールの問題は何ですか?

1。

# A simple example to count the number of boolean and
# integers in an iterable of mixed data types.
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

出力:

>>> booleans_found_so_far
0
>>> integers_found_so_far
4

2。

another_dict = {}
another_dict[True] = "JavaScript"
another_dict[1] = "Ruby"
another_dict[1.0] = "Python"

出力:

>>> another_dict[True]
"Python"

3。

>>> some_bool = True
>>> "wtf"*some_bool
'wtf'
>>> some_bool = False
>>> "wtf"*some_bool
''

💡 説明:

  • ブール値はintサブクラスです

    >>> isinstance(True, int)
    True
    >>> isinstance(False, int)
    True
  • Trueの整数値は1Falseの整数値は0です。

    >>> True == 1 == 1.0 and False == 0 == 0.0
    True
  • その背後にある根拠については、このStackOverflowの回答を参照してください。


▶クラス属性とインスタンス属性

1。

class A:
    x = 1

class B(A):
    pass

class C(A):
    pass

Ouptut:

>>> 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
(3, 2, 3)
>>> a = A()
>>> a.x, A.x
(3, 3)
>>> a.x += 1
>>> a.x, A.x
(4, 3)

2。

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

💡 説明:

  • クラスインスタンス内のクラス変数と変数は、クラスオブジェクトの辞書として内部的に扱われます。 現在のクラスの辞書に変数名が見つからない場合は、その親クラスが検索されます。
  • +=演算子は、新しいオブジェクトを作成することなく、インプレースで可変オブジェクトを変更します。 したがって、あるインスタンスの属性を変更すると、他のインスタンスとクラス属性にも影響します。

▶収穫なし

some_iterable = ('a', 'b')

def some_func(val):
    return "something"

出力:

>>> [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']

💡 説明:


▶不変の突然変異を!

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) #This throws no error
>>> 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])

しかし、私はタプルが不変だと思った…

💡 説明:

  • https://docs.python.org/2/reference/datamodel.htmlから引用

    不変シーケンス不変シーケンスタイプのオブジェクトは、作成後は変更できません。 (オブジェクトに他のオブジェクトへの参照が含まれている場合、これらの他のオブジェクトは変更可能であり、変更される可能性がありますが、不変オブジェクトによって直接参照されるオブジェクトのコレクションは変更できません。

  • +=演算子はリストをその場で変更します。 アイテムの割り当ては機能しませんが、例外が発生すると、そのアイテムは既に変更されています。


▶外のスコープから消える変数

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

出力(Python 2.x):

>>> print(e)
# prints nothing

出力(Python 3.x):

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

💡 説明:

  • ソース: https : //docs.python.org/3/reference/compound_stmts.html#except

    例外がターゲットas使用さasて割り当てられると、except節の終わりにクリアされます。 これは、

    except E as N:
        foo

    翻訳された

    except E as N:
        try:
            foo
        finally:
            del N

    つまり、except句の後に参照できるようにするには、例外を別の名前に割り当てる必要があります。 例外がクリアされるのは、トレースバックが添付されてスタックフレームとの参照サイクルを形成し、次のガベージコレクションが発生するまでそのフレーム内のすべてのローカルを保持し続けるためです。

  • この節はPythonではスコープされていません。 例の中のすべてが同じスコープ内にあり、変数eexcept句の実行のために削除されています。 別の内部スコープを持つ関数の場合も同じです。 以下の例はこれを説明しています:

    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では、変数名eException()インスタンスに割り当てられます。したがって、印刷しようとすると何も印刷されません。

    出力(Python 2.x):

    >>> e
    Exception()
    >>> print e
    # Nothing is printed!

▶Trueが実際にFalseの場合

True = False
if True == False:
    print("I've lost faith in truth!")

出力:

I've lost faith in truth!

💡 説明:

  • 当初、Pythonはbool型を持たなかった(人々はfalseの場合は0、trueの場合は1のようなゼロ以外の値)。 その後、 TrueFalse 、およびbool型を追加しましたが、下位互換性のためにTrueおよびFalse定数を作成できませんでした。これらは単に組み込み変数でした。
  • Python 3は後方互換性がないため、最終的にこれを修正することができました。したがって、この例はPython 3.xでは動作しません!

▶1つの命令で塗りつぶしからなしまで…

some_list = [1, 2, 3]
some_dict = {
  "key_1": 1,
  "key_2": 2,
  "key_3": 3
}

some_list = some_list.append(4)
some_dict = some_dict.update({"key_4": 4})

出力:

>>> print(some_list)
None
>>> print(some_dict)
None

💡 説明

list.appenddict.updatelist.sortなどのシーケンス/マッピングオブジェクトの項目を変更するほとんどのメソッドは、インプレースオブジェクトを変更し、 Noneを返します。 この背後にある論理的根拠は、操作がインプレースで実行できる場合( ここで参照されます )、オブジェクトのコピーを作成しないようにしてパフォーマンスを向上させることです。


▶サブクラスの関係

出力:

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

サブクラスの関係は推移的であると予想されました。 (すなわち、 ABサブクラスであり、 BCサブクラスである場合、 ACサブクラスでなければならない

💡 説明:

  • サブクラスの関係は、Pythonでは推移的であるとは限りません。 誰でも独自の任意の__subclasscheck__をメタクラスで定義することができます。
  • issubclass(cls, Hashable)が呼び出されると、 clsやそれが継承したもので非__hash____hash__ “メソッドが__hash__れます。
  • objectはハッシュ可能ですが、 listはハッシュ可能ではないため、推移関係が解除されます。
  • より詳細な説明はここで見つけることができます

▶不思議なキータイプの変換*

class SomeClass(str):
    pass

some_dict = {'s':42}

出力:

>>> type(list(some_dict.keys())[0])
str
>>> s = SomeClass('s')
>>> some_dict[s] = 40
>>> some_dict # expected: Two different keys-value pairs
{'s': 40}
>>> type(list(some_dict.keys())[0])
str

💡 説明:

  • SomeClassstrクラスの__hash__メソッドを継承するため、オブジェクトと文字列s両方が同じ値にハッシュされstr

  • SomeClass("s") == "s"strクラスから__eq__メソッドも継承するため、 SomeClass("s") == "s"Trueと評価されstr

  • 両方のオブジェクトは同じ値にハッシュし、等しいので、辞書内の同じキーで表されます。

  • 望ましい振る舞いについては、 SomeClass __eq__メソッドを再定義することができSomeClass

    class SomeClass(str):
      def __eq__(self, other):
          return (
              type(self) is SomeClass
              and type(other) is SomeClass
              and super().__eq__(other)
          )
    
      # When we define a custom __eq__, Python stops automatically inheriting the
      # __hash__ method, so we need to define it as well
      __hash__ = str.__hash__
    
    some_dict = {'s':42}

    出力:

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

▶あなたがこれを推測できるかどうかを見てみましょうか?

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

出力:

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

💡 説明:

  • Python言語リファレンスによると、代入文は

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

    そして

    代入文は、式リストを評価します(これは、単一の式またはコンマ区切りのリストであり、後者はタプルを生成することに注意してください)。単一の結果のオブジェクトを左から右へそれぞれのターゲットリストに割り当てます。

  • + in (target_list "=")+は、 1つ以上のターゲットリストが存在する可能性があることを意味します。 この場合、ターゲットリストはa, ba[b] (式リストはちょうど1で、私たちの場合は{}, 5 )。

  • 式リストが評価された後、値は左から右へとターゲットリストに展開されます。 したがって、私たちの場合は、最初に{}, 5タプルをa, bに展開a, b a = {}b = 5ます。

  • aは現在、変更可能なオブジェクトである{}割り当てられています。

  • 2つ目のターゲットリストはa[b] (以前の文ではbb両方が定義されていないため、エラーが発生することがありますが、 {}b5代入しaことを思い出してください)。

  • ここでは、辞書内のキー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[b][0]と同じオブジェクトです)

  • So to sum it up, you can break the example down to

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

    And the circular reference can be justified by the fact that a[b][0] is the same object as a

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


Section: Appearances are deceptive!

▶ Skipping lines?

出力:

>>> value = 11
>>> valuе = 32
>>> value
11

ウット?

Note: The easiest way to reproduce this is to simply copy the statements from the above snippet and paste them into your file/shell.

💡 説明

Some non-Western characters look identical to letters in the English alphabet but are considered distinct by the interpreter.

>>> ord('е') # cyrillic 'e' (Ye)
1077
>>> ord('e') # latin 'e', as used in English and typed using standard keyboard
101
>>> 'е' == 'e'
False

>>> value = 42 # latin e
>>> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
>>> value
42

The built-in ord() function returns a character’s Unicode code point , and different code positions of Cyrillic ‘e’ and Latin ‘e’ justify the behavior of the above example.


▶ Teleportation *

import numpy as np

def energy_send(x):
    # Initializing a numpy array
    np.array([float(x)])

def energy_receive():
    # Return an empty numpy array
    return np.empty((), dtype=np.float).tolist()

出力:

>>> energy_send(123.456)
>>> energy_receive()
123.456

Where’s the Nobel Prize?

💡 説明:

  • Notice that the numpy array created in the energy_send function is not returned, so that memory space is free to reallocate.
  • numpy.empty() returns the next free memory slot without reinitializing it. This memory spot just happens to be the same one that was just freed (usually, but not always).

▶ Well, something is fishy…

def square(x):
    """
    A simple function to calculate the square of a number by addition.
    """
    sum_so_far = 0
    for counter in range(x):
        sum_so_far = sum_so_far + x
  return sum_so_far

Output (Python 2.x):

>>> square(10)
10

Shouldn’t that be 100?

Note: If you’re not able to reproduce this, try running the file mixed_tabs_and_spaces.py via the shell.

💡 説明

  • Don’t mix tabs and spaces! The character just preceding return is a “tab”, and the code is indented by multiple of “4 spaces” elsewhere in the example.

  • This is how Python handles tabs:

    First, tabs are replaced (from left to right) by one to eight spaces such that the total number of characters up to and including the replacement is a multiple of eight <…>

  • So the “tab” at the last line of square function is replaced with eight spaces, and it gets into the loop.

  • Python 3 is kind enough to throw an error for such cases automatically.

    Output (Python 3.x):

    TabError: inconsistent use of tabs and spaces in indentation


Section: Watch out for the landmines!

▶ Modifying a dictionary while iterating over it

x = {0: None}

for i in x:
    del x[i]
    x[i+1] = None
    print(i)

Output (Python 2.7- Python 3.5):

0
1
2
3
4
5
6
7

Yes, it runs for exactly eight times and stops.

💡 説明:

  • Iteration over a dictionary that you edit at the same time is not supported.
  • It runs eight times because that’s the point at which the dictionary resizes to hold more keys (we have eight deletion entries, so a resize is needed). This is actually an implementation detail.
  • How deleted keys are handled and when the resize occurs might be different for different Python implementations.
  • For more information, you may refer to this StackOverflow thread explaining a similar example in detail.

▶ Stubborn del operator *

class SomeClass:
    def __del__(self):
        print("Deleted!")

Output: 1.

>>> x = SomeClass()
>>> y = x
>>> del x # this should print "Deleted!"
>>> del y
Deleted!

Phew, deleted at last. You might have guessed what saved from __del__ being called in our first attempt to delete x . Let’s add more twist to the example.

2。

>>> x = SomeClass()
>>> y = x
>>> del x
>>> y # check if y exists
<__main__.SomeClass instance at 0x7f98a1a67fc8>
>>> del y # Like previously, this should print "Deleted!"
>>> globals() # oh, it didn't. Let's check all our global variables and confirm
Deleted!
{'__builtins__': <module '__builtin__' (built-in)>, 'SomeClass': <class __main__.SomeClass at 0x7f98a1a5f668>, '__package__': None, '__name__': '__main__', '__doc__': None}

Okay, now it’s deleted 😕

💡 説明:

  • del x doesn’t directly call x.__del__() .
  • Whenever del x is encountered, Python decrements the reference count for x by one, and x.__del__() when x’s reference count reaches zero.
  • In the second output snippet, y.__del__() was not called because the previous statement ( >>> y ) in the interactive interpreter created another reference to the same object, thus preventing the reference count to reach zero when del y was encountered.
  • Calling globals caused the existing reference to be destroyed and hence we can see “Deleted!” being printed (finally!).

▶ Deleting a list item while iterating

list_1 = [1, 2, 3, 4]
list_2 = [1, 2, 3, 4]
list_3 = [1, 2, 3, 4]
list_4 = [1, 2, 3, 4]

for idx, item in enumerate(list_1):
    del item

for idx, item in enumerate(list_2):
    list_2.remove(item)

for idx, item in enumerate(list_3[:]):
    list_3.remove(item)

for idx, item in enumerate(list_4):
    list_4.pop(idx)

出力:

>>> list_1
[1, 2, 3, 4]
>>> list_2
[2, 4]
>>> list_3
[]
>>> list_4
[2, 4]

Can you guess why the output is [2, 4] ?

💡 説明:

  • It’s never a good idea to change the object you’re iterating over. The correct way to do so is to iterate over a copy of the object instead, and list_3[:] does just that.

    >>> some_list = [1, 2, 3, 4]
    >>> id(some_list)
    139798789457608
    >>> id(some_list[:]) # Notice that python creates new object for sliced list.
    139798779601192

Difference between del , remove , and pop :

  • del var_name just removes the binding of the var_name from the local or global namespace (That’s why the list_1 is unaffected).
  • remove removes the first matching value, not a specific index, raises ValueError if the value is not found.
  • pop removes the element at a specific index and returns it, raises IndexError if an invalid index is specified.

Why the output is [2, 4] ?

  • The list iteration is done index by index, and when we remove 1 from list_2 or list_4 , the contents of the lists are now [2, 3, 4] . The remaining elements are shifted down, ie, 2 is at index 0, and 3 is at index 1. Since the next iteration is going to look at index 1 (which is the 3 ), the 2 gets skipped entirely. A similar thing will happen with every alternate element in the list sequence.
  • Refer to this StackOverflow thread explaining the example
  • See also this nice StackOverflow thread for a similar example related to dictionaries in Python.

▶ Loop variables leaking out!

1。

for x in range(7):
    if x == 6:
        print(x, ': for x inside loop')
print(x, ': x in global')

出力:

6 : for x inside loop
6 : x in global

But x was never defined outside the scope of for loop…

2。

# This time let's initialize x first
x = -1
for x in range(7):
    if x == 6:
        print(x, ': for x inside loop')
print(x, ': x in global')

出力:

6 : for x inside loop
6 : x in global

3。

x = 1
print([x for x in range(5)])
print(x, ': x in global')

Output (on Python 2.x):

[0, 1, 2, 3, 4]
(4, ': x in global')

Output (on Python 3.x):

[0, 1, 2, 3, 4]
1 : x in global

💡 説明:

  • In Python, for-loops use the scope they exist in and leave their defined loop-variable behind. This also applies if we explicitly defined the for-loop variable in the global namespace before. In this case, it will rebind the existing variable.

  • The differences in the output of Python 2.x and Python 3.x interpreters for list comprehension example can be explained by following change documented in What’s New In Python 3.0 documentation:

    “List comprehensions no longer support the syntactic form [... for var in item1, item2, ...] . Use [... for var in (item1, item2, ...)] instead. Also, note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a list() constructor, and in particular the loop control variables are no longer leaked into the surrounding scope.”


▶ Beware of default mutable arguments!

def some_func(default_arg=[]):
    default_arg.append("some_string")
    return default_arg

出力:

>>> some_func()
['some_string']
>>> some_func()
['some_string', 'some_string']
>>> some_func([])
['some_string']
>>> some_func()
['some_string', 'some_string', 'some_string']

💡 説明:

  • The default mutable arguments of functions in Python aren’t really initialized every time you call the function. Instead, the recently assigned value to them is used as the default value. When we explicitly passed [] to some_func as the argument, the default value of the default_arg variable was not used, so the function returned as expected.

    def some_func(default_arg=[]):
        default_arg.append("some_string")
        return default_arg

    出力:

    >>> some_func.__defaults__ #This will show the default argument values for the function
    ([],)
    >>> some_func()
    >>> some_func.__defaults__
    (['some_string'],)
    >>> some_func()
    >>> some_func.__defaults__
    (['some_string', 'some_string'],)
    >>> some_func([])
    >>> some_func.__defaults__
    (['some_string', 'some_string'],)
  • A common practice to avoid bugs due to mutable arguments is to assign None as the default value and later check if any value is passed to the function corresponding to that argument. 例:

    def some_func(default_arg=None):
        if not default_arg:
            default_arg = []
        default_arg.append("some_string")
        return default_arg

▶ Catching the Exceptions

some_list = [1, 2, 3]
try:
    # This should raise an ``IndexError``
    print(some_list[4])
except IndexError, ValueError:
    print("Caught!")

try:
    # This should raise a ``ValueError``
    some_list.remove(4)
except IndexError, ValueError:
    print("Caught again!")

Output (Python 2.x):

Caught!

ValueError: list.remove(x): x not in list

Output (Python 3.x):

  File "<input>", line 3
    except IndexError, ValueError:
                     ^
SyntaxError: invalid syntax

💡 説明

  • To add multiple Exceptions to the except clause, you need to pass them as parenthesized tuple as the first argument. The second argument is an optional name, which when supplied will bind the Exception instance that has been raised. Example,

    some_list = [1, 2, 3]
    try:
       # This should raise a ``ValueError``
       some_list.remove(4)
    except (IndexError, ValueError), e:
       print("Caught again!")
       print(e)

    Output (Python 2.x):

    Caught again!
    list.remove(x): x not in list
    

    Output (Python 3.x):

      File "<input>", line 4
        except (IndexError, ValueError), e:
                                         ^
    IndentationError: unindent does not match any outer indentation level
  • Separating the exception from the variable with a comma is deprecated and does not work in Python 3; the correct way is to use as . Example,

    some_list = [1, 2, 3]
    try:
        some_list.remove(4)
    
    except (IndexError, ValueError) as e:
        print("Caught again!")
        print(e)

    出力:

    Caught again!
    list.remove(x): x not in list
    

▶ Same operands, different story!

1。

a = [1, 2, 3, 4]
b = a
a = a + [5, 6, 7, 8]

出力:

>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4]

2。

a = [1, 2, 3, 4]
b = a
a += [5, 6, 7, 8]

出力:

>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4, 5, 6, 7, 8]

💡 説明:

  • a += b doesn’t always behave the same way as a = a + b . Classes may implement the op= operators differently, and lists do this.

  • The expression a = a + [5,6,7,8] generates a new list and sets a ‘s reference to that new list, leaving b unchanged.

  • The expression a += [5,6,7,8] is actually mapped to an “extend” function that operates on the list such that a and b still point to the same list that has been modified in-place.


▶ The out of scope variable

a = 1
def some_func():
    return a

def another_func():
    a += 1
    return a

出力:

>>> some_func()
1
>>> another_func()
UnboundLocalError: local variable 'a' referenced before assignment

💡 説明:

  • When you make an assignment to a variable in scope, it becomes local to that scope. So a becomes local to the scope of another_func , but it has not been initialized previously in the same scope which throws an error.

  • Read this short but an awesome guide to learn more about how namespaces and scope resolution works in Python.

  • To modify the outer scope variable a in another_func , use global keyword.

    def another_func()
        global a
        a += 1
        return a

    出力:

    >>> another_func()
    2

▶ Be careful with chained operations

>>> (False == False) in [False] # makes sense
False
>>> False == (False in [False]) # makes sense
False
>>> False == False in [False] # now what?
True

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

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

💡 説明:

As per https://docs.python.org/2/reference/expressions.html#not-in

Formally, if a, b, c, …, y, z are expressions and op1, op2, …, opN are comparison operators, then a op1 b op2 c … y opN z is equivalent to a op1 b and b op2 c and … y opN z, except that each expression is evaluated at most once.

While such behavior might seem silly to you in the above examples, it’s fantastic with stuff like a == b == c and 0 <= x <= 100 .

  • False is False is False is equivalent to (False is False) and (False is False)
  • True is False == False is equivalent to True is False and False == False and since the first part of the statement ( True is False ) evaluates to False , the overall expression evaluates to False .
  • 1 > 0 < 1 is equivalent to 1 > 0 and 0 < 1 which evaluates to True .
  • The expression (1 > 0) < 1 is equivalent to True < 1 and
    >>> int(True)
    1
    >>> True + 1 #not relevant for this example, but just for fun
    2

    So, 1 < 1 evaluates to False


▶ Name resolution ignoring class scope

1。

x = 5
class SomeClass:
    x = 17
    y = (x for i in range(10))

出力:

>>> list(SomeClass.y)[0]
5

2。

x = 5
class SomeClass:
    x = 17
    y = [x for i in range(10)]

Output (Python 2.x):

>>> SomeClass.y[0]
17

Output (Python 3.x):

>>> SomeClass.y[0]
5

💡 説明

  • Scopes nested inside class definition ignore names bound at the class level.
  • A generator expression has its own scope.
  • Starting from Python 3.X, list comprehensions also have their own scope.

▶ Needle in a Haystack

1。

x, y = (0, 1) if True else None, None

出力:

>>> x, y  # expected (0, 1)
((0, 1), None)

Almost every Python programmer has faced a similar situation.

2。

t = ('one', 'two')
for i in t:
    print(i)

t = ('one')
for i in t:
    print(i)

t = ()
print(t)

出力:

one
two
o
n
e
tuple()

💡 説明:

  • For 1, the correct statement for expected behavior is x, y = (0, 1) if True else (None, None) .
  • For 2, the correct statement for expected behavior is t = ('one',) or t = 'one', (missing comma) otherwise the interpreter considers t to be a str and iterates over it character by character.
  • () is a special token and denotes empty tuple .


Section: The Hidden treasures!

This section contains few of the lesser-known interesting things about Python that most beginners like me are unaware of (well, not anymore).

▶ Okay Python, Can you make me fly? *

Well, here you go

import antigravity

Output: Sshh.. It’s a super secret.

💡 説明:

  • antigravity module is one of the few easter eggs released by Python developers.
  • import antigravity opens up a web browser pointing to the classic XKCD comic about Python.
  • Well, there’s more to it. There’s another easter egg inside the easter egg . If look at the code , there’s a function defined that purports to implement the XKCD’s geohashing algorithm .

goto , but why? *

from goto import goto, label
for i in range(9):
    for j in range(9):
        for k in range(9):
            print("I'm trapped, please rescue!")
            if k == 2:
                goto .breakout # breaking out from a deeply nested loop
label .breakout
print("Freedom!")

Output (Python 2.3):

I'm trapped, please rescue!
I'm trapped, please rescue!
Freedom!

💡 説明:

  • A working version of goto in Python was announced as an April Fool’s joke on 1st April 2004.
  • Current versions of Python do not have this module.
  • Although it works, but please don’t use it. Here’s the reason to why goto is not present in Python.

▶ Brace yourself! *

If you are one of the people who doesn’t like using whitespace in Python to denote scopes, you can use the C-style {} by importing,

from __future__ import braces

出力:

  File "some_file.py", line 1
    from __future__ import braces
SyntaxError: not a chance

Braces? とんでもない! If you think that’s disappointing, use Java.

💡 説明:

  • The __future__ module is normally used to provide features from future versions of Python. The “future” here is however ironic.
  • This is an easter egg concerned with the community’s feelings on this issue.

▶ Let’s meet Friendly Language Uncle For Life *

Output (Python 3.x)

>>> from __future__ import barry_as_FLUFL
>>> "Ruby" != "Python" # there's no doubt about it
  File "some_file.py", line 1
    "Ruby" != "Python"
              ^
SyntaxError: invalid syntax

>>> "Ruby" <> "Python"
True

そこに行く。

💡 説明:

  • This is relevant to PEP-401 released on April 1, 2009 (now you know, what it means).
  • Quoting from the PEP-401

    Recognized that the != inequality operator in Python 3.0 was a horrible, finger pain inducing mistake, the FLUFL reinstates the <> diamond operator as the sole spelling.

  • There were more things that Uncle Barry had to share in the PEP; you can read them here .

▶ Even Python understands that love is complicated *

import this

Wait, what’s this ? this is love

出力:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

It’s the Zen of Python!

>>> love = this
>>> this is love
True
>>> love is True
False
>>> love is False
False
>>> love is not True or False
True
>>> love is not True or False; love is love  # Love is complicated
True

💡 説明:

  • this module in Python is an easter egg for The Zen Of Python ( PEP 20 ).
  • And if you think that’s already interesting enough, check out the implementation of this.py . Interestingly, the code for the Zen violates itself (and that’s probably the only place where this happens).
  • Regarding the statement love is not True or False; love is love , ironic but it’s self-explanatory.

▶ Yes, it exists!

The else clause for loops. One typical example might be:

  def does_exists_num(l, to_find):
      for num in l:
          if num == to_find:
              print("Exists!")
              break
      else:
          print("Does not exist")

出力:

>>> some_list = [1, 2, 3, 4, 5]
>>> does_exists_num(some_list, 4)
Exists!
>>> does_exists_num(some_list, -1)
Does not exist

The else clause in exception handling. An example,

try:
    pass
except:
    print("Exception occurred!!!")
else:
    print("Try block executed successfully...")

出力:

Try block executed successfully...

💡 説明:

  • The else clause after a loop is executed only when there’s no explicit break after all the iterations.
  • else clause after try block is also called “completion clause” as reaching the else clause in a try statement means that the try block actually completed successfully.

▶ Inpinity *

The spelling is intended. Please, don’t submit a patch for this.

Output (Python 3.x):

>>> infinity = float('infinity')
>>> hash(infinity)
314159
>>> hash(float('-inf'))
-314159

💡 説明:

  • Hash of infinity is 10⁵ x π.
  • Interestingly, the hash of float('-inf') is “-10⁵ x π” in Python 3, whereas “-10⁵ xe” in Python 2.

▶ Mangling time! *

class Yo(object):
    def __init__(self):
        self.__honey = True
        self.bitch = True

出力:

>>> Yo().bitch
True
>>> Yo().__honey
AttributeError: 'Yo' object has no attribute '__honey'
>>> Yo()._Yo__honey
True

Why did Yo()._Yo__honey work? Only Indian readers would understand.

💡 説明:

  • Name Mangling is used to avoid naming collisions between different namespaces.
  • In Python, the interpreter modifies (mangles) the class member names starting with __ (double underscore) and not ending with more than one trailing underscore by adding _NameOfTheClass in front.
  • So, to access __honey attribute, we are required to append _Yo to the front which would prevent conflicts with the same name attribute defined in any other class.


Section: Miscellaneous

+= is faster

# using "+", three strings:
>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.25748300552368164
# using "+=", three strings:
>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.012188911437988281

💡 説明:

  • += is faster than + for concatenating more than two strings because the first string (example, s1 for s1 += s2 + s3 ) is not destroyed while calculating the complete string.

▶ Let’s make a giant string!

def add_string_with_plus(iters):
    s = ""
    for i in range(iters):
        s += "xyz"
    assert len(s) == 3*iters

def add_bytes_with_plus(iters):
    s = b""
    for i in range(iters):
        s += b"xyz"
    assert len(s) == 3*iters

def add_string_with_format(iters):
    fs = "{}"*iters
    s = fs.format(*(["xyz"]*iters))
    assert len(s) == 3*iters

def add_string_with_join(iters):
    l = []
    for i in range(iters):
        l.append("xyz")
    s = "".join(l)
    assert len(s) == 3*iters

def convert_list_to_string(l, iters):
    s = "".join(l)
    assert len(s) == 3*iters

出力:

>>> timeit(add_string_with_plus(10000))
1000 loops, best of 3: 972 µs per loop
>>> timeit(add_bytes_with_plus(10000))
1000 loops, best of 3: 815 µs per loop
>>> timeit(add_string_with_format(10000))
1000 loops, best of 3: 508 µs per loop
>>> timeit(add_string_with_join(10000))
1000 loops, best of 3: 878 µs per loop
>>> l = ["xyz"]*10000
>>> timeit(convert_list_to_string(l, 10000))
10000 loops, best of 3: 80 µs per loop

Let’s increase the number of iterations by a factor of 10.

>>> timeit(add_string_with_plus(100000)) # Linear increase in execution time
100 loops, best of 3: 9.75 ms per loop
>>> timeit(add_bytes_with_plus(100000)) # Quadratic increase
1000 loops, best of 3: 974 ms per loop
>>> timeit(add_string_with_format(100000)) # Linear increase
100 loops, best of 3: 5.25 ms per loop
>>> timeit(add_string_with_join(100000)) # Linear increase
100 loops, best of 3: 9.85 ms per loop
>>> l = ["xyz"]*100000
>>> timeit(convert_list_to_string(l, 100000)) # Linear increase
1000 loops, best of 3: 723 µs per loop

💡 説明

  • You can read more about timeit from here. It is generally used to measure the execution time of snippets.
  • Don’t use + for generating long strings — In Python, str is immutable, so the left and right strings have to be copied into the new string for every pair of concatenations. If you concatenate four strings of length 10, you’ll be copying (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90 characters instead of just 40 characters. Things get quadratically worse as the number and size of the string increases (justified with the execution times of add_bytes_with_plus function)
  • Therefore, it’s advised to use .format. or % syntax (however, they are slightly slower than + for short strings).
  • Or better, if already you’ve contents available in the form of an iterable object, then use ''.join(iterable_object) which is much faster.
  • add_string_with_plus didn’t show a quadratic increase in execution time unlike add_bytes_with_plus because of the += optimizations discussed in the previous example. Had the statement been s = s + "x" + "y" + "z" instead of s += "xyz" , the increase would have been quadratic.
    def add_string_with_plus(iters):
        s = ""
        for i in range(iters):
            s = s + "x" + "y" + "z"
        assert len(s) == 3*iters
    
    >>> timeit(add_string_with_plus(10000))
    100 loops, best of 3: 9.87 ms per loop
    >>> timeit(add_string_with_plus(100000)) # Quadratic increase in execution time
    1 loops, best of 3: 1.09 s per loop

▶ Explicit typecast of strings

a = float('inf')
b = float('nan')
c = float('-iNf')  #These strings are case-insensitive
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

💡 説明:

'inf' and 'nan' are special strings (case-insensitive), which when explicitly typecasted to float type, are used to represent mathematical “infinity” and “not a number” respectively.


▶ Minor Ones

  • join() is a string operation instead of list operation. (sort of counter-intuitive at first usage)

    💡 Explanation: If join() is a method on a string then it can operate on any iterable (list, tuple, iterators). If it were a method on a list, it’d have to be implemented separately by every type. Also, it doesn’t make much sense to put a string-specific method on a generic list object API.

  • Few weird looking but semantically correct statements:

    • [] = () is a semantically correct statement (unpacking an empty tuple into an empty list )
    • 'a'[0][0][0][0][0] is also a semantically correct statement as strings are sequences (iterables supporting element access using integer indices) in Python.
    • 3 --0-- 5 == 8 and --5 == 5 are both semantically correct statements and evaluate to True .
  • Given that a is a number, ++a and --a are both valid Python statements but don’t behave the same way as compared with similar statements in languages like C, C++ or Java.

    >>> a = 5
    >>> a
    5
    >>> ++a
    5
    >>> --a
    5

    💡 説明:

    • There is no ++ operator in Python grammar. It is actually two + operators.
    • ++a parses as +(+a) which translates to a . Similarly, the output of the statement --a can be justified.
    • This StackOverflow thread discusses the rationale behind the absence of increment and decrement operators in Python.
  • Python uses 2 bytes for local variable storage in functions. In theory, this means that only 65536 variables can be defined in a function. However, python has a handy solution built in that can be used to store more than 2^16 variable names. The following code demonstrates what happens in the stack when more than 65536 local variables are defined (Warning: This code prints around 2^18 lines of text, so be prepared!):

    import dis
    exec("""
    def f():
        """ + """
        """.join(["X"+str(x)+"=" + str(x) for x in range(65539)]))
    
    f()
    
    print(dis.dis(f))
  • Multiple Python threads won’t run your Python code concurrently (yes you heard it right!). It may seem intuitive to spawn several threads and let them execute your Python code concurrently, but, because of the Global Interpreter Lock in Python, all you’re doing is making your threads execute on the same core turn by turn. Python threads are good for IO-bound tasks, but to achieve actual parallelization in Python for CPU-bound tasks, you might want to use the Python multiprocessing module.

  • List slicing with out of the bounds indices throws no errors

    >>> some_list = [1, 2, 3, 4, 5]
    >>> some_list[111:]
    []
  • int('١٢٣٤٥٦٧٨٩') returns 123456789 in Python 3. In Python, Decimal characters include digit characters, and all characters that can be used to form decimal-radix numbers, eg U+0660, ARABIC-INDIC DIGIT ZERO. Here’s an interesting story related to this behavior of Python.

  • 'abc'.count('') == 4 . Here’s an approximate implementation of count method, which would make the things more clear

    def count(s, sub):
        result = 0
        for i in range(len(s) + 1 - len(sub)):
            result += (s[i:i + len(sub)] == sub)
        return result

    The behavior is due to the matching of empty substring( '' ) with slices of length 0 in the original string.


貢献する

All patches are Welcome! Please see CONTRIBUTING.md for further details.

For discussions, you can either create a new issue or ping on the Gitter channel

謝辞

The idea and design for this collection were initially inspired by Denys Dovhan’s awesome project wtfjs . The overwhelming support by the community gave it the shape it is in right now.

Some nice Links!

🎓 ライセンス

© Satwik Kansal

助けて

If you have any wtfs, ideas or suggestions, please share.

Want to share wtfpython with friends?

You can use these quick links for Twitter and Linkedin.

Twitter | リンク

Need a pdf version?

I’ve received a few requests for the pdf version of wtfpython. You can add your details here to get the pdf as soon as it is finished.







-satwikkansal
-, , , , ,

執筆者: