開発時によくあるPythonの罠と注意点を詳しく解説

高洛峰
リリース: 2017-03-16 17:05:39
オリジナル
1134 人が閲覧しました

最近、Pythonを使用しているときに、datetime.datetime.now()の変数objectfunction、モジュールloop依存関係などのデフォルトパラメータとして使用するなど、いくつかの落とし穴に遭遇しました。 。

簡単なクエリのためにここに記録し、後で補足します。

変更可能なオブジェクトをデフォルトパラメータとして使用しないようにします

関数を使用するプロセスには、デフォルトパラメータが関与することがよくあります。 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 スコープ内のローカル変数であると認識しますが、コードが print "num is:" 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
ログイン後にコピー

Python のスコープによると、foo 関数内order を解析する場合、この関数ではグローバル 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()
ログイン後にコピー

クラス属性Hide

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 値で

inheritance を使用する場合は、クラス属性の非表示にも注意する必要があります。クラスの場合、クラスの 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
ログイン後にコピー


以上が開発時によくあるPythonの罠と注意点を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート