开发中常遇到的Python陷阱和注意点-乾颐堂
最近使用Python的过程中遇到了一些坑,例如用datetime.datetime.now()这个可变对象作为函数的默认参数,模块循环依赖等等。
在此记录一下,方便以后查询和补充。
避免可变对象作为默认参数
在使用函数的过程中,经常会涉及默认参数。在Python中,当使用可变对象作为默认参数的时候,就可能产生非预期的结果。
下面看一个例子:
1
2
3
4
5
6
7
|
def append_item(a = 1 , b = []): b.append(a) print b append_item(a = 1 ) append_item(a = 3 ) append_item(a = 5 ) |
结果为:
1
2
3
|
[1] [1, 3] [1, 3, 5] |
从结果中可以看到,当后面两次调用append_item函数的时候,函数参数b并没有被初始化为[],而是保持了前面函数调用的值。
之所以得到这个结果,是因为在Python中,一个函数参数的默认值,仅仅在该函数定义的时候,被初始化一次。
下面看一个例子证明Python的这个特性:
1
2
3
4
5
6
7
8
9
|
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 ) |
结果为:
1
2
3
4
|
Init Test 1 3 5 |
从这个例子的结果就可以看到,Test类仅仅被实例化了一次,也就是说默认参数跟函数调用次数无关,仅仅在函数定义的时候被初始化一次。
可变默认参数的正确使用
对于可变的默认参数,我们可以使用下面的模式来避免上面的非预期结果:
1
2
3
4
5
6
7
8
9
|
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
2
3
|
[1] [3] [5] |
Python中的作用域
Python的作用域解析顺序为Local、Enclosing、Global、Built-in,也就是说Python解释器会根据这个顺序解析变量。
看一个简单的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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() |
结果为:
1
2
3
|
global_var is : 0 outer_var is : 1 inner_var is : 2 |
在Python中,关于作用域有一点需要注意的是,在一个作用域里面给一个变量赋值的时候,Python会认为这个变量是当前作用域的本地变量。
对于这一点也是比较容易理解的,对于下面代码var_func中给num变量进行了赋值,所以此处的num就是var_func作用域的本地变量。
1
2
3
4
5
6
|
num = 0 def var_func(): num = 1 print "num is :" , num var_func() |
问题一
但是,当我们通过下面的方式使用变量的时候,就会产生问题了:
1
2
3
4
5
6
|
num = 0 def var_func(): print "num is :" , num num = 1 var_func() |
结果如下:
1
|
UnboundLocalError: local variable 'num' referenced before assignment |
之所以产生这个错误,就是因为我们在var_func中给num变量进行了赋值,所以Python解释器会认为num是var_func作用域的本地变量,但是当代码执行到print "num is :", num语句的时候,num还是未定义。
问题二
上面的错误还是比较明显的,还有一种比较隐蔽的错误形式如下:
1
2
3
4
5
6
7
8
9
|
li = [ 1 , 2 , 3 ] def foo(): li.append( 4 ) print li foo() def bar(): li + = [ 5 ] print li bar() |
代码的结果为:
1
2
|
[1, 2, 3, 4] UnboundLocalError: local variable 'li' referenced before assignment |
在foo函数中,根据Python的作用域解析顺序,该函数中使用了全局的li变量;但是在bar函数中,对li变量进行了赋值,所以li会被当作bar作用域中的变量。
对于bar函数的这个问题,可以通过global关键字。
1
2
3
4
5
6
7
8
9
10
11
12
|
li = [ 1 , 2 , 3 ] def foo(): li.append( 4 ) print li foo() def bar(): global li li + = [ 5 ] print li bar() |
类属性隐藏
在Python中,有类属性和实例属性。类属性是属于类本身的,被所有的类实例共享。
类属性可以通过类名访问和修改,也可以通过类实例进行访问和修改。但是,当实例定义了跟类同名的属性后,类属性就被隐藏了。
看下面这个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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属性了。
1
2
3
4
5
6
7
|
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__属性,如果没有找到类属性,就会继续查找父类。但是,如果子类定义了跟父类同名的类属性后,子类的类属性就会隐藏父类的类属性。
看一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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
2
3
4
5
|
1 1 1 1 2 1 3 2 3 { 'count' : 2, '__module__' : '__main__' , '__doc__' : None} { '__module__' : '__main__' , '__doc__' : None} |
tuple是“可变的”
在Python中,tuple是不可变对象,但是这里的不可变指的是tuple这个容器总的元素不可变(确切的说是元素的id),但是元素的值是可以改变的。
1
2
3
4
5
6
7
|
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]确是可变的。
1
2
3
4
5
|
36764576 38639896 (1, 2, 3, [4, 5, 6, 7, 8]) 36764576 38639896 |
Python的深浅拷贝
在对Python对象进行赋值的操作中,一定要注意对象的深浅拷贝,一不小心就可能踩坑了。
当使用下面的操作的时候,会产生浅拷贝的效果:
使用切片[:]操作
使用工厂函数(如list/dir/set)
使用copy模块中的copy()函数
使用copy模块里面的浅拷贝函数copy():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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的时候就会产生错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 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:
1
2
3
4
|
# module_x.py def inc_count(): import module_y module_y.count + = 1 |
www.qytang.com/
http://www.qytang.com/cn/list/29/
http://www.qytang.com/cn/list/28/428.htm
http://www.qytang.com/cn/list/28/426.htm
http://www.qytang.com/cn/list/28/425.htm
http://www.qytang.com/cn/list/28/424.htm
http://www.qytang.com/cn/list/28/423.htm
http://www.qytang.com/cn/list/28/422.htm
http://www.qytang.com/cn/list/28/421.htm
http://www.qytang.com/cn/list/28/420.htm
http://www.qytang.com/cn/list/28/417.htm
http://www.qytang.com/cn/list/28/416.htm
http://www.qytang.com/cn/list/28/407.htm
http://www.qytang.com/cn/list/28/403.htm
开发中常遇到的Python陷阱和注意点-乾颐堂的更多相关文章
- 开发中常遇到的Python陷阱和注意点
最近使用Python的过程中遇到了一些坑,例如用datetime.datetime.now()这个可变对象作为函数的默认参数,模块循环依赖等等. 在此记录一下,方便以后查询和补充. 避免可变对象作为默 ...
- python高性能编程方法一-乾颐堂
阅读 Zen of Python,在Python解析器中输入 import this. 一个犀利的Python新手可能会注意到"解析"一词, 认为Python不过是另一门脚本语言. ...
- 使用python把图片存入数据库-乾颐堂
一般情况下我们是把图片存储在文件系统中,而只在数据库中存储文件路径的,但是有时候也会有特殊的需求:把图片二进制存入数据库. 今天我们采用的是python+mysql的方式 MYSQL 是支持把图片存入 ...
- python 多继承详解-乾颐堂
1 2 3 4 5 6 7 8 9 10 class A(object): # A must be new-style class def __init__(self): prin ...
- 用 python 实现各种排序算法-乾颐堂
总结了一下常见集中排序的算法 归并排序 归并排序也称合并排序,是分治法的典型应用.分治思想是将每个问题分解成个个小问题,将每个小问题解决,然后合并. 具体的归并排序就是,将一组无序数按n/2递归分解成 ...
- python时间处理详解-乾颐堂
1.获取当前时间的两种方法: import datetime,time now = time.strftime("%Y-%m-%d %H:%M:%S") print now now ...
- C语言开发中常见报错的解决方案
C语言开发中常见报错的解决方案 整理来源于网络,侵权请通知删除.*禁止转载 ---- fatal error C1003: error count exceeds number; stopping c ...
- android开发中常犯的几个错误整理
新手程序猿,在开发中难免会犯各种各样的错误,以下是整理的一些android开发中常见的错误,一起来看看吧. 1.避免将多个类放在一个文件夹里面,除非是一次性使用的内部类. 就是一个文件,最好给分它同名 ...
- Go开发中的十大常见陷阱[译]
原文: The Top 10 Most Common Mistakes I've Seen in Go Projects 作者: Teiva Harsanyi 译者: Simon Ma 我在Go开发中 ...
随机推荐
- Django中组合搜索功能
需求分析 很多电商网站中有组合搜索的功能,所谓组合搜索就是网页中组合多个条件,对数据库中进行查询,并且将结果显示在页面中,看个例子吧: 注意红框中的标识,我们可以根据URL来做组合搜索. video- ...
- node实现缓存
内容: 1.缓存基本原理 2.node实现缓存 1.缓存基本原理 第一重要.缓存策略: cache-control:用于控制缓存,常见的取值有private.no-cache.max-age.must ...
- 解决问题E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用)
在用sudo apt-get install 安装软件时,结果终端提示: “E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用) E: 无法锁定管理目录(/ ...
- php内存回收机制的学习
今天朋友去面试,回来问了一下怎么样,结果他说一脸懵逼,看来我们平时还是学习的太少了啊.于是比较好奇,果断问了一下都有哪些问题,朋友说第一个问题就是“描述PHP的垃圾回收机制”,我当时听了也是一脸茫然, ...
- uva-10054-欧拉回路
题意:一个项链上面的每一个珠子有俩种颜色,前面一个珠子后面的颜色和后面珠子的前面颜色一样,有一天它断了, 一个人去搜集,问,搜集到的珠子能不能再次串成项链 原以为是链表,原来链表这组数据过不了. 71 ...
- C常用问题
linux系统,gcc编译器包含引用的头文件位置
- UI5-文档-4.11-Pages and Panels
在完成了应用程序结构的所有工作之后,是时候改进我们的应用程序的外观了.在这一步中,您还将了解控件聚合. Preview A panel is now displaying the controls f ...
- 转载 JS组件Bootstrap Select2使用方法详解
JS组件Bootstrap Select2使用方法详解 作者:懒得安分 字体:[增加 减小] 类型:转载 时间:2016-01-26我要评论 这篇文章主要为大家介绍了JS组件Bootstrap Sel ...
- Mysql生成索引的方式
1.选择索引的数据类型 MySQL支持很多数据类型,选择合适的数据类型存储数据对性能有很大的影响.通常来说,可以遵循以下一些指导原则: (1)越小的数据类型通常更好:越小的数据类型通常在磁盘.内存和C ...
- Javascript 浏览器检测
推荐 Browser Detecter, 很好用,自己也很容易扩展. 原文链接:http://www.quirksmode.org/js/detect.html <script type=&qu ...