この記事を読むのに約10分かかります。
Pythonは、美しく設計され、解釈された高レベルの言語であり、プログラマーを快適にする多くの機能を提供します。ただし、Pythonの出力結果の一部は、初心者にはそれほど明白ではないように見える場合があります。
この興味深いプロジェクトは、Pythonの理解できない、直感に反する例とあまり知られていない機能を収集し、これらの現象の背後にある実際の原則について議論することを目的としています。
次の例のいくつかはWTFについて考えさせないかもしれませんが、それでもあなたが知らないPythonのいくつかの興味深い機能を教えてくれるかもしれません。これはプログラミング言語の内部原理を学ぶ良い方法だと思います。あなたもそれを楽しむでしょう!
経験豊富なPythonプログラマーであれば、例に対する正しい答えを一度に見つけることができるかどうかを確認するために挑戦することができます。これらの例のいくつかに精通している場合は、これらの落とし穴を踏むように促される可能性があります。時間の甘い思い出。
それでは、始めましょう...
**注:**すべての例はPython 3.5.2の対話型インタープリターでテストされており、特に指定がない限り、すべてのPythonバージョンに適用できるはずです。
個人的には、次の例を順番に、それぞれの例を読むことをお勧めします。
>>> a ="some_string">>>id(a)140420665652016>>>id("some"+"_"+"string") #2つのID値は同じであることに注意してください.140420665652016
>>> a ="wtf">>> b ="wtf">>> a is b
True
>>> a ="wtf!">>> b ="wtf!">>> a is b
False
>>> a, b ="wtf!","wtf!">>> a is b #3にのみ適用.7以下,3.7の後の戻り結果はFalseです.
True
>>>' a'*20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>>' a'*21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
わかりやすいですよね?
'wtf'は常駐しますが、' '。join([' w '、' t '、' f '] は常駐しません)!が含まれているため、 'wtf!'は常駐しません。このルールのCPythonの実装はここにあります。aと bの値が同じ行で " wtf! "に設定されている場合、Pythonインタープリターは新しいオブジェクトを作成し、同時に2番目の変数を参照します(翻訳:3.7以下にのみ適用可能、詳細状況については、こちらを参照してください)。異なる行で割り当て操作を実行すると、すでに wtf!が存在することを「認識」しません。 オブジェクト("wtf!"は上記のように暗黙的に常駐しないため)これはコンパイラの最適化であり、特にインタラクティブな環境に適しています。'a' * 20が 'aaaaaaaaaaaaaaaaaaaa'に置き換えられることを意味します。クロックサイクル。長さが20未満の文字列のみが一定の折り畳みを受けます(なぜですか?式 'a' * 10 ** 10によって生成された .pycファイルのサイズを想像してください)。関連するソースコード実装はこちらです。some_dict ={}
some_dict[5.5]="Ruby"
some_dict[5.0]="JavaScript"
some_dict[5]="Python"
Output:
>>> some_dict[5.5]"Ruby">>> some_dict[5.0]"Python">>> some_dict[5]"Python"
" 「Python」は「JavaScript」の存在を排除しますか?
>>>5==5.0
True
>>> hash(5)==hash(5.0)
True
**注:**異なる値を持つオブジェクトも同じハッシュ値を持つ場合があります(ハッシュの衝突)。
some_dict [5] =" Python "ステートメントを実行すると、Pythonは 5と 5.0を some_dictの同じキーとして認識するため、既存の値" JavaScript "は" Python "で上書きされます。def some_func():try:return'from_try'finally:return'from_finally'
Output:
>>> some_func()'from_finally'
returnステートメントによって決定されます。finally句を実行する必要があるため、 finally句の returnは常に最後に実行されたステートメントになります。classWTF:
pass
Output:
>>> WTF()==WTF() #2つの異なるオブジェクトは等しくてはいけません
False
>>> WTF() is WTF() #同じではありません
False
>>> hash(WTF())==hash(WTF()) #ハッシュ値も異なる必要があります
True
>>> id(WTF())==id(WTF())
True
id関数を呼び出すときに、 WTFクラスのオブジェクトを作成し、それを id関数に渡します。次に、 id関数はそのid値(つまり、メモリアドレス)を取得し、オブジェクトを破棄します。オブジェクトは破壊されました。is操作の結果が Falseになるのでしょうか?このコードを見てみましょう。classWTF(object):
def __init__(self):print("I")
def __del__(self):print("D")
Output:
>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF())==id(WTF())
I
D
I
D
True
ご覧のとおり、オブジェクトの破棄の順序がすべての違いの原因です。
some_string ="wtf"
some_dict ={}for i, some_dict[i]inenumerate(some_string):
pass
Output:
>>> some_dict #作成されたインデックス辞書.{0:'w',1:'t',2:'f'}
forの定義は次のとおりです。 for_stmt:'for' exprlist 'in' testlist ':' suite ['else'':' suite]
その中で、 exprlistは割り当てターゲットを参照します。これは、{exprlist} =(next_value}と同様の操作が反復可能オブジェクトの各アイテムに対して実行されることを意味します。
興味深い例はこれを示しています:
for i inrange(4):print(i)
i =10
Output:
0123
このループは一度だけ実行されると思いますか?
? 説明:
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
array =[1,8,15]
g =(x for x in array if array.count(x)>0)
array =[2,8,22]
Output:
>>> print(list(g))[8]
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]
Output:
>>> print(list(g1))[1,2,3,4]>>>print(list(g2))[1,2,3,4,5]
in句は宣言時に実行され、条件付き句は実行時に実行されます。arrayは[2、8、22]に再割り当てされているため、前の 1、 8、および 15の場合、 count(8)の結果のみがより大きくなります。 0なので、ジェネレーターは 8のみを生成します。g1と g2の出力の違いは、変数 array_1と array_2が再割り当てされる方法によって引き起こされます。in句は宣言時に実行されるため、 array_1は新しいオブジェクト [1,2,3,4,5]にバインドされ、古いオブジェクトを参照します。 [1,2,3,4](破壊されていません)。array_2のスライス割り当てにより、同じ古いオブジェクト[1,2,3,4]が代わりに[1,2,3,4,5]に更新されます。したがって、 g2と array_2は引き続き同じオブジェクトを参照します(このオブジェクトは[1,2,3,4,5]に更新されました)。 isはそれが何であるかではありません!/予期せず is!以下はインターネット上で非常に有名な例です。
>>> 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と == **の違い
is演算子は、2つのオペランドが同じオブジェクトを参照しているかどうかをチェックします(つまり、2つのバジェットオブジェクトが同じであるかどうかをチェックします)。== オペレーターは、2つのオペランドの値が等しいかどうかを比較します。isは同じ参照を意味し、==は値が等しいことを意味します。次の例は、この点をよく説明できます。>>>[]==[]
True
>>>[] is [] #2つの空のリストは異なるメモリアドレスにあります.
False
256 既存のオブジェクトであり、 257はではありません
Pythonを起動すると、 -5から 256までの値がすでに割り当てられています。これらの番号は頻繁に使用されるため、事前に準備しておくのに適しています。
https://docs.python.org/3/c-api/long.htmlから引用
現在の実装では、-5〜256のすべての整数の整数オブジェクトの配列が保持されます。この範囲の整数を作成する場合、既存のオブジェクトへの参照を返すだけで済みます。したがって、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を作成したことを理解するほど賢くないため、メモリ内に別のオブジェクトを作成します。
aと bが同じ行で同じ値で初期化されると、それらは同じオブジェクトを指します。
>>> a, b =257,257>>>id(a)140640774013296>>>id(b)140640774013296>>> a =257>>> b =257>>>id(a)140640774013392>>>id(b)140640774013488
257に設定されている場合、Pythonインタープリターは新しいオブジェクトを作成し、同時に2番目の変数を参照します。異なる行でそれを行うと、それが行われたことを「認識」しません。 257オブジェクトがあります。.pyファイルで試してみるとこの例では、ファイルが一度にコンパイルされるため、同じ動作は見られません。# 最初に可変行を初期化します
row =[""]*3 #row i['','','']
# そして可変ボードを作成します
board =[row]*3
Output:
>>> board
[['','',''],['','',''],['','','']]>>> board[0]['','','']>>> board[0][0]''>>> board[0][0]="X">>> board
[[' X','',''],['X','',''],['X','','']]
3つの「X」を割り当てたことがありますか?
row変数を初期化すると、次の図はメモリ内の状況を示しています。

「行」を掛けて「ボード」を初期化すると、メモリ内の状況は下図のようになります(各要素「ボード[0]」、「ボード[1]」、「ボード[2]」はすべてrowと同じリストを参照します。)

変数 rowを使用して boardを生成しないことで、この状況を回避できます(この問題はこの要件を提案しています)。
>>> board =[['']*3for _ inrange(3)]>>> board[0][0]="X">>> board
[[' X','',''],['','',''],['','','']]
funcs =[]
results =[]for x inrange(7):
def some_func():return x
funcs.append(some_func)
results.append(some_func()) #この関数が実行されることに注意してください
funcs_results =[func()for func in funcs]
Output:
>>> results
[0,1,2,3,4,5,6]>>> funcs_results
[6,6,6,6,6,6,6]
各反復で some_funcを funcsに追加する前の xの値が異なっていても、すべての関数は6を返します。
// もう一つの例
>>> powers_of_x =[lambda x: x**i for i inrange(10)]>>>[f(2)for f in powers_of_x][512,512,512,512,512,512,512,512,512,512]
funcs =[]for x inrange(7):
def some_func(x=x):return x
funcs.append(some_func)
**Output:**
>>> funcs_results =[func()for func in funcs]>>> funcs_results
[0,1,2,3,4,5,6]
isnot…isnotis(not…)/isnot…is not is(not…) **>>>' something' is not None
True
>>>' something'is(not None)
False
is notは単一のバイナリ演算子であり、 isと notを別々に使用するのとは異なります。Output:
>>> 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
Output:
>>> print("\\ C:\\")
\ C:\
>>> print(r"\ C:")
\ C:>>>print(r"\ C:\")
File "<stdin>", line 1print(r"\ C:\")^
SyntaxError: EOL while scanning string literal
rで始まる元の文字列では、バックスラッシュに特別な意味はありません。>>> print(repr(r"wt\"f"))'wt\\"f'
x = True
y = False
Output:
>>> 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 == notyが SyntaxError例外をスローする理由は、 x ==(not yではなく(x == not)yと同等であると見なされるためです。 ) `。notトークンが not in演算子の一部であることを期待しますが( ==演算子と not in演算子の優先順位は同じであるため)、 notトークンの後に inを見つけることができません。フラグを立てると、 SyntaxError例外がスローされます。Output:
>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # 次のステートメントはスローします`SyntaxError`異常な
>>> # print('''wtfpython')>>> # print("""wtfpython")
>>> print("wtf""python")
wtfpython
>>> print("wtf""") # or "wtf"""
wtf
''' また、 " "はPythonの文字列区切り文字でもあります。Pythonインタープリターが最初に3つの引用符を検出すると、区切り記号として3つの終了引用符を見つけようとします。存在しない場合は 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)
Output:
(' Time at noon is', datetime.time(12,0))
midnight_timeは出力されません。
Python 3.5より前では、 datetime.time [Object Storage](https://cloud.tencent.com/product/cos?from=10680)がUTCの深夜時刻(変換:は 00:00)の場合、そのブール値は Falseと見なされます。を使用する場合obj: ステートメントを使用して objが nullであるか、「空の」値であるかを確認すると、間違いを犯しやすくなります。
# 簡単な例,以下の反復可能なオブジェクトのブール値と整数値の数を数えます
mixed_list =[False,1.0,"some_string",3, True,[], False]
integers_found_so_far =0
booleans_found_so_far =0for item in mixed_list:ifisinstance(item, int):
integers_found_so_far +=1
elif isinstance(item, bool):
booleans_found_so_far +=1
Output:
>>> integers_found_so_far
4>>> booleans_found_so_far
0
another_dict ={}
another_dict[True]="JavaScript"
another_dict[1]="Ruby"
another_dict[1.0]="Python"
Output:
>>> another_dict[True]"Python"
>>> some_bool = True
>>>" wtf"*some_bool
' wtf'>>> some_bool = False
>>>" wtf"*some_bool
''
intのサブクラスです>>> isinstance(True, int)
True
>>> isinstance(False, int)
True
Trueの整数値は 1であり、 Falseの整数値は 0です。>>> True ==1==1.0 and False ==0==0.0
True
classA:
x =1classB(A):
pass
classC(A):
pass
Output:
>>> 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)
classSomeClass:
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]
Output:
>>> 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
** dict **属性です)。現在のクラスのディクショナリで見つからない場合は、その親クラスに移動します。+= オペレーターは、新しいオブジェクトを作成するのではなく、その場で可変オブジェクトを変更します。したがって、1つのインスタンスのプロパティを変更すると、他のインスタンスおよびクラスのプロパティに影響します。some_iterable =('a','b')
def some_func(val):return"something"
Output:
>>>[ 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])
Output:
>>> some_tuple[2]="change this"
TypeError:'tuple' object does not support item assignment
>>> another_tuple[2].append(1000) #ここにエラーはありません
>>> another_tuple([1,2],[3,4],[5,6,1000])>>> another_tuple[2]+=[99,999]
TypeError:'tuple' object does not support item assignment
>>> another_tuple([1,2],[3,4],[5,6,1000,99,999])
タプルは不変だと思いました...
+= オペレーターはリストをその場で変更します。要素の割り当て操作は機能しませんが、例外がスローされると、要素はその場で変更されます。( 翻訳:不変のオブジェクトの場合、これはタプルを指します。 + =はアトミック操作ではなく、 extendと=の2つのアクションです。=操作は例外をスローしますが、 extend操作は例外です。変更は成功しました。詳細な説明はここにあります)
e =7try:
raise Exception()
except Exception as e:
pass
Output (Python 2.x):
>>> print(e)
# prints nothing
Output (Python 3.x):
>>> print(e)
NameError: name 'e' is not defined
asを使用して例外をターゲットに割り当てると、except句の最後で例外がクリアされます。 except E as N:
foo
に翻訳されます
except E as N:try:
foo
finally:
del N
これは、 except句の後で参照する前に、例外を他の変数に割り当てる必要があることを意味します。例外がクリアされる理由は、追加のトレースバック情報(トラックバック)がスタックフレームで形成されるためです。次のガベージコレクションが発生する前にスタックフレーム内のすべてのローカル変数がアクティブになるようにする循環参照(翻訳:つまり、収集されません)
eは、 except句の実行により削除されます。独立した内部スコープを持つものの場合関数によって状況が異なります。次の例は、これを示しています。 def f(x):del(x)print(x)
x =5
y =[5,4,3]
**Output:**
>>> f(x)
UnboundLocalError: local variable 'x' referenced before assignment
>>> f(y)
UnboundLocalError: local variable 'x' referenced before assignment
>>> x
5>>> y
[5,4,3]
Exception()インスタンスが変数 eに割り当てられているため、結果を出力しようとすると、その出力は空になります(翻訳:通常のExceptionインスタンスは空として出力されます)>>> e
Exception()>>> print e
# 何も印刷されません!
True = False
if True == False:print("I've lost faith in truth!")
Output:
I've lost faith in truth!
True、 False、および boolを追加しましたが、後方互換性のために、 Trueと Falseを定数として設定することはできませんが、組み込み変数として設定します。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})
Output:
>>> print(some_list)
None
>>> print(some_dict)
None
list.append、 dict.update、 list.sortなどのシーケンス/マッピングオブジェクトを変更するほとんどのメソッドは、オブジェクトをその場で変更し、 Noneを返します。これの理由は次の場合です。操作はその場で実行できるため、パフォーマンスを向上させるためにオブジェクトのコピーを作成する必要がありません。(ここを参照)
Output:
>>> from collections import Hashable
>>> issubclass(list, object)
True
>>> issubclass(object, Hashable)
True
>>> issubclass(list, Hashable)
False
サブクラスの関係は一時的なものである必要がありますか?(つまり、 Aが Bのサブクラスであり、 Bが Cのサブクラスである場合、 A *は Cである必要があります。サブクラス。)
** subclasscheck **を定義できます。issubclass(cls、Hashable)が呼び出されると、「 ** hash__」メソッドまたは clsの「 __hash **」から継承されたメソッドを検索するだけです。objectはハッシュ可能ですが、 listはハッシュ可能ではないため、この一時的な関係が壊れます。classSomeClass(str):
pass
some_dict ={'s':42}
Output:
>>> type(list(some_dict.keys())[0])
str
>>> s =SomeClass('s')>>> some_dict[s]=40>>> some_dict #期待される:2つの異なるキーと値のペア
{' s':40}>>>type(list(some_dict.keys())[0])
str
SomeClassは strから ** hash **メソッドを自動的に継承するため、 sオブジェクトと"s"文字列のハッシュ値は同じです。SomeClassは strクラスの ** eq **メソッドも継承するため、 SomeClass(" s ")==" s "は Trueです。SomeClassの** eq **メソッドを再定義できます。classSomeClass(str):
def __eq__(self, other):return(type(self) is SomeClass
and type(other) is SomeClass
and super().__eq__(other))
# カスタマイズするとき__eq__メソッド時間,Pythonは自動的に継承しなくなります__hash__方法
# だから私たちもそれを定義する必要があります
__ hash__ = str.__hash__
some_dict ={'s':42}
Output:
>>> 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
Output:
>>> a
{5:({...},5)}
( target_list "=")+(expression_list | yield_expression)
割り当てステートメントは、式リストを評価し(これは、単一の式またはコンマ区切りのリストである可能性があり、後者はタプルを返します)、ターゲットリストの各項目に左から右に単一の結果オブジェクトを割り当てます。
( target_list "=")+ の+は、** 1つ以上**のターゲットリストが存在する可能性があることを意味します。この例では、ターゲットリストは a、bおよび a [b]です(式に注意してください)式リストは1つしか存在できません。この例では、 ()、5)です。()、5タプルを組み合わせます。 a、bに値を割り当てると、a =()と b = 5を取得できます。 a割り当てられた ()は可変オブジェクトです。a [b]です(前のステートメントでは、 aと bの両方がまだ定義されていないため、ここでエラーが報告されると思われるかもしれません。ただし、忘れないでください。aを追加しただけです。 ()を割り当て、 bを 5に割り当てます)。5の値をタプル({}、5) に設定して循環参照を作成します(出力の{...}は aと同じオブジェクトを参照します) 。以下は、より単純な循環参照の例です。>>> some_list = some_list[0]=[0]>>> some_list
[[...]]>>> some_list[0][[...]]>>> some_list is some_list[0]
True
>>> some_list[0][0][0][0][0][0]== some_list
True
私たちの例はこの状況です( a [b] [0]と aは同じオブジェクトです)
a, b ={},5
a[b]= a, b
そして、 a [b] [0]と aが同じオブジェクトであることにより、それが循環参照であることを証明することができます。
>>> a[b][0] is a
True
疲れたら、休憩しましょう。同様の話を後で共有します。
( 終了)