CodeGym /コース /Python SELF JA /標準エラー

標準エラー

Python SELF JA
レベル 20 , レッスン 1
使用可能

7.1 関数引数のデフォルト値としての式の誤用

もうたくさん学んだね、じゃあ初心者がやりがちな標準的なミスを見ていこうか。 他人の失敗から学ぶ方が自分で失敗するよりいいよね?

Pythonでは、関数に任意の引数を持たせることができる。 これらの引数にはデフォルト値を設定できるんだ。 これってとっても便利なんだけど、可変の型に設定されたときに 困ったことになることがあるんだ。例えば、次の関数定義を見てみよう:


def foo(bar=[]):  # barはオプション引数
# デフォルトでは空のリストに設定されている。
    bar.append("baz")  # この行が問題になるかも...
    return bar

ここでよくあるミスは、オプション引数の値が、その引数に値が指定されずに 関数が呼び出されるたびにデフォルト値に設定されると考えることだ。

上記のコードでは、たとえば、foo() 関数を何度も呼び出すと (bar 引数の値を指定せずに)、常に ["baz"] が返されると 思われるかもしれない。foo() が呼び出されるたびに (引数 bar を指定せずに)、bar[] (つまり新しい空のリスト) に設定されると想定されるためだ。

でも実際に何が起こるか見てみよう:


result = foo()
print(result)  # ["baz"]
            
result = foo()
print(result)  # ["baz", "baz"]
            
result = foo()
print(result)  # ["baz", "baz", "baz"]

なぜ関数が foo() を呼び出すたびに 既存のリストに baz を追加し続けるのか? 毎回新しいリストを作成するのではないでしょうか?

この質問の答えは、Python の「内部構造」をより深く理解することにある。 具体的には、デフォルト値は関数定義時に一度だけ初期化される

つまり、引数 bar はデフォルト値 (つまり空のリスト) に初期化されるのは foo() が初めて定義されたときのみ。 しかし、その後の foo() の呼び出し (引数 bar を指定せずに) では、関数が最初に 定義されたときに bar に対して作成された同じリストが 使用され続ける。

このミスを回避するためによく使われる「回避策」は次の定義です:


def foo(bar=None):             
    if bar is None:
        bar = []                                          
    bar.append("baz")   
    return bar
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]

7.2 クラス変数の誤用

次の例を考えてみよう:


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 に値を割り当ててみよう:


B.x = 2
print(A.x, B.x, C.x)
# 1 2 1

期待通りです。

ではクラス A の変数を変更してみましょう:


A.x = 3
print(A.x, B.x, C.x)
# 3 2 3

おかしいですね、A.x だけを変更したはずなのに、なぜ C.x も変わったのでしょうか?

Pythonでは、クラス変数は辞書のように扱われ、 メソッド解決順 (MRO) と呼ばれるものに従います。 つまり、上記のコードでは、属性 xクラス C に見つからない場合、それは ベースクラスで見つかります (上記の例では A のみですが、Python は複数継承をサポートしています)。

つまり、CA とは独立した 自分のプロパティ x を持っていません。 したがって、C.x への参照は実際には A.x への参照です。 これを理解しておかないと、問題が発生する可能性があります。 Python を学ぶときは、クラス属性とその操作に特に注意してください。

7.3 例外ブロックのパラメータの誤指定

次のコードを考えてみよう:


try:
    l = ["a", "b"]
    int(l[2])
except ValueError, IndexError:  # 両方の例外をキャッチするためだよね?
    pass
        
Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
IndexError: list index out of range

問題は、except 式がリストのように指定された例外を受け取らないことです。そのため、上記のコードでは、例外 IndexErrorexcept 式でキャッチされず、代わりにパラメータ IndexError にバインドされます。

重要! インターネット上の例でこのようなコードに出会うことがあるかもしれません。これは Python 2.x で使用されていたものです。

複数の例外を except 式でキャッチする正しい方法は、最初のパラメータをキャッチしたいすべての例外を含むタプルとして指定することです。また、可読性を高めるために、以下のように as キーワードを使用するのも良いでしょう:


try:
    l = ["a", "b"]
    int(l[2])
except (ValueError, IndexError) as e:  
    pass
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION