最近使用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 is None:
        = []
    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 = [123]
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 = [123]
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 = (123, [456])
print id(tpl)
print id(tpl[3])
tpl[3].extend([78])
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陷阱和注意点-乾颐堂的更多相关文章

  1. 开发中常遇到的Python陷阱和注意点

    最近使用Python的过程中遇到了一些坑,例如用datetime.datetime.now()这个可变对象作为函数的默认参数,模块循环依赖等等. 在此记录一下,方便以后查询和补充. 避免可变对象作为默 ...

  2. python高性能编程方法一-乾颐堂

    阅读 Zen of Python,在Python解析器中输入 import this. 一个犀利的Python新手可能会注意到"解析"一词, 认为Python不过是另一门脚本语言. ...

  3. 使用python把图片存入数据库-乾颐堂

    一般情况下我们是把图片存储在文件系统中,而只在数据库中存储文件路径的,但是有时候也会有特殊的需求:把图片二进制存入数据库. 今天我们采用的是python+mysql的方式 MYSQL 是支持把图片存入 ...

  4. python 多继承详解-乾颐堂

    1 2 3 4 5 6 7 8 9 10 class A(object):    # A must be new-style class    def __init__(self):     prin ...

  5. 用 python 实现各种排序算法-乾颐堂

    总结了一下常见集中排序的算法 归并排序 归并排序也称合并排序,是分治法的典型应用.分治思想是将每个问题分解成个个小问题,将每个小问题解决,然后合并. 具体的归并排序就是,将一组无序数按n/2递归分解成 ...

  6. python时间处理详解-乾颐堂

    1.获取当前时间的两种方法: import datetime,time now = time.strftime("%Y-%m-%d %H:%M:%S") print now now ...

  7. C语言开发中常见报错的解决方案

    C语言开发中常见报错的解决方案 整理来源于网络,侵权请通知删除.*禁止转载 ---- fatal error C1003: error count exceeds number; stopping c ...

  8. android开发中常犯的几个错误整理

    新手程序猿,在开发中难免会犯各种各样的错误,以下是整理的一些android开发中常见的错误,一起来看看吧. 1.避免将多个类放在一个文件夹里面,除非是一次性使用的内部类. 就是一个文件,最好给分它同名 ...

  9. Go开发中的十大常见陷阱[译]

    原文: The Top 10 Most Common Mistakes I've Seen in Go Projects 作者: Teiva Harsanyi 译者: Simon Ma 我在Go开发中 ...

随机推荐

  1. Django中组合搜索功能

    需求分析 很多电商网站中有组合搜索的功能,所谓组合搜索就是网页中组合多个条件,对数据库中进行查询,并且将结果显示在页面中,看个例子吧: 注意红框中的标识,我们可以根据URL来做组合搜索. video- ...

  2. node实现缓存

    内容: 1.缓存基本原理 2.node实现缓存 1.缓存基本原理 第一重要.缓存策略: cache-control:用于控制缓存,常见的取值有private.no-cache.max-age.must ...

  3. 解决问题E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用)

    在用sudo apt-get install 安装软件时,结果终端提示: “E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用) E: 无法锁定管理目录(/ ...

  4. php内存回收机制的学习

    今天朋友去面试,回来问了一下怎么样,结果他说一脸懵逼,看来我们平时还是学习的太少了啊.于是比较好奇,果断问了一下都有哪些问题,朋友说第一个问题就是“描述PHP的垃圾回收机制”,我当时听了也是一脸茫然, ...

  5. uva-10054-欧拉回路

    题意:一个项链上面的每一个珠子有俩种颜色,前面一个珠子后面的颜色和后面珠子的前面颜色一样,有一天它断了, 一个人去搜集,问,搜集到的珠子能不能再次串成项链 原以为是链表,原来链表这组数据过不了. 71 ...

  6. C常用问题

    linux系统,gcc编译器包含引用的头文件位置

  7. UI5-文档-4.11-Pages and Panels

    在完成了应用程序结构的所有工作之后,是时候改进我们的应用程序的外观了.在这一步中,您还将了解控件聚合. Preview A panel is now displaying the controls f ...

  8. 转载 JS组件Bootstrap Select2使用方法详解

    JS组件Bootstrap Select2使用方法详解 作者:懒得安分 字体:[增加 减小] 类型:转载 时间:2016-01-26我要评论 这篇文章主要为大家介绍了JS组件Bootstrap Sel ...

  9. Mysql生成索引的方式

    1.选择索引的数据类型 MySQL支持很多数据类型,选择合适的数据类型存储数据对性能有很大的影响.通常来说,可以遵循以下一些指导原则: (1)越小的数据类型通常更好:越小的数据类型通常在磁盘.内存和C ...

  10. Javascript 浏览器检测

    推荐 Browser Detecter, 很好用,自己也很容易扩展. 原文链接:http://www.quirksmode.org/js/detect.html <script type=&qu ...