【Python】远离 Python 最差实践,避免挖坑
原文链接:http://blog.guoyb.com/2016/12/03/bad-py-style/
最近在看一些陈年老系统,其中有一些不好的代码习惯遗留下来的坑;加上最近自己也写了一段烂代码导致服务器负载飙升,所以就趁此机会总结下我看到过/写过的自认为不好的 Python 代码习惯,时刻提醒自己远离这些“最差实践”,避免挖坑。
下面所举的例子中,有一部分会造成性能问题,有一部分会导致隐藏 bug,或日后维护、重构困难,还有一部分纯粹是我认为不够 pythonic。所以大家自行甄别,取精去糟吧。
函数默认参数使用可变对象
这个例子我想大家应该在各种技术文章中见过许多遍了,也足以证明这是一个大坑。
先看错误示范吧:
def use_mutable_default_param(idx=0, ids=[]):
ids.append(idx)
print(idx)
print(ids)
use_mutable_default_param(idx=1)
use_mutable_default_param(idx=2)
输出:
1
[1]
2
[1, 2]
理解这其中的原因,最重要的是有两点:
函数本身也是一个对象,默认参数绑定于这个函数对象上
append 这类方法会直接修改对象,所以下次调用此函数时,其绑定的默认参数已经不再是空list了
正确的做法如下:
def donot_use_mutable_default_param(idx=0, ids=None):
if ids is None:
ids = []
ids.append(idx)
print(idx)
print(ids)
try…except不具体指明异常类型
虽然在 Python 中使用 try…except 不会带来严重的性能问题,但是不加区分,直接捕获所有类型异常的做法,往往会掩盖掉其他的 bug,造成难以追查的 bug。
一般的,我觉得应该尽量少的使用 try…except,这样可以在开发期尽早的发现问题。即使要使用 try…except,也应该尽可能的指定出要捕获的具体异常,并在 except 语句中将异常信息记入 log,或者处理完之后,再直接raise出来。
关于dict的冗余代码
我经常能够看到这样的代码:
d = {}
datas = [1, 2, 3, 4, 2, 3, 4, 1, 5]
for k in datas:
if k not in d:
d[k] = 0
d[k] += 1
其实,完全可以使用 collections.defaultdict 这一数据结构更简单优雅的实现这样的功能:
default_d = defaultdict(lambda: 0)
datas = [1, 2, 3, 4, 2, 3, 4, 1, 5]
for k in datas:
default_d[k] += 1
同样的,这样的代码:
# d is a dict
if 'list' not in d:
d['list'] = []
d['list'].append(x)
完全可以用这样一行代码替代:
# d is a dict
d.setdefault('list', []).append(x)
同样的,下面这两种写法一看就是带有浓浓的C味儿:
# d is a dict
for k in d:
v = d[k]
# do something
# l is a list
for i in len(l):
v = l[i]
# do something
应该用更 pythonic 的写法:
# d is a dict
for k, v in d.iteritems():
# do something
pass
# l is a list
for i, v in enumerate(l):
# do something
pass
另外,enumerate 其实还有个第二参数,表示序号从几开始。如果想要序号从1开始数起,可以使用 enumerate(l, 1)。
使用flag变量而不使用for…else语句
同样,这样的代码也很常见:
search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey']
found = False
for s in search_list:
if s.startswith('C'):
found = True
# do something when found
print('Found')
break
if not found:
# do something when not found
print('Not found')
其实,用 for…else 更优雅:
search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey']
for s in search_list:
if s.startswith('C'):
# do something when found
print('Found')
break
else:
# do something when not found
print('Not found')
过度使用 tuple unpacking
在 Python 中,允许对 tuple 类型进行 unpack 操作,如下所示:
# human = ('James', 180, 32)
name,height,age = human
这个特性用起来很爽,比写 name=human[0] 之类的不知道高到哪里去了。所以,这一特性往往被滥用,一个 human 在程序的各处通过上面的方式 unpack。
然而如果后来需要在 human 中插入一个表示性别的数据 sex,那么对于所有的这种 unpack 都需要进行修改,即使在有些逻辑中并不会使用到性别。
# human = ('James', 180, 32)
name,height,age, _ = human
# or
# name, height, age, sex = human
有如下几种方式解决这一问题:
老老实实写 name=human[0] 这种代码,在需要使用性别信息处加上 sex=human[3]
使用 dict 来表示 human
使用 namedtuple
# human = namedtuple('human', ['name', 'height', 'age', 'sex'])
h = human('James', 180, 32, 0)
# then you can use h.name, h.sex and so on everywhere.
到处都是 import *
import * 是一种懒惰的行为,它不仅会污染当前的命名空间,并且还会使得 pyflakes 等代码检查工具失效。在后续查看代码或者 debug 的过程中,往往也很难从一堆 import * 中找到一个第三方函数的来源。
可以说这种习惯是百害而无一利的。
文件操作
文件操作不要使用裸奔的f = open(‘filename’)了,使用with open(‘filename’) as f来让context manager帮你处理异常情况下的关闭文件等乱七八糟的事情多好。
野蛮使用 class.name 判断类型
我曾经遇见过一个 bug:为了实现某特定功能,我新写了一个 class B(A),在 B 中重写了 A 的若干函数。整个实现很简单,但是就是有一部分 A 的功能无法生效。最后追查到的原因,就是在一些逻辑代码中,硬性的判断了entity.class.name == ‘A’。
除非你就是想限定死继承层级中的当前类型(也就是,屏蔽未来可能会出现的子类),否则,不要使用 class.name,而改用 isinstance 这个内建函数。毕竟,Python 把这两个变量的名字都刻意带上那么多下划线,本来就是不太想让你用嘛。
循环内部有多层函数调用
循环内部有多层函数调用,有如下两方面的隐患:
Python 没有 inline 函数,所以函数调用本来就会导致一定的开销,尤其是本身逻辑简单的时候,这个开销所占的比例就会挺可观的。
更严重的是,在之后维护这份代码时,会容易让人忽略掉函数是在循环中被调用的,所以容易在函数内部添加了一些开销较大却不必每次循环都调用的函数,比如 time.localtime()。如果是直接一个平铺直叙的循环,我想大部分的程序员都应该知道把 time.localtime()写到循环的外面,但是引入多层的函数调用之后,就不一定了哦。
所以我建议,在循环内部,如非特别复杂的逻辑,都应该直接写在循环里,不要进行函数调用。如果一定要包装一层函数调用,应该在函数的命名或注释中,提示后续的维护者,这个函数会在循环内部使用。
Python 是一门非常容易入门的语言,严格的缩进要求和丰富的内置数据类型,使得大部分 Python 代码都能做到比较好的规范。但是,不严格要求自己,也很容易就写出犯二的代码。上面列出的只是很小的一部分,唯有多读、多写、多想,才能培养敏锐的代码嗅觉,第一时间发现坏味道啊。欢迎大家补充~
【Python】远离 Python 最差实践,避免挖坑的更多相关文章
- 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)
目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构 前言 学习Python也有一个半月时间了,学到现在感觉 ...
- Python编程从入门到实践笔记——异常和存储数据
Python编程从入门到实践笔记——异常和存储数据 #coding=gbk #Python编程从入门到实践笔记——异常和存储数据 #10.3异常 #Python使用被称为异常的特殊对象来管理程序执行期 ...
- Python编程从入门到实践笔记——文件
Python编程从入门到实践笔记——文件 #coding=gbk #Python编程从入门到实践笔记——文件 #10.1从文件中读取数据 #1.读取整个文件 file_name = 'pi_digit ...
- Python编程从入门到实践笔记——类
Python编程从入门到实践笔记——类 #coding=gbk #Python编程从入门到实践笔记——类 #9.1创建和使用类 #1.创建Dog类 class Dog():#类名首字母大写 " ...
- Python编程从入门到实践笔记——函数
Python编程从入门到实践笔记——函数 #coding=gbk #Python编程从入门到实践笔记——函数 #8.1定义函数 def 函数名(形参): # [缩进]注释+函数体 #1.向函数传递信息 ...
- Python编程从入门到实践笔记——用户输入和while循环
Python编程从入门到实践笔记——用户输入和while循环 #coding=utf-8 #函数input()让程序暂停运行,等待用户输入一些文本.得到用户的输入以后将其存储在一个变量中,方便后续使用 ...
- Python编程从入门到实践笔记——字典
Python编程从入门到实践笔记——字典 #coding=utf-8 #字典--放在{}中的键值对:跟json很像 #键和值之间用:分隔:键值对之间用,分隔 alien_0 = {'color':'g ...
- Python编程从入门到实践笔记——if语句
Python编程从入门到实践笔记——if语句 #coding=utf-8 cars=['bwm','audi','toyota','subaru','maserati'] bicycles = [&q ...
- Python编程从入门到实践笔记——操作列表
Python编程从入门到实践笔记——操作列表 #coding=utf-8 magicians = ['alice','david','carolina'] #遍历整个列表 for magician i ...
随机推荐
- Andorid- 反序列化,采用pull解析 xml 文件
MainActivity.java 主入口,通过获得 XML文件 ,然后将解析后的文件标签以及文本内容拼接到 StringBuffer中,最后显示在TextView上 package com.exam ...
- mycelipse中关于编码的配置
(1)修改工作空间的编码方式: Window->Preferences->General->Workspace->Text file Encoding在Others里选择需要的 ...
- Mssql备份失败
Mssql备份失败出现如下提示 备份时先删除默认的备份设备,自己选择路径
- ArcGIS API for JavaScript开发笔记(一)——ArcGIS for Javascript API 3.14本地部署
堪称史上最详细的< ArcGIS forJavascript API 3.14本地部署>文档,有图有真相~~~ ---------环境:Windows server 2012R2,IIS ...
- ArcGIS for Server的安装及站点中的集群配置 分类: ArcGIS for server 2015-07-18 14:14 16人阅读 评论(0) 收藏
坚信并为之坚持是一切希望的原因. (不足之处,欢迎批评指正!) --------------------环境:Windows server2008R2虚拟机两台----------------- ...
- 。。。。。。不带http https : 不报错 spring boot elasticsearch rest
......不带http https : 不报错 先telnet http://onf:8080/getES653/道路桥梁正在“理疗”%20这14条道路纳入市政中修 @GetMapping(&qu ...
- Day03 javascript详解
day03 js 详解 JavaScript的基础 JavaScript的变量 JavaScript的数据类型 JavaScript的语句 JavaScript的数组 JavaScript的函数 Ja ...
- Redhat 7改动默认执行级别方法 --RHEL7使用systemd创建符号链接指向默认执行级别
今天装了下正式版的RHEL7,发现熟悉的inittab中没有了改动默认执行级别,打开inittab例如以下 [root@localhost init.d]# vi /etc/inittab # ini ...
- centos linux 系统日常管理4 scp,rsync,md5sum,sha1sum,strace ,find Rsync 常见错误及解决方法 第十七节课
centos linux 系统日常管理4 scp,rsync,md5sum,sha1sum,strace ,find Rsync 常见错误及解决方法 第十七节课 rsync可以增量同步,scp不行 ...
- PhotoSwipe中文API(二)
配置 选项是在键 - 值对添加作为参数传递给PhotoSwipe构造,例如通过: var options = { index: 3, escKey: false, // ui option timeT ...