来源:http://www.cnblogs.com/wilber2013/p/5178620.html

最近使用Python的过程中遇到了一些坑,例如用datetime.datetime.now()这个可变对象作为函数的默认参数,模块循环依赖等等。

在此记录一下,方便以后查询和补充。

避免可变对象作为默认参数

在使用函数的过程中,经常会涉及默认参数。在Python中,当使用可变对象作为默认参数的时候,就可能产生非预期的结果。

下面看一个例子:

def append_item(a = 1, b = []):b.append(a)print bappend_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 bappend_item(a=1)
append_item(a=3)
append_item(a=5) 

结果为:

[1]
[3]
[5]

Python中的作用域

Python的作用域解析顺序为Local、Enclosing、Global、Built-in,也就是说Python解释器会根据这个顺序解析变量。

看一个简单的例子:

global_var = 0def outer_func():outer_var = 1def inner_func():inner_var = 2print "global_var is :", global_varprint "outer_var is :", outer_varprint "inner_var is :", inner_varinner_func()outer_func()

结果为:

global_var is : 0
outer_var is : 1
inner_var is : 2

在Python中,关于作用域有一点需要注意的是,在一个作用域里面给一个变量赋值的时候,Python会认为这个变量是当前作用域的本地变量

对于这一点也是比较容易理解的,对于下面代码var_func中给num变量进行了赋值,所以此处的num就是var_func作用域的本地变量。

num = 0def var_func():num = 1print "num is :", numvar_func()

问题一

但是,当我们通过下面的方式使用变量的时候,就会产生问题了:

num = 0def var_func():print "num is :", numnum = 1var_func()

结果如下,之所以产生这个错误,就是因为我们在var_func中给num变量进行了赋值,所以Python解释器会认为numvar_func作用域的本地变量,但是当代码执行到print "num is :", num语句的时候,num还是未定义。

UnboundLocalError: local variable 'num' referenced before assignment

问题二

上面的错误还是比较明显的,还有一种比较隐蔽的错误形式如下:

li = [1, 2, 3]def foo():li.append(4)print lifoo()def bar():li +=[5]print libar()

代码的结果为:

[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 lifoo()def bar():global lili +=[5]print libar()

类属性隐藏

在Python中,有类属性和实例属性。类属性是属于类本身的,被所有的类实例共享。

类属性可以通过类名访问和修改,也可以通过类实例进行访问和修改。但是,当实例定义了跟类同名的属性后,类属性就被隐藏了。

看下面这个例子:

class Student(object):books = ["Python", "JavaScript", "CSS"]def __init__(self, name, age):self.name = nameself.age = agepasswilber = Student("Wilber", 27)
print "%s is %d years old" %(wilber.name, wilber.age)print Student.books
print wilber.bookswilber.books = ["HTML", "AngularJS"]print Student.books
print wilber.booksdel wilber.booksprint 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中,类实例属性的访问规则算是比较直观的。

但是,仍然存在一些不是很直观的地方,特别是对C++和Java程序员来说,更是如此。

在这里,我们需要明白以下几个地方:

1.Python是一门动态语言,任何实体都可以动态地添加或删除属性。

2.一个类定义了一个作用域。

3.类实例也引入了一个作用域,这与相应类定义的作用域不同。

4.在类实例中查找属性的时候,首先在实例自己的作用域中查找,如果没有找到,则再在类定义的作用域中查找。

5.在对类实例属性进行赋值的时候,实际上会在类实例定义的作用域中添加一个属性(如果还不存在的话),并不会影响到相应类中定义的同名属性。

下面看一个例子,加深对上述几点的理解:

class A:cls_i = 0cls_j = {}def __init__(self):self.instance_i = 0self.instance_j = {}

在这里,我们先定义类A的一个实例a,然后再看看类A的作用域和实例a的作用域中分别有什么:

>>> a = A()
>>> a.__dict__
{'instance_j': {}, 'instance_i': 0}
>>> A.__dict__
{'__init__': , '__module__': '__main__', 'cls_i': 0, 'cls_j': {}, '__doc__': None}

我们看到,a的作用域中有instance_i和instance_j,A的作用域中有cls_i和cls_j。

我们再来看看名字查找是如何发生的:

>>> a.cls_i
0
>>> a.instance_i
0
在查找cls_i的时候,实例a的作用域中是没有它的,却在A的作用域中找到了它;在查找instance_i的时候,直接可在a的作用域中找到它。如果我们企图通过实例a来修改cls_i的值,那会怎样呢:
>>> a.cls_i = 1
>>> a.__dict__
{'instance_j': {}, 'cls_i': 1, 'instance_i': 0}
>>> A.__dict__
{'__init__': , '__module__': '__main__', 'cls_i': 0, 'cls_j': {}, '__doc__': None}

我们可以看到,a的作用域中多了一个cls_i属性,其值为1;同时,我们也注意到A作用域中的cls_i属性的值仍然为0;在这里,我们其实是增加了一个实例属性,并没有修改到类属性。

如果我们通过实例a操纵cls_j中的数据(注意不是cls_j本身),又会怎么样呢:

>>> a.cls_j['a'] = 'a'
>>> a.__dict__
{'instance_j': {}, 'cls_i': 1, 'instance_i': 0}
>>> A.__dict__
{'__init__': , '__module__': '__main__', 'cls_i': 0, 'cls_j': {'a': 'a'}, '__doc__': None}

我们可以看到a的作用域没有发生什么变化,但是A的作用域发生了一些变化,cls_j中的数据发生了变化。

实例的作用域发生变化,并不会影响到该类的其它实例,但是类的作用域发生变化,则会影响到该类的所有实例,包括在这之前创建的实例:

>>> A.cls_k = 0
>>> i.cls_k
0

http://www.cnblogs.com/frydsh/p/3194710.html

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 copywill = ["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 copywill = ["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_ydef inc_count():module_y.count += 1print module_y.count# module_y.py
import module_xcount = 10def run():module_x.inc_count()run()            

其实,在编码的过程中就应当避免循环依赖的情况,或者代码重构的过程中消除循环依赖。

当然,上面的问题也是可以解决的,常用的解决办法就是把引用关系搞清楚,让某个模块在真正需要的时候再导入(一般放到函数里面)。

对于上面的例子,就可以把module_x.py修改为如下形式,在函数内部导入module_y

# module_x.py
def inc_count():import module_ymodule_y.count += 1print module_y.count

记录遇到的Python陷阱和注意点相关推荐

  1. python中continue用法案例_记录今天学习python中for与while循环针对break和continue的用法...

    python中有两个主要的循环for与while,其中针对这两个循环有两种不同的中断用法break与continue. 首先先看下面的循环代码: 1: for i in range(10):#变量i带 ...

  2. OpenCV函数应用:基于二值图像的三种孔洞填充方法记录(附python,C++代码)

    系列文章目录 函数系列: OpenCV函数简记_第一章数字图像的基本概念(邻域,连通,色彩空间) OpenCV函数简记_第二章数字图像的基本操作(图像读写,图像像素获取,图像ROI获取,图像混合,图形 ...

  3. 坑爹的Python陷阱(避坑指南)

    点击上方"视学算法",星标公众号 重磅干货,第一时间送达 作者:xybaby 来源:http://www.cnblogs.com/xybaby/ 我个人对陷阱的定义是这样的:代码看 ...

  4. 【错误记录】执行 Python 程序报错 ( NameError: name ‘reload‘ is not defined )

    文章目录 一.报错信息 二.解决方案 一.报错信息 在 Windows 的 cmd 命令行运行 python 脚本时 , 报如下错误 : 执行 python ApkTool.py -analyse - ...

  5. 简单记录一下使用python pyppeteer爬取努努书坊的爬虫

    RT,是用python+pyppeteer随便写的,比较简单,权当做记录备忘而已. 不作任何这方面的疑问解答. import pyppeteer,asyncio,sys,io,os,re,time,d ...

  6. 程序员必知的 Python 陷阱与缺陷列表

    我个人对陷阱的定义是这样的:代码看起来可以工作,但不是以你"想当然""的方式.如果一段代码直接出错,抛出了异常,我不认为这是陷阱.比如,Python程序员应该都遇到过的& ...

  7. python从键盘输入列表有缺陷_程序员必知的Python陷阱与缺陷列表-阿里云开发者社区...

    代码看起来可以工作,但不是以你"想当然""的方式.如果一段代码直接出错,抛出了异常,我不认为这是陷阱.比如,Python程序员应该都遇到过的"UnboundLo ...

  8. 【学习记录】基于python爬取Flickr图片及元数据

    为复现师姐论文成果,爬取Flickr网站数据,只需爬取图片元数据,无需爬取图片: (一已成功,二失败了,这里记录给自己看.) 一.用Python的icrawler包 icrawler是一个轻型框架,自 ...

  9. 随手记录第六话 -- 在Mac上搭建整个开发环境记录(Java、Python、Vue、Docker、idea)

    最近公司新买了一批Mac,冲着Mac的性能我也来体验了一波. 结果可想而知,各种问题都随之而来,有时也搞得措手不及,在这里记录一下. 1.java 1.1 JDK 还是jdk8,其他版本的自选 jdk ...

最新文章

  1. Linux的su命令,sudo命令和限制root远程登录
  2. 总结MYSQL的优化
  3. vue-awesome-swiper
  4. vue中如何使用mockjs摸拟接口的各种数据
  5. UpdatePanel控件的简单属性学习
  6. Norse Attack Map网络攻击实时监测地图
  7. dremwere怎样让多个图片并列排放_PPT图片布局不好看?六步教你,看完就会。
  8. 计算机加分乘法套用,乘法表 - it610.com
  9. 虚拟化未来是I don’t care
  10. 双非院校,0项目经验,三个月入职大厂NLP算法岗,月薪30k+
  11. docker的安装与加速器的配置
  12. 微信小程序打卡签到页面(有效果图)
  13. Win10应用商店无法联网
  14. 不同走法的象棋能否走完整个棋盘问题
  15. 精选大数据面试真题10道(附答案详细解析)
  16. js实现倒计时广告效果
  17. 黑鲨3能升级鸿蒙5g吗,配上高通骁龙865 5G芯片的黑鲨游戏手机3,到底有多懂手游玩家!...
  18. 2016年GitHub上史上最全的Android开源项目分类汇总
  19. 如何用DirectShow替代付费的摄像头SDK
  20. nagiso中nsca的配置

热门文章

  1. 论文浅尝 | 对于知识图谱嵌入表示的几何形状理解
  2. 玩转算法之面试 第八章-递归与回溯
  3. Android官方开发文档Training系列课程中文版:Activity测试之创建单元测试
  4. Android官方开发文档Training系列课程中文版:Android的安全建议
  5. 技术思考:也谈知识图谱平台中的数据流程与构建范式思考
  6. 【LeetCode】4月2日打卡-Day18-矩阵操作
  7. (SQLlite3 Source Code 系列之一) 前言
  8. Django-C001-快速入门
  9. js 简单弹框toast
  10. Asp.net MVC在Razor中输出Html的两种方式