Pythonのあまり知られていない話

この記事を読むのに約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

わかりやすいですよね?

? 説明:


> ハッシュブラウニーの時間です!/ケーキの時間です!

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

**注:**異なる値を持つオブジェクトも同じハッシュ値を持つ場合があります(ハッシュの衝突)。


**> どこにでも戻る!/どこにでも戻る! **

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

Output:

>>> some_func()'from_finally'

? 説明:


> 深く、私たちはすべて同じです。/本質的に、私たちはすべて同じです。*

classWTF:
 pass

Output:

>>> WTF()==WTF() #2つの異なるオブジェクトは等しくてはいけません
False
>>> WTF() is WTF() #同じではありません
False
>>> hash(WTF())==hash(WTF()) #ハッシュ値も異なる必要があります
True
>>> id(WTF())==id(WTF())
True

? 説明:

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_stmt:'for' exprlist 'in' testlist ':' suite ['else'':' suite]

その中で、 exprlistは割り当てターゲットを参照します。これは、{exprlist} =(next_value}と同様の操作が反復可能オブジェクトの各アイテムに対して実行されることを意味します。

興味深い例はこれを示しています:

for i inrange(4):print(i)
  i =10

Output:

0123

このループは一度だけ実行されると思いますか?

? 説明:

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

? 説明


> 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と == **の違い

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

> Xが最初の試みで勝つtic-tac-toe!/スワイプ!

# 最初に可変行を初期化します
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

? 説明:


> 意外なコンマ/偶発的なコンマ

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

? 説明:

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

? 説明:


> ハーフトリプルクォート文字列/ 3つのクォーテーションマーク

Output:

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # 次のステートメントはスローします`SyntaxError`異常な
>>> # print('''wtfpython')>>> # print("""wtfpython")

? 説明:

>>> print("wtf""python")
 wtfpython
 >>> print("wtf""") # or "wtf"""
 wtf

> 真夜中の時間は存在しませんか?/真夜中は存在しませんか?

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であるか、「空の」値であるかを確認すると、間違いを犯しやすくなります。


> booleansの何が問題になっていますか?/ Booleans?

# 簡単な例,以下の反復可能なオブジェクトのブール値と整数値の数を数えます
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
''

? 説明:

>>> isinstance(True, int)
 True
 >>> isinstance(False, int)
 True
>>> 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

? 説明:


> 生成なし/生成なし

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

? 説明:

 except E as N:
  foo

に翻訳されます

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

これは、 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]
>>> e
 Exception()>>> print e
 # 何も印刷されません!

> Trueが実際にFalse /真亦假 ##の場合

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

Output:

I've lost faith in truth!

? 説明:


> 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})

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である必要があります。サブクラス。)

? 説明:


> 不思議な鍵の種類の変換/不思議な鍵の種類の変換*

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

? 説明:

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)

割り当てステートメントは、式リストを評価し(これは、単一の式またはコンマ区切りのリストである可能性があり、後者はタプルを返します)、ターゲットリストの各項目に左から右に単一の結果オブジェクトを割り当てます。

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

疲れたら、休憩しましょう。同様の話を後で共有します。

( 終了)

Recommended Posts

Pythonのあまり知られていない話
pythonの@abstractmethod