驚くべきスニペットを通じて Python を探索し、理解します。
翻訳: 中国語 |ベトナム語 Tiếng Việt |スペイン語 スペイン語 |韓国語 한국어ロシア語ドイツ語 |翻訳を追加
その他のモード: インタラクティブな Web サイト |インタラクティブノートブック
Python は、美しく設計されたインタプリタベースの高レベルプログラミング言語であり、プログラマーの快適性を高める多くの機能を提供します。ただし、Python スニペットの結果が一見しただけでは明らかではない場合があります。
これは、Python の直感に反するスニペットやあまり知られていない機能の内部で何が起こっているのかを説明しようとする楽しいプロジェクトです。
以下に示す例の一部は、本当の意味では WTF ではないかもしれませんが、皆さんが気づいていないかもしれない Python の興味深い部分をいくつか明らかにします。私は、これはプログラミング言語の内部を学ぶのに良い方法だと思います。そして、あなたもきっと面白いと思うでしょう。
経験豊富な Python プログラマーであれば、最初の試行でほとんどのコードを正しく理解するのは難しいことだと考えることができます。あなたもすでに経験したことがあるかもしれません。私ならあなたの懐かしい思い出を呼び起こすことができるかもしれません。 ?
PS: 何度も読んでいる場合は、ここで新しい変更について学ぶことができます (アスタリスクの付いた例は、最新のメジャー リビジョンで追加されたものです)。
それでは、行きましょう...
is
演算子を使用しない方法is not ...
ではありませんis (not ...)
del
デルオペgoto
ですが、なぜですか?+=
方が速いdict
検索の速度低下 *dict
*すべての例は次のように構成されています。
▶ 派手なタイトル
# Set up the code. # Preparation for the magic...出力 (Python バージョン):
> >> triggering_statement Some unexpected output(オプション): 予期しない出力を説明する 1 行。
説明:
- 何が起こっているのか、なぜそれが起こっているのかについての簡単な説明。
# Set up code # More examples for further clarification (if necessary)出力 (Python バージョン):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
注:すべての例は Python 3.5.2 対話型インタープリターでテストされており、出力前に明示的に指定しない限り、すべての Python バージョンで動作するはずです。
私の意見では、これらの例を最大限に活用する良い方法は、すべての例を順番に読むことです。
何らかの理由で、Python 3.8 の「Walrus」演算子 ( :=
) が非常に人気になっています。チェックしてみましょう。
1.
# Python version 3.8+
> >> a = "wtf_walrus"
> >> a
'wtf_walrus'
> >> a := "wtf_walrus"
File "<stdin>" , line 1
a := "wtf_walrus"
^
SyntaxError : invalid syntax
>> > ( a := "wtf_walrus" ) # This works though
'wtf_walrus'
> >> a
'wtf_walrus'
2.
# Python version 3.8+
> >> a = 6 , 9
> >> a
( 6 , 9 )
> >> ( a := 6 , 9 )
( 6 , 9 )
> >> a
6
> >> a , b = 6 , 9 # Typical unpacking
> >> a , b
( 6 , 9 )
>> > ( a , b = 16 , 19 ) # Oops
File "<stdin>" , line 1
( a , b = 16 , 19 )
^
SyntaxError : invalid syntax
>> > ( a , b := 16 , 19 ) # This prints out a weird 3-tuple
( 6 , 16 , 19 )
>> > a # a is still unchanged?
6
>> > b
16
セイウチオペレーターの簡単な復習
Walrus 演算子 ( :=
) は Python 3.8 で導入されました。式内の変数に値を割り当てたい場合に便利です。
def some_func ():
# Assume some expensive computation here
# time.sleep(1000)
return 5
# So instead of,
if some_func ():
print ( some_func ()) # Which is bad practice since computation is happening twice
# or
a = some_func ()
if a :
print ( a )
# Now you can concisely write
if a := some_func ():
print ( a )
出力 (> 3.8):
5
5
5
これによりコードが 1 行節約され、 some_func
2 回呼び出すことが暗黙的に防止されました。
括弧なしの「代入式」(walrus 演算子の使用) はトップレベルで制限されているため、最初のスニペットのa := "wtf_walrus"
ステートメントでSyntaxError
発生します。括弧で囲むと期待どおりに機能し、 がa
られました。
通常どおり、 =
演算子を含む式を括弧で囲むことはできません。したがって、 (a, b = 6, 9)
の構文エラーになります。
Walrus 演算子の構文はNAME:= expr
という形式です。 NAME
は有効な識別子、 expr
は有効な式です。したがって、反復可能なパッキングとアンパッキングはサポートされていません。つまり、
(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 # Both reference same memory location
True
同様に、 (a, b := 16, 19)
3 タプルにすぎない(a, (b := 16), 19)
と同等です。
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
3.
> >> a , b = "wtf!" , "wtf!"
> >> a is b # All versions except 3.7.x
True
> >> a = "wtf!" ; b = "wtf!"
> >> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print ( a is b )
# prints True when the module is invoked!
4.
出力 (< Python3.7)
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
当然ですよね?
'wtf'
はインターンされますが、 ''.join(['w', 't', 'f'])
はインターンされません)'wtf!'
という理由が説明されます。の理由でインターンされなかった!
。このルールの CPython 実装はここにあります。 a
とb
"wtf!"
に設定されている場合同じ行で、Python インタープリターは新しいオブジェクトを作成し、同時に 2 番目の変数を参照します。別々の行で実行すると、すでに"wtf!"
があることを「認識」しません。オブジェクトとして (上記の事実に従って、 "wtf!"
は暗黙的にインターンされないため)。それはコンパイル時の最適化です。この最適化は、CPython の 3.7.x バージョンには適用されません (詳細については、この問題を確認してください)。a, b = "wtf!", "wtf!"
は単一のステートメントですが、 a = "wtf!"; b = "wtf!"
1 行に 2 つのステートメントが含まれています。これはa = "wtf!"; b = "wtf!"
、そしてsome_file.py
で呼び出されたときにそれらが同じである理由も説明します'a'*20
が'aaaaaaaaaaaaaaaaaaaa'
に置き換えられることを意味します。定数フォールディングは、長さが 21 未満の文字列に対してのみ発生します (なぜですか? 式'a'*10**10
の結果として生成される.pyc
ファイルのサイズを想像してください)。同じものの実装ソースは次のとおりです。 > >> ( 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
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 ただし、各式は最大 1 回評価される点が異なります。
上記の例では、このような動作はばかばかしいように思えるかもしれませんが、 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
True
と評価される(1 > 0) and (0 < 1)
と同等です。(1 > 0) < 1
True < 1
と同等であり、 > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
はFalse
と評価されます。is
演算子を使用しない方法以下は、インターネット上に存在する非常に有名な例です。
1.
> >> a = 256
> >> b = 256
> >> a is b
True
> >> a = 257
> >> b = 257
> >> a is b
False
2.
> >> a = []
> >> b = []
> >> a is b
False
> >> a = tuple ()
> >> b = tuple ()
> >> a is b
True
3.出力
> >> 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 () # These are two empty objects 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,
メモリ内に別のオブジェクトを作成し続けます。
同様の最適化は、空のタプルなどの他の不変オブジェクトにも適用されます。リストは変更可能であるため、 [] 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 などによって変更される可能性があります)。問題の原因となる正確な実装の変更はまだ解明中です。この問題の更新情報を確認してください。
1.
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 ] # "Python" destroyed the existence of "Ruby"?
"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]
)、次にキーを浮動小数点数ではなく整数5
取得するように設定 ( some_dict[5]
) することです。 5.0
ですが、これが必要になることはまれです。
Python は5.0
含む辞書から5
どのように見つけたのでしょうか? Python は、ハッシュ関数を使用してすべての項目をスキャンする必要がなく、これを一定時間で実行します。 Python が dict 内のキーfoo
検索するとき、最初にhash(foo)
を計算します (定数時間で実行されます)。 Python では、等しいと比較されるオブジェクトも同じハッシュ値を持つ必要があるため (ドキュメントはこちら)、 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 () # 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 を持つことができます。
しかし、なぜ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 # If a == b
True
> >> dictionary == another_ordered_dict # and b == c
True
> >> ordered_dict == another_ordered_dict # then why isn't c == a ??
False
# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
TypeError : unhashable type : 'dict'
# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
> >> 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 }) # changing the order
2
ここで何が起こっているのでしょうか?
自動詞の等価性がdictionary
、 ordered_dict
、 another_ordered_dict
の間で成立しなかった理由は、 OrderedDict
クラスでの__eq__
メソッドの実装方法にあります。ドキュメントから
OrderedDict オブジェクト間の等価性テストは順序に依存し、
list(od1.items())==list(od2.items())
として実装されます。OrderedDict
オブジェクトと他の Mapping オブジェクト間の等価性テストは、通常の辞書と同様に順序に依存しません。
この動作の同等性の理由は、通常のディクショナリが使用される場所であればどこでもOrderedDict
オブジェクトを直接置換できるためです。
では、順序を変更すると、生成されるset
オブジェクトの長さに影響が出るのはなぜでしょうか?答えは、自動詞的等価性のみが欠如していることです。セットは一意の要素の「順序なし」コレクションであるため、要素が挿入される順序は重要ではありません。しかし、この場合、それは重要です。少し分解してみましょう。
> >> some_set = set ()
> >> some_set . add ( dictionary ) # these are the mapping objects from the snippets above
> >> 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
したがって、 False
はordered_dict == another_ordered_dict
another_set
another_ordered_dict in another_set
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 (): # A gotcha!
try :
for i in range ( 3 ):
try :
1 / i
except ZeroDivisionError :
# Let's throw it here and handle it outside for loop
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
return
、 break
、またはcontinue
ステートメントが「try…finally」ステートメントのtry
スイートで実行されると、途中でfinally
句も実行されます。return
ステートメントによって決まります。 finally
節は常に実行されるため、 finally
節で実行されるreturn
ステートメントは常に最後に実行されます。return
またはbreak
ステートメントを実行すると、一時的に保存された例外が破棄されるということです。 some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
出力:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
for
ステートメントは、Python 文法では次のように定義されます。
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
ループが 1 回だけ実行されることを期待していましたか?
説明:
i = 10
はループの反復に影響しません。各反復の開始前に、反復子によって提供される次の項目 (この場合は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
1.
array = [ 1 , 8 , 15 ]
# A typical generator expression
gen = ( x for x in array if array . count ( x ) > 0 )
array = [ 2 , 8 , 22 ]
出力:
> >> print ( list ( gen )) # Where did the other values go?
[ 8 ]
2.
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 ]
3.
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
のみを生成します。
2 番目の部分のg1
とg2
の出力の違いは、変数array_1
とarray_2
に値が再割り当てされる方法によるものです。
最初のケースでは、 array_1
新しいオブジェクト[1,2,3,4,5]
にバインドされていますが、 in
句は宣言時に評価されるため、依然として古いオブジェクト[1,2,3,4]
を参照しています。 (破壊されません)。
2 番目のケースでは、 array_2
へのスライスの割り当てにより、同じ古いオブジェクト[1,2,3,4]
が[1,2,3,4,5]
に更新されます。したがって、 g2
と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
分離して使用する場合とは動作が異なります。False
と評価されis not
。それ以外の場合はTrue
と評価されます。None
がFalse
あるため、 (not None)
はTrue
に評価され、式は'something' is True
になります。 # 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' , '' , '' ]]
"X"
を 3 つ割り当てたわけではありませんね。
row
変数を初期化すると、メモリ内で何が起こるかをこの視覚化で説明します。
そして、 row
乗算することによってboard
が初期化されると、これがメモリ内で起こることです (要素board[0]
、 board[1]
、およびboard[2]
のそれぞれは、 row
によって参照される同じリストへの参照です)
ここでは、 row
変数を使用してboard
生成しないことで、このシナリオを回避できます。 (この号で質問されています)。
> >> 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 ()) # note the function call here
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 ]
x
の値は、 some_func
funcs
に追加する前の反復ごとに異なりますが、ループの完了後に評価されると、すべての関数が 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
検索します。したがって、すべての関数は、変数に割り当てられた最新の値を計算に使用します。以下のように、周囲のコンテキスト (つまり、ローカル変数ではない) からの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 ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
それでは、「究極の」基本クラスはどれでしょうか?ところで、混乱にはさらに多くのことがあります。
2.
> >> class A : pass
> >> isinstance ( A , A )
False
> >> isinstance ( type , type )
True
> >> isinstance ( object , object )
True
3.
> >> issubclass ( int , object )
True
> >> issubclass ( type , object )
True
> >> issubclass ( object , type )
False
type
Python のメタクラスです。object
であり、クラスとそのオブジェクト (インスタンス) が含まれます。type
クラスobject
のメタクラスであり、すべてのクラス ( type
を含む) はobject
から直接的または間接的に継承されています。object
とtype
間には実際の基本クラスはありません。上記のスニペットで混乱が生じているのは、これらの関係 ( issubclass
とisinstance
) を Python クラスの観点から考えているためです。 object
とtype
の関係は純粋な Python では再現できません。より正確に言えば、次の関係は純粋な Python では再現できません。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
のサブクラスである必要があります)
__subclasscheck__
定義できます。issubclass(cls, Hashable)
が呼び出されると、単にcls
またはそれが継承するもので Falsey 以外の " __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
2 回アクセスすると、同じオブジェクトが得られますが、同じオブジェクトは得られません。 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
classm
またはmethod
に 2 回アクセスすると、 SomeClass
の同じインスタンスに等しいが同じではないオブジェクトが作成されます。
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
が false です。 > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
は真実です。staticmethod
関数を「no-op」記述子に変換し、関数をそのまま返します。メソッド オブジェクトは決して作成されないため、 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([])
可能オブジェクトが空であるため、 True
を返します。
all([[]])
渡された配列に 1 つの要素[]
があり、Python では空のリストは false であるため、 False
を返します。
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
出力:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
で示される) では、バックスラッシュは後続の文字をエスケープする動作とともにそのまま渡します。 > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
) では、バックスラッシュが末尾の引用符をエスケープしており、パーサーは終了引用符なしで残っています (そのためSyntaxError
発生します)。そのため、生の文字列の末尾ではバックスラッシュが機能しません。 x = True
y = False
出力:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
演算子の方がnot
演算子よりも優先されます。not x == y
not (x == y)
と同等であり、これはnot (True == False)
が最終的にTrue
と評価されることと同等です。x == not y
SyntaxError
を引き起こします。これは、一見すると予想されるかもしれない(x == not) y
および not x == (not y)
と同等であると考えられるためです。not
トークンがnot in
演算子の一部であることを予期していました ( ==
演算子とnot in
演算子の両方の優先順位が同じであるため) が、 not
トークンに続くin
トークンを見つけることができなかった場合、 SyntaxError
が発生します。出力:
> >> print ( 'wtfpython' '' )
wtfpython
> >> print ( "wtfpython" "" )
wtfpython
> >> # The following statements raise `SyntaxError`
>> > # print('''wtfpython')
>> > # print("""wtfpython")
File "<input>" , line 3
print ("""wtfpython")
^
SyntaxError: EOF while scanning triple-quoted string literal
>>> print("wtf" "python")
wtfpython
>>> print("wtf" "") # or "wtf"""
wtf
'''
と"""
も Python の文字列区切り文字であり、現在検出されている三重引用符で囲まれた文字列リテラルをスキャンするときに、Python インタプリタが区切り文字として終了三重引用符を予期していたため、SyntaxError が発生します。1.
# A simple example to count the number of booleans 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
出力:
> >> integers_found_so_far
4
> >> booleans_found_so_far
0
2.
> >> some_bool = True
> >> "wtf" * some_bool
'wtf'
> >> some_bool = False
> >> "wtf" * some_bool
''
3.
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 !
bool
Python の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
型がありませんでした (人々は false に 0 を使用し、true には 1 などのゼロ以外の値を使用しました)。 True
、 False
、およびbool
型は 2.x バージョンで追加されましたが、下位互換性のために、 True
とFalse
定数にすることはできませんでした。これらは単なる組み込み変数であり、再割り当てが可能でした。
Python 3 には下位互換性がなく、この問題は最終的に修正されたため、最後のスニペットは Python 3.x では機能しません。
1.
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 changed, but B.x didn't
( 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
+=
演算子は、新しいオブジェクトを作成せずに、変更可能なオブジェクトをその場で変更します。したがって、1 つのインスタンスの属性を変更すると、他のインスタンスとクラス属性にも影響します。 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
の処理におけるバグです。yield
が許可されなくなり、 SyntaxError
がスローされます。1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
出力 (> 3.3):
> >> list ( some_func ( 3 ))
[]
"wtf"
はどこへ行ったのでしょうか?それは、 yield from
の何らかの特別な効果によるものですか?それを検証してみましょう、
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
出力:
> >> list ( some_func ( 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 . value
> >> some_string
[ "wtf" ]
1.
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
2.
> >> x = float ( 'nan' )
> >> y = x / x
> >> y is y # identity holds
True
> >> y == y # equality fails of y
False
> >> [ y ] == [ y ] # but the equality succeeds for the list containing y
True
'inf'
と'nan'
は特殊な文字列 (大文字と小文字は区別されません) であり、明示的にfloat
型にタイプキャストされると、それぞれ数学的な「無限大」と「非数値」を表すために使用されます。
IEEE 標準NaN != NaN
によれば、このルールに従うと Python のコレクション要素の再帰性の仮定が崩れます。つまり、 x
list
ようなコレクションの一部である場合、比較などの実装はx == x
という仮定に基づきます。この仮定により、2 つの要素を比較する際に ID が最初に比較され (高速であるため)、ID が一致しない場合にのみ値が比較されます。次のスニペットは状況をより明確にします。
> >> 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
の ID が異なるため、値も異なると考えられます。したがって、今回は比較によりFalse
返されます。
興味深い読み物: 再帰性と文明のその他の柱
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 ) #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/3/reference/datamodel.html から引用
不変シーケンス 不変シーケンス タイプのオブジェクトは、作成後に変更することはできません。 (オブジェクトに他のオブジェクトへの参照が含まれている場合、これらの他のオブジェクトは変更可能である可能性があり、変更される可能性があります。ただし、不変オブジェクトによって直接参照されるオブジェクトのコレクションは変更できません。)
+=
演算子はリストをその場で変更します。項目の割り当ては機能しませんが、例外が発生した時点で、項目はすでに適切な場所に変更されています。
公式の Python FAQ にも説明があります。
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#exceed
as
target を使用して例外が割り当てられている場合、その例外は、 except
句の最後でクリアされます。これはまるで
except E as N :
foo
に翻訳されました
except E as N :
try :
foo
finally :
del N
これは、Exception 句の後で例外を参照できるようにするには、例外に別の名前を割り当てる必要があることを意味します。例外がクリアされるのは、例外にトレースバックが付加されているため、例外はスタック フレームとの参照サイクルを形成し、次のガベージ コレクションが発生するまでそのフレーム内のすべてのローカルを維持するためです。
句のスコープは 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()
インスタンスに割り当てられます。したがって、印刷しようとすると、何も印刷されません。
出力(Python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
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
SomeClass
str
クラスの__hash__
メソッドを継承するため、オブジェクトs
と文字列"s"
ハッシュは同じ値になります。
SomeClass
SomeClass("s") == "s"
str
クラスから__eq__
メソッドも継承しているため、 True
に評価されます。
両方のオブジェクトが同じ値にハッシュし、等しいため、それらは辞書の同じキーで表されます。
望ましい動作のために、 SomeClass
の__eq__
メソッドを再定義できます
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 , 's' : 42 }
> >> keys = list ( some_dict . keys ())
> >> type ( keys [ 0 ]), type ( keys [ 1 ])
( __main__ . SomeClass , str )
a , b = a [ b ] = {}, 5
出力:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
割り当てステートメントは、式リストを評価します(これは単一の式またはコンマ区切りリストであり、後者がタプルを生成する可能性があることを忘れないでください)。
+
in (target_list "=")+
は、 1つ以上のターゲットリストがあることを意味します。この場合、ターゲットリストはa, b
およびa[b]
です(式リストは正確に1であり、この場合は{}, 5
)です。
式リストが評価された後、その値は左から右にターゲットリストに開梱されます。したがって、私たちの場合、最初に{}, 5
タプルがa, b
に開梱され、 a = {}
とb = 5
あります。
a
{}
に割り当てられます。これは、可変オブジェクトです。
2番目のターゲットリストはa[b]
a
{}
a
とb
両方5
b
のステートメントで定義されていなかったため、これがエラーをスローすることが期待されます。
これで、辞書のキー5
タプル({}, 5)
に設定してa
出力の円形参照( {...}
を作成します。循環参照のもう1つの簡単な例はあるかもしれません
> >> 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でバリューエラーを上げます。 Pythonはまだ大きな整数で動作できることに注意してください。エラーは、整数と文字列間を変換するときにのみ発生します。
幸いなことに、操作がそれを超えると予想される場合、許可された数字の制限を増やすことができます。これを行うには、次のいずれかを使用できます。
コードがこの値を超えると予想される場合は、デフォルトの制限の変更に関する詳細については、ドキュメントを確認してください。
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
出力(Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
はい、それは正確に8回実行され、停止します。
RuntimeError: dictionary keys changed during iteration
。del
操作 class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
出力: 1。
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
ついに削除されました。 x
削除する最初の試みで__del__
救ったものを推測したかもしれません。この例にさらにひねりを加えましょう。
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 }
さて、今それは削除されていますか?
del x
x.__del__()
を直接呼び出しません。del x
が遭遇すると、Pythonは現在のスコープからx
という名前を削除し、1 dyを1で削除します。オブジェクトx
の参照カウントが参照されます。 __del__()
、オブジェクトの参照カウントがゼロに達する場合にのみ呼び出されます。>>> y
)が同じオブジェクトへの別の参照を作成したため、 __del__()
は呼び出されませんでした(具体的には、最後の非None
の結果値を参照する_
マジック変数REPLの式)。したがって、 del y
が発生したときに参照カウントがゼロに達するのを防ぎます。globals
呼び出す(または実際には、 None
結果が得られるものを実行する)により、 _
新しい結果を参照し、既存の参照を削除しました。これで、参照カウントが0に達し、「削除された!」が表示されます。印刷される(最終的に!)。1.
a = 1
def some_func ():
return a
def another_func ():
a += 1
return a
2.
def some_closure_func ():
a = 1
def some_inner_func ():
return a
return some_inner_func ()
def another_closure_func ():
a = 1
def another_inner_func ():
a += 1
return a
return another_inner_func ()
出力:
> >> some_func ()
1
> >> another_func ()
UnboundLocalError : local variable 'a' referenced before assignment
>> > some_closure_func ()
1
> >> another_closure_func ()
UnboundLocalError : local variable 'a' referenced before assignment
スコープ内の変数に割り当てられると、その範囲のローカルになります。したがって、 a
another_func
の範囲にローカルになりますが、同じスコープで以前に初期化されていないため、エラーが発生します。
another_func
の外側スコープ変数a
を変更するには、 global
キーワードを使用する必要があります。
def another_func ()
global a
a += 1
return a
出力:
> >> another_func ()
2
another_closure_func
では、 a
another_inner_func
の範囲のローカルになりますが、同じスコープで以前に初期化されていないため、エラーがスローされます。
another_inner_func
で外側のスコープ変数a
を変更するには、 nonlocal
キーワードを使用します。非ローカルステートメントは、最も近い外側(グローバルを除く)で定義された変数を参照するために使用されます。
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
出力:
> >> another_func ()
2
キーワードはglobal
とnonlocal
、Pythonインタープリターに新しい変数を宣言しないように指示し、対応する外側のスコープでそれらを調べます。
この短いが、Pythonで名前空間とスコープ解像度がどのように機能するかについて詳しく知るための素晴らしいガイドを読んでください。
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 ]
なぜ出力が[2, 4]
なのか推測できますか?
あなたが繰り返しているオブジェクトを変更することは決して良い考えではありません。そうするための正しい方法は、代わりにオブジェクトのコピーを反復することです。そして、 list_3[:]
まさにそれを行います。
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
del
、 remove
、 pop
の違い:
del var_name
ローカルまたはグローバルネームスペースからvar_name
のバインディングを削除するだけです(そのため、 list_1
は影響を受けません)。remove
特定のインデックスではなく、最初の一致する値を削除し、値が見つからない場合にValueError
を上げます。pop
特定のインデックスで要素を削除して返し、無効なインデックスが指定されている場合はIndexError
を上げます。なぜ出力が[2, 4]
なのか?
list_2
またはlist_4
から1
削除すると、リストの内容は現在[2, 3, 4]
になります。残りの要素は下にシフトします。つまり、 2
インデックス0、3はインデックス1にあります。次の反復はインデックス1( 3
)を調べるため、 3
2
完全にスキップされます。同様のことは、リストシーケンス内のすべての代替要素で発生します。 > >> numbers = list ( range ( 7 ))
> >> numbers
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> first_three , remaining = numbers [: 3 ], numbers [ 3 :]
> >> first_three , remaining
([ 0 , 1 , 2 ], [ 3 , 4 , 5 , 6 ])
>> > numbers_iter = iter ( numbers )
>> > list ( zip ( numbers_iter , first_three ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
# so far so good, let's zip the remaining
>> > list ( zip ( numbers_iter , remaining ))
[( 4 , 3 ), ( 5 , 4 ), ( 6 , 5 )]
要素3
numbers
リストからどこに行きましたか?
def zip ( * iterables ):
sentinel = object ()
iterators = [ iter ( it ) for it in iterables ]
while iterators :
result = []
for it in iterators :
elem = next ( it , sentinel )
if elem is sentinel : return
result . append ( elem )
yield tuple ( result )
next
関数を呼び出すことで各アイテムをresult
リストに追加し、反復可能なもののいずれかが使い果たされるたびに停止します。result
リストの既存の要素が破棄されます。それが、 numbers_iter
の3
で起こったことです。zip
使用して上記を行う正しい方法は、 > >> numbers = list ( range ( 7 ))
> >> numbers_iter = iter ( numbers )
> >> list ( zip ( first_three , numbers_iter ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
> >> list ( zip ( remaining , numbers_iter ))
[( 3 , 3 ), ( 4 , 4 ), ( 5 , 5 ), ( 6 , 6 )]
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
しかし、 x
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.
出力(Python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
出力(Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
Pythonでは、For-Loopsが存在するスコープを使用し、定義されたループ変数を残します。これは、以前にグローバルネームスペースのfor-loop変数を明示的に定義した場合にも適用されます。この場合、既存の変数を逆転させます。
リスト理解の例のPython 2.xおよびPython 3.x通訳者の出力の違いは、Python 3.0 Changelogの新しいものに文書化された変更をフォローすることで説明できます。
「リストの包括的なは、構文形式をサポートしなくなりました
[... for var in item1, item2, ...]
[... for var in (item1, item2, ...)]
]。包括的には異なるセマンティクスがあります。list()
コンストラクター内の発電機式の構文糖に近いもので、特にループ制御変数は周囲の範囲に漏れなくなります。」
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' ]
Pythonの関数のデフォルトの可変引数は、関数を呼び出すたびに実際に初期化されていません。代わりに、最近割り当てられた値はデフォルト値として使用されます。引数として[]
some_func
に明示的に渡すと、 default_arg
変数のデフォルト値は使用されなかったため、機能は予想どおりに返されました。
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' ],)
可変引数のためにバグを避けるための一般的な慣行は、デフォルト値としてNone
を割り当て、後でその引数に対応する関数に値が渡されるかどうかを後で確認することです。例:
def some_func ( default_arg = None ):
if default_arg is None :
default_arg = []
default_arg . append ( "some_string" )
return default_arg
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!" )
出力(Python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
出力(Python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
句に複数の例外を追加するには、最初の引数として括弧付きのタプルとしてそれらを渡す必要があります。 2番目の引数はオプションの名前で、提供されると提起された例外インスタンスにバインドされます。例、
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
出力(Python 2.x):
Caught again!
list.remove(x): x not in list
出力(Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
コンマで変数から例外を分離することは非推奨であり、Python 3では機能しません。正しい方法は、 as
使用することです。例、
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
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
常にa = a + b
と同じように動作するわけではありません。クラスはop=
演算子を異なる方法で実装する場合があり、リストがこれを行います。
式a = a + [5,6,7,8]
は、新しいリストを生成し、その新しいリストへのa
を設定し、 b
は変更されません。
式a += [5,6,7,8]
は、実際にはリストで動作する「拡張」関数にマッピングされ、 a
とb
内側に変更された同じリストを指すようにします。
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 )]
出力(Python 2.x):
> >> SomeClass . y [ 0 ]
17
出力(Python 3.x):
> >> SomeClass . y [ 0 ]
5
リストの中間要素を取得するために、素朴な関数を実装しましょう。
def get_middle ( some_list ):
mid_index = round ( len ( some_list ) / 2 )
return some_list [ mid_index - 1 ]
Python 3.x:
> >> get_middle ([ 1 ]) # looks good
1
> >> get_middle ([ 1 , 2 , 3 ]) # looks good
2
> >> get_middle ([ 1 , 2 , 3 , 4 , 5 ]) # huh?
2
> >> len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 # good
2.5
> >> round ( len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 ) # why?
2
Pythonが2.5〜2を丸めたようです。
round()
銀行家の丸めを使用します。ここで、.5の画分が最も近い偶数に丸められます。 > >> round ( 0.5 )
0
> >> round ( 1.5 )
2
> >> round ( 2.5 )
2
> >> import numpy # numpy does the same
> >> numpy . round ( 0.5 )
0.0
> >> numpy . round ( 1.5 )
2.0
> >> numpy . round ( 2.5 )
2.0
get_middle([1])
は、インデックスがround(0.5) - 1 = 0 - 1 = -1
であり、リストの最後の要素を返すため、1のみを返したことに注意してください。私は、次のシナリオの1つ以上に出会っていない、これまでPythonistの経験に出会ったことがありません。
1.
x , y = ( 0 , 1 ) if True else None , None
出力:
> >> x , y # expected (0, 1)
(( 0 , 1 ), None )
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 ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
出力
> >> len ( ten_words_list )
9
4.十分に強く主張しないでください
a = "python"
b = "javascript"
出力:
# An assert statement with an assertion failure message.
> >> assert ( a == b , "Both languages are different" )
# No AssertionError is raised
5.
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
6.
def some_recursive_func ( a ):
if a [ 0 ] == 0 :
return
a [ 0 ] -= 1
some_recursive_func ( a )
return a
def similar_recursive_func ( a ):
if a == 0 :
return a
a -= 1
similar_recursive_func ( a )
return a
出力:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
1の場合、予想される動作の正しいステートメントはx, y = (0, 1) if True else (None, None)
です。
2の場合、予想される動作の正しいステートメントはt = ('one',)
またはt = 'one',
です(commaが欠落しています)そうでなければ、インタープリターはt
str
と見なし、文字ごとにそれを繰り返します。
()
特別なトークンであり、空のtuple
を示します。
3では、すでに把握しているように、リストに5番目の要素( "that"
)の後に欠落しているコンマがありません。したがって、暗黙の文字通りの連結によって、
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
個々の式a == b
主張する代わりに、タプル全体を主張するため、4番目のスニペットではAssertionError
は提起されませんでした。次のスニペットは物事をクリアします、
> >> a = "python"
> >> b = "javascript"
> >> assert a == b
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError
>> > assert ( a == b , "Values are not equal" )
< stdin > : 1 : SyntaxWarning : assertion is always true , perhaps remove parentheses ?
>> > assert a == b , "Values are not equal"
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError : Values are not equal
5番目のスニペットに関してはNone
list.append
、 dict.update
、 list.sort
などのシーケンス/マッピングオブジェクトのアイテムを変更するほとんどの方法。これの背後にある理論的根拠は、操作を内に行うことができる場合にオブジェクトのコピーを作成することを避けることにより、パフォーマンスを改善することです(ここから参照)。
最後の1つはかなり明白である必要があります。変速可能なオブジェクト( list
など)は関数で変更でき、不変( a -= 1
)の再割り当ては値の変更ではありません。
これらのニットピックを知っていると、長期的にはデバッグの努力の時間を節約できます。
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
のスペースであると思われるかもしれませんが、ドキュメントによるとSEPが指定されていない、または
None
でない場合、別の分割アルゴリズムが適用されます。連続した空白の実行は単一のセパレーターと見なされ、結果には、文字列に導かれているか、末尾の白人がある場合、スタートまたは終了時に空の文字列が含まれません。したがって、空の文字列または文字列を空間だけで構成する文字列を分離してから、None Sepater Returns[]
。 SEPが与えられた場合、連続した区切り文字は一緒にグループ化されず、空の文字列を区切るとみなされます(たとえば、'1,,2'.split(',')
戻ります['1', '', '2']
)。指定されたセパレーターの返された['']
で空の文字列を分割します。
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
出力
> >> from module import *
> >> some_weird_name_func_ ()
"works!"
> >> _another_weird_name_func ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name '_another_weird_name_func' is not defined
多くの場合、ワイルドカードの輸入を使用しないことをお勧めします。これの最初の明らかな理由は、ワイルドカードの輸入では、主要なアンダースコアがある名前が輸入されないことです。これにより、ランタイム中にエラーが発生する可能性があります。
from ... import a, b, c
使用していたら、上記のNameError
発生しなかったでしょう。
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
WildCardのインポートを本当に使用したい場合は、WildCardのインポートを行うときに利用可能になる公開オブジェクトのリストを含むリスト__all__
モジュールに定義する必要があります。
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
出力
> >> _another_weird_name_func ()
"works!"
> >> some_weird_name_func_ ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'some_weird_name_func_' is not defined
> >> x = 7 , 8 , 9
> >> sorted ( x ) == x
False
> >> sorted ( x ) == sorted ( x )
True
> >> y = reversed ( x )
> >> sorted ( y ) == sorted ( y )
False
sorted
メソッドは常にリストを返し、リストとタプルを比較すると、Pythonでは常にFalse
返します。
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
sorted
ものとは異なり、 reversed
メソッドはイテレーターを返します。なぜ?ソートでは、イテレーターを内側に変更するか、余分なコンテナ(リスト)を使用する必要があるため、反転は最後のインデックスから最初のインデックスを反復するだけで機能することができます。
したがって、 sorted(y) == sorted(y)
比較中に、 sorted()
の最初の呼び出しはイテレーターy
を消費し、次の呼び出しは空のリストを返すだけです。
> >> x = 7 , 8 , 9
> >> y = reversed ( x )
> >> sorted ( y ), sorted ( y )
([ 7 , 8 , 9 ], [])
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 )
出力(<3.5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
真夜中の時間は印刷されていません。
Python 3.5の前に、 datetime.time
オブジェクトのブール値は、UTCの真夜中を表している場合、 False
であると見なされました。 if obj:
構文を使用すると、 obj
がnullか「空」に相当するかどうかを確認すると、エラーが発生しやすくなります。
このセクションには、私のような初心者のほとんどが気づいていない(まあ、そうではありません)、Pythonについてのあまり知られていない興味深いものがいくつか含まれています。
さあ、どうぞ
import antigravity
出力: sshh ...それは超秘密です。
antigravity
モジュールは、Python開発者がリリースした数少ないイースターエッグの1つです。import antigravity
Pythonに関するクラシックXKCDコミックを指すWebブラウザを開きます。goto
、しかしなぜですか? from goto import goto , label
for i in range ( 9 ):
for j in range ( 9 ):
for k in range ( 9 ):
print ( "I am trapped, please rescue!" )
if k == 2 :
goto . breakout # breaking out from a deeply nested loop
label . breakout
print ( "Freedom!" )
出力(Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
の実用バージョンは、2004年4月1日にエイプリルフールの冗談として発表されました。goto
がPythonに存在しない理由は次のとおりです。あなたがスコープを示すためにPythonでWhitespaceを使用したくない人の1人である場合、あなたはcスタイル{}をインポートして使用できます、
from __future__ import braces
出力:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
ブレース?とんでもない!それが残念だと思うなら、Javaを使用してください。さて、もう1つの驚くべきことは、 __future__
モジュールコードで提起されたSyntaxError
がどこにあるかを見つけることができますか?
__future__
モジュールは、通常、Pythonの将来のバージョンの機能を提供するために使用されます。しかし、この特定の文脈における「未来」は皮肉です。future.c
ファイルにここに存在します。future.c
に適切なコードを実行してから、通常のインポートステートメントとして扱います。出力(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
それでは行きます。
これは、2009年4月1日にリリースされたPEP-401に関連しています(今、それが何を意味するのか知っています)。
PEP-401から引用
Python 3.0の!=不平等演算子は恐ろしい指張りの誘発的な間違いであることを認識して、Fluflは<> Diamond演算子を唯一のスペルとして復活させます。
バリーおじさんがPEPで共有しなければならなかったことがもっとありました。ここで読むことができます。
インタラクティブな環境ではうまく機能しますが、Pythonファイルを介して実行するとSyntaxError
が上昇します(この問題を参照)。ただし、ステートメントをeval
またはcompile
て機能させることができます。
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
待って、これは何ですか? this
愛です❤️
出力:
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!
それはパイソンの禅です!
> >> 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
モジュールは、Pythonの禅のイースターエッグです(PEP 20)。love is not True or False; love is love
、皮肉ですが、それは自明です(そうでis not
場合は、 is
に関連する例を参照してください。ループのelse
句。典型的な例の1つは次のとおりです。
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
例外処理のelse
句。一例として、
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
出力:
Try block executed successfully ...
else
句は、すべての反復後に明示的なbreak
がない場合にのみ実行されます。あなたはそれを「ノーブレイク」条項と考えることができます。try
ブロックの後のelse
の条項は、「完了条項」とも呼ばれます。Tryステートメントでelse
句に到達することは、Tryブロックが実際に正常に完了したことを意味します。 def some_func ():
Ellipsis
出力
> >> some_func ()
# No output, No Error
> >> SomeRandomString
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'SomeRandomString' is not defined
> >> Ellipsis
Ellipsis
Ellipsis
世界的に利用可能な組み込みオブジェクトです...
> >> ...
Ellipsis
pass
ステートメントのように) > >> import numpy as np
> >> three_dimensional_array = np . arange ( 8 ). reshape ( 2 , 2 , 2 )
array ([
[
[ 0 , 1 ],
[ 2 , 3 ]
],
[
[ 4 , 5 ],
[ 6 , 7 ]
]
])
three_dimensional_array
は、配列の配列です。最も内側のすべてのアレイの2番目の要素(インデックス1
)を印刷したいとしましょう。省略記号を使用して、前のすべての寸法をバイパスできます > >> three_dimensional_array [:,:, 1 ]
array ([[ 1 , 3 ],
[ 5 , 7 ]])
> >> three_dimensional_array [..., 1 ] # using Ellipsis.
array ([[ 1 , 3 ],
[ 5 , 7 ]])
n_dimensional_array[firs_dim_slice, ..., last_dim_slice]
)(Callable[..., int]
またはTuple[str, ...]
)))スペルは意図されています。このためにパッチを提出しないでください。
出力(Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
はpython 3の「-10⁵xπ」ですが、python 2では「-10⁵xe」です。1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
出力:
> >> Yo (). bro
True
> >> Yo (). __honey
AttributeError : 'Yo' object has no attribute '__honey'
> >> Yo (). _Yo__honey
True
2.
class Yo ( object ):
def __init__ ( self ):
# Let's try something symmetrical this time
self . __honey__ = True
self . bro = True
出力:
> >> Yo (). bro
True
> >> Yo (). _Yo__honey__
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'Yo' object has no attribute '_Yo__honey__'
なぜYo()._Yo__honey
が機能したのですか?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
出力:
> >> A (). __variable
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'A' object has no attribute '__variable'
> >> A (). some_func ()
'Some value'
__
(ダブルアンダースコア「ダンダー」)で始まるクラスメンバー名を変更(マングル)し、 _NameOfTheClass
前に追加することで1つ以上の末尾のアンダースコアで終わりません。__honey
属性にアクセスするには、 _Yo
正面に追加する必要がありました。これにより、他のクラスで定義されている同じ名前属性との競合が防止されます。return __variable
で__variable
__variable __variableは、 _A__variable
にマングルされました。出力:
> >> value = 11
> >> valuе = 32
> >> value
11
ウート?
注:これを再現する最も簡単な方法は、上記のスニペットからステートメントをコピーしてファイル/シェルに貼り付けることです。
一部の非西側の文字は、英語のアルファベットの文字と同じように見えますが、通訳によって異なると見なされます。
> >> 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
組み込みのord()
関数は、文字のUnicodeコードポイントを返し、キリル語の「E」とLatin 'E'の異なるコード位置は、上記の例の動作を正当化します。
# `pip install numpy` first.
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
ノーベル賞はどこですか?
energy_send
関数で作成されたnumpy配列は返されないため、メモリスペースが自由に再配置できるようにすることに注意してください。numpy.empty()
再透過化することなく、次のフリーメモリスロットを返します。このメモリスポットは、たまたま解放されたものと同じです(通常はそうではありませんが)。 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
出力(Python 2.x):
> >> square ( 10 )
10
それは100であるべきではありませんか?
注:これを再現できない場合は、シェルを介してファイルMixed_tabs_and_spaces.pyを実行してみてください。
タブやスペースを混ぜないでください!リターンの直前の文字は「タブ」であり、コードは例の他の場所にある「4つのスペース」の倍数によってインデントされます。
これは、Pythonがタブを処理する方法です:
まず、タブは(左から右に)1〜8つのスペースで置き換えられ、交換までの文字の総数が8つの倍数である<...>
したがって、 square
関数の最後の行の「タブ」は8つのスペースに置き換えられ、ループに入ります。
Python 3は、そのようなケースのエラーを自動的にスローするのに十分なほど親切です。
出力(Python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
より速いです # 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
+=
、最初の文字列(例、 s1 += s2 + s3
のs1
)が完全な文字列を計算しながら破壊されないため+
2つ以上の文字列を連結する場合よりも速いです。 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
出力:
# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
> >> NUM_ITERS = 1000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS )
124 µs ± 4.73 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS )
211 µs ± 10.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS )
61 µs ± 2.18 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS )
117 µs ± 3.21 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS )
10.1 µs ± 1.06 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
反復回数を10倍に増やしましょう。
> >> NUM_ITERS = 10000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS ) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS ) # Quadratic increase
6.82 ms ± 134 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS ) # Linear increase
645 µs ± 24.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS ) # Linear increase
1.17 ms ± 7.25 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS ) # Linear increase
86.3 µs ± 2 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
これらのリンクでTimeITまたは%TimeITの詳細を読むことができます。それらは、コードピースの実行時間を測定するために使用されます。
長い文字列を生成するために+
使用しないでください。Pythonでは、 str
不変であるため、左右の文字列を連結するたびに新しい文字列にコピーする必要があります。長さ10の4つの文字列を連結すると、コピー(10+10)+((10+10)+10)+(((10+10)+10)+10)= 90文字文字。文字列の数とサイズが増加するにつれて、事態は二次的に悪化します( add_bytes_with_plus
関数の実行時間で正当化されます)
したがって、 .format.
または%
構文(ただし、非常に短い文字列の場合、それらは+
よりもわずかに遅くなります)。
または、既に繰り返し可能なオブジェクトの形で使用可能なコンテンツを既に使用できる場合は、はるかに高速な''.join(iterable_object)
を使用します。
add_bytes_with_plus
とは異なり、前の例で説明した+=
最適化のために、 add_string_with_plus
実行時間の2次増加を示していませんでした。ステートメントがs = s + "x" + "y" + "z"
である場合s += "xyz"
ではなく、増加は2次でした。
def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s = s + "x" + "y" + "z"
assert len ( s ) == 3 * iters
> >> % timeit - n100 add_string_with_plus ( 1000 )
388 µs ± 22.4 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n100 add_string_with_plus ( 10000 ) # Quadratic increase in execution time
9 ms ± 298 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
巨大な文字列をフォーマットして作成するための非常に多くの方法は、Pythonの禅とは対照的に、
それを行う明白な方法は 1 つ、できれば 1 つだけである必要があります。
dict
検索の鈍化 * some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
出力:
> >> % timeit some_dict [ '5' ]
28.6 ns ± 0.115 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> some_dict [ 1 ] = 1
> >> % timeit some_dict [ '5' ]
37.2 ns ± 0.265 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> % timeit another_dict [ '5' ]
28.5 ns ± 0.142 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> another_dict [ 1 ] # Trying to access a key that doesn't exist
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
KeyError : 1
> >> % timeit another_dict [ '5' ]
38.5 ns ± 0.0913 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
なぜ同じルックアップが遅くなっているのですか?
str
、 int
、任意のオブジェクト...)を処理する一般的な辞書ルックアップ関数と、 str
の単なるキーで構成される辞書の一般的なケースに特化したものがあります。lookdict_unicode
という名前)は、既存のキー(検索キーを含む)がすべての文字列であることを知っており、 __eq__
メソッドを呼び出す代わりに、より高速でシンプルな文字列の比較をキーの比較に使用します。str
キーでdict
インスタンスが初めてアクセスされると、将来の検索が一般的な関数を使用するため、変更されたものです。dict
インスタンスでは可逆的ではなく、キーは辞書に存在する必要さえありません。そのため、ルックアップに失敗したことを試みることは同じ効果があります。dict
s * import sys
class SomeClass :
def __init__ ( self ):
self . some_attr1 = 1
self . some_attr2 = 2
self . some_attr3 = 3
self . some_attr4 = 4
def dict_size ( o ):
return sys . getsizeof ( o . __dict__ )
出力:( Python 3.8、その他のPython 3バージョンは少し異なる場合があります)
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104
> >> dict_size ( o2 )
104
> >> del o1 . some_attr1
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
> >> dict_size ( o1 )
232
もう一度やり直してみましょう...新しい通訳で:
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104 # as expected
> >> o1 . some_attr5 = 5
> >> o1 . some_attr6 = 6
> >> dict_size ( o1 )
360
> >> dict_size ( o2 )
272
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
これらの辞書が肥大化する理由は何ですか?そして、なぜ新しく作成されたオブジェクトも肥大化しているのですか?
__init__
に多くの属性を追加できます。 「ウンシェア」)。サイズ変更が行われたときに複数のインスタンスが存在する場合、同じクラスのすべての将来のインスタンスでキー共有が無効になります。CPYTHONは、インスタンスが同じ属性セットを使用しているかどうかを知ることができなくなり、共有しようとすることで救済を行うことにしました。キー。__init__
のすべての属性を初期化してください! join()
リスト操作の代わりに文字列操作です。 (最初の使用法では直感に反します)
説明: join()
文字列のメソッドである場合、任意の繰り返し(リスト、タプル、イテレーター)で動作できます。リストのメソッドである場合、すべてのタイプで個別に実装する必要があります。また、文字列固有のメソッドを一般的なlist
オブジェクトAPIに配置することはあまり意味がありません。
いくつかの奇妙な見た目でありながら意味的に正しい声明:
[] = ()
意味的に正しいステートメントです(空のtuple
空のlist
に編む)'a'[0][0][0][0][0]
も意味的に正しいです。PythonにはCから分岐した他の言語のような文字データ型がないため、文字列から単一の文字を選択するとa戻ります。シングルキャラクター文字列。3 --0-- 5 == 8
および--5 == 5
は両方とも意味的に正しいステートメントであり、 True
に評価されます。 a
数字であることを考えると、 ++a
と--a
はどちらも有効なPythonステートメントですが、C、C ++、Javaなどの言語の同様のステートメントと比較して同じように動作しません。
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
説明:
++
オペレーターがあります。実際には2つ+
演算子です。++a
as as +(+a)
はa
に変換されます。同様に、ステートメントの出力--a
は正当化できます。PythonのWalrusオペレーターに注意する必要があります。しかし、スペースインベーダーオペレーターについて聞いたことがありますか?
> >> a = 42
> >> a -= - 1
> >> a
43
代替の増分オペレーターとして、別のものと一緒に使用されます
> >> a += + 1
> >> a
> >> 44
説明:このいたずらは、レイモンド・ヘッティンガーのツイートから来ています。 Space Invaderオペレーターは、実際には単なる奇形a -= (-1)
です。これはa = a - (- 1)
に相当します。 a += (+ 1)
の場合も同様です。
Pythonには、文書化されていないコンバースインレクトオペレーターがいます。
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
説明: False
とTrue
0および1で置き換えて数学を行う場合、Truth Tableは逆の意味オペレーターと同等です。 (ソース)
私たちはオペレーターを話しているので、マトリックス増殖の@
オペレーターもあります(心配しないでください、今回は実際には)。
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
説明: @
オペレーターは、科学コミュニティを念頭に置いてPython 3.5に追加されました。任意のオブジェクトは、 __matmul__
マジックメソッドを過負荷にして、この演算子の動作を定義できます。
Python 3.8以降、 f'{some_var=}
などの典型的なfストリング構文を使用して、クイックデバッグを使用できます。例、
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
Pythonは、関数のローカル可変ストレージに2バイトを使用します。理論的には、これは、関数で65536変数のみを定義できることを意味します。ただし、Pythonには、2^16以上の変数名を保存するために使用できる便利なソリューションが組み込まれています。次のコードは、65536を超えるローカル変数が定義されている場合にスタックで何が起こるかを示しています(警告:このコードは約2^18行のテキストを印刷するので、準備してください!)
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
複数のPythonスレッドはPythonコードを同時に実行しません(はい、正しく聞いたことがあります!)。いくつかのスレッドをスポーンしてPythonコードを同時に実行できるようにすることは直感的に思えるかもしれませんが、Pythonでのグローバルなインタープリターロックのため、あなたがしているのは、スレッドを同じコアターンでターンで実行することだけです。 PythonスレッドはIOバウンドタスクに適していますが、CPUバインドタスクのPythonで実際の並列化を実現するには、Pythonマルチプロセッシングモジュールを使用することをお勧めします。
print
方法がすぐに値を印刷しない場合がある場合があります。例えば、
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
これにより、出力バッファーがn
に遭遇した後、プログラムが実行されたときにフラッシュされるためend
3秒後にwtfpython
印刷されます。 flush=True
引数を渡して、バッファーをフラッシュさせることができます。
バウンドインデックスの外でスライスすることは、エラーをスローしません
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
反復可能なスライスすると、常に新しいオブジェクトが作成されるとは限りません。例えば、
> >> some_str = "wtfpython"
> >> some_list = [ 'w' , 't' , 'f' , 'p' , 'y' , 't' , 'h' , 'o' , 'n' ]
> >> some_list is some_list [:] # False expected because a new object is created.
False
> >> some_str is some_str [:] # True because strings are immutable, so making a new object is of not much use.
True
int('١٢٣٤٥٦٧٨٩')
は、python 3で123456789
を返します。Pythonでは、10進数文字が数字文字と、u+0660、アラビア語 - インディック桁ゼロなどの10進数の数字を形成するために使用できるすべての文字が含まれます。これは、Pythonのこの動作に関連する興味深い話です。
Python 3以降のアンダースコア(より良い読みやすさのため)で数値リテラルを分離できます。
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
。これはcount
方法のおおよその実装です。これにより、物事がより明確になります
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
動作は、元の文字列に長さ0のスライスを持つ空のサブストリング( ''
)の一致によるものです。
wtfpythonに貢献できるいくつかの方法、
詳細については、Converting.MDをご覧ください。物事を議論するために新しい問題を自由に作成してください。
PS:バックリンクリクエストで手を差し伸べないでください。プロジェクトに非常に関連していない限り、リンクは追加されません。
このコレクションのアイデアとデザインは、当初Denys Dovhanの素晴らしいプロジェクトWTFJSに触発されました。 Pythonistasによる圧倒的なサポートは、今の形を与えました。
© Satwik Kansal
If you like wtfpython, you can use these quick links to share it with your friends,
ツイッター |リンクトイン |フェイスブック
I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.
以上です! For upcoming content like this, you can add your email here.