# Python常见编程错误和陷阱

Python 专栏收录该内容
42 篇文章 2 订阅

Introduction

Python中的一行程序在本质上意味着具有多个表达式的复杂推导。例如：

l = [ m for a , b in zip ( this , that ) if b . method ( a ) != b for m in b if not m . method ( a , b ) and reduce ( lambda x , y : a + y . method ( ) , ( m , a , b ) ) ]

GIL（全局解释器锁）意味着在Python程序中，任意一个时间点只能有一个线程在运行。这意味着当我们创建一个线程并希望它并行运行时，它并不会那样。Python解释器实际的工作是在不同的运行线程之间快速进行切换。但这只是对实际发生事情的一个非常简单的解释，实际情况要复杂的多。有很多种并行运行的实例，例如使用本质为C扩展的各种库。但运行Python代码时，大部分时间里它不会并行执行。换句话说，Python中的线程并不像Java或C++中的线程。

[潜在的Python陷阱]

10大最常见的Python编程错误 - Master the 10 Most Common Python Programming Problems

(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 adefault 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  bar argument) would always return 'baz', since the assumption(假定) would be thateach timefoo() 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 anexisting 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 isinitialized(初始化) to its default (i.e., an empty list) only whenfoo() is first defined, but then calls tofoo() (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 theattribute(属性)x is not found in class C, it will be looked up in its base classes (onlyA in the above example, although Python supports multipleinheritance(继承)). In other words,C doesn’t have its ownx property, independent of A. Thus, references(参考) toC.x are in fact references toA.x. This causes a Python problem unless it’s handled properly. Learn more aoutclassattributes(属性) 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 Exception, e is used to bind(绑) the exception to theoptional secondparameter(参数)specified(指定) (in this casee), in order to make it available for further inspection(视察). As a result, in the above code, theIndexError exception isnot being caught by the exceptstatement(声明); 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 thatsyntax(语法) 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 isshorthand(速记法的) forLocal,Enclosing, Global, Built-in. Seemsstraightforward(简单的) 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 isautomatically(自动地) 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 thatlst += [5] is really just shorthand(速记法的) forlst = lst + [5], we see that we are attempting toassign a value tolst (therefore presumed(假定) by Python to be in the localscope(范围)). However, the value we are looking toassign(分配) tolst is based onlst 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 oflist 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 ofi 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 orvariables(变量)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, sinceb.py does not require anything from a.py to be definedat the time it is imported. The onlyreference(参考) inb.py toa is the call to a.f(). But that call is ing() and nothing ina.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 importa.py, which in turn calls f(), which attempts to accessb.x. But  b.x has not yet beendefined(定义).Hence(因此) theAttributeError exception. At least one solution(解决方案) to this is quitetrivial(不重要的). Simplymodify(修改)b.py to importa.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>
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 thescope(范围) of theexcept block so that it remainsaccessible(易接近的). 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 toNone.

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 nameself.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

• 2
点赞
• 1
评论
• 5
收藏
• 一键三连
• 扫一扫，分享海报

10-26

09-11 54
09-20 7315
06-11 5863
06-13 7万+
11-30 3706
01-08 141
03-14 32
11-15 538
11-27 61
07-10 1874
08-31 173
06-16 173
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客

1.余额是钱包充值的虚拟货币，按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载，可以购买VIP、C币套餐、付费专栏及课程。