ホームページ  >  記事  >  バックエンド開発  >  Python プログラマーが犯しやすい 10 の間違い

Python プログラマーが犯しやすい 10 の間違い

高洛峰
高洛峰オリジナル
2016-11-23 13:35:32974ブラウズ

勉強や仕事の過程に関係なく、人は間違いを犯します。 Python の構文はシンプルで柔軟ですが、注意しないと、初心者も経験豊富な Python プログラマーもつまずく可能性があります。この記事では、一般的な 10 の間違いをすべての人に共有します。必要な友人は参照してください。よくある間違い 1: 式を関数のデフォルト パラメーターとして誤って使用する

Python では、関数のパラメーターにデフォルト値を設定できます。 , そのため、このパラメータはオプションになります。これは優れた言語機能ですが、デフォルト値が変更可能な型である場合には、混乱を招く状況が生じる可能性もあります。次の Python 関数定義を見てみましょう:
>>> def foo(bar=[]): # bar の値が指定されていない場合は、デフォルトで []、
になります。 ... bar.append("baz") # しかし、このコード行が問題を引き起こすことが後でわかります。
... リターンバー

Python プログラマーが犯すよくある間違いは、関数が呼び出されるたびに、オプションのパラメーターに値が渡されなければ、オプションのパラメーターが指定された値に設定されることを当然のことと考えることです。 .デフォルト値。上記のコードでは、foo() 関数を繰り返し呼び出すと常に 'baz' が返されるはずだと思うかもしれません。デフォルトでは、foo() 関数が実行されるたびに (bar 変数の値が指定されていない)、bar が返されるからです。変数は [ ] (つまり、新しい空のリスト) に設定されます。

ただし、実際の実行結果は次のようになります:
>>> foo()
["baz"]
>>> foo()["baz", "baz"]
> ;>> foo()
["バズ"、"バズ"、"バズ"]

おかしくないですか? foo() 関数が呼び出されるたびに、新しい空のリストを作成するのではなく、デフォルト値「baz」が既存のリストに追加されるのはなぜですか?

答えは、オプションのパラメーターのデフォルト値の設定は、Python で 1 回だけ、つまり関数の定義時にのみ実行されるということです。したがって、foo() 関数が定義されたときのみ、bar パラメータはデフォルト値 (つまり、空のリスト) に初期化されますが、その後 foo() 関数が呼び出されるたびに、bar パラメータは引き続き初期化されます。そのリストの元の値で初期化されます。

もちろん、一般的な解決策は次のとおりです:
>>> def foo(bar=None):
... bar が None の場合: # または bar:
... bar = []
。 .. bar.append("baz")
... return bar
...
>>> foo()
["baz"]
>>> foo()
[" baz "]
> :
... x = 1
...
>>> クラス B(A):
... pass
...
>>> クラス 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

Python 言語では、クラス変数はメソッドの解析順序に従います。解決命令、MRO)。したがって、上記のコードでは、クラス C には x 属性がないため、インタプリタはその基本クラスを探します (ただし、Python は多重継承をサポートしていますが、この例では、C の基本クラスは A のみです)。言い換えれば、C は A から独立した独自の x 属性を持たず、実際に A に属します。したがって、C.x を参照すると、実際には A.x を参照します。ここでの関係を適切に処理しないと、例の問題が発生します。
よくある間違い 3: 例外ブロック (例外ブロック) のパラメーターの誤った指定

次のコードを見てください:
>>> try:
... l = ["a", "b"]
... int(l[2])
... ValueError、IndexError を除く: # 両方の例外をキャッチするには、そうですか?
... pass
...
Traceback (most 最新の呼び出し最後):
File "< ;stdin>"、 の 3 行目
IndexError: リストのインデックスが範囲外です

このコードの問題は、Except ステートメントがこの方法での例外の指定をサポートしていないことです。 Python 2.x では、例外をさらに表示するには、変数 e を使用して例外をオプションの 2 番目のパラメーターにバインドする必要があります。したがって、上記のコードでは、Except ステートメントは IndexError 例外をキャッチせず、代わりに、発生した例外を IndexError という名前のパラメータにバインドします。

Except ステートメントで複数の例外を正しくキャッチするには、最初のパラメーターをタプルとして指定し、キャッチする例外のタイプをタプルに記述する必要があります。また、移植性を向上させるには、Python 2 と Python 3 の両方でサポートされている as キーワードを使用します。
>>> try:
... l = ["a", "b"]
... int(l[2])
... e:
としての (ValueError, IndexError) を除く. pass
...
>>>
よくある間違い 4: Python の変数名解析の誤った理解

Python の変数名解析は、いわゆる LEGB 原則に従います。つまり、「L: ローカル スコープ」 ; E: 上位構造の def または lambda のローカル スコープ; G: グローバル スコープ; B: 組み込みスコープ (ローカル、エンクロージング、グローバル、ビルトイン) の順に検索します。シンプルに見えませんか?ただし、実際には、この原則が適用される方法にはいくつかの特別な特徴があります。これに関して言えば、次のよくある Python プログラミング エラーについて言及する必要があります。以下のコードを見てください:
>>> x = 10
>>> def foo():
... ;> foo()
Traceback (最新の呼び出し):
ファイル「、1 行目、
ファイル「」、2 行目、foo
UnboundLocalError: ローカル変数 'x' が代入前に参照されました

何が問題だったのでしょうか?

上記のエラーは、特定のスコープ内の変数に値を割り当てると、その変数が Python インタプリタによって自動的にスコープのローカル変数とみなされ、上位スコープ内の同じ名前の変数を置き換えるために発生します。変数。

だからこそ、最初はうまくいったコードが表示されますが、関数内に代入ステートメントを追加した後、UnboundLocalError が発生するのは、多くの人が驚くのも不思議ではありません。

Python プログラマーは、リストを扱うときに特にこの罠に陥る可能性があります。

次のコード例を見てください:
>>> lst = [1, 2, 3]
>>> def foo1():
... lst.append(5) # Notここで質問
>>> foo1()
>>> [1, 2, 3, 5]
>>>
>>> def foo2():
... lst += [5] # ... しかし、ここで何かが間違っています!
...
>>> トレースバック (ほとんど最近の呼び出し last):
ファイル「、1 行目、
ファイル「、2 行目、foo 内
UnboundLocalError: local代入前に変数「lst」が参照されました

え?関数 foo1 は正常に実行されるのに、foo2 でエラーが発生するのはなぜですか?

答えは前の例と同じですが、もう少しわかりにくいです。 foo1 関数は lst 変数に値を割り当てませんが、foo2 は値を割り当てます。 lst += [5] は lst = lst + [5] の単なる略語であることがわかります。このことから、foo2 関数が lst に値を割り当てようとしていることがわかります (したがって、ローカル変数とみなされます)。 Python インタプリタによる関数のスコープ)。ただし、lst に割り当てたい値は lst 変数自体に基づいています (この時点では、関数のローカル スコープ内の変数ともみなされます)。これは、変数がまだ定義されていないことを意味します。そのときエラーが発生しました。
よくある間違い 5: リストのトラバース中にリストを変更する

次のコードの問題は非常に明白です:
>>> od = lambda x : bool(x % 2)
>>>数値 = [範囲 (10) の n の場合]
> for i in range(len(数値)):
... 奇数の場合(数値[i]):
... del 数値[ i ] # 問題: 反復処理中にリストから項目を削除しています
...
トレースバック (最新の呼び出しは最後):
ファイル ""、2 行目、 内です
IndexError : リストのインデックスが範囲外です

反復処理中にリストまたは配列から要素を削除することは、経験豊富な Python 開発者なら誰でも知っていることです。ただし、上記の例は明白ですが、経験豊富な開発者は、より複雑なコードを作成するときにうっかり同じ間違いを犯す可能性があります。

幸いなことに、Python 言語には多くのエレガントなプログラミング パラダイムが組み込まれており、正しく使用すればコードを大幅に簡素化できます。コードを簡素化することのもう 1 つの利点は、リストを走査するときに要素を削除するエラーが発生する可能性が低くなることです。これを実現できるプログラミング パラダイムの 1 つはリスト内包表記です。さらに、リスト内包表記は、この問題を回避するのに特に役立ちます。
>>> od = lambda x : bool(x % 2)
>>> ; 数値 = [n の範囲 (10)]
>>> 数値[:] = [奇数でない場合は n の n] # ああ、素晴らしいですね
>> ; > 数値
[0, 2, 4, 6, 8]
よくある間違い 6: Python がクロージャで変数をバインドする方法を理解していない
>>>
... return [lambda x : i * x for i in range(5)]
>>> create_multipliers():
... print multiplier(2)
...

出力結果は次のようになると思います:

しかし、実際の出力結果は次のようになります:

ショックです!

この結果は、主に Python の遅延バインディング メカニズムが原因で発生します。つまり、クロージャ内の変数の値は、内部関数が呼び出されたときにのみクエリされます。したがって、上記のコードでは、create_multipliers() によって返された関数が呼び出されるたびに、変数 i の値が近くのスコープで検索されます (その時点までにループは終了しているため、最終的に変数 i が割り当てられます)値は 4)。

この一般的な Python の問題を解決するには、いくつかのハックを使用する必要があります:
>>> def create_multipliers():
... return [lambda x, i=i : i * x for i in range (5) )]
...
>>> create_multipliers():
... print multiplier(2)
...
0
2
4
6
8

注意してください!ここではデフォルトのパラメーターを使用して、このラムダ匿名関数を実装します。エレガントだと思う人もいるかもしれないし、賢いと思う人もいるかもしれないし、嘲笑する人もいるかもしれない。ただし、Python プログラマーであれば、とにかくこのソリューションについて知っておく必要があります。
よくある間違い 7: モジュール間の循環依存関係

以下に示すように、相互に参照する 2 つのファイル a.py と b.py があるとします。

a.py ファイル ファイル内のコード:
import b
def f():
return b. まず、a.py モジュールをインポートしてみます。

コードは正常に実行されます。もしかしたらこれは予想外かも知れません。結局のところ、ここには循環参照の問題があるので、問題があるはずですよね?

その答えは、循環参照が存在するだけでは問題が発生するわけではないということです。モジュールがすでに参照されている場合、Python はそのモジュールが再度参照されるのを防ぐことができます。ただし、各モジュールが他のモジュールによって定義された関数または変数に間違ったタイミングでアクセスしようとすると、問題が発生する可能性があります。

それでは例に戻りますが、a.py モジュールをインポートするとき、それが b.py モジュールを参照しても問題はありません。これは、b.py モジュールが参照されるときに a.py モジュールにアクセスする必要がないためです。 py モジュールで定義された変数または関数。 b.py モジュール内のモジュール a への唯一の参照は、モジュール a の foo() 関数の呼び出しです。ただし、その関数呼び出しは g() 関数内で発生し、g() 関数は a.py モジュールでも b.py モジュールでも呼び出されません。したがって、問題は発生しません。

しかし、(つまり、事前に a.py モジュールを参照せずに) b.py モジュールをインポートしようとするとどうなるでしょうか:
>>> import b
Traceback (most 最新の呼び出し最後):
File "< ;stdin>"、 の 1 行目
のファイル「b.py」、1 行目
import a
ファイル "a.py"、<の 6 行目;module>
print f()
ファイル "a.py"、4 行目、f
return b.x
AttributeError: 'module' オブジェクトには属性 'x' がありません

おっと。状況は良くありません!ここでの問題は、b.py をインポートするプロセスで、a.py モジュールを参照しようとし、a.py モジュールが foo() 関数を呼び出し、その後 b.x 変数にアクセスしようとすることです。ただし、この時点では b.x 変数が定義されていないため、AttributeError 例外が発生しました。

この問題を解決する非常に簡単な方法があります。それは、単に b.py モジュールを変更し、g() 関数内で a.py のみを参照することです:
x = 1
def g():
import a #これは g() が呼び出されたときにのみ評価されます
print a.f()

ここで b.py モジュールをインポートしても問題はありません:
>>> import b
>>> b.g()
1 # モジュール 'a' が最後に 'print f()' を呼び出したため、初めて出力されました
1 # 2 回目に出力されました。これは 'g' への呼び出しです
よくある間違い 8: モジュールの名前付けとPython 標準ライブラリのモジュール名の競合

Python 言語の大きな利点の 1 つは、独自の強力な標準ライブラリです。ただし、このため、細心の注意を払わないと、モジュールに Python 独自の標準ライブラリ モジュールと同じ名前を付ける可能性があります (たとえば、コード内に email.py というモジュールがある場合、これは競合します) Python 標準ライブラリの同じ名前のモジュールと)

これは、難しい問題を引き起こす可能性があります。たとえば、モジュール A をインポートするときに、モジュール A が Python 標準ライブラリのモジュール B を参照しようとすると、同じ名前のモジュール B がすでに存在するため、モジュール A は独自のコードではなく、誤ってモジュール B を参照します。 Python標準ライブラリのモジュールB。これは重大な間違いの原因にもなります。

したがって、Python プログラマは、Python 標準ライブラリ モジュールと同じ名前を使用しないように特に注意する必要があります。結局のところ、上流モジュールの名前を変更する PEP 提案を提案して提案を通過させるよりも、独自のモジュールの名前を変更する方がはるかに簡単です。
よくある間違い 9: Python 2 と Python 3 の違いを解決できない

次のコードがあるとします:
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]))
ただし KeyError as e:
print('key error ' )
e:
print('value error')
print(e)
bad()


の ValueError を除く Python 2 の場合、コードは通常通り実行されます:
$ python foo.py 1
key error
1
$ python foo.py 2
value error
2

しかし、今度は Python 3 に切り替えて、もう一度実行しましょう:
$ python3 foo.py 1
key error
Traceback (most 最近の呼び出し最後):
File " foo.py"、19 行目、 内です
bad()
ファイル "foo.py"、17 行目、bad
print(e)
UnboundLocalError: 割り当て前にローカル変数 'e' が参照されました

一体何が起こっているの?ここでの「問題」は、Python 3 では、例外ブロックのスコープ外では例外オブジェクトにアクセスできないことです。 (この設計の理由は、そうしないと、ガベージ コレクターが実行され、参照がメモリからクリアされるまで、その参照ループがスタック フレームに残るためです。)

この問題を回避する 1 つの方法は、例外オブジェクトへの参照を維持することです。例外オブジェクトにアクセスできるように、Except コード ブロックのスコープ外にあります。次のコードはこのメソッドを使用しているため、Python 2 と Python 3 の出力結果は一貫しています:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
Raise ValueError(2)
def Good():
例外 = None
try:
bar(int(sys.argv[1]))
KeyError as e:
例外 = e
print ('キーエラー')
ValueError を除く e:
例外 = e
print('value error')
print(例外)
good()


Python 3 でコードを実行します:
$ python3 foo.py 1
key error
1
$ python3 foo.py 2
値エラー
2

素晴らしい!
よくある間違い 10: del メソッドの間違った使用

mod.py ファイルに次のコードを記述するとします:
import foo
class Bar(object):
...
def __del__(self):
foo。 cleanup(self.myhandle)

では、another_mod.py ファイルで次の操作を実行します。
import mod
mybar = mod.Bar()


another_mod.py モジュールを実行すると、AttributeError 例外が発生します。

なぜですか?インタプリタが終了すると、モジュールのグローバル変数が None に設定されるためです。したがって、上記の例では、__del__ メソッドが呼び出される前に foo が None に設定されています。

このやや難しい Python プログラミングの問題を解決するには、方法の 1 つは 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)


この実装は、プログラムが正常に終了したときに必要なクリーンアップ関数をクリーンに呼び出すことをサポートします。明らかに、上記の例では、foo.cleanup 関数が self.myhandle にバインドされたオブジェクトの処理方法を決定します。

概要

Python は、作業効率を大幅に向上させる多くのプログラミング メカニズムとパラダイムを提供する強力で柔軟なプログラミング言語です。しかし、他のソフトウェア ツールや言語と同様、その言語の機能に対する理解や評価が限られている場合は、恩恵を受けるどころか、妨げられることがあります。ことわざにあるように、「十分に知っていると考えると、自分や他の人が危険にさらされる可能性があります。

Python 言語の微妙な点、特にこの記事で挙げた 10 のよくある間違いについてよく理解しておくと、Python を使用するのに役立ちます。」よくある間違いを回避しながら効果的に言語を習得できます

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。