点击上方“视学算法”,星标公众号
重磅干货,第一时间送达
作者:xybaby
来源:http://www.cnblogs.com/xybaby/

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

>>> a=1>>> def func():...     a+=1...     print a...>>> func()traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 2, in funcUnboundLocalError: local variable  a  referenced before assignment

   
对于“UnboundLocalError”,还有更高级的版本:

import random

def func(ok):    if ok:        a = random.random()    else:        import random        a = random.randint(1, 10)    return a

func(True)# UnboundLocalError: local variable  random  referenced before assignment

可能对于很多python新手来说,这个Error让人摸不着头脑。但我认为这不算陷阱,因为这段代码一定会报错,而不是默默的以错误的方式运行。不怕真小人,就怕伪君子。我认为缺陷就好比伪君子。

那么Python中哪些真正算得上陷阱呢

第一:以mutable对象作为默认参数

这个估计是最广为人知的了,Python和其他很多语言一样,提供了默认参数,默认参数确实是个好东西,可以让函数调用者忽略一些细节(比如GUI编程,Tkinter,QT),对于lambda表达式也非常有用。但是如果使用了可变对象作为默认参数,那么事情就不那么愉快了

>>> def f(lst = []):...     lst.append(1)...     return lst...>>> f()[1]>>> f()[1, 1]

  

惊喜不惊喜?!究其原因,python中一切都是对象,函数也不列外,默认参数只是函数的一个属性。而默认参数在函数定义的时候已经求值了。

Default parameter values are evaluated when the function definition is executed.

stackoverflow上有一个更适当的例子来说明默认参数是在定义的时候求值,而不是调用的时候。

>>> import time>>> def report(when=time.time()):...     return when...>>> report()1500113234.487932>>> report()1500113234.487932

python docoment 给出了标准的解决办法:

A way around this is to use None as the default, and explicitly test for it in the body of the function

>>> def report(when=None):...     if when is None:...             when = time.time()...     return when...>>> report()1500113446.746997>>> report()1500113448.552873

第二: x += y vs x = x + y

一般来说,二者是等价的,至少看起来是等价的(这也是陷阱的定义 — 看起来都OK,但不一定正确)。

>>> x=1;x += 1;print x2>>> x=1;x = x+1;print x2>>> x=[1];x+=[2];print x[1, 2]>>> x=[1];x=x+[2];print x[1, 2]

呃,被光速打脸了?

>>> x=[1];print id(x);x=x+[2];print id(x)43571328004357132728>>> x=[1];print id(x);x+=[2];print id(x)43571328004357132800

  
前者x指向一个新的对象,后者x在原来的对象是修改,当然,那种效果是正确的取决于应用场景。至少,得知道,二者有时候并不一样

第三,神奇的小括号—()

小括号(parenthese)在各种编程语言中都有广泛的应用,python中,小括号还能表示元组(tuple)这一数据类型, 元组是immutable的序列。

>>> a = (1, 2)>>> type(a)<type  tuple >>>> type(())<type  tuple >

但如果只有一个元素呢

>>> a=(1)>>> type(a)<type  int >

神奇不神奇,如果要表示只有一个元素的元组,正确的姿势是:

>>> a=(1,)>>> type(a)<type  tuple >

第四:生成一个元素是列表的列表

这个有点像二维数组,当然生成一个元素是字典的列表也是可以的,更通俗的说,生成一个元素是可变对象的序列

很简单嘛:

>>> a= [[]] * 10>>> a[[], [], [], [], [], [], [], [], [], []]>>> a[0].append(10)>>> a[0][10]

看起来很不错,简单明了,but

>>> a[1][10]>>> a[[10], [10], [10], [10], [10], [10], [10], [10], [10], [10]]

我猜,这应该不是你预期的结果吧,究其原因,还是因为python中list是可变对象,上述的写法大家都指向的同一个可变对象,正确的姿势

>>> a = [[] for _ in xrange(10)]>>> a[0].append(10)>>> a[[10], [], [], [], [], [], [], [], [], []]

另外一个在实际编码中遇到的问题,dict.fromkeys, 也有异曲同工之妙:创建的dict的所有values指向同一个对象。

fromkeys(seq[, value])

Create a new dictionary with keys from seq and values set to value. 

第五,在访问列表的时候,修改列表

列表(list)在python中使用非常广泛,当然经常会在访问列表的时候增加或者删除一些元素。比如,下面这个函数,试图删掉列表中为3的倍数的元素:

>>> def modify_lst(lst):...     for idx, elem in enumerate(lst):...         if elem % 3 == 0:...             del lst[idx]...

测试一下,

>>> lst = [1,2,3,4,5,6]>>> modify_lst(lst)>>> lst[1, 2, 4, 5]

好像没什么错,不过这只是运气好

>>> lst = [1,2,3,6,5,4]>>> modify_lst(lst)>>> lst[1, 2, 6, 5, 4]

上面的例子中,6这个元素就没有被删除。如果在modify_lst函数中print idx, item就可以发现端倪:lst在变短,但idx是递增的,所以在上面出错的例子中,当3被删除之后,6变成了lst的第2个元素(从0开始)。在C++中,如果遍历容器的时候用迭代器删除元素,也会有同样的问题。

如果逻辑比较简单,使用list comprehension是不错的注意

第六,闭包与lambda

这个也是老生长谈的例子,在其他语言也有类似的情况。先看一个例子:

>>> def create_multipliers():...     return [lambda x:i*x for i in range(5)]...>>> for multiplier in create_multipliers():...     print multiplier(2)...

create_multipliers函数的返回值时一个列表,列表的每一个元素都是一个函数 -- 将输入参数x乘以一个倍数i的函数。预期的结果时0,2,4,6,8. 但结果是5个8,意外不意外。

由于出现这个陷阱的时候经常使用了lambda,所以可能会认为是lambda的问题,但lambda表示不愿意背这个锅。问题的本质在与python中的属性查找规则,LEGB(local,enclousing,global,bulitin),在上面的例子中,i就是在闭包作用域(enclousing),而Python的闭包是 迟绑定 , 这意味着闭包中用到的变量的值,是在内部函数被调用时查询得到的。

解决办法也很简单,那就是变闭包作用域为局部作用域。

>>> def create_multipliers():...     return [lambda x, i = i:i*x for i in range(5)]...

第七,定义del

大多数计算机专业的同学可能都是先学的C、C++,构造、析构函数的概念应该都非常熟。于是,当切换到python的时候,自然也想知道有没有相应的函数。比如,在C++中非常有名的RAII,即通过构造、析构来管理资源(如内存、文件描述符)的声明周期。那在python中要达到同样的效果怎么做呢,即需要找到一个对象在销毁的时候一定会调用的函数,于是发现了init, del函数,可能简单写了两个例子发现确实也能工作。但事实上可能掉进了一个陷阱,在python documnet是有描述的:

Circular references which are garbage are detected when the option cycle detector is enabled (it’s on by default), but can only be cleaned up if there are no Python-level del() methods involved.

简单来说,如果在循环引用中的对象定义了del,那么python gc不能进行回收,因此,存在内存泄漏的风险

第八,不同的姿势import同一个module

示例在stackoverflow的例子上稍作修改,假设现在有一个package叫mypackage,里面包含三个python文件:mymodule.py, main.py, init.py。mymodule.py代码如下:

l = []class A(object):    pass

main.py代码如下:

def add(x):    from mypackage import mymodule    mymodule.l.append(x)    print "updated list",mymodule.l, id(mymodule)

def get():    import mymodule    print  module in get , id(mymodule)    return mymodule.l

if __name__ ==  __main__ :    import sys    sys.path.append( ../ )    add(1)

    ret = get()    print "lets check", ret

运行python main.py,结果如下:  

updated list [1] 4406700752module in get 4406700920lets check []

从运行结果可以看到,在add 和 get函数中import的mymodule不是同一个module,ID不同。当然,在python2.7.10中,需要main.py的第13行才能出现这样的效果。你可能会问,谁会写出第13行这样的代码呢?事实上,在很多项目中,为了import的时候方便,会往sys.path加入一堆路径。那么在项目中,大家同意一种import方式就非常有必要了

第九,python升级

python3.x并不向后兼容,所以如果从2.x升级到3.x的时候得小心了,下面列举两点:

在python2.7中,range的返回值是一个列表;而在python3.x中,返回的是一个range对象。

map()、filter()、 dict.items()在python2.7返回列表,而在3.x中返回迭代器。当然迭代器大多数都是比较好的选择,更加pythonic,但是也有缺点,就是只能遍历一次。在instagram的分享中,也提到因为这个导致的一个坑爹的bug。

第十:++i —i

这个陷阱主要是坑来自C、C++背景的同学。简单来说,++i是对i取两次正号,—i是对i取两次负号,运算完之后i的值不变。

第十一:setattr getattr getattribute

Python中有大量的magic method(形似xx),其中许多跟属性访问有关,比如getsetdelete_,getattr, setattr, delattr, getattribute前三个跟descriptor相关,详细可参见《python descriptor 详解》。坑爹的是,getattrsetattr相差很大,在《python属性查找(attribute look up)》一文中有详细介绍。简单说来,setattrgetattribute是对应的,都是修改python默认的属性修改、查找机制,而getattr只是默认查找机制无法找到属性的时候才会调用,setattr应该叫setattribute__才恰当!

第负一,gil

以GIL结尾,因为gil是Python中大家公认的缺陷!

其他语言过来的同学可能看到python用threading模块,拿过来就用,结果发现效果不对啊,然后就会喷,什么鬼

总结:

毫无疑问的说,python是非常容易上手,也非常强大的一门语言。python非常灵活,可定制化很强。同时,也存在一些陷阱,搞清楚这些陷阱能够更好的掌握、使用这么语言。本文列举了一些python中的一些缺陷,这是一份不完全列表,欢迎大家补充。

references:

instagram-pycon-2017
python-2-x-gotchas-and-landmines
module-reimported-if-imported-from-different-path

本文版权归作者xybaby
原文地址:http://www.cnblogs.com/xybaby/)

- END -
如果看到这里,说明你喜欢这篇文章,请转发、点赞。扫描下方二维码或者微信搜索「perfect_iscas」,添加好友后即可获得10套程序员全栈课程+1000套PPT和简历模板向我私聊「进群」二字即可进入高质量交流群。
扫描二维码进群↓


在看 

坑爹的Python陷阱(避坑指南)相关推荐

  1. 集成灶怎么选品牌(拒绝陷阱避坑指南)

    很多朋友在选购集成灶,大多数选择会到网上去看一些网友或博主的推荐.看多了听多了,就对某个品牌印象深刻,最终就选择了常听到的某个品牌. 作为深耕集成灶行业7年的我,经常被问哪个品牌好?我最常说的是品牌不 ...

  2. python避坑指南_Linux下Python3.6的安装及避坑指南

    Python3的安装 1.安装依赖环境 Python3在安装的过程中可能会用到各种依赖库,所以在正式安装 Python 3之前,需要将这些依赖库先行安装好. yum -y install zlib-d ...

  3. Python+Selenium 网页自动化 exe 程序编程实现(最全避坑指南)

    前言 在我的日常工作中,经常需要在内网(不连接互联网)的网页版办公系统中进行抓取网页数据.修改表单等大量重复性的操作.我就想是否可以编写出自动化的工具,将这些日常琐碎的操作变得轻松而高效.虽然本人非计 ...

  4. HarmonyOS 开发避坑指南

    Harmony OS 开发避坑指南--源码下载和编译 本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个 ...

  5. 【吐血经验】在 windows 上安装 spark 遇到的一些坑 | 避坑指南

    在 windows 上安装 spark 遇到的一些坑 | 避坑指南 最近有个活:给了我一个阿里云桌面(windows 10系统),让我在上面用 scala + spark 写一些东西. 总是报错不断, ...

  6. Ununtu 18.04 安装Carla 0.9.13 以及Carla ros bridge 超级避坑指南(更新于2022.10.20)

    Carla0.9.13 以及Carla ros bridge 超级避坑指南 Carla0.9.13 以及Carla ros bridge 超级避坑指南 站在巨人肩膀前进 显卡驱动问题 首先就是虚幻4的 ...

  7. 【A卡,Windows】stable diffusion webui下载安装避坑指南

    观前提醒 本文内容都是本人亲身经历的,一个一个安装下载测试所感,当然如果你更想用傻瓜式集成包的,那还是跳过这篇文章吧. 当然我不推荐这篇文章的操作,因为我用了差不多1h才有一副图,有N卡,就用N卡,这 ...

  8. 百度程序员开发避坑指南(3)

    前两期我们分享了日常工作中前端.移动端开发的相关问题,感兴趣的同学可以在文末推荐阅读跳转查看.本期我们分享三个议题:golang对象池减少gc压力.FFmpeg中的并发控制.paddle的静态图和动态 ...

  9. 2023年Matlab毕设避坑指南及选题推荐

    大家好! 2021-2022学年,接了很多matlab数字图像处理/语音信号处理的单子,其中碰到了很多同学们的遭遇,给大家分享下.可以说因为都是第一次,没经验,往往会贪图便宜而踩坑被骗.基础不好不会做 ...

  10. Hi3861开发环境搭建 ||避坑指南|| [适用于几乎所有以Hi3861为主控的开发板]

    Hi3861开发环境搭建 ||避坑指南|| [适用于几乎所有以Hi3861为主控的开发板] 前言: 这几天为了搭建Hi3861的开发环境,看了不少官方文档和视频,但是依然折腾了很久才配置好编译.上传都 ...

最新文章

  1. 数据结构与算法 | 线性表 —— 链表
  2. 龙芯派启用串口3-5
  3. WPF 蒙罩层 LoadingPage
  4. php 判断同时存在英文跟数字,php判断输入是否是纯数字,英文,汉字的方法
  5. 携程微服务框架实践及思考
  6. 2021-2025年中国云日志服务行业市场供需与战略研究报告
  7. vmware-linux虚拟机上网配置
  8. Hadoop原理——HDFS原理
  9. 自动驾驶仿真:如何通过TCP方式进行VTD驾驶员仿真
  10. 机器视觉Halcon教程(1.介绍)
  11. NAT 穿透是如何工作的:技术原理及企业级实践
  12. 数据库常用日期统计查询
  13. 从零讲JAVA ,给你一条清晰地学习道路!该学什么就学什么!!
  14. 计网 | PGP协议实验(邮件加密解密)
  15. 用于光波导耦合的倾斜光栅分析
  16. android手机应用程序开发,Android手机应用程序开发标准
  17. Lesson 29 Come in, Amy
  18. 普通的加载千篇一律,有趣的 loading 万里挑一
  19. matlab文件名批量读取dat文件,matlab批量读取dat
  20. 【JavaScript】(9)——实例:滑动效果的弹出框

热门文章

  1. mysql提示符详解_MySQL字符集使用详解
  2. flutter ios启动白屏_Flutter技术架构概览
  3. Arm收购进展、元宇宙、GPU涨价……听听黄仁勋怎么说
  4. 经典不过时,回顾DeepCompression神经网络压缩
  5. 神爱程序员,于是带来Python
  6. 掌握哪些机器学习工具更受企业青睐?
  7. Arm 发布Mali多媒体套件,机器学习将不再是高端手机的专享
  8. AI大潮来袭,Python将纳入高考?!你怎么看
  9. 神经网络要从原子抓起:原来白花花的银子才是神经芯片的未来
  10. 这次性能优化, QPS 翻倍了