Python Language
共通落とし穴
サーチ…
前書き
Pythonはあいまいさや予期しない振る舞いがなく、明確で読みやすい言語です。残念ながら、これらの目標はすべての場合に達成可能ではありません。そのため、Pythonはあなたが期待していたものとは異なる何かを行うかもしれないいくつかのコーナーケースを持っています。
このセクションでは、Pythonコードを書くときに遭遇する可能性があるいくつかの問題を示します。
反復処理中のシーケンスを変更する
for
ループはシーケンスを反復処理するので、ループ内でこのシーケンスを変更すると、 (特にエレメントの追加や削除時に)予期しない結果になる可能性があります 。
alist = [0, 1, 2]
for index, value in enumerate(alist):
alist.pop(index)
print(alist)
# Out: [1]
注意: list.pop()
は、リストから要素を削除するために使用されています。
2番目の要素は、イテレーションが順番にインデックスを通過するため、削除されませんでした。上記のループは2回反復され、次の結果が返されます。
# Iteration #1
index = 0
alist = [0, 1, 2]
alist.pop(0) # removes '0'
# Iteration #2
index = 1
alist = [1, 2]
alist.pop(1) # removes '2'
# loop terminates, but alist is not empty:
alist = [1]
この問題は、インデックスが増加する方向に反復しながらインデックスが変化しているために発生します。この問題を回避するには、ループを逆方向に反復することができます。
alist = [1,2,3,4,5,6,7]
for index, item in reversed(list(enumerate(alist))):
# delete all even items
if item % 2 == 0:
alist.pop(index)
print(alist)
# Out: [1, 3, 5, 7]
最後からループを反復することで、項目が削除(または追加)されるので、リストの前の項目のインデックスには影響しません。したがって、この例では、 alist
からもすべてのアイテムが適切に削除されalist
。
反復処理中のリストに要素を挿入または追加すると、同様の問題が発生し、無限ループが発生する可能性があります。
alist = [0, 1, 2]
for index, value in enumerate(alist):
# break to avoid infinite loop:
if index == 20:
break
alist.insert(index, 'a')
print(alist)
# Out (abbreviated): ['a', 'a', ..., 'a', 'a', 0, 1, 2]
break
条件がなければ、コンピュータはメモリ不足でプログラムが続行されない限り、ループは'a'
を挿入します。このような状況では、通常、新しいリストを作成し、元のリストをループしながら新しいリストに項目を追加することをお勧めします。
for
ループを使用する場合は、プレースホルダ変数を使用してリスト要素を変更することはできません 。
alist = [1,2,3,4]
for item in alist:
if item % 2 == 0:
item = 'even'
print(alist)
# Out: [1,2,3,4]
上記の例では、 item
変更しても元のリストでは何も変更されません 。リストインデックス( alist[2]
)を使用する必要があり、 enumerate()
はこれに対してうまくいきます:
alist = [1,2,3,4]
for index, item in enumerate(alist):
if item % 2 == 0:
alist[index] = 'even'
print(alist)
# Out: [1, 'even', 3, 'even']
いくつかのケースではwhile
ループを使用する方が良いでしょう。
リスト内のすべての項目を削除する場合は、次のようにします。
zlist = [0, 1, 2]
while zlist:
print(zlist[0])
zlist.pop(0)
print('After: zlist =', zlist)
# Out: 0
# 1
# 2
# After: zlist = []
単にzlist
をリセットすると同じ結果が得られますが、
zlist = []
上記の例はlen()
と組み合わせて、ある点の後で停止することも、リスト内のx
個の項目以外のすべてを削除することもできます:
zlist = [0, 1, 2]
x = 1
while len(zlist) > x:
print(zlist[0])
zlist.pop(0)
print('After: zlist =', zlist)
# Out: 0
# 1
# After: zlist = [2]
または、特定の条件を満たす要素を削除しながらリストをループすることができます (この場合、すべての偶数要素を削除します)。
zlist = [1,2,3,4,5]
i = 0
while i < len(zlist):
if zlist[i] % 2 == 0:
zlist.pop(i)
else:
i += 1
print(zlist)
# Out: [1, 3, 5]
要素を削除した後は、 i
をインクリメントしないことに注意してください。ある要素を削除することによってzlist[i]
チェックすることによってので、次の項目のインデックスは、1減少しているzlist[i]
ため、同じ値を持つi
の次の反復で、あなたは正しく、リスト内の次の項目をチェックします。
不要なアイテムをリストから削除することを考えると、反対の方法は、新しいリストに必要なアイテムを追加することです。次の例では、後者の代替でwhile
ループの例:
zlist = [1,2,3,4,5]
z_temp = []
for item in zlist:
if item % 2 != 0:
z_temp.append(item)
zlist = z_temp
print(zlist)
# Out: [1, 3, 5]
ここでは、望ましい結果を新しいリストに集めています。その後、オプションで一時リストを元の変数に再割り当てすることができます。
このような思考の流れの中で、Pythonのもっともエレガントで強力な機能、 リスト内包を呼び出すことで、一時的なリストを削除し、前述のインプレースリスト/インデックスの変異イデオロギーから逸脱することができます。
zlist = [1,2,3,4,5]
[item for item in zlist if item % 2 != 0]
# Out: [1, 3, 5]
変更可能なデフォルト引数
def foo(li=[]):
li.append(1)
print(li)
foo([2])
# Out: [2, 1]
foo([3])
# Out: [3, 1]
このコードは期待通りに動作しますが、引数を渡さないとどうなりますか?
foo()
# Out: [1] As expected...
foo()
# Out: [1, 1] Not as expected...
これは、関数とメソッドのデフォルトの引数が、実行時ではなく定義時に評価されるためです。ですから、私たちはli
リストのインスタンスを1つしか持てません。
それを回避する方法は、デフォルトの引数に不変の型だけを使うことです:
def foo(li=None):
if not li:
li = []
li.append(1)
print(li)
foo()
# Out: [1]
foo()
# Out: [1]
改良されていますがif not li
がFalse
に正しく評価されても、長さゼロのシーケンスなど、多くの他のオブジェクトも同様に機能します。次の引数の例は、意図しない結果を引き起こす可能性があります。
x = []
foo(li=x)
# Out: [1]
foo(li="")
# Out: [1]
foo(li=0)
# Out: [1]
慣用的なアプローチは、 None
オブジェクトに対して引数を直接チェックすることです。
def foo(li=None):
if li is None:
li = []
li.append(1)
print(li)
foo()
# Out: [1]
乗算と共通参照のリスト
次のように掛け合わせて入れ子構造のリスト構造を作成する場合を考えてみましょう。
li = [[]] * 3
print(li)
# Out: [[], [], []]
一見すると、3つの異なる入れ子リストを含むリストがあると思います。最初のものに1
を追加しようとしましょう:
li[0].append(1)
print(li)
# Out: [[1], [1], [1]]
1
がli
すべてのリストに追加されました。
その理由は、 [[]] * 3
は3つの異なるlist
のlist
を作成しないからです。むしろ、同じlist
オブジェクトへの3つの参照を保持するlist
を作成します。したがって、 li[0]
追加すると、その変更はli
すべての下位要素で表示されます。これは以下と同じです:
li = []
element = [[]]
li = element + element + element
print(li)
# Out: [[], [], []]
element.append(1)
print(li)
# Out: [[1], [1], [1]]
これは、 id
を使って、含まれているlist
メモリアドレスを出力すると、さらに裏付けられます:
li = [[]] * 3
print([id(inner_list) for inner_list in li])
# Out: [6830760, 6830760, 6830760]
解決策は、内部リストをループで作成することです。
li = [[] for _ in range(3)]
1つのlist
を作成して3つの参照を作成するのではなく、3つの異なるリストを作成します。これは、 id
関数を使用して検証することができます。
print([id(inner_list) for inner_list in li])
# Out: [6331048, 6331528, 6331488]
あなたもこれを行うことができます。これにより、各append
呼び出しで新しい空のリストが作成されappend
。
>>> li = []
>>> li.append([])
>>> li.append([])
>>> li.append([])
>>> for k in li: print(id(k))
...
4315469256
4315564552
4315564808
シーケンスをループするためにインデックスを使用しないでください。
しないでください:
for i in range(len(tab)):
print(tab[i])
行う :
for elem in tab:
print(elem)
for
あなたのための最も反復操作を自動化します。
インデックスと要素の両方が本当に必要な場合は、列挙型を使用します 。
for i, elem in enumerate(tab):
print((i, elem))
"=="を使用してTrueまたはFalseをチェックする場合は注意してください
if (var == True):
# this will execute if var is True or 1, 1.0, 1L
if (var != True):
# this will execute if var is neither True nor 1
if (var == False):
# this will execute if var is False or 0 (or 0.0, 0L, 0j)
if (var == None):
# only execute if var is None
if var:
# execute if var is a non-empty string/list/dictionary/tuple, non-0, etc
if not var:
# execute if var is "", {}, [], (), 0, None, etc.
if var is True:
# only execute if var is boolean True, not 1
if var is False:
# only execute if var is boolean False, not 0
if var is None:
# same as var == None
できるかどうかは確認しないでください。エラーを処理してください
Pythonistasは通常、「許可よりも許しを求めるのは簡単です」と言います。
しないでください:
if os.path.isfile(file_path):
file = open(file_path)
else:
# do something
行う:
try:
file = open(file_path)
except OSError as e:
# do something
Python 2.6+
さらに優れていPython 2.6+
:
with open(file_path) as file:
はるかに一般的なので、はるかに優れています。ほとんどのものにtry/except
を適用することができます。あなたはそれを防ぐために何をすべきか心配する必要はありません。あなたが危険にさらされているエラーに気を付けるだけです。
タイプに対してチェックしない
Pythonは動的に型付けされているため、型のチェックで柔軟性が失われます。代わりに、動作を確認してダックタイピングを使用してください。関数内に文字列があると予想される場合は、 str()
を使用して任意のオブジェクトを文字列に変換します。リストが必要な場合は、list list()
を使用して任意のイテレートをリストに変換します。
しないでください:
def foo(name):
if isinstance(name, str):
print(name.lower())
def bar(listing):
if isinstance(listing, list):
listing.extend((1, 2, 3))
return ", ".join(listing)
行う:
def foo(name) :
print(str(name).lower())
def bar(listing) :
l = list(listing)
l.extend((1, 2, 3))
return ", ".join(l)
最後の方法を使用して、 foo
は任意のオブジェクトを受け入れます。 bar
は、文字列、タプル、セット、リストなどを受け入れます。格安DRY。
スペースとタブを混在させないでください
オブジェクトを最初の親として使用する
これはやりにくいですが、プログラムが成長するにつれてあなたを噛んでしまいます。 Python 2.x
は古いクラスと新しいクラスがあります。古いものは、よく、古いものです。彼らはいくつかの機能を欠いており、継承で扱いにくいことがあります。使用できるようにするには、クラスのいずれかが「新しいスタイル」でなければなりません。これを行うには、 object
から継承させobject
。
しないでください:
class Father:
pass
class Child(Father):
pass
行う:
class Father(object):
pass
class Child(Father):
pass
Python 3.x
すべてのクラスが新しいスタイルなので、そうする必要はありません。
initメソッドの外部でクラス属性を初期化しない
他の言語から来ている人は、それがJavaやPHPでやっているので、魅力的です。クラス名を記述し、属性をリストしてデフォルト値を与えます。しかし、Pythonではうまくいくようですが、これはあなたが考えるようには機能しません。そうすることで、クラス属性(静的属性)が設定され、オブジェクト属性を取得しようとすると空でない限り値が得られます。その場合、クラス属性を返します。それは2つの大きな危険を意味します:
class属性が変更された場合、初期値が変更されます。
可変オブジェクトをデフォルト値として設定すると、同じオブジェクトがインスタンス間で共有されます。
しないでください(静的にしない限り):
class Car(object):
color = "red"
wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
行う :
class Car(object):
def __init__(self):
self.color = "red"
self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
整数と文字列の同一性
Pythonは、一連の整数に対して内部キャッシュを使用して、繰り返しの作成から不要なオーバーヘッドを減らします。
実際には、これは整数アイデンティティを比較するときに混乱する動作につながる可能性があります。
>>> -8 is (-7 - 1)
False
>>> -3 is (-2 - 1)
True
別の例を使用すると、
>>> (255 + 1) is (255 + 1)
True
>>> (256 + 1) is (256 + 1)
False
待って?
私たちは、アイデンティティの操作があることがわかりますis
利回りTrue
いくつかの整数の( -3
、 256
他人(のため)が、無-8
、 257
)。
具体的には、 [-5, 256]
範囲の整数はインタプリタの起動時に内部的にキャッシュされ、一度だけ作成されます。このように、これらは同一であり、で自分のアイデンティティを比較することis
利回りTrue
。この範囲外の整数は(通常)オンザフライで作成され、そのIDはFalse
と比較されます。
これはテストの一般的な範囲であるため、一般的な落とし穴です。しかし、多くの場合、コードは後のステージングプロセス(または悪い生産)で失敗します。
解決策は、アイデンティティ( is
)演算子でis
なく 、 等価 ( ==
)演算子を使用して常に値を比較することです 。
Pythonは一般的に使用される文字列への参照を保持し、文字列のアイデンティティ(つまりis
を使用する)を比較するときに同様の混乱を招く可能性is
あります。
>>> 'python' is 'py' + 'thon'
True
'python'
という文字列がよく使われるので、Pythonは'python'
という文字列への参照がすべて1つのオブジェクトを持っています。
一般的でない文字列の場合、文字列が等しい場合でも識別情報の比較に失敗します。
>>> 'this is not a common string' is 'this is not' + ' a common string'
False
>>> 'this is not a common string' == 'this is not' + ' a common string'
True
したがって、Integersのルールと同じように、 常にアイデンティティ( is
)演算子でis
なく 等価 ( ==
)演算子を使用して文字列値を比較します 。
intリテラルの属性へのアクセス
Pythonのすべてがリテラルであっても、オブジェクトであると聞いたことがあります。これは、たとえば、 7
もオブジェクトであることを意味します。これは属性を持つことを意味します。たとえば、これらの属性の1つはbit_length
です。呼び出された値を表すために必要なビット数を返します。
x = 7
x.bit_length()
# Out: 3
上記のコードが動作を見て、あなたは直感的だと思うかもしれません7.bit_length()
のみそれが提起見つけるために、同様に機能するSyntaxError
。どうして?なぜならインタープリタは属性アクセスと浮動小数点(例えば7.2
または7.bit_length()
)を区別する必要があるからです。それはできないので、例外が発生します。
int
リテラルの属性にアクセスするには、いくつかの方法があります。
# parenthesis
(7).bit_length()
# a space
7 .bit_length()
この場合、2つのドット(この7..bit_length()
)を使用すると、 float
リテラルが作成され、 float
はbit_length()
メソッドがないため、この場合は機能しません。
この問題は、 float
リテラルに2つを含めることができないことを知るのに十分な "スマート"なので、 float
リテラルの属性にアクセスするときには存在しません.
、 例えば:
7.2.as_integer_ratio()
# Out: (8106479329266893, 1125899906842624)
チェインまたはオペレータ
いくつかの等価比較のいずれかをテストするとき:
if a == 3 or b == 3 or c == 3:
これを省略すると魅力的です
if a or b or c == 3: # Wrong
これは間違っています; or
オペレータが有する低い優先順位より==
式のように評価されるように、 if (a) or (b) or (c == 3):
正しい方法は、すべての条件を明示的にチェックすることです。
if a == 3 or b == 3 or c == 3: # Right Way
代わりに、組み込みのany()
関数を連鎖or
演算子の代わりに使用することもできます。
if any([a == 3, b == 3, c == 3]): # Right
あるいは、より効率的にするために:
if any(x == 3 for x in (a, b, c)): # Right
または、それを短くする:
if 3 in (a, b, c): # Right
ここでは、 in
演算子を使用してin
比較する値を含むタプルに値が存在するかどうかをテストします。
同様に、
if a == 1 or 2 or 3:
これは
if a in (1, 2, 3):
sys.argv [0]は実行されているファイルの名前です
sys.argv[0]
の最初の要素は、実行されているPythonファイルの名前です。残りの要素はスクリプト引数です。
# script.py
import sys
print(sys.argv[0])
print(sys.argv)
$ python script.py
=> script.py
=> ['script.py']
$ python script.py fizz
=> script.py
=> ['script.py', 'fizz']
$ python script.py fizz buzz
=> script.py
=> ['script.py', 'fizz', 'buzz']
辞書は順序付けられていません
Python辞書は、例えばC ++ std::map
ようなキーでソートされると期待されるかもしれませんが、そうではありません:
myDict = {'first': 1, 'second': 2, 'third': 3}
print(myDict)
# Out: {'first': 1, 'second': 2, 'third': 3}
print([k for k in myDict])
# Out: ['second', 'third', 'first']
Pythonには、キーで要素を自動的にソートするビルトインクラスはありません。
ただし、ソートが必須ではなく、キー/値ペアの挿入順序を辞書に記憶させたい場合は、 collections.OrderedDict
を使用できます。
from collections import OrderedDict
oDict = OrderedDict([('first', 1), ('second', 2), ('third', 3)])
print([k for k in oDict])
# Out: ['first', 'second', 'third']
標準辞書でOrderedDict
を初期化しても、 OrderedDict
辞書は決して並べ替えられません。この構造がすることは、キー挿入の順序を保持することだけです。
辞書の実装はPython 3.6でメモリ消費量を改善するために変更されました 。この新しい実装の副作用は、関数に渡されるキーワード引数の順序も保持するということです。
def func(**kw): print(kw.keys())
func(a=1, b=2, c=3, d=4, e=5)
dict_keys(['a', 'b', 'c', 'd', 'e']) # expected order
警告 :ことを注意してください「 この新しい実装のため保存の観点は、実装の詳細とみなされ、依拠すべきではない 」それは将来変更される可能性として、。
グローバルインタープリタロック(GIL)とスレッドのブロック
Pythonの GIL についてたくさん書かれています。マルチスレッド(マルチプロセスと混同しないでください)アプリケーションを扱うときに混乱を招くことがあります。
ここに例があります:
import math
from threading import Thread
def calc_fact(num):
math.factorial(num)
num = 600000
t = Thread(target=calc_fact, daemon=True, args=[num])
print("About to calculate: {}!".format(num))
t.start()
print("Calculating...")
t.join()
print("Calculated")
スレッドが開始された直後にCalculating...
されることが期待されますが、結局のところ新しいスレッドで計算が実行されたかったのです!しかし、実際には、計算が完了した後に印刷されることがわかります。これは、新しいスレッドが実行中にGILをロックするC関数( math.factorial
)に依存しているためです。
これにはいくつかの方法があります。最初は、ネイティブPythonで階乗関数を実装することです。これによりループ内にメインスレッドが制御権を持つことができます。欠点は、C関数をもう使用していないので、この解決策はかなり遅くなることです。
def calc_fact(num):
""" A slow version of factorial in native Python """
res = 1
while num >= 1:
res = res * num
num -= 1
return res
実行を開始する前に一定期間sleep
することもできます。注意:これは実際にあなたのプログラムがC関数内で起こっている計算を中断するのを許可しませんが、あなたが期待しているかもしれないあなたのメインスレッドがスポーンの後に続くことを許します。
def calc_fact(num):
sleep(0.001)
math.factorial(num)
リストの理解とループの変数の漏れ
次のリストの理解を考えてみましょう
i = 0
a = [i for i in range(3)]
print(i) # Outputs 2
これは、リストの理解がループ制御変数を周囲のスコープ( ソース )に「漏らす」という事実のために、Python 2でのみ発生します。この動作は、見つけにくいバグにつながり、Python 3で修正されています。
i = 0
a = [i for i in range(3)]
print(i) # Outputs 0
同様に、forループは反復変数のプライベートスコープを持たない
i = 0
for i in range(3):
pass
print(i) # Outputs 2
このタイプの動作は、Python 2とPython 3の両方で発生します。
リークする変数の問題を回避するには、リスト内包表記および新しいループを適切な方法で使用します。
複数返品
関数xyzは、aとbの2つの値を返します。
def xyz():
return a, b
コードを呼び出すxyzは、xyzが1つの値しか返さないと仮定して、結果を1つの変数に格納します。
t = xyz()
値t
、実際のタプル(a、b)はこれに任意のアクションでありt
がタプルがタプルについて予期しないエラーを有するコードに深い失敗する可能性がされていないと仮定します。
TypeError:type tupleは...メソッドを定義していません
修正は次のようになります:
a, b = xyz()
初心者はタプルのエラーメッセージを読むだけでこのメッセージの理由を見つけるのが難しいでしょう!
PythonのJSONキー
my_var = 'bla';
api_key = 'key';
...lots of code here...
params = {"language": "en", my_var: api_key}
JavaScriptに慣れている場合、Python辞書の変数評価は期待通りではありません。 JavaScriptのこのステートメントは、次のようにparams
オブジェクトになります。
{
"language": "en",
"my_var": "key"
}
ただし、Pythonでは次のような辞書になります。
{
"language": "en",
"bla": "key"
}
my_var
が評価され、その値がキーとして使用されます。