http://blog.csdn.net/pipisorry/article/details/45175457
Introduction
本文介绍python编程中很难捕捉10大错误
(Note: This article is intended for a more advanced audience than Common Mistakes of Python Programmers, which is
geared(适合) more toward those who are newer to the language.)
Common Mistake #1: Misusing expressions as defaults for function arguments
Python allows you to specify(指定) that a function argument isoptional by providing a
default value for it. While this is a greatfeature(特色) of the language, it can lead to someconfusion(混淆)
when the default value ismutable. For example, consider this Python functiondefinition(定义):
>>> 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 expressioneach time the function is called without supplying a value for the optional argument. In the above code, for example,
one might expect that callingfoo()
repeatedly (i.e., without
specifying(指定) a
argument) would always return
bar‘baz‘
, since the
assumption(假定) would be thateach time
foo()
is called (without a bar
argument specified)bar
is set to
[]
(i.e., a new empty list).
But let’s look at what actually happens when you do this:
>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]
Huh? Why did it keep appending(附加) the default value of"baz"
to an
existing list each time foo()
was called, rather than creating anew list each time?
The more advanced Python programming answer is that the default value for a function argument is onlyevaluated(评价) once, at the time that the
function isdefined(定义). Thus, thebar
argument is
initialized(初始化) to its default (i.e., an empty list) only whenfoo()
is first defined, but then calls to
foo()
(i.e., without abar
argument
specified(规定的)) will continue to use the same list to whichbar
was originally initialized.
FYI, a common workaround(工作区) for this is as follows:
>>> def foo(bar=None):
... if bar is None: # or if not bar:
... bar = []
... bar.append("baz")
... return bar
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]
Common Mistake #2: Using classvariables(变量) 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 A.x
. Why did C.x
change too?
In Python, class variables(变量) areinternally(内部地)
handled as dictionaries and follow what is often referred to asMethod 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 (onlyA
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(参考) toC.x
are in fact references to
A.x
. This causes a Python problem unless it’s handled properly. Learn more aoutclass
attributes(属性) in Python.
Common Mistake #3:
Specifying(指定)
parameters(参数) incorrectly for anexception(例外) 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, thesyntax(语法)except
is used to bind(绑) the exception to theoptional second
Exception, e
parameter(参数)specified(指定) (in this casee
),
in order to make it available for further
inspection(视察). As a result, in the above code, theIndexError
exception is
not being caught by the except
statement(声明); rather, theexception(例外)
instead ends up being bound to a parameter namedIndexError
.
The proper way to catch multiple exceptions in an except
statement is to specify the first parameter as atuple containing all exceptions
to be caught. Also, for maximum portability(可移植性), use theas
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
...
>>>
Common Mistake #4: Misunderstanding Pythonscope(范围) rules
Python(巨蟒) scoperesolution(分辨率) is based
on what is known as theLEGB rule, which is
shorthand(速记法的) forLocal,
Enclosing, Global, Built-in. Seems
straightforward(简单的) enough, right? Well, actually, there are somesubtleties(微妙)
to the way this works in Python, which brings us to the common more advanced Python programming problem below. 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 ascope(范围),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 ismodified(修改) by adding anassignment(分配)statement(声明)
somewhere in the body of a function. (You can read more about thishere.)
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 problem, but is admittedly moresubtle(微妙的).foo1
is not making an assignment to lst
, whereasfoo2
is. Remembering that
lst += [5]
is really just
shorthand(速记法的) forlst = lst + [5]
, we see that we are attempting to
assign a value tolst
(therefore
presumed(假定) by Python to be in the localscope(范围)). However, the value we are looking
toassign(分配) tolst
is based on
lst
itself (again, now presumed to be in the local scope), which has not yet beendefined(定义).Boom(兴旺).
Common Mistake #5: Modifying a list whileiterating(迭代)
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 Python problem that is 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 morecomplex(复杂的).
Fortunately, Python incorporates(包含) a number ofelegant(高雅的)
programmingparadigms(范例) which, when used properly, can result insignificantly(意味深长地)simplified(简化了的)
andstreamlined(流线型的) code. A sidebenefit(利益)
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 thisspecific(特殊的) problem,
as shown by thisalternate(交替的)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]
Common Mistake #6: Confusing how Pythonbinds(捆绑)variables(变量)
inclosures(关闭)
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(输出):
0
2
4
6
8
But you actually get:
8
8
8
8
8
Surprise!
This happens due to Python’s late binding
behavior(行为) which says that the values ofvariables(变量) used inclosures(关闭)
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
i
is looked up in the surrounding
scope(范围) at the time it is called (and by then, theloop(环) has completed, soi
has already been assigned(分配) its final value of 4).
The solution(解决方案) to this common Python problem is a bit of a hack:
>>> 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
Voilà! We are taking advantage of default arguments here to
generate(形成)
anonymous(匿名的) functions in order to achieve the desired behavior. Some would call thiselegant(高雅的).
Some would call itsubtle(微妙的). Some hate it. But if you’re a Python developer, it’s important to understand in any case.
Common Mistake #7: Creatingcircular(通知) moduledependencies(依赖性)
Let’s say you have two files, 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 whichpresumably(大概) 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.
So returning to our example, when we imported a.py
, it had no problem importingb.py
, since
b.py
does not require anything from a.py
to be definedat the time it is imported. The only
reference(参考) inb.py
to
a
is the call to a.f()
. But that call is ing()
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 importeda.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 importingb.py
, it attempts to import
a.py
, which in turn calls f()
, which attempts to access
b.x
. But b.x
has not yet beendefined(定义).Hence(因此)
theAttributeError
exception.
At least one solution(解决方案) to this is quitetrivial(不重要的).
Simplymodify(修改)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‘
Common Mistake #8: Nameclashing(冲突) 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 notconsciously(自觉地)
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 namedemail.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 the Python Standard Library. This is where bad Python errors happen.
Care should therefore 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 aPython(巨蟒)
Enhancement Proposal (PEP) to request a name changeupstream(上游部门) and to try and get thatapproved(批准).
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 notaccessible(易接近的) beyond thescope(范围)
of theexcept
block. (The reason for this is that, otherwise, it would keep areference(参考) cycle with thestack(堆)frame(框架)
in memory until the garbagecollector(收藏家) runs andpurges(净化)
the references from memory. More technical detail about this is availablehere).
One way to avoid this issue is to
maintain(维持) a
reference(参考) to theexception(例外) objectoutside the
scope(范围) of theexcept
block so that it remains
accessible(易接近的). Here’s a version of the previous example that uses this technique, therebyyielding(屈服)
code that is both Python 2 and Python 3 friendly:
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()
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 whenmigrating(移动) code from Python 2 to 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 globalvariables(变量)
are all set toNone
. As a result, in the above example, at the point that
__del__
is invoked(调用), the namefoo
has already been set to
None
.
A solution(解决方案) to this somewhat more advanced Python programming problem would be to useatexit.register()
instead. That way, when your program is finishedexecuting(实行) (when exiting normally, that is), your registered handlers are kicked offbefore
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 andreliable(可靠的)
way of calling any neededcleanup(第四位击球员的)functionality(功能)
upon normal programtermination(结束). Obviously, it’s up tofoo.cleanup
to decide what to do with the object bound to the name
self.myhandle
, but you get the idea.
Wrap-up
Python is a powerful andflexible(灵活的) language with manymechanisms(机制)
andparadigms(范例) that can greatly improveproductivity(生产力).
As with any software tool or language, though, having a limited understanding orappreciation(欣赏) of itscapabilities(才能)
can sometimes be more of animpediment(口吃) than abenefit(利益),
leaving one in theproverbial(谚语的) state of “knowing enough to be dangerous”.
Familiarizing(熟悉) oneself with the keynuances(细微差别)
of Python, such as (but by no means limited to) themoderately(适度地) advanced programming problems raised in this article, will helpoptimize(最优化)
use of the language while avoiding some of its more common errors.
You might also want to check out our Insider’s Guide to Python Interviewing for suggestions on interview questions that can helpidentify(确定) Python experts.
from:http://blog.csdn.net/pipisorry/article/details/45175457
ref:http://www.toptal.com/python/top-10-mistakes-that-python-programmers-make