Python中常见的陷阱

可能重复:
Python 2.x陷阱和地雷

今天,多年以后,我又一次被可变的默认论点所困扰。 除非需要,我通常不会使用可变默认参数,但是我认为随着时间的推移,我忘记了这一点。 今天在应用程序中,我在PDF生成函数的参数列表中添加了tocElements = [],现在在每次调用“generate pdf”后,“Table of Contents”变得越来越长。 🙂

还有什么我应该添加到我的清单,必须避免?

  • 总是以相同的方式导入模块,例如from y import ximport 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 

使用“==”检查TrueFalse时要小心

 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)) 

打印这个错误:

 错误:找不到人员。 

如果personvariables是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)) 

如果pathsome 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会话,并带走了解释语言提供的主要优势。