Python常见编程错误和陷阱

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

http://blog.csdn.net/pipisorry/article/details/45175457

Introduction

本文介绍python编程中常见的错误的陷阱

潜在的Python陷阱

沉迷于一行程序

许多人热衷于一行程序带来的兴奋感。即使他们的一行解决方案比一个多行解决方案低效,他们也会吹嘘。

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 ) ) ]

很多人都写类似的代码。这样的代码在一个星期后就会变得难以理解。如果你想做一些稍微复杂的事情,例如根据条件简单地在一个列表或集合中添加一个元素,你可能就会犯错误。

单行代码并不是什么成就,是的,他们可能看起来很灵活,但不是什么成就。想象一下,这就像是你在打扫房间时把所有的东西都塞进你的衣橱。好的代码应该是干净的,易于阅读的和高效的。

利用错误的方式初始化一个集合

这是一个更微妙的问题,可能让你措手不及。集合推导很像列表推导。

上面就是集合推导的一个例子。集合就像列表,也是一个容器。所不同的是,一个集合中不能有任何重复的值,而且是无序的。看到集合推导人们经常错误地认为{}能初始化一个空集合。但其实不然,它初始化一个空字典。

如果你想初始化一个空集合,可以简单地调用set()方法。

注意一个空集合用set()表示,但是一个包含一些元素的集合就就要用花括号包围元素来表示。

这和直觉是相反的,因为你期望类似于set([1, 2])的一些东西。

误解GIL

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

许多人会尝试为Python辩解,说这些都是真正的线程。这确实是真的,但并不能改变这样一个事实:Python处理线程的方式和你期望的方式是不同的。Ruby语言也有相同的情况(Ruby也有一个解释器锁)。

指定的解决方案是使用multiprocessing模块。multiprocessing模块提供Process类,它是一个对fork的很好的覆盖。然而,fork过程比一个线程的代价高得多,所以你可能不会每次都能看到性能上的提升,因为不同的process之间需要做大量的工作来进行相互协调。

然而,这个问题并不存在于每一个Python的实现版本中。例如,Python的一个实现PyPy-stm就试图摆脱GIL(仍未稳定)。建立在其他平台,如JVM(Jython)或CLR(IronPython),上的Python实现,也没有GIL的问题。

总之,使用Thread类时要多加小心,你得到的可能不是你想要的。

使用老式类

在Python 2中,有两种类型的类,分别为“老式”类和“新式”类。如果你使用Python 3,那么你默认使用“新式”类。为了确保在Python2中使用“新式”类,你需要让你新创建的每一个类都继承object类,且类不能已继承了内置类型,例如int或list。换句话说,你的基类、类如果不继承其他类,就总是需要继承object类。

这些“新式”类解决一些老式类的根本缺陷,这一点我们不需要深入了解。然而,如果有人感兴趣,他们可以在相关文档中找到相关信息。

使用可变的默认参数

我多次见到过如下的代码:

永远不要使用可变的默认参数,可以使用如下的代码代替:

与其解释这个问题是什么,不如展示下使用可变默认参数的影响:

同一个变量c在函数调用的每一次都被反复引用。这可能有一些意想不到的后果。

总结

这些只是相对来说刚接触Python的人可能会遇到的一些问题。然而请注意,可能会遇到的问题远非就这么些。然而另一些缺陷是人们像使用Java或C++一样使用Python,并且试图按他们熟悉的方式使用Python。所以作为本篇文章的一个延续,尝试深入一些东西,例如Python的super函数。看看类方法、静态方法和 __slots__等。

[潜在的Python陷阱]

http://blog.csdn.net/pipisorry/article/details/45175457



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

ref:http://www.toptal.com/python/top-10-mistakes-that-python-programmers-make


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

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值