Python は、動的セマンティクスを備えた高レベルのオブジェクト指向およびインタープリター型プログラミング言語です。その高レベルの統合データ構造は、動的な書き込みとバインディングと組み合わされて、 迅速なアプリケーション開発 、および既存のコンポーネントやサービスを接続するためのスクリプト言語または接着剤として使用するため。 Pythonはモジュールとパッケージで動作するため、プログラムのモジュール性とコードの再利用が促進されます。
Pythonのシンプルで習得しやすい構文で、に送信できます。 Python開発者 間違った方向に-特に言語を学んでいる人-途中でその微妙な部分のいくつかを失い、の力を過小評価している Pythonの多様な言語 。
そのことを念頭に置いて、この記事では、最も高度なPython開発者の一部でさえ不意を突かれる可能性のある微妙で見にくいバグの「トップ10」リストを紹介します。
(( 注意: この記事は、言語に不慣れな人を対象としたCommon Python ProgrammerErrorsよりも上級者を対象としています。 )
Pythonでは、関数の引数をオプションとして指定し、デフォルト値を指定できます。これは言語の優れた機能ですが、デフォルトがである場合、混乱を招く可能性があります。 可変 。たとえば、Python関数の次の定義について考えてみます。
C ++コードを理解する
>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified ... bar.append('baz') # but this line could be problematic, as we'll see... ... return bar
よくある誤解は、オプションの引数に値を指定しなくても関数が呼び出されるたびに、オプションの引数が特定のデフォルトの式に設定されるというものです。たとえば、上記のコードでは、foo()
を呼び出すことを期待できます。複数回(つまり、bar引数を指定せずに)は常にbaz
を返します。これは、仮説が毎回foo()
であるためです。呼び出されます(指定されたbar引数なしで)barは[]
に設定されます(つまり、新しい空のリスト)。
しかし、これが行われたときに実際に何が起こるかを見てみましょう:
>>> foo() ['baz'] >>> foo() ['baz', 'baz'] >>> foo() ['baz', 'baz', 'baz']
ねえ? baz
のデフォルト値がなぜだったのか既存のリストに 毎回 そのfoo()
毎回新しいリストを作成する代わりに、が呼び出されましたか?最も高度なPythonプログラミングの答えは、 関数の引数のデフォルト値は、関数が定義されたときに1回だけ評価されます。 したがって、bar引数は、foo()
の場合にのみ、デフォルト値(つまり、空のリスト)に初期化されます。最初に定義されましたが、次にfoo()
を呼び出します。 (つまり、bar
引数が指定されていない場合)bar
と同じリストを使用します。もともと初期化されました。
ちなみに、これに対する一般的な解決策は次のとおりです。
>>> def foo(bar=None): ... if bar is None: # or if not bar: ... bar = [] ... bar.append('baz') ... return bar ... >>> foo() ['baz'] >>> foo() ['baz'] >>> foo() ['baz']
次の例を考えてみましょう。
>>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print A.x, B.x, C.x 1 1 1
理にかなっています。
>>> B.x = 2 >>> print A.x, B.x, C.x 1 2 1
はい、予想通りです。
>>> A.x = 3 >>> print A.x, B.x, C.x 3 2 3
これは何ですか?変更するだけですA.x
なぜC.x
変わった?
Pythonでは、クラス変数は辞書のように内部的に処理され、しばしば呼ばれるものに従います。 メソッド解決順序(MRO) 。したがって、上記のコードでは、属性xがクラスCに見つからないため、その基本クラスで検索されます(Pythonは多重継承をサポートしていますが、上記の例ではAのみ)。言い換えると、CにはAとは独立した独自のプロパティxがありません。したがって、C.xへの参照は実際にはA.xへの参照です。適切に処理されない限り、これはPythonの問題を引き起こします。についての詳細 Pythonのクラス属性 。
次のコードがあるとします。
>>> try: ... l = ['a', 'b'] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... pass ... Traceback (most recent call last): File '', line 3, in IndexError: list index out of range
ここでの問題は、レポートexcept
この方法で指定された例外のリストは取得されません。対照的に、Python2.xの構文except Exception, e
は、例外を2番目のパラメーターにバインドするために使用されます オプション さらなる検査に利用できるようにするために、指定されています(この場合はe
)。その結果、上記のコードでは、例外IndexError
レポートにキャプチャされていませんexcept
;むしろ、例外は最終的にIndexError
というパラメーターにバインドされます。
レポートで複数の例外をキャッチする正しい方法except
は、最初のパラメーターをとして指定することです。 ダブル キャッチされるすべての例外を含む。また、移植性を最大化するには、構文がPython2およびPython3でサポートされているため、as
キーワードを使用します。
>>> try: ... l = ['a', 'b'] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
Pythonのスコープ解決は、ルールと呼ばれるものに基づいています LEGB 、の略です L オカル、 IS 閉会、 G ローブ、 B uilt-in。かなり簡単そうですね。実際、これがPythonで機能する方法にはいくつかの微妙な点があります。これにより、以下の一般的な、より高度なPythonプログラミングの問題が発生します。
次のことを考慮してください。
>>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File '', line 1, in File '', line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment
何が問題ですか?
上記のエラーは、 割り当て スコープ内の変数に、 その変数は、Pythonによって自動的に、そのスコープ内でローカルと見なされます そして、任意の外部スコープで、同様の名前の任意の変数に従います。
したがって、それらの多くはUnboundLocalError
を取得して驚いています。上記の作業コードで、関数の本体のどこかにレポートステートメントを追加して変更した場合。 (これについてもっと読むことができます ここに 。)
Linuxカーネルのほとんどが書かれているプログラミング言語はどれですか?
これは、使用時に開発者を混乱させることが特に一般的です リスト 。次の例を考えてみましょう。
>>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5) # This works ok... ... >>> foo1() >>> lst [1, 2, 3, 5] >>> lst = [1, 2, 3] >>> def foo2(): ... lst += [5] # ... but this bombs! ... >>> foo2() Traceback (most recent call last): File '', line 1, in File '', line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment
ねえ?なぜfoo2
失敗しましたが、foo1
とてもうまくいきましたか?
答えは前の例の問題と同じですが、確かにもっと微妙です。 foo1
作っていない 割り当て a lst
、foo2
もしそれが。それを覚えているlst += [5]
は実際にはlst = lst + [5]
の略で、lst
に値を割り当てようとしていることがわかります。 (したがって、Pythonはそれがローカルスコープにあると想定します)。ただし、lstに割り当てようとしている値は、同じlst
に基づいています。 (ここでも、ローカルであると推定されます)、これはまだ定義されていません。ブーム。
次のコードの問題はかなり明白なはずです。
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i] # BAD: Deleting item from a list while iterating over it ... Traceback (most recent call last): File '', line 2, in IndexError: list index out of range
リストまたは配列からアイテムを繰り返し処理しながら削除することは、経験豊富なソフトウェア開発者にはよく知られているPythonの問題です。ただし、上記の例は非常に明白かもしれませんが、高度な開発者でさえ、このはるかに複雑なコードにうっかり気を紛らわされる可能性があります。
幸い、Pythonには多くの洗練されたプログラミングパラダイムが組み込まれており、正しく使用すると、コードが大幅に簡素化および合理化されます。これの副次的な利点は、単純なコードが誤ってリストからアイテムを削除し、繰り返し処理することによってバグに捕らえられる可能性が低いことです。そのようなパラダイムの1つは、[リスト内包表記]((https://docs.python.org/2/tutorial/datastructures.html#tut-listcomps)です。一方、リスト内包表記は、この特定の問題を回避するのに特に役立ちます。上記のコードのこの代替実装に示されています。これは完全に機能します。
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all >>> numbers [0, 2, 4, 6, 8]
次の例を検討します。
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
次の結果が期待できます。
0 2 4 6 8
しかし、実際には次のようになります。
デザインのプロトタイピングとは
8 8 8 8 8
驚き!
これは行動が原因で発生します 遅いリンク Pythonは、クロージャで使用される変数の値が、内部関数が呼び出された瞬間にフェッチされることを示しています。したがって、上記のコードでは、返された関数のいずれかが呼び出されると、i
の値が呼び出されます。欲しかった それが呼ばれたときのその周りの領域 (そしてその時点で、円は完成しているので、i
にはすでに最終値4が割り当てられています)。
この一般的なPythonの問題の解決策は、ちょっとしたハックです。
>>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ... >>> for multiplier in create_multipliers(): ... print multiplier(2) ... 0 2 4 6 8
Voilà!目的の動作を実現するために、デフォルトの引数を利用して無名関数を生成しています。これをエレガントと呼ぶ人もいます。微妙だと言う人もいます。嫌いな人もいます。ただし、Python開発者の場合、これを理解することが重要です。
次のように、a.pyとb.pyの2つのファイルがあり、それぞれが他方をインポートするとします。
でa.py
:
import b def f(): return b.x print f()
そしてb.py
:
import a x = 1 def g(): print a.f()
まず、インポートしてみましょうa.py
:
>>> import a 1
それは非常にうまくいきました。多分あなたは驚いたでしょう。結局のところ、ここには循環インポートがありますが、これはおそらく問題になるはずですよね?
答えは、単なる プレゼンス 循環インポートの問題は、Pythonではそれほど問題ではありません。モジュールがすでにインポートされている場合、Pythonはそれを再度インポートしようとしないほど賢いです。ただし、各モジュールが他のモジュールで定義されている関数または変数にアクセスしようとしているポイントによっては、問題が発生する可能性があります。
例に戻ると、a.py
をインポートしたとき、b.py
なので、b.py
のインポートに問題はありませんでした。何も必要ありませんb.py
インポート時に定義されます。 b.py
の唯一の参照a a
は、a.f()
への呼び出しです。しかし、その呼び出しはg()
にありますa.py
には何もありませんまたはb.py
g()
を呼び出します。だから、人生は美しいです。
しかし、インポートしようとするとどうなりますかb.py
(もちろん、以前にインポートしたことがなくてもa.py
):
>>> import b Traceback (most recent call last): File '', line 1, in File 'b.py', line 1, in import a File 'a.py', line 6, in print f() File 'a.py', line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'
ええとああ。それは良いことではありません!ここでの問題は、インポートプロセスb.py
で、a.py
をインポートしようとし、その結果、f()
を呼び出し、b.x
にアクセスしようとすることです。しかしb.x
まだ定義されていません。したがって、例外AttributeError
。
これに対する少なくとも1つの解決策は非常に簡単です。変更するだけですb.py
インポートするa.py
内部g()
:
x = 1 def g(): import a # This will be evaluated only when g() is called print a.f()
インポートすると、すべて問題ありません。
>>> import b >>> b.g() 1 # Printed a first time since module 'a' calls 'print f()' at the end 1 # Printed a second time, this one is our call to 'g'
Pythonの利点の1つは、最初から付属しているライブラリモジュールの数が多いことです。しかし、結果として、これを意識的に回避しなければ、モジュールの1つの名前と、Pythonに付属する標準ライブラリ内の同じ名前のモジュールとの間で名前の衝突に遭遇することはそれほど難しくありません(たとえば、コードにemail.py
という名前のモジュールがあり、同じ名前の標準ライブラリモジュールと競合する可能性があります。
スタートアップのための資金調達方法
これは、モジュールの標準Pythonライブラリバージョンをインポートしようとする別のライブラリをインポートするなど、非常に深刻な問題を引き起こす可能性がありますが、同じ名前のモジュールがすでにあるため、他のパッケージが誤ってバージョンをインポートします。標準のPythonライブラリ内にあるものの1つであり、これが最も深刻なエラーが発生する場所です。
したがって、標準のPythonライブラリモジュールと同じ名前を使用しないように注意する必要があります。 Pythonの改善提案を提示するよりも、パッケージ内のモジュールの名前を変更する方がはるかに簡単です。 (PEP) アップストリーム名の変更を要求し、承認を受けるため。
次のファイルを検討してくださいfoo.py
:
import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e) bad()
Python 2では、これはうまく機能します。
$ python foo.py 1 key error 1 $ python foo.py 2 value error 2
しかし、Python3を試してみましょう。
$ python3 foo.py 1 key error Traceback (most recent call last): File 'foo.py', line 19, in bad() File 'foo.py', line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment
ここで何が起こったのですか? 「問題」は、Python 3では、except
ブロックの範囲を超えて例外オブジェクトにアクセスできないことです。 (これは、ガベージコレクターが実行されて参照がメモリから削除されるまで、スタックフレームを使用して参照ループをメモリに保持するためです。これに関する技術的な詳細については、こちらをご覧ください。 ここに )。
この問題を回避する1つの方法は、例外オブジェクトへの参照をexceptブロックのスコープ外に保ち、アクセス可能な状態を維持することです。これは、この手法を使用する上記の例のバージョンです。これにより、コードが投与され、Python2およびPython3との互換性が高まります。
import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def good(): exception = None try: bar(int(sys.argv[1])) except KeyError as e: exception = e print('key error') except ValueError as e: exception = e print('value error') print(exception) good()
これの実行はPy3kで行われます:
ノードjsでAPIを休ませます
$ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2
¡ユピ!
(ちなみに、 Python採用ガイド コードをPython2からPython3に移行する際に注意すべき、いくつかの重要な違いについて説明します。)
__del__
の誤用mod.py
というファイルにこれがあったとしましょう。
import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)
そして、あなたはこれをanother_mod.py
からやろうとしました:
import mod mybar = mod.Bar()
醜い例外が発生しますAttributeError
。
どうして?なぜなら、報告されているように ここに インタプリタがオフになると、モジュールのグローバル変数はNone
に設定されます。その結果、上記の例では、 __del__
呼び出されると、fooという名前はすでにNone
に設定されています。
この問題の解決策は、Pythonプログラミングよりもいくらか高度です。 atexit.register()
代わりに。このように、プログラムが実行されると(つまり、正常に終了すると)、登録済みのマネージャーが解雇されます。 前 通訳がオフになっていること。
この知識があれば、前のコードの解決策mod.py
これは次のようになります。
import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle)
このアプリケーションは、プログラムの通常の終了後に必要なクリーニング機能を呼び出すためのクリーンで信頼性の高い方法を提供します。明らかに、self.myhandleという名前に付けられたオブジェクトをどう処理するかを決めるのはfoo.cleanup次第ですが、要点はわかります。
Pythonは、生産性を大幅に向上させることができる多くのメカニズムとパラダイムを備えた強力で柔軟な言語です。ただし、他のソフトウェアや言語ツールと同様に、その機能の理解や認識が限られていると、資産よりも障害になることがあり、「危険を十分に理解している」ということわざの状態になります。
この記事で説明した中程度に高度なプログラミングの問題など、Pythonの重要なニュアンスに精通することで、言語の使用を最適化し、より一般的な間違いを回避できます。
あなたは私たちをチェックする必要があります Pythonインタビューのインサイダーガイド 、Pythonの専門家を特定するのに役立つ面接の質問に関する提案。
この記事のヒントがお役に立てば幸いです。フィードバックをお待ちしております。