開発中によく遭遇するPythonトラップと注意点
24557 ワード
可変オブジェクトをデフォルトパラメータとして使用しない
関数を使用するときは、デフォルトのパラメータが頻繁に使用されます.Pythonでは、デフォルトのパラメータとして可変オブジェクトを使用すると、予期せぬ結果が発生する可能性があります.
次に例を示します.
結果:
結果から、後の2回にわたって
この結果が得られたのは,Pythonにおいて1つの関数パラメータのデフォルト値が,その関数定義時に1回だけ初期化されるためである.
次に、Pythonのこの特性を証明する例を示します.
結果:
この例の結果から,
可変デフォルトパラメータの正しい使用
可変のデフォルトパラメータでは、次のモードを使用して、予想外の結果を回避できます.
結果:
Pythonにおける役割ドメイン
Pythonの役割ドメイン解析順序は,Local,Enclosing,Global,Built-in,つまりPython解釈器がこの順序で変数を解析する.
簡単な例を見てみましょう
結果:
Pythonでは、1つの役割ドメインで変数に値を割り当てると、Pythonはこの変数が現在の役割ドメインのローカル変数であると考えられることに注意してください.
この点も比較的分かりやすく、
問題1
しかし、変数を次のように使用すると、問題が発生します.
その結果、このエラーが発生したのは、
問題2
上のエラーはまだ明らかで、もう一つの隠れたエラー形式は以下の通りです.
コードの結果は次のとおりです.
クラス属性の非表示
Pythonには、クラス属性とインスタンス属性があります.クラス属性はクラス自体に属し、すべてのクラスインスタンスによって共有されます.
クラスプロパティは、クラス名でアクセスおよび変更できます.また、クラスインスタンスでアクセスおよび変更できます.ただし、インスタンスがクラスと同じ名前のプロパティを定義すると、クラスプロパティは非表示になります.
次の例を見てください.
コードの結果、
Python値で継承を使用する場合も、クラス属性の非表示に注意してください.1つのクラスについては、クラスの
クラス名でクラス属性にアクセスすると、まずクラスの
例を見てみましょう
その結果、クラス
tupleは「可変」
Pythonではtupleは可変オブジェクトですが、ここでの可変とはtupleというコンテナ全体の要素が可変(正確には要素のid)ではありませんが、要素の値は変更できます.
コード結果は、
Pythonの深浅コピー
Pythonオブジェクトに値を付ける操作では、オブジェクトの奥行きコピーに注意し、うっかり穴を踏む可能性があります.
次の操作を使用すると、浅いコピーの効果が得られます.
スライス[:]操作を使用
工場関数(list/dir/setなど)を使用する
copyモジュールのcopy()関数を使用
copyモジュールの浅いコピー関数copy()を使用します.
copyモジュールの深いコピー関数deepcopy()を使用します.
モジュールサイクル依存
Pythonで
実際には、符号化の過程でサイクル依存を回避したり、コード再構成の過程でサイクル依存を解消したりしなければならない.
もちろん、上記の問題も解決できます.よく使われる解決策は、引用関係を明らかにし、あるモジュールを本当に必要なときに導入させることです(一般的に関数に入れます).
上記の例では、
関数を使用するときは、デフォルトのパラメータが頻繁に使用されます.Pythonでは、デフォルトのパラメータとして可変オブジェクトを使用すると、予期せぬ結果が発生する可能性があります.
次に例を示します.
def append_item(a = 1, b = []):
b.append(a)
print b
append_item(a=1)
append_item(a=3)
append_item(a=5)
結果:
[1]
[1, 3]
[1, 3, 5]
結果から、後の2回にわたって
append_item
関数が呼び出されると、関数パラメータbは[]
に初期化されず、前の関数呼び出しの値が保持されることがわかる.この結果が得られたのは,Pythonにおいて1つの関数パラメータのデフォルト値が,その関数定義時に1回だけ初期化されるためである.
次に、Pythonのこの特性を証明する例を示します.
class Test(object):
def __init__(self):
print("Init Test")
def arg_init(a, b = Test()):
print(a)
arg_init(1)
arg_init(3)
arg_init(5)
結果:
Init Test
1
3
5
この例の結果から,
Test
クラスは一度だけインスタンス化された,すなわちデフォルトパラメータは関数呼び出し回数に関係なく,関数定義時に一度だけ初期化された.可変デフォルトパラメータの正しい使用
可変のデフォルトパラメータでは、次のモードを使用して、予想外の結果を回避できます.
def append_item(a = 1, b = None):
if b is None:
b = []
b.append(a)
print b
append_item(a=1)
append_item(a=3)
append_item(a=5)
結果:
[1]
[3]
[5]
Pythonにおける役割ドメイン
Pythonの役割ドメイン解析順序は,Local,Enclosing,Global,Built-in,つまりPython解釈器がこの順序で変数を解析する.
簡単な例を見てみましょう
global_var = 0
def outer_func():
outer_var = 1
def inner_func():
inner_var = 2
print "global_var is :", global_var
print "outer_var is :", outer_var
print "inner_var is :", inner_var
inner_func()
outer_func()
結果:
global_var is : 0
outer_var is : 1
inner_var is : 2
Pythonでは、1つの役割ドメインで変数に値を割り当てると、Pythonはこの変数が現在の役割ドメインのローカル変数であると考えられることに注意してください.
この点も比較的分かりやすく、
var_func
にはnum
変数が割り当てられているので、ここでのnum
はvar_func
ドメインのローカル変数である.num = 0
def var_func():
num = 1
print "num is :", num
var_func()
問題1
しかし、変数を次のように使用すると、問題が発生します.
num = 0
def var_func():
print "num is :", num
num = 1
var_func()
その結果、このエラーが発生したのは、
var_func
でnum
変数に値を付けたため、Python解釈器はnum
がvar_func
の役割ドメインのローカル変数であると考えているが、コードがprint "num is :", num
文に実行されるとnum
はまだ定義されていない.UnboundLocalError: local variable 'num' referenced before assignment
問題2
上のエラーはまだ明らかで、もう一つの隠れたエラー形式は以下の通りです.
li = [1, 2, 3]
def foo():
li.append(4)
print li
foo()
def bar():
li +=[5]
print li
bar()
コードの結果は次のとおりです.
[1, 2, 3, 4]
UnboundLocalError: local variable 'li' referenced before assignment
foo
関数では、Pythonの役割ドメイン解析順序に従って、この関数にはグローバルなli
変数が使用される.しかし、bar
関数では、li
変数が付与されているため、li
はbar
の役割ドメインの変数として扱われる.bar
関数のこの問題については、global
キーワードを使用することができる.li = [1, 2, 3]
def foo():
li.append(4)
print li
foo()
def bar():
global li
li +=[5]
print li
bar()
クラス属性の非表示
Pythonには、クラス属性とインスタンス属性があります.クラス属性はクラス自体に属し、すべてのクラスインスタンスによって共有されます.
クラスプロパティは、クラス名でアクセスおよび変更できます.また、クラスインスタンスでアクセスおよび変更できます.ただし、インスタンスがクラスと同じ名前のプロパティを定義すると、クラスプロパティは非表示になります.
次の例を見てください.
class Student(object):
books = ["Python", "JavaScript", "CSS"]
def __init__(self, name, age):
self.name = name
self.age = age
pass
wilber = Student("Wilber", 27)
print "%s is %d years old" %(wilber.name, wilber.age)
print Student.books
print wilber.books
wilber.books = ["HTML", "AngularJS"]
print Student.books
print wilber.books
del wilber.books
print Student.books
print wilber.books
コードの結果、
wilber
インスタンスは、最初はクラスのbooks
プロパティに直接アクセスできたが、インスタンスwilber
がbooks
という名前のインスタンスプロパティを定義した後、wilber
インスタンスのbooks
プロパティはクラスのbooks
プロパティを「非表示」にした.wilber
インスタンスのbooks
プロパティが削除されると、wilber.books
はクラスのbooks
プロパティに対応します.Wilber is 27 years old
['Python', 'JavaScript', 'CSS']
['Python', 'JavaScript', 'CSS']
['Python', 'JavaScript', 'CSS']
['HTML', 'AngularJS']
['Python', 'JavaScript', 'CSS']
['Python', 'JavaScript', 'CSS']
Python値で継承を使用する場合も、クラス属性の非表示に注意してください.1つのクラスについては、クラスの
__dict__
属性によってすべてのクラス属性を表示できます.クラス名でクラス属性にアクセスすると、まずクラスの
__dict__
属性が検索され、クラス属性が見つからない場合は親が検索され続けます.ただし、子クラスが親と同じ名前のクラス属性を定義すると、子クラスのクラス属性は親クラスのクラス属性を非表示にします.例を見てみましょう
class A(object):
count = 1
class B(A):
pass
class C(A):
pass
print A.count, B.count, C.count
B.count = 2
print A.count, B.count, C.count
A.count = 3
print A.count, B.count, C.count
print B.__dict__
print C.__dict__
その結果、クラス
B
がcount
というクラス属性を定義すると、親クラスのcount
属性が非表示になります.1 1 1
1 2 1
3 2 3
{'count': 2, '__module__': '__main__', '__doc__': None}
{'__module__': '__main__', '__doc__': None}
tupleは「可変」
Pythonではtupleは可変オブジェクトですが、ここでの可変とはtupleというコンテナ全体の要素が可変(正確には要素のid)ではありませんが、要素の値は変更できます.
tpl = (1, 2, 3, [4, 5, 6])
print id(tpl)
print id(tpl[3])
tpl[3].extend([7, 8])
print tpl
print id(tpl)
print id(tpl[3])
コード結果は、
tpl
オブジェクトの各要素は可変であるが、tpl[3]
はlist
オブジェクトである.すなわち、このtpl
オブジェクトについては、id(tpl[3])
は可変ではないが、tpl[3]
は確かに可変である.36764576
38639896
(1, 2, 3, [4, 5, 6, 7, 8])
36764576
38639896
Pythonの深浅コピー
Pythonオブジェクトに値を付ける操作では、オブジェクトの奥行きコピーに注意し、うっかり穴を踏む可能性があります.
次の操作を使用すると、浅いコピーの効果が得られます.
スライス[:]操作を使用
工場関数(list/dir/setなど)を使用する
copyモジュールのcopy()関数を使用
copyモジュールの浅いコピー関数copy()を使用します.
import copy
will = ["Will", 28, ["Python", "C#", "JavaScript"]]
wilber = copy.copy(will)
print id(will)
print will
print [id(ele) for ele in will]
print id(wilber)
print wilber
print [id(ele) for ele in wilber]
will[0] = "Wilber"
will[2].append("CSS")
print id(will)
print will
print [id(ele) for ele in will]
print id(wilber)
print wilber
print [id(ele) for ele in wilber]
copyモジュールの深いコピー関数deepcopy()を使用します.
import copy
will = ["Will", 28, ["Python", "C#", "JavaScript"]]
wilber = copy.deepcopy(will)
print id(will)
print will
print [id(ele) for ele in will]
print id(wilber)
print wilber
print [id(ele) for ele in wilber]
will[0] = "Wilber"
will[2].append("CSS")
print id(will)
print will
print [id(ele) for ele in will]
print id(wilber)
print wilber
print [id(ele) for ele in wilber]
モジュールサイクル依存
Pythonで
import
を使用してモジュールを導入する場合、モジュールループ依存が発生する場合があります.例えば、次の例では、module_x
モジュールとmodule_y
モジュールが互いに依存し、module_y.py
を実行するとエラーが発生します.# module_x.py
import module_y
def inc_count():
module_y.count += 1
print module_y.count
# module_y.py
import module_x
count = 10
def run():
module_x.inc_count()
run()
実際には、符号化の過程でサイクル依存を回避したり、コード再構成の過程でサイクル依存を解消したりしなければならない.
もちろん、上記の問題も解決できます.よく使われる解決策は、引用関係を明らかにし、あるモジュールを本当に必要なときに導入させることです(一般的に関数に入れます).
上記の例では、
module_x.py
を次のように変更して、関数内部にmodule_y
をインポートできます.# module_x.py
def inc_count():
import module_y
module_y.count += 1
print module_y.count
http://www.cnblogs.com/wilber2013/p/5178620.html