最近、Python を使用しているときに、変数オブジェクト datetime.datetime.now() を関数のデフォルト パラメーターとして使用したり、モジュールの循環依存関係など、いくつかの落とし穴に遭遇しました。
今後の問い合わせや追加のためにここに記録してください。
可変オブジェクトをデフォルトパラメータとして使用しないようにします
関数を使用するプロセスでは、デフォルトパラメータが関係することがよくあります。 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]
append_item 関数が次の 2 回呼び出されるとき、関数パラメーター b は [] に初期化されず、前の関数呼び出しの値が保持されます。
この結果が得られる理由は、Python では関数パラメーターのデフォルト値が関数の定義時に 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 クラスは 1 回だけインスタンス化されることに注意してください。つまり、デフォルトのパラメーターは関数呼び出しの回数とは関係がなく、関数の定義時に 1 回だけ初期化されることになります。
変数のデフォルト パラメーターの正しい使用方法
変数のデフォルト パラメーターについては、次のパターンを使用して上記の予期しない結果を回避できます:
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 のスコープ解決順序は、ローカル、エンクロージング、グローバル、組み込みです。これは、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()
結果は次のとおりです:
UnboundLocalError: local variable 'num' referenced before assignment
このエラーが発生する理由は、var_func の num 変数に値を割り当てたため、Python インタプリタは num が var_func スコープ内のローカル変数であると認識しますが、コードが実行されて「num は」と表示されるためです。 : "、num ステートメントを実行するとき、num はまだ未定義です。
質問 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 関数のこの問題では、グローバル キーワードを使用できます。
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 値で継承を使用する場合は、クラス属性の非表示にも注意する必要があります。クラスの場合、クラスの __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}
タプルは「変数」です
在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