码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2015-04-21 20:43:40      阅读:245      评论:0      收藏:0      [点我收藏+]

标签:python   编程   错误   

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 bar argument) would always return ‘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 Exception, e is used to bind(绑) the exception to theoptional second 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 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 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


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

标签:python   编程   错误   

原文地址:http://blog.csdn.net/pipisorry/article/details/45175457

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!