최근 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 함수를 두 번 호출하면 , 함수 매개변수 b는 []로 초기화되지 않지만 이전 함수 호출의 값을 유지합니다.
이러한 결과가 나오는 이유는 Python에서는 함수 정의 시 함수 매개변수의 기본값이 한 번만 초기화되기 때문입니다.
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, 내장입니다. 즉, 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에서 범위에 관해 주의할 점 중 하나는 범위의 변수에 값을 할당할 때 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 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
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()
클래스 속성은 숨겨져 있습니다
파이썬에는 클래스 속성과 인스턴스 속성이 있습니다. 클래스 속성은 클래스 자체에 속하며 모든 클래스 인스턴스에서 공유됩니다.
클래스 속성은 클래스 이름을 통해 액세스 및 수정이 가능하며, 클래스 인스턴스를 통해서도 액세스 및 수정이 가능합니다. 그러나 인스턴스가 클래스와 동일한 이름의 속성을 정의하면 클래스 속성이 숨겨집니다.
다음 예를 보세요.
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 인스턴스에서 책에 직접 액세스할 수 있습니다. 하지만 Wilber 인스턴스가 정의된 경우 books라는 인스턴스 속성을 삭제한 후 Wilber 인스턴스의 books 속성은 Wilber 인스턴스의 books 속성인 wilber.books를 삭제한 후 클래스의 books 속성을 "숨깁니다". 다시 클래스의 books 속성에 해당합니다.
Wilber is 27 years old ['Python', 'JavaScript', 'CSS'] ['Python', 'JavaScript', 'CSS'] ['Python', 'JavaScript', 'CSS'] ['HTML', 'AngularJS'] ['Python', 'JavaScript', 'CSS'] ['Python', 'JavaScript', 'CSS']
파이썬 값에서 상속을 사용할 때는 클래스 속성이 숨겨지는 것도 주의해야 합니다. 클래스의 경우 클래스의 __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