最近使用Python的过程当中遇到了一些坑,例如用datetime.datetime.now()
这个可变对象做为函数的默认参数,模块循环依赖等等。python
在此记录一下,方便之后查询和补充。app
在使用函数的过程当中,常常会涉及默认参数。在Python中,当使用可变对象做为默认参数的时候,就可能产生非预期的结果。函数
下面看一个例子:ui
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并无被初始化为[]
,而是保持了前面函数调用的值。code
之因此获得这个结果,是由于在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)
结果为:ip
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的做用域解析顺序为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中,关于做用域有一点须要注意的是,在一个做用域里面给一个变量赋值的时候,Python会认为这个变量是当前做用域的本地变量。
对于这一点也是比较容易理解的,对于下面代码var_func
中给num
变量进行了赋值,因此此处的num
就是var_func
做用域的本地变量。
num = 0 def var_func(): num = 1 print "num is :", num var_func()
可是,当咱们经过下面的方式使用变量的时候,就会产生问题了:
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
上面的错误仍是比较明显的,还有一种比较隐蔽的错误形式以下:
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值使用继承的时候,也要注意类属性的隐藏。对于一个类,能够经过类的__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对象进行赋值的操做中,必定要注意对象的深浅拷贝,一不当心就可能踩坑了。
当使用下面的操做的时候,会产生浅拷贝的效果:
使用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