Python中常见的陷阱
可能重复:
Python 2.x陷阱和地雷
今天,多年以后,我又一次被可变的默认论点所困扰。 除非需要,我通常不会使用可变默认参数,但是我认为随着时间的推移,我忘记了这一点。 今天在应用程序中,我在PDF生成函数的参数列表中添加了tocElements = [],现在在每次调用“generate pdf”后,“Table of Contents”变得越来越长。 🙂
还有什么我应该添加到我的清单,必须避免?
-
总是以相同的方式导入模块,例如
from y import x
和import x
被视为不同的模块 。 -
不要使用范围代替列表,因为
range()
无论如何都会成为一个迭代器,下面的代码将失败:myIndexList = [0, 1, 3] isListSorted = myIndexList == range(3) # will fail in 3.0 isListSorted = myIndexList == list(range(3)) # will not
同样的事情可以用xrange错误地完成:
myIndexList == xrange(3)
-
小心捕获多个exceptiontypes:
try: raise KeyError("hmm bug") except KeyError, TypeError: print TypeError
这打印“嗯bug”,虽然它不是一个bug; 它看起来像我们正在捕获这两种types的exception,但相反,我们只捕获KeyError作为variablesTypeError,而不是:
try: raise KeyError("hmm bug") except (KeyError, TypeError): print TypeError
不要使用索引来循环一个序列
别 :
for i in range(len(tab)) : print tab[i]
做:
for elem in tab : print elem
为了自动化大部分迭代操作。
使用enumerate
如果你真的需要索引和元素。
for i, elem in enumerate(tab): print i, elem
使用“==”检查True或False时要小心
if (var == True) : # this will execute if var is True or 1, 1.0, 1L if (var != True) : # this will execute if var is neither True nor 1 if (var == False) : # this will execute if var is False or 0 (or 0.0, 0L, 0j) if (var == None) : # only execute if var is None if var : # execute if var is a non-empty string/list/dictionary/tuple, non-0, etc if not var : # execute if var is "", {}, [], (), 0, None, etc. if var is True : # only execute if var is boolean True, not 1 if var is False : # only execute if var is boolean False, not 0 if var is None : # same as var == None
不要检查是否可以,只要做,并处理错误
Pythonistas通常会说:“要求宽恕比容许要容易”。
别 :
if os.path.isfile(file_path) : file = open(file_path) else : # do something
做:
try : file = open(file_path) except OSError as e: # do something
甚至更好的Python 2.6 / 3:
with open(file_path) as file :
这更好一些,因为它更具有普遍性。 几乎所有的东西都可以应用“try / except”。 你不需要关心怎么做才能防止它,只是你冒险的错误。
不要检查types
Python是dynamictypes的,因此检查types会使你失去灵活性。 相反,通过检查行为来使用鸭子打字。 EG,你需要一个函数中的string,然后使用str()来转换string中的任何对象。 你期待一个列表,使用list()来转换列表中的任何迭代。
别 :
def foo(name) : if isinstance(name, str) : print name.lower() def bar(listing) : if isinstance(listing, list) : listing.extend((1, 2, 3)) return ", ".join(listing)
做:
def foo(name) : print str(name).lower() def bar(listing) : l = list(listing) l.extend((1, 2, 3)) return ", ".join(l)
用最后的方法,foo会接受任何对象。 Bar将接受string,元组,集合,列表等等。 便宜干:-)
不要混合使用空格和制表符
只是不要。 你会哭的。
使用对象作为第一个父项
这很棘手,但随着程序的增长,它会咬你。 Python 2.x中有新旧类。 旧的是老的。 他们缺乏一些function,并且可能因为inheritance而带来尴尬的行为。 为了能够使用,您的任何课程都必须具有“新风格”。 要做到这一点,使其从“对象”inheritance:
别 :
class Father : pass class Child(Father) : pass
做:
class Father(object) : pass class Child(Father) : pass
在Python 3.x中,所有的类都是新的风格,所以你不需要这样做。
不要在__init__
方法之外初始化类属性
来自其他语言的人发现这很诱人,因为你在Java或PHP中做了什么工作。 你写的类名,然后列出你的属性,并给他们一个默认值。 它似乎在Python中工作,但是,这不符合你的想法。
这样做会设置类属性(静态属性),那么当你试图获得对象属性时,它会给你它的值,除非它是空的。 在这种情况下,它将返回类属性。
这意味着两大危害:
- 如果类属性被改变,则初始值被改变。
- 如果将可变对象设置为默认值,则会获得跨实例共享的同一对象。
不要(除非你想静态):
class Car(object): color = "red" wheels = [wheel(), Wheel(), Wheel(), Wheel()]
做:
class Car(object): def __init__(self): self.color = "red" self.wheels = [wheel(), Wheel(), Wheel(), Wheel()]
当你需要大量的数组时,你可能会想要input如下所示的内容:
>>> a=[[1,2,3,4,5]]*4
当然,它会给你你期望的东西
>>> from pprint import pprint >>> pprint(a) [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]
但不要指望你的人口的元素是单独的对象:
>>> a[0][0] = 2 >>> pprint(a) [[2, 2, 3, 4, 5], [2, 2, 3, 4, 5], [2, 2, 3, 4, 5], [2, 2, 3, 4, 5]]
除非这是你所需要的…
值得一提的是一个解决方法:
a = [[1,2,3,4,5] for _ in range(4)]
Python语言陷阱 – 以非常模糊的方式失败的事情
-
使用可变的默认参数。
-
前导零表示八进制。
09
是Python 2.x中一个非常模糊的语法错误 -
在超类或子类中拼写重写的方法名称。 超类拼写错误更糟糕,因为没有任何子类正确覆盖它。
Pythondevise陷阱
-
花时间内省(例如试图自动确定types或超类标识或其他东西)。 首先,从源头上看是很明显的。 更重要的是,在奇怪的Python内省上花费的时间通常表明基本上没有把握多态。 SO上的80%的Python内省问题没有得到多态性。
-
花时间在代码高尔夫上。 仅仅因为你的应用程序的心智模式是四个关键字(“做”,“什么”,“我”,“意思”),并不意味着你应该build立一个超复杂的内部装饰器驱动的框架来做到这一点。 Python允许你把DRY带到一个愚蠢的级别。 SO上的其他Python内省问题试图减less复杂的问题来编写高尔夫练习。
-
的Monkeypatching。
-
没有真正阅读标准库,并重新发明轮子。
-
将Python与交互式types相融合,并将其与适当的程序结合在一起。 在交互式input的时候,你可能会丢失一个variables,而必须使用
globals()
。 而且,当你打字的时候,几乎所有东西都是全球的。 在适当的程序中,你永远不会“失去”一个variables的轨迹,没有什么是全球性的。
突变默认参数:
def foo(bar=[]): bar.append('baz') return bar
默认值只计算一次,而不是每次调用函数。 重复调用foo()
会返回['baz']
, ['baz', 'baz']
, ['baz', 'baz', 'baz']
,…
如果你想mutate酒吧做这样的事情:
def foo(bar=None): if bar is None: bar = [] bar.append('baz') return bar
或者,如果你喜欢争论是最终的:
def foo(bar=[]): not_bar = bar[:] not_bar.append('baz') return not_bar
我不知道这是否是一个常见的错误,但是Python没有增量和减量运算符,所以允许双符号
++i
和
--i
在语法上是正确的代码,但没有做任何“有用的”或你可能会期待的。
在查看标准库之前,滚动您自己的代码。 例如,写这个:
def repeat_list(items): while True: for item in items: yield item
当你可以使用这个:
from itertools import cycle
经常被忽视的模块(除了itertools
)的例子包括:
-
optparse
用于创build命令行parsing器 -
ConfigParser
以标准方式读取configuration文件 - 用于创build和pipe理临时文件的临时文件
-
shelve
将Python对象存储到磁盘,当一个完整的数据库是矫枉过正时,方便
避免使用关键字作为自己的标识符。
而且, from somemodule import *
使用它总是好的。
如果你来自C ++,那么意识到在类定义中声明的variables是静态的。 您可以在init方法中初始化非静态成员。
例:
class MyClass: static_member = 1 def __init__(self): self.non_static_member = random()
不使用function工具。 从风格的angular度来看,这不仅仅是一个错误,从速度的angular度来看这是一个错误,因为很多function工具都是在C语言中优化的。
这是最常见的例子:
temporary = [] for item in itemlist: temporary.append(somefunction(item)) itemlist = temporary
正确的做法是:
itemlist = map(somefunction, itemlist)
正确的做法是:
itemlist = [somefunction(x) for x in itemlist]
如果您只需要一次只能使用一个处理过的项目,而不是一次处理完所有项目,则可以使用可迭代的等价物节省内存并提高速度
# itertools-based iterator itemiter = itertools.imap(somefunction, itemlist) # generator expression-based iterator itemiter = (somefunction(x) for x in itemlist)
惊讶的是没有人这样说:
缩进时混合制表符和空格。
真的,这是一个杀手。 相信我。 特别是 ,如果它运行。
像Pythonista一样的代码:习惯Python
当每个常用操作(如大写,简单匹配/search)都存在完美的string方法时,导入re
并使用完整的正则expression式来进行string匹配/转换。
- 不要将大量输出消息写入标准输出
- string是不可变的 – build立它们不是使用“+”运算符,而是使用str.join()函数。
- 阅读这些文章:
- pythongotchas
- 要避免的事情
- 寻找Python用户
- Python地雷
最后一个链接是原来的,这个问题是重复的。
在错误消息中使用%s
格式化程序。 几乎在任何情况下,都应该使用%r
。
例如,想像这样的代码:
try: get_person(person) except NoSuchPerson: logger.error("Person %s not found." %(person))
打印这个错误:
错误:找不到人员。
如果person
variables是string"wolever"
,unicodestringu"wolever"
或Person
类的实例(将__str__
定义为def __str__(self): return self.name
),则def __str__(self): return self.name
。 而如果使用%r
,则会有三个不同的错误消息:
... logger.error("Person %r not found." %(person))
会产生更多有用的错误:
错误:未find人员'wolever'。 错误:找不到人。 错误:未find人员。
另一个很好的理由是path复制/粘贴更容易。 想像:
try: stuff = open(path).read() except IOError: logger.error("Could not open %s" %(path))
如果path
是some path/with 'strange' "characters"
,错误消息将是:
错误:无法打开某些path/“奇怪”“字符”
这是很难在视觉上parsing和难以复制/粘贴到壳。
而如果使用%r
,错误将是:
错误:无法打开“某些path/与\”怪异“”字符“”
易于视觉分析,易于复制粘贴,一切都好。
正常的复制(分配)是通过引用完成的,所以通过调整相同的对象并插入来填充容器,最后引用最后一个添加的对象。
改用copy.deepcopy
。
我不得不训练自己的坏习惯是使用X and Y or Z
作为内联逻辑。
除非你能100%地保证Y
是一个真正的价值,即使你的代码在18个月的时间内改变,你也会为自己设定一些意想不到的行为。
谢天谢地,在后来的版本中, Y if X else Z
可以使用Y if X else Z
我会停止在2.6中使用不推荐使用的方法,以便您的应用程序或脚本将准备好,并更容易转换为Python 3。
import this
美丽胜过丑陋。
显式比隐式更好。
简单胜于复杂。
复杂比复杂好。
平面比嵌套更好。
稀疏比密集好。
可读性计数。
特例不足以打破规则。
虽然实用性胜过纯净。
错误不应该默默通过。
除非明确沉默。
面对歧义,拒绝猜测的诱惑。
应该有一个 – 最好只有一个 – 明显的方法来做到这一点。
尽pipe这种方式一开始可能并不明显,除非你是荷兰人。
现在比从未好。
虽然从来没有比现在更好。
如果实施很难解释,这是一个坏主意。
如果实施很容易解释,这可能是一个好主意。
命名空间是一个好主意 – 让我们做更多的!
import not_this
写难看的代码。
编写隐式代码。
编写复杂的代码。
编写嵌套代码。
编写密码。
写不可读的代码。
写特殊情况。
争取纯洁。
忽略错误和例外。
在释放之前编写最佳代码。
每个实施需要一个stream程图。
不要使用名称空间。
我也开始学习Python,我所做的最大的错误之一就是不断地使用C ++ / C#索引的“for”循环。 Python对于(i; i <length; i ++)types的循环有一个很好的理由 – 大多数时候有更好的方法来做同样的事情。
例子:我有一个迭代列表并返回所选项目的索引的方法:
for i in range(len(myList)): if myList[i].selected: retVal.append(i)
相反,Python具有列表理解function,可以以更加优雅和易读的方式解决相同的问题:
retVal = [index for index, item in enumerate(myList) if item.selected]
永远不要假设有一个multithreading的Python应用程序和一个支持SMP的机器(例如一个装有多核CPU的机器)将会给你带来真正的并行性到你的应用程序中的好处。 最有可能的不是因为GIL(全局解释器锁),它在字节码解释器级同步您的应用程序。
有一些解决方法,例如通过将并发代码放在C API调用中或使用多个进程(而不是线程)通过包装器(例如http://www.parallelpython.org上提供的那个)来利用SMP,但是如果在Python中需要真正的multithreading,应该看看像Jython,IronPython等等(GIL是CPython解释器的一个特性,所以其他实现不受影响)。;
根据Python 3000 FAQ(在Artima上提供),上述内容仍然代表最新的Python版本。
一些个人意见,但我觉得最好不要 :
-
使用已弃用的模块(使用警告)
-
过度使用类和inheritance(典型的静态语言传统可能)
-
明确地使用声明性algorithm(作为
itertools
使用迭代) -
从标准库中重新实现函数,“因为我不需要所有这些function”
-
使用function的原因(降低与旧的Python版本的兼容性)
-
使用元类时,你并不需要,更普遍的做法太“魔术”
-
避免使用生成器
-
(更个人的)尝试在低级基础上微观优化CPython代码。 最好花时间在algorithm上,然后通过
ctypes
调用一个小的C共享库来进行优化(在内部循环中获得5倍的性能提升是很容易的) -
迭代器足够时使用不必要的列表
-
在你需要的库都可用之前直接为3.x编译一个项目(这一点现在可能有点争议!)
与默认的可变参数有些相关,当一个空列表被传递时,如何检查“缺失”的情况会导致差异:
def func1(toc=None): if not toc: toc = [] toc.append('bar') def func2(toc=None): if toc is None: toc = [] toc.append('bar') def demo(toc, func): print func.__name__ print ' before:', toc func(toc) print ' after:', toc demo([], func1) demo([], func2)
这是输出:
func1 before: [] after: [] func2 before: [] after: ['bar']
++n
和--n
可能无法按照来自C或Java背景的人员的预期工作。
++n
是一个正数,正好是n
。
--n
是负数的负数,简单地说是n
。
甚至在你开始之前的第一个错误: 不要害怕空白 。
当你向某人展示一段Python代码时,他们会留下深刻的印象,直到你告诉他们必须正确缩进。 出于某种原因,大多数人认为,一种语言不应该强加给他们一定的风格,尽pipe如此,他们都会缩小代码。
你已经提到了默认参数…一个几乎和可变默认参数一样糟糕:默认值不是None
。
考虑一下将会烹饪一些食物的function:
def cook(breakfast="spam"): arrange_ingredients_for(breakfast) heat_ingredients_for(breakfast) serve(breakfast)
因为它指定了breakfast
的默认值,所以其他function不可能在没有特殊情况的情况下说出“默认早餐”
def order(breakfast=None): if breakfast is None: cook() else: cook(breakfast)
但是,如果cook
使用None
作为默认值,则可以避免这种情况:
def cook(breakfast=None): if breakfast is None: breakfast = "spam" def order(breakfast=None): cook(breakfast)
Django bug #6988就是一个很好的例子。 Django的caching模块有一个“保存到caching”function,看起来像这样:
def set(key, value, timeout=0): if timeout == 0: timeout = settings.DEFAULT_TIMEOUT _caching_backend.set(key, value, timeout)
但是,对于memcached后端, 0
的超时意味着“永不超时”……正如你所看到的,这是不可能指定的。
迭代时不要修改列表。
odd = lambda x : bool(x % 2) numbers = range(10) for i in range(len(numbers)): if odd(numbers[i]): del numbers[i]
解决这个问题的一个常见build议是反向遍历列表:
for i in range(len(numbers)-1,0,-1): if odd(numbers[i]): del numbers[i]
但更好的是使用列表理解build立一个新的列表来取代旧的:
numbers[:] = [n for n in numbers if not odd(n)]
my_variable = <something> ... my_varaible = f(my_variable) ... use my_variable and thinking it contains the result from f, and not the initial value
Python不会以任何方式警告你,在第二个任务中拼错variables名并创build一个新的variables名。
类似于可变默认参数是可变类属性。
>>> class Classy: ... foo = [] ... def add(self, value): ... self.foo.append(value) ... >>> instance1 = Classy() >>> instance2 = Classy() >>> instance1.add("Foo!") >>> instance2.foo ['Foo!']
不是你所期望的。
创build一个与stdlib同名的本地模块。 这几乎总是由于意外(如在这个问题中所报道的那样)完成的,但是通常会导致隐含的错误消息。
混杂的exception处理
这是我看到生产代码中令人惊讶的数量,这让我感到害怕。
try: do_something() # do_something can raise a lot errors eg files, sockets except: pass # who cares we'll just ignore it
是你想压制的那个例外,还是更严重? 但是还有更微妙的例子。 这可以让你拉出你的头发试图弄清楚。
try: foo().bar().baz() except AttributeError: # baz() may return None or an incompatible *duck type* handle_no_baz()
问题在于foo或baz也可能是罪魁祸首。 我认为这可能是更隐蔽的,因为这是惯用的python ,你正在检查你的types适当的方法。 但是每个方法调用都有机会返回意想不到的事情,并且抑制应该引发exception的错误。
知道一个方法可以抛出的exception并不总是显而易见的。 例如,urllib和urllib2使用套接字,它有自己的例外,当你最不期待的时候,它会渗透起来,并将丑陋的头部往后拉。
exception处理是处理系统级语言(如C)的错误时的生产力优势。但是我发现,不正确地抑制exception可以创build真正神秘的debugging会话,并带走了解释语言提供的主要优势。