记录遇到的Python陷阱和注意点
最近使用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、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}
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
print module_y.count
记录遇到的Python陷阱和注意点的更多相关文章
- 程序员必知的Python陷阱与缺陷列表
本文关注的主要是python陷阱,具体而言,是指CPython,而且除非特别说明,所有代码示例都是在python2.7运行的. 我个人对陷阱的定义是这样的:代码看起来可以工作,但不是以你“想当然“”的 ...
- 开发中常遇到的Python陷阱和注意点-乾颐堂
最近使用Python的过程中遇到了一些坑,例如用datetime.datetime.now()这个可变对象作为函数的默认参数,模块循环依赖等等. 在此记录一下,方便以后查询和补充. 避免可变对象作为默 ...
- 记录两个python的小问题
使用python也前前后后也一个月的样子,记录两个一直没注意的问题. 1. 元组的使用(拼接字符串) 直接看下面的代码: >>> kel = 'some','strings' > ...
- 开发中常遇到的Python陷阱和注意点
最近使用Python的过程中遇到了一些坑,例如用datetime.datetime.now()这个可变对象作为函数的默认参数,模块循环依赖等等. 在此记录一下,方便以后查询和补充. 避免可变对象作为默 ...
- 记录我的 python 学习历程-Day02-while 循环/格式化输出/运算符/编码的初识
一.流程控制之--while 循环 循环就是重复做同一件事,它可以终止当前循环,也可以跳出这一次循环,继续下一次循环. 基本结构(基本循环) while 条件: 循环体 示例 # 这是一个模拟音乐循环 ...
- 记录我的 python 学习历程-Day06 is id == / 代码块 / 集合 / 深浅拷贝
一.is == id 用法 在Python中,id是内存地址, 你只要创建一个数据(对象)那么就会在内存中开辟一个空间,将这个数据临时加载到内存中,这个空间有一个唯一标识,就好比是身份证号,标识这个空 ...
- 首篇-记录自己学习python之路!
对于自己学习python的目的比较明确——爬虫和量化. 目前找了一些资源进行学习,先进行量化方面的学习,爬虫滞后.目前的目标是“180天掌握尽可能多的量化能力”! 以后定时发送自己学习思考内容以作自己 ...
- 记录我的 python 学习历程-Day11 两个被忽视的坑、补充知识点、函数名的应用、新版格式化输出、迭代器
补充知识点 函数形参中默认参数的陷阱 针对不可变数据类型,它是没有陷阱的 def func(name, sex='男'): print(name) print(sex) func('Dylan') # ...
- 记录我的 python 学习历程-Day12 生成器/推导式/内置函数Ⅰ
一.生成器 初识生成器 生成器的本质就是迭代器,在python社区中,大多数时候都把迭代器和生成器是做同一个概念. 唯一的不同就是: 迭代器都是Python给你提供的已经写好的工具或者通过数据转化得来 ...
随机推荐
- jQuery Countdown Timer 倒计时效果
这个一款简单的 jQuery 倒计时插件,用于显示剩余的天数,小时,分钟和秒.倒计时功能是非常有用的一个小功能,可以告诉用户多久以后您的网站将会发布或者关闭进行维护,还可以用于举办活动的开始和停止的倒 ...
- Droidicon – 1600+ 漂亮的 Android 图标
Droidicon 提供超过1600款定制图标,让你可以超级容易的把图标和徽章添加到您的应用程序中.你可以自定义你想要的各种形式的图标,给图标添加描边,透明度和颜色过滤器.包括这些 Google Ma ...
- 详细讲解nodejs中使用socket的私聊的方式
详细讲解nodejs中使用socket的私聊的方式 在上一次我使用nodejs+express+socketio+mysql搭建聊天室,这基本上就是从socket.io的官网上的一份教程式复制学习,然 ...
- javascript --- 设计模式之Module模式
基本用法 先看一下最简单的一个实现,代码如下: var Calculator = function(){ // 这里可以声明私有成员 var eqCtrl = document.getElement( ...
- JavaScript强化教程 -- cocosjs场景切换
场景切换 在main.js,将StartScene作为我们初始化运行的场景,代码如下: cc.LoaderScene.preload(g_resources, function () { cc.dir ...
- 利用IIS导出,导入快速部署 web站点
部署负载均衡站点的时候会创建多个站点拷贝.用脚本可以提高效率,并且减少错误 1 以管理员身份运行CMD 2 Cd C:\Windows\System32\inetsrv 3 导出指定的应用程序池 ap ...
- sharepoint2010升级到sharepoint2013的升级步骤和过程
- 学习笔记:腾讯云——服务器mysql操作
1.进入数据库 (注意:在linux系统下要进入mysql所在的文件夹下才能打开数据库) 操作1:进入到指定目录下 命令行:cd /opt/lampp/bin 操作2:进入到数据库 命令行:./mys ...
- RxJava 和 RxAndroid 三(生命周期控制和内存优化)
rxjava rxandroid 赵彦军 前言:对Rxjava.Rxandroid不了解的同学可以先看看 RxJava 和 RxAndroid RxJava 和 RxAndroid 二(操作符的使用) ...
- iOS 更改webView文字颜色丶文字大小丶背景色的方法
在webView的delegate回调方法 - (void)webViewDidFinishLoad:(UIWebView *)webView;中写上一下语句即可 //字体大小 [webView ...