远离 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 代码都能做到比较好的规范。但是,不严格要求自己,也很容易就写出犯二的代码。上面列出的只是很小的一部分,唯有多读、多写、多想,才能培养敏锐的代码嗅觉,第一时间发现坏味道啊。欢迎大家补充~

原文地址:https://www.cnblogs.com/Detector/p/8652968.html

时间: 2024-10-20 14:44:55

远离 Python 最差实践,避免挖坑的相关文章

python计算机视觉项目实践 答案

有问题的找我哈,转载标明出处http://blog.csdn.net/ikerpeng/article/details/25027567 里面具体的图没有贴了,原理还是比较好理解的.需要的找我! 基于朴素贝叶斯的图片分类 摘要 图片分类问题是计算机视觉中比较常见的问题.图片分类在日常的生活中,以及图片搜索中等方面都有很多很实际的用途.如何准确快速有效的进行图片分类,提高图片分类的准确率和召回率是现在主要要解决的问题.因此一个好的分类学习的算法以及一个好的特征提取的方式是非常重要的.本文所采取的学

Java编程最差实践常见问题详细说明(1)转

Java编程最差实践常见问题详细说明(1)转 原文地址:http://www.odi.ch/prog/design/newbies.php 每天在写Java程序, 其实里面有一些细节大家可能没怎么注意, 这不, 有人总结了一个我们编程中常见的问题. 虽然一般没有什么大问题, 但是最好别这样做. 另外这里提到的很多问题其实可以通过Findbugs(http://findbugs.sourceforge.net/ )来帮我们进行检查出来. 字符串连接误用  错误的写法: Java代码   Strin

Java编程最差实践常见问题详细说明(2)转

Java编程最差实践常见问题详细说明(2)转 2012-12-13 13:57:20|  分类: JAVA |  标签:java  |举报|字号 订阅 反射使用不当  错误的写法: Java代码   Class beanClass = ... if (beanClass.newInstance() instanceof TestBean) ... 这里的本意是检查beanClass是否是TestBean或是其子类, 但是创建一个类实例可能没那么简单, 首先实例化一个对象会带来一定的消耗, 另外有

用 Vim 写 Python 的最佳实践

先来晒个图: 对于一些 Python 的小项目,使用 vim 是一个不错的选择.本文内容整理自我在知乎的回答 用用 Vim 写 Python 的最佳实践是什么?,下面的内容是对知乎旧有回答的一个补充,尤其有一些主要针对 vim8. 如果想要更多内容,可以查看知乎对于该问题的一些回答. 语法检查 如果用 vim8, 那么可以用异步检测的 w0rp/ale 代替 syntastic 了,再也不用羡慕 flycheck, 也不用因为语法检查而卡顿了. 关于 ale 这部分的个性化配置,其实有点 "吹毛

Redis的Python实践,以及四中常用应用场景详解——学习董伟明老师的《Python Web开发实践》

首先,简单介绍:Redis是一个基于内存的键值对存储系统,常用作数据库.缓存和消息代理. 支持:字符串,字典,列表,集合,有序集合,位图(bitmaps),地理位置,HyperLogLog等多种数据结构. 支持事务.分片.主从复之.支持RDB(内存数据保存的文件)和AOF(类似于MySQL的binlog)两种持久化方式.3.0加入订阅分发.Lua脚本.集群等特性. 命令参考:http://doc.redisfans.com 中文官网:http://www.redis.net.cn 安装(都大同小

图片切碎片脚本 python PIL库实践

python PIL库实践运用,对图像进行切碎片操作. 原图如图一 图一 我们想要的是图片的不同部分,比如图二中1.2.3.4每一个分区单独的碎片, 图二 做法是做出4张跟原图大小一样的碎片模版图,白底,想要的区域涂黑(非纯白)即可.1区域的碎片模板图如图三所示: 图三 脚本处理图片的方法:先找出碎片模版中的非白区域(可以不规则),然后将原图中这个区域内的所有像素点的颜色都放到新的一张跟原图像素大小一样的新图上,新图的其余位置都设置成透明(可以根据自己的不同需求进行不同的调整). 1碎片模版得到

《Python机器学习及实践:从零开始通往Kaggle竞赛之路》

<Python 机器学习及实践–从零开始通往kaggle竞赛之路>很基础 主要介绍了Scikit-learn,顺带介绍了pandas.numpy.matplotlib.scipy. 本书代码基于python2.x.不过大部分可以通过修改print()来适应python3.5.x. 提供的代码默认使用 Jupyter Notebook,建议安装Anaconda3. 最好是到https://www.kaggle.com注册账号后,运行下第四章的代码,感受下. 监督学习: 2.1.1分类学习(Cla

Python机器学习及实践 课后小题

目录 第二章 2.3章末小结 @(Python机器学习及实践-----从零开始通往Kaggle竞赛之路) 第二章 2.3章末小结 1 机器学习模型按照使用的数据类型,可分为监督学习和无监督学习两大类. 监督学习主要包括分类和回归的模型. 分类:线性分类,支持向量机(SVM),朴素贝叶斯,k近邻,决策树,集成模型(随机森林(多个决策树)等). 回归:线性回归,支持向量机(SVM),k近邻,回归树,集成模型(随机森林(多个决策树)等). 无监督学习主要包括:数据聚类(k-means)和数据降维(主成

Python应用与实践-转自(吴秦(Tyler))

1.      Python是什么? 1.1.      Python语言 1.2.      Python哲学 2.      Python在工作中的应用 2.1.      实例1:文件批量处理 2.2.      实例2:xml与excel互转 2.3.      总结 3.      为什么选择Python? 3.1.      前途!钱途! 3.2.      开发效率极高 3.3.      总而言之 4.      还有谁在用Python? 4.1.      国外 4.2.