Python开发人员最常见的8个错误
大多数python开发人员具有不同的核心编程语言背景,例如java,C#或c ++。 因此,他们习惯于用艰苦的方式做事,而当它们以简单易学的Python语言被引入时,它们会误解Python的多样性和功能,并常常最终导致自己误导其失去某些细微之处。
在本文中,我将尝试解决Python程序员遇到的错误。 这些错误甚至是本文中的大多数错误都是针对中级甚至专家级的开发人员的。 想知道? 如果您是初学者或中级开发人员,请继续阅读文章Python开发人员最常犯的10个错误,因为当前文章适合更高级的读者。
1.遍历列表时修改列表
这是每个Python开发人员一生中至少面对一次的问题。在下面的代码片段中查看问题:
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i] # 在列表上进行迭代时删除列表中的项目...Traceback (most recent call last): File "<stdin>", line 2, in <module> IndexError: list index out of range
这个问题非常明显,但即使是高级开发人员,在复杂的工作流程中添加代码时也会犯类似的错误。
有几种解决方案。我想在这里讨论一个最佳解决方案,但据我说这是最简单的解决方案,因此我不太可能产生错误。我建议列表理解在这种情况下非常有用。看看上面的代码具有列表理解的实现:
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)] >>> numbers [0, 2, 4, 6, 8]
2.创建循环模块依赖项
假设您有两个文件a.py和b.py,每个文件都导入另一个文件,如下所示:
在a.py中:
import b def f(): return b.x print f()
在b.py中:
import a x = 1 def g(): print a.f()
首先,让我们尝试导入a.py:
>>> import a 1
一切正常,没有错误。它应该给您一个错误,但是这里的问题是,如果存在循环依赖关系,您有时可以摆脱它,因为python足够聪明,可以跟踪导入的软件包。当每个模块尝试访问另一个模块的功能时会发生问题,因为不会声明另一个模块,这将导致AttributeError,如下所示:
>>> import b Traceback (most recent call last): File "<stdin>", line 1, in <module> File "b.py", line 1, in <module> import a File "a.py", line 6, in <module> print f() File "a.py", line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'
要解决此问题,我们需要在函数内部导入其他依赖模块:
x = 1 def g(): import a # 仅在调用g()时才会计算 print a.f()
现在一切都应该运行良好:
>>> import b >>> b.g()1 #自模块'a'在最后调用'print f()'以来首次打印 1 #第二次打印,这是我们对“ g”的调用
3.错误使用表达式作为函数参数的默认值
这是很难调试的错误之一,因为它不会给您带来错误,而且在大多数情况下它可以正常工作,并且开发人员可以摆脱它。当我们可以指定一个可变的可选函数参数时,就会发生这种情况。例如:
>>> def foo(bar=[]): ... bar.append("baz") ... return bar
看起来我们已经创建了一个函数,该函数会将baz附加在指定给它的列表的末尾,否则每次在不使用bar参数的情况下调用它都将返回[“ baz”],因为bar将被初始化为[]。但是,当我们执行它时,我们得到以下结果:
>>> foo() ["baz"] >>> foo() ["baz", "baz"] >>> foo() ["baz", "baz", "baz"]
输出结果并非预期的那样,现在您将看到,如果没有人注意到此问题,那么如果调用它,大多数开发人员将如何摆脱它。通过查看代码,很难找到尚未遇到此问题或不知道Python如何评估函数的人,因为每次它将bar的值初始化为[]时,都很难。但是在Python中,默认参数仅被评估一次,因此在第一次调用时它会按预期工作,但是在第二次和第三次调用中,它使用现有的条形列表而不是对其进行初始化。她是我们如何解决这个问题的方法:
>>> def foo(bar=None): ... if bar is None: #或者 if not bar: ... bar = [] ... bar.append("baz") ... return bar ...>>> foo()["baz"] >>> foo()["baz"] >>> foo()["baz"]
4.错误使用类变量
考虑以下示例:
>>> class A(object): ... x = 1 ... >>> class B(A): ... pass ...>>> class C(A): ... pass ...>>> print A.x, B.x, C.x 1 1 1
这个说得通。
>>> B.x = 2 >>> print A.x, B.x, C.x 1 2 1
再次如预期
>>> A.x = 3 >>> print A.x, B.x, C.x 3 2 3
什么?通过更改A类的值,C类不会受到影响是很奇怪的。这是因为在Python中,类变量在内部作为字典处理,并且遵循称为“方法解析顺序”的顺序。发生这种情况是因为在类C中找不到属性x,所以在基类A中对其进行了查找。
5.为异常块指定不正确的参数
对于给定的代码:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... pass...Traceback (most recent call last): File "<stdin>", line 3, in <module> IndexError: list index out of range 追溯(最近一次通话): <模块>中第3行的文件“ <stdin>” IndexError:列表索引超出范围
看来您正在尝试捕获这两个异常,但这是行不通的。此错误通常是由来自python 2.x背景的开发人员犯的,因为在Python 2中,此语法用于将异常绑定到可选参数。我们的代码应该捕获了IndexError异常。正确的方法是使用元组,方法是指定要在元组中捕获并使用as绑定到参数的所有异常。 python 2&3也支持此语法:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ...>>>
6.对Python作用域规则的误解
Python范围解析基于所谓的LEGB规则,它是Local,Enclosing,Global,Built-in的简写。但这会使开发人员遇到麻烦,例如:
>>> x = 10 >>> def foo(): ... x += 1 ... print x...>>> foo()Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment 追溯(最近一次通话): <模块>中第1行的文件“ <stdin>” 文件“ <stdin>”,第2行,在foo中 UnboundLocalError:分配前已引用局部变量“ x”
没道理我们已经声明x应该可以正常运行。调用该函数会在这里寻找变量x,但找不到变量x,它将把它带到外部作用域。直到我们对其进行分配之前,它都可以正常工作。我们得到UnboundLocalError以避免函数意外更改变量的值。有关更多信息,请参见此处。
为了进一步说明,下面是一些其他示例:
>>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5) ...>>> foo1()>>> lst[1, 2, 3, 5] >>> lst = [1, 2, 3] >>> def foo2(): ... lst += [5] ...>>> foo2()Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment 追溯(最近一次通话): <模块>中第1行的文件“ <stdin>” 文件“ <stdin>”,行2,在foo中 UnboundLocalError:分配前已引用局部变量“ lst”
在这两种情况下,我们都尝试从外部范围更新列表。在第一个示例中它起作用了,但是在第二个示例中却没有起作用,因为我们在函数主体中的该列表上使用了赋值运算符。这将尝试将计算/评估的值存储到foo2中不存在的局部变量lst中。
7.混淆Python如何在闭包中绑定变量
考虑以下示例:、
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
您可能期望以下输出:
0 2 4 6 8
但是您实际上得到:
8 8 8 8 8
由于Python的后期绑定行为,即在调用内部函数时会搜索闭包中使用的变量的值。因此,在上面的代码中,无论何时调用任何返回的函数,i的值都会在调用时在周围的范围内进行搜索,但是到发生这种情况时,循环就完成了,因此我已经为其分配了i最终值为4。一种解决方法是:
>>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ...>>> for multiplier in create_multipliers(): ... print multiplier(2) ... 0 2 4 6 8
8.重新发明轮子
这是在来自低级语言背景(例如c ++,c#等)的开发人员中最常见的。由于庞大的社区和大量的开放源代码内容以及对python社区的帮助,如果没有现有的解决方案,很难发现问题。但是在这里,我谈论的是重新创建python提供的基础。其中一些可能包括使用装饰器,生成器,内置函数。我最好的例子是排序功能。当我为开发人员编写满足其独特需求的自定义排序函数时,我已经看到了好几次。在所有情况下,整个功能都可以用更简单,更优雅和更强大的代码代替:
list.sort(key=…, reverse=…)
很难找到无法解决我们问题的实际方案。我们甚至可以使用此方法对元组,字典或任何Python对象进行排序。
看下面的例子:
>>> vowels = ['e', 'a', 'u', 'o', 'i'] >>> vowels.sort(reverse=True) >>> print('Sorted list (in Descending):', vowels) Sorted list (in Descending): ['u', 'o', 'i', 'e', 'a']
这是元组列表的示例