您的位置:首页 > 编程语言 > Python开发

Python 程序员经常犯的 10 个错误

2015-12-09 13:41 986 查看

http://www.oschina.net/translate/top-10-mistakes-that-python-programmers-make?cmp&p=2#

About Python

Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. Its high-level
built in data structures, combined with dynamic typing and dynamic binding, make it very attractive forRapid
Application Development, as well as for use as a scripting or glue language to connect existing components or services. Python supports modules and packages, thereby encouraging program modularity and code reuse.

About this article

Python’s simple, easy-to-learn syntax can mislead Python developers – especially those who are newer to the language – into missing some of its subtleties and underestimating the power of the language.

With that in mind, this article presents a “top 10” list of somewhat subtle, harder-to-catch mistakes that can bite even the most advanced Python developer in the rear.



译者信息


关于Python

Python是一种解释性、面向对象并具有动态语义的高级程序语言。它内建了高级的数据结构,结合了动态类型和动态绑定的优点,这使得它在快速应用开发中非常有吸引力,并且可作为脚本或胶水语言来连接现有的组件或服务。Python支持模块和包,从而鼓励了程序的模块化和代码重用。

关于这篇文章

Python简单易学的语法可能会使Python开发者–尤其是那些编程的初学者–忽视了它的一些微妙的地方并低估了这门语言的能力。

有鉴于此,本文列出了一个“10强”名单,枚举了甚至是高级Python开发人员有时也难以捕捉的错误。



Common Mistake #1: Misusing expressions as defaults for function arguments

Python allows you to specify that a function argument is optional by providing a default value for it. While this is a great feature of the language, it can lead to some
confusion when the default value is mutable.
For example, consider this Python function definition:
>>> def foo(bar=[]):        # bar is optional and defaults to [] if not specified
...    bar.append("baz")    # but this line could be problematic, as we'll see...
...    return bar


A common mistake is to think that the optional argument will be set to the specified default expression each time the function is called without supplying a value for the optional argument. In the above code, for
example, one might expect that calling
foo()
repeatedly (i.e., without specifying a
bar
argument) would always return
'baz'
,
since the assumption would be that each time
foo()
is called (without a
bar
argument specified)
bar
is
set to
[]
(i.e., a new empty list).
译者信息


常见错误 #1: 滥用表达式作为函数参数的默认值

Python允许为函数的参数提供默认的可选值。尽管这是语言的一大特色,但是它可能会导致一些易变默认值的混乱。例如,看一下这个Python函数的定义:
>>> def foo(bar=[]):        # bar is optional and defaults to [] if not specified
...    bar.append("baz")    # but this line could be problematic, as we'll see...
...    return bar


一个常见的错误是认为在函数每次不提供可选参数调用时可选参数将设置为默认指定值。在上面的代码中,例如,人们可能会希望反复(即不明确指定bar参数)地调用foo()时总返回'baz',由于每次foo()调用时都假定(不设定bar参数)bar被设置为[](即一个空列表)。

But let’s look at what actually happens when you do this:
>>> foo()
["baz", "baz"]>>> foo()
["baz", "baz", "baz"]

Huh?  Why did it keep appending the default value of 
"baz"
to an existing list each time [code]foo()
was called, rather than creating
a new list each time?

The answer is that the default value for a function argument is only evaluated once, at the time that the function is defined. Thus, the
bar
argument is initialized to
its default (i.e., an empty list) only when
foo()
is first defined, but then calls to
foo()
(i.e., without a
bar
argument
specified) will continue to use the same list to which
bar
was originally initialized.

FYI, a common workaround for this is as follows:
译者信息


但是让我们看一下这样做时究竟会发生什么:
>>> foo()
["baz", "baz"]>>> foo()
["baz", "baz", "baz"]


耶?为什么每次foo()调用时都要把默认值"baz"追加到现有列表中而不是创建一个新的列表呢?

答案是函数参数的默认值只会评估使用一次—在函数定义的时候。因此,bar参数在初始化时为其默认值(即一个空列表),即foo()首次定义的时候,但当调用foo()时(即,不指定bar参数时)将继续使用bar原本已经初始化的参数。

下面是一个常见的解决方法:
[code]Common Mistake #2: Using class variables incorrectly

Consider the following example:
>>> class A(object):
...     x = 1
...
>>> class B(A):
...     pass
...
>>> class C(A):
...     pass
...
>>> print A.x, B.x, C.x
1 1 1


Makes sense.
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1


Yup, again as expected.
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3


What the $%#!&?? We only changed [code]A.x
. Why did
C.x
change too?

In Python, class variables are internally handled as dictionaries and follow what is often referred to as Method
Resolution Order (MRO). So in the above code, since the attribute
x
is not found in class
C
, it will be looked up in its base classes (only
A
in
the above example, although Python supports multiple inheritance). In other words,
C
doesn’t have its own
x
property, independent of
A
.
Thus, references to
C.x
are in fact references to
A.x
.
译者信息


常见错误 #2: 错误地使用类变量

考虑一下下面的例子:
>>> 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.x,为什么C.x也改了?

在Python中,类变量在内部当做字典来处理,其遵循常被引用的方法解析顺序(MRO)。所以在上面的代码中,由于class
C中的x属性没有找到,它会向上找它的基类(尽管Python支持多重继承,但上面的例子中只有A)。换句话说,class C中没有它自己的x属性,其独立于A。因此,C.x事实上是A.x的引用。

Common Mistake #3: Specifying parameters incorrectly for an exception block

Suppose you have the following code:
>>> 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


The problem here is that the
except
statement does not take a list of exceptions specified in this manner. Rather, In Python 2.x, the syntax
except
Exception, e
is used to bind the exception to the optional second parameter specified (in this case
e
), in order to make it available for further inspection. As
a result, in the above code, the
IndexError
exception is not being caught by the
except
statement; rather, the exception
instead ends up being bound to a parameter named
IndexError
.

The proper way to catch multiple exceptions in an
except
statement is to specify the first parameter as a tuplecontaining
all exceptions to be caught. Also, for maximum portability, use the
as
keyword, since that syntax is supported by both Python 2 and Python 3:
>>> try:
...     l = ["a", "b"]
...     int(l[2])
... except (ValueError, IndexError) as e:
...     pass
...
>>>

译者信息


常见错误 #3: 为 except 指定错误的参数

假设你有如下一段代码:
>>> 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


这里的问题在于 except 语句并不接受以这种方式指定的异常列表。相反,在Python 2.x中,使用语法 except Exception, e 是将一个异常对象绑定到第二个可选参数(在这个例子中是 e)上,以便在后面使用。所以,在上面这个例子中,IndexError 这个异常并不是被except语句捕捉到的,而是被绑定到一个名叫
IndexError的参数上时引发的。

在一个except语句中捕获多个异常的正确做法是将第一个参数指定为一个含有所有要捕获异常的元组。并且,为了代码的可移植性,要使用as关键词,因为Python
2 和Python 3都支持这种语法:
>>> try:
...     l = ["a", "b"]
...     int(l[2])
... except (ValueError, IndexError) as e:
...     pass
...
>>>

Common Mistake #4: Misunderstanding Python scope rules

Python scope resolution is based on what is known as the LEGB rule,
which is shorthand for Local, Enclosing,Global, Built-in. Seems straightforward
enough, right? Well, actually, there are some subtleties to the way this works in Python. Consider the following:
>>> 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


What’s the problem?

The above error occurs because, when you make an assignment to a variable in a scope, that variable is automatically considered by Python to be local to that scope and
shadows any similarly named variable in any outer scope.

Many are thereby surprised to get an
UnboundLocalError
in previously working code when it is modified by adding an assignment statement somewhere in the body of a function. (You can read more about this here.)
译者信息


常见错误 #4: 不理解Python的作用域

Python是基于 LEGB 来进行作用于解析的, LEGB 是 Local, Enclosing, Global, Built-in
的缩写。看起来“见文知意”,对吗?实际上,在Python中还有一些需要注意的地方,先看下面一段代码:
>>> 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


这里出什么问题了?

上面的问题之所以会发生是因为当你给作用域中的一个变量赋值时,Python 会自动的把它当做是当前作用域的局部变量,从而会隐藏外部作用域中的同名变量。

很多人会感到很吃惊,当他们给之前可以正常运行的代码的函数体的某个地方添加了一句赋值语句之后就得到了一个 UnboundLocalError 的错误。 (你可以在这里了解到更多)

It is particularly common for this to trip up developers when using lists.
Consider the following example:
>>> lst = [1, 2, 3]
>>> def foo1():
...     lst.append(5)   # This works ok...
...
>>> foo1()
>>> lst
[1, 2, 3, 5]

>>> lst = [1, 2, 3]
>>> def foo2():
...     lst += [5]      # ... but this bombs!
...
>>> 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


Huh? Why did
foo2
bomb while
foo1
ran fine?

The answer is the same as in the prior example, but is admittedly more subtle.
foo1
is not making anassignment to
lst
, whereas
foo2
is.
Remembering that
lst += [5]
is really just shorthand for
lst = lst + [5]
, we see that we are attempting to assign a value
to
lst
(therefore presumed by Python to be in the local scope). However, the value we are looking to assign to
lst
is based on
lst
itself
(again, now presumed to be in the local scope), which has not yet been defined. Boom.
译者信息


尤其是当开发者使用 lists 时,这个问题就更加常见. 请看下面这个例子:
>>> 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


嗯?为什么 foo2 报错,而foo1没有问题呢?

原因和之前那个例子的一样,不过更加令人难以捉摸。foo1 没有对 lst 进行赋值操作,而 foo2 做了。要知道, lst += [5] 是 lst = lst + [5] 的缩写,我们试图对 lst 进行赋值操作(Python把他当成了局部变量)。此外,我们对 lst 进行的赋值操作是基于 lst 自身(这再一次被Python当成了局部变量),但此时还未定义。因此出错!

Common Mistake #5: Modifying a list while iterating over it

The problem with the following code should be fairly obvious:
>>> 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]  # BAD: Deleting item from a list while iterating over it
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: list index out of range


Deleting an item from a list or array while iterating over it is a faux pas well known to any experienced software developer. But while the example above may be fairly obvious, even advanced developers can be unintentionally bitten by this in code that is
much more complex.

Fortunately, Python incorporates a number of elegant programming paradigms which, when used properly, can result in significantly simplified and streamlined code. A side benefit of this is that simpler code is less likely to be bitten by the accidental-deletion-of-a-list-item-while-iterating-over-it
bug. One such paradigm is that of list comprehensions. Moreover,
list comprehensions are particularly useful for avoiding this specific problem, as shown by this alternate implementation of the above code which works perfectly:
>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> numbers[:] = [n for n in numbers if not odd(n)]  # ahh, the beauty of it all
>>> numbers
[0, 2, 4, 6, 8]

译者信息


常见错误#5:当迭代时修改一个列表(List)

下面代码中的问题应该是相当明显的:
>>> 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]  # BAD: Deleting item from a list while iterating over it
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: list index out of range


当迭代的时候,从一个 列表 (List)或者数组中删除元素,对于任何有经验的开发者来说,这是一个众所周知的错误。尽管上面的例子非常明显,但是许多高级开发者在更复杂的代码中也并非是故意而为之的。

幸运的是,Python包含大量简洁优雅的编程范例,若使用得当,能大大简化和精炼代码。这样的好处是能得到更简化和更精简的代码,能更好的避免程序中出现当迭代时修改一个列表(List)这样的bug。一个这样的范例是递推式列表(list
comprehensions)。而且,递推式列表(list comprehensions)针对这个问题是特别有用的,通过更改上文中的实现,得到一段极佳的代码:

>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> numbers[:] = [n for n in numbers if not odd(n)]  # ahh, the beauty of it all
>>> numbers
[0, 2, 4, 6, 8]

Common Mistake #6: Confusing how Python binds variables in closures

Considering the following example:
>>> def create_multipliers():
...     return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
...     print multiplier(2)
...


You might expect the following output:

But you actually get:
8
8
8
8
8


Surprise!

This happens due to Python’s late binding behavior which says that the values of variables used in closures are looked up at the time the inner function is called. So in the above code, whenever any of the returned
functions are called, the value of [code]i
is looked up in the surrounding scope at the time it is called (and by then, the loop has completed, so
i
has
already been assigned its final value of 4).

The solution to this is a bit of a hack:

Voilà! We are taking advantage of default arguments here to generate anonymous functions in order to achieve the desired behavior. Some would call this elegant. Some would call it subtle. Some hate it. But if you’re a Python developer, it’s important to
understand in any case.
译者信息


常见错误 #6: 不明白Python在闭包中是如何绑定变量的

看下面这个例子:
>>> def create_multipliers():
...     return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
...     print multiplier(2)
...


你也许希望获得下面的输出结果:

但实际的结果却是:
[code]8
8
8
8
8


惊讶吧!

这之所以会发生是由于Python中的“后期绑定”行为——闭包中用到的变量只有在函数被调用的时候才会被赋值。所以,在上面的代码中,任何时候,当返回的函数被调用时,Python会在该函数被调用时的作用域中查找 i 对应的值(这时,循环已经结束,所以 i 被赋上了最终的值——4)。

解决的方法有一点hack的味道:

在这里,我们利用了默认参数来生成一个匿名的函数以便实现我们想要的结果。有人说这个方法很巧妙,有人说它难以理解,还有人讨厌这种做法。但是,如果你是一个 Python 开发者,理解这种行为很重要。

Common Mistake #7: Creating circular module dependencies

Let’s say you have two files, [code]a.py
and
b.py
, each of which imports the other, as follows:

In
a.py
:
import b

def f():
return b.x

print f()


And in
b.py
:
import a

x = 1

def g():
print a.f()


First, let’s try importing
a.py
:
>>> import a
1


Worked just fine. Perhaps that surprises you. After all, we do have a circular import here which presumably should be a problem, shouldn’t it?

The answer is that the mere presence of a circular import is not in and of itself a problem in Python. If a module has already been imported, Python is smart enough not to try to re-import it. However, depending
on the point at which each module is attempting to access functions or variables defined in the other, you may indeed run into problems.
译者信息


常见错误 #7: 创建循环依赖模块

让我们假设你有两个文件,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中,仅仅引入一个循环依赖的模块是没有问题的。如果一个模块已经被引入了,Python并不会去再次引入它。但是,根据每个模块要访问其他模块中的函数和变量位置的不同,就很可能会遇到问题。

So returning to our example, when we imported
a.py
, it had no problem importing
b.py
, since
b.py
does not require anything
from
a.py
to be defined at the time it is imported. The only reference in
b.py
to
a
is
the call to
a.f()
. But that call is in
g()
and nothing in
a.py
or
b.py
invokes
g()
.
So life is good.

But what happens if we attempt to import
b.py
(without having previously imported
a.py
, that is):
>>> 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'


Uh-oh. That’s not good! The problem here is that, in the process of importing
b.py
, it attempts to import
a.py
, which in turn calls
f()
,
which attempts to access
b.x
. But
b.x
has not yet been defined. Hence the
AttributeError
exception.

At least one solution to this is quite trivial. Simply modify
b.py
to import
a.py
within
g()
:
x = 1

def g():
import a    # This will be evaluated only when g() is called
print a.f()


No when we import it, everything is fine:
>>> import b
>>> b.g()
1    # Printed a first time since module 'a' calls 'print f()' at the end
1    # Printed a second time, this one is our call to 'g'

译者信息


所以,回到我们这个例子,当我们引入 a.py 时,再引入 b.py 不会产生任何问题,因为当引入的时候,b.py 不需要 a.py 中定义任何东西。b.py 中唯一引用 a.py 中的东西是调用 a.f()。 但是那个调用是发生在g() 中的,并且 a.py 和 b.py 中都没有调用 g()。所以运行正常。

但是,如果我们尝试去引入b.py 会发生什么呢?(在这之前不引入a.py),如下所示:
>>> 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'


啊哦。 出问题了!此处的问题是,在引入b.py的过程中,Python尝试去引入 a.py,但是a.py 要调用f(),而f() 有尝试去访问 b.x。但是此时 b.x 还没有被定义呢。所以发生了 AttributeError 异常。

至少,解决这个问题很简单,只需修改b.py,使其在g()中引入 a.py:
x = 1

def g():
import a    # 只有当g()被调用的时候才会引入a
print a.f()


现在,当我们再引入b,没有任何问题:
>>> import b
>>> b.g()
1    # Printed a first time since module 'a' calls 'print f()' at the end
1    # Printed a second time, this one is our call to 'g'

Common Mistake #8: Name clashing with Python Standard Library modules

One of the beauties of Python is the wealth of library modules that it comes with “out of the box”. But as a result, if you’re not consciously avoiding it, it’s not that difficult to run into a name clash between the name of one of your modules and a module
with the same name in the standard library that ships with Python (for example, you might have a module named
email.py
in your code, which would be in conflict with the standard library module of the same name).

This can lead to gnarly problems, such as importing another library which in turns tries to import the Python Standard Library version of a module but, since you have a module with the same name, the other package mistakenly imports your version instead of
the one within
stdlib
. This is where bad stuff happens.

Care should therefore definitely be exercised to avoid using the same names as those in the Python Standard Library modules. It’s way easier for you to change the name of a module within your package than it is to file a Python
Enhancement Proposal (PEP) to request a name change upstream and to try and get that approved.
译者信息


常见错误 #8: 与Python标准库中的模块命名冲突

Python一个令人称赞的地方是它有丰富的模块可供我们“开箱即用”。但是,如果你没有有意识的注意的话,就很容易出现你写的模块和Python自带的标准库的模块之间发生命名冲突的问题(如,你也许有一个叫 email.py 的模块,但这会和标准库中的同名模块冲突)。

这可能会导致很怪的问题,例如,你引入了另一个模块,但这个模块要引入一个Python标准库中的模块,由于你定义了一个同名的模块,就会使该模块错误的引入了你的模块,而不是 stdlib 中的模块。这就会出问题了。

因此,我们必须要注意这个问题,以避免使用和Python标准库中相同的模块名。修改你包中的模块名要比通过 Python Enhancement Proposal (PEP) 给Python提建议来修改标准库的模块名容易多了。

Common Mistake #9: Failing to address differences between Python 2 and Python 3

Consider the following file
foo.py
:
import sys

def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)

def bad():
e = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)

bad()


On Python 2, this runs fine:
$ python foo.py 1
key error
1
$ python foo.py 2
value error
2


But now let’s give it a whirl on Python 3:
$ python3 foo.py 1
key error
Traceback (most recent call last):
File "foo.py", line 19, in <module>
bad()
File "foo.py", line 17, in bad
print(e)
UnboundLocalError: local variable 'e' referenced before assignment


What has just happened here? The “problem” is that, in Python 3, the exception object is not accessible beyond the scope of the
except
block. (The reason for this is that, otherwise, it would keep a reference cycle
with the stack frame in memory until the garbage collector runs and purges the references from memory. More technical detail about this is available here).

One way to avoid this issue is to maintain a reference to the exception object outside the scope of the
except
block so that it remains accessible. Here’s a version of
the previous example that uses this technique, thereby yielding code that is both Python 2 and Python 3 friendly:
import sysdef bar(i):
if i == 1:        raise KeyError(1)    if i == 2:        raise ValueError(2)def good():
exception = None
try:
bar(int(sys.argv[1]))    except KeyError as e:
exception = e
print('key error')    except ValueError as e:
exception = e
print('value error')
print(exception)

good()


Running this on Py3k:
$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2


Yippee!

(Incidentally, our Python Hiring Guide discusses a number of other important differences to be aware of
when migrating code from Python 2 to Python 3.)
译者信息


常见错误 #9: 未能解决Python 2和Python 3之间的差异

请看下面这个 filefoo.py:
import sys

def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)

def bad():
e = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)

bad()


在Python 2中运行正常:
$ python foo.py 1
key error
1
$ python foo.py 2
value error
2


但是,现在让我们把它在Python 3中运行一下:
$ python3 foo.py 1
key error
Traceback (most recent call last):
File "foo.py", line 19, in <module>
bad()
File "foo.py", line 17, in bad
print(e)
UnboundLocalError: local variable 'e' referenced before assignment


出什么问题了? “问题”就是,在 Python 3 中,异常的对象在 except 代码块之外是不可见的。(这样做的原因是,它将保存一个对内存中堆栈帧的引用周期,直到垃圾回收器运行并且从内存中清除掉引用。了解更多技术细节请参考这里


一种解决办法是在 except 代码块的外部作用域中定义一个对异常对象的引用,以便访问。下面的例子使用了该方法,因此最后的代码可以在Python 2 和 Python 3中运行良好。
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
exception = e
print('key error')
except ValueError as e:
exception = e
print('value error')
print(exception)

good()


在Py3k中运行:
$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2


正常!

(顺便提一下, 我们的 Python Hiring Guide 讨论了当我们把代码从Python 2 迁移到 Python 3时的其他一些需要知道的重要差异。)

Common Mistake #10: Misusing the
__del__
method

Let’s say you had this in a file called
mod.py
:
import foo

class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)


And you then tried to do this from
another_mod.py
:
import mod
mybar = mod.Bar()


You’d get an ugly
AttributeError
exception.

Why? Because, as reported here, when the interpreter
shuts down, the module’s global variables are all set to
None
. As a result, in the above example, at the point that
__del__
is
invoked, the name
foo
has already been set to
None
.

A solution would be to use
atexit.register()
instead.
That way, when your program is finished executing (when exiting normally, that is), your registered handlers are kicked off before the interpreter is shut down.

With that understanding, a fix for the above
mod.py
code might then look something like this:
import foo
import atexit

def cleanup(handle):
foo.cleanup(handle)

class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)


This implementation provides a clean and reliable way of calling any needed cleanup functionality upon normal program termination. Obviously, it’s up to
foo.cleanup
to decide what to do with the object bound to
the name
self.myhandle
, but you get the idea.
译者信息


常见错误 #10: 误用__del__方法

假设你有一个名为 calledmod.py 的文件:
import foo

class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)


并且有一个名为 another_mod.py 的文件:
import mod
mybar = mod.Bar()


你会得到一个 AttributeError 的异常。

为什么呢?因为,正如这里所说,当解释器退出的时候,模块中的全局变量都被设置成了 None。所以,在上面这个例子中,当 __del__ 被调用时,foo
已经被设置成了None。

解决方法是使用 atexit.register() 代替。用这种方式,当你的程序结束执行时(意思是正常退出),你注册的处理程序会在解释器退出之前执行。

了解了这些,我们可以将上面 mod.py 的代码修改成下面的这样:
import foo
import atexit

def cleanup(handle):
foo.cleanup(handle)

class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)


这种实现方式提供了一个整洁并且可信赖的方法用来在程序退出之前做一些清理工作。很显然,它是由foo.cleanup 来决定对绑定在 self.myhandle 上对象做些什么处理工作的,但是这就是你想要的。

Wrap-up

Python is a powerful and flexible language with many mechanisms and paradigms that can greatly improve productivity. As with any software tool or language, though, having a limited understanding or appreciation of its capabilities can sometimes be more of
an impediment than a benefit, leaving one in the proverbial state of “knowing enough to be dangerous”.

Familiarizing oneself with the key nuances of Python, such as (but by no means limited to) the issues raised in this article, will help optimize use of the language while avoiding some of its more common pitfalls.

You might also want to check out our Insider’s Guide to Python Interviewing for suggestions on interview
questions that can help identify Python experts.

We hope you’ve found the pointers in this article helpful and welcome your feedback.
译者信息


总结

Python是一门强大的并且很灵活的语言,它有很多机制和语言规范来显著的提高你的生产力。和其他任何一门语言或软件一样,如果对它能力的了解有限,这很可能会给你带来阻碍,而不是好处。正如一句谚语所说的那样 “knowing enough to be dangerous”(译者注:意思是自以为已经了解足够了,可以做某事了,但其实不是)。

熟悉Python的一些关键的细微之处,像本文中所提到的那些(但不限于这些),可以帮助我们更好的去使用语言,从而避免一些常见的陷阱。

你可以查看“Python 面试官指南” 来获得一些关于如何辨别一个开发者是否是Python专家的建议。

我们希望你在这篇文章中找到了一些对你有帮助的东西,并希望你得到你的反馈。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: