设为首页 收藏本站
查看: 295|回复: 0

[经验分享] Top 10 Mistakes that Python Programmers Make

[复制链接]

尚未签到

发表于 2017-4-30 08:37:09 | 显示全部楼层 |阅读模式

About Python


[size=1.2em]
Pythonis
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


[size=1.2em]
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.

[size=1.2em]
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.

[size=1.2em]

[size=1.2em]
(Note: This article is intended for a more advanced audience thanCommon
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


[size=1.2em]
Python allows you to specify that a function argument isoptionalby
providing adefault valuefor it. While this is a great feature of the
language, it can lead to some confusion when the default value ismutable.
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

[size=1.2em]
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 abarargument)
would always return'baz',
since the assumption would be thateach timefoo()is
called (without abarargument
specified)baris
set to[](i.e.,
a new empty list).

[size=1.2em]
But let’s look at what actually happens when you do this:


>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]

[size=1.2em]
Huh? Why did it keep appending the default value of"baz"to
anexistinglist each timefoo()was
called, rather than creating anewlist each time?

[size=1.2em]
The answer is thatthe default value for a function argument is only evaluated
once, at the time that the function is defined.
Thus, thebarargument
is initialized to its default (i.e., an empty list) only whenfoo()is
first defined, but then calls tofoo()(i.e.,
without abarargument
specified) will continue to use the same list to whichbarwas
originally initialized.

[size=1.2em]
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 class variables incorrectly


[size=1.2em]
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

[size=1.2em]
Makes sense.


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

[size=1.2em]
Yup, again as expected.


>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3

[size=1.2em]
What the$%#!&?? We only changedA.x.
Why didC.xchange
too?

[size=1.2em]
In Python, class variables are internally handled as dictionaries and follow what is often referred to asMethod
Resolution Order (MRO). So in the above code, since the attributexis
not found in classC,
it will be looked up in its base classes (onlyAin
the above example, although Python supports multiple inheritance). In other words,Cdoesn’t
have its ownxproperty,
independent ofA.
Thus, references toC.xare
in fact references toA.x.



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


[size=1.2em]
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

[size=1.2em]
The problem here is that theexceptstatement
doesnottake a list of exceptions specified in this manner. Rather,
In Python 2.x, the syntaxexcept
Exception, eis used to bind the exception to theoptionalsecond
parameter specified (in this casee),
in order to make it available for further inspection. As a result, in the above code, theIndexErrorexception
isnotbeing caught by theexceptstatement;
rather, the exception instead ends up being bound to a parameter namedIndexError.

[size=1.2em]
The proper way to catch multiple exceptions in anexceptstatement
is to specify the first parameter as atuplecontaining
all exceptions to be caught. Also, for maximum portability, use theaskeyword,
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 Python scope rules


[size=1.2em]
Python scope resolution is based on what is known as theLEGBrule,
which is shorthand forLocal,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

[size=1.2em]
What’s the problem?

[size=1.2em]
The above error occurs because, when you make anassignmentto 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.

[size=1.2em]
Many are thereby surprised to get anUnboundLocalErrorin
previously working code when it is modified by adding an assignment statement somewhere in the body of a function. (You can read more about thishere.)

[size=1.2em]
It is particularly common for this to trip up developers when usinglists.
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

[size=1.2em]
Huh? Why didfoo2bomb
whilefoo1ran
fine?

[size=1.2em]
The answer is the same as in the prior example, but is admittedly more subtle.foo1is
not making anassignmenttolst,
whereasfoo2is.
Remembering thatlst
+= [5]is really just shorthand forlst
= lst + [5], we see that we are attempting toassigna value
tolst(therefore
presumed by Python to be in the local scope). However, the value we are looking to assign tolstis
based onlstitself
(again, now presumed to be in the local scope), which has not yet been defined. Boom.


Editor's note: want posts just like this delivered straight to your inbox? Subscribe below to receive our latest engineering articles.









We will never share your email with any 3rd party or spam you








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


[size=1.2em]
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):
...         del numbers  # 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

[size=1.2em]
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.

[size=1.2em]
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 oflist
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]


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


[size=1.2em]
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)
...

[size=1.2em]
You might expect the following output:


0
2
4
6
8

[size=1.2em]
But you actually get:


8
8
8
8
8

[size=1.2em]
Surprise!

[size=1.2em]
This happens due to Python’slate bindingbehavior 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 ofiis
looked upin the surrounding scope at the time it is called(and by
then, the loop has completed, soihas
already been assigned its final value of 4).

[size=1.2em]
The solution to this 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

[size=1.2em]
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.



Common Mistake #7: Creating circular module dependencies


[size=1.2em]
Let’s say you have two files,a.pyandb.py,
each of which imports the other, as follows:

[size=1.2em]
Ina.py:


import b
def f():
return b.x
print f()

[size=1.2em]
And inb.py:


import a
x = 1
def g():
print a.f()

[size=1.2em]
First, let’s try importinga.py:


>>> import a
1

[size=1.2em]
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?

[size=1.2em]
The answer is that the merepresenceof 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.

[size=1.2em]
So returning to our example, when we importeda.py,
it had no problem importingb.py,
sinceb.pydoes
not require anything froma.pyto
be definedat the time it is imported. The only reference inb.pytoais
the call toa.f().
But that call is ing()and
nothing ina.pyorb.pyinvokesg().
So life is good.

[size=1.2em]
But what happens if we attempt to importb.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'

[size=1.2em]
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 callsf(),
which attempts to accessb.x.
Butb.xhas
not yet been defined. Hence theAttributeErrorexception.

[size=1.2em]
At least one solution to this is quite trivial. Simply modifyb.pyto
importa.pywithing():


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

[size=1.2em]
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: Name clashing with Python Standard Library modules


[size=1.2em]
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 namedemail.pyin
your code, which would be in conflict with the standard library module of the same name).

[size=1.2em]
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 stuff happens.

[size=1.2em]
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 change upstream and to try and get that approved.



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


[size=1.2em]
Consider the following 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()

[size=1.2em]
On Python 2, this runs fine:


$ python foo.py 1
key error
1
$ python foo.py 2
value error
2

[size=1.2em]
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

[size=1.2em]
What has just happened here? The “problem” is that, in Python 3, the exception object is not accessible beyond the scope of theexceptblock.
(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 availablehere).

[size=1.2em]
One way to avoid this issue is to maintain a reference to the exception objectoutsidethe
scope of theexceptblock
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 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()

[size=1.2em]
Running this on Py3k:


$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2

[size=1.2em]
Yippee!

[size=1.2em]
(Incidentally, ourPython
Hiring Guidediscusses a number of other important differences to be aware of when migrating code from Python 2 to Python 3.)



Common Mistake #10: Misusing the__del__method


[size=1.2em]
Let’s say you had this in a file calledmod.py:


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

[size=1.2em]
And you then tried to do this fromanother_mod.py:


import mod
mybar = mod.Bar()

[size=1.2em]
You’d get an uglyAttributeErrorexception.

[size=1.2em]
Why? Because, as reportedhere,
when the interpreter shuts down, the module’s global variables are all set toNone.
As a result, in the above example, at the point that__del__is
invoked, the namefoohas
already been set toNone.

[size=1.2em]
A solution would be to useatexit.register()instead.
That way, when your program is finished executing (when exiting normally, that is), your registered handlers are kicked offbeforethe
interpreter is shut down.

[size=1.2em]
With that understanding, a fix for the abovemod.pycode
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)

[size=1.2em]
This implementation provides a clean and reliable way of calling any needed cleanup functionality upon normal program termination. Obviously, it’s up tofoo.cleanupto
decide what to do with the object bound to the nameself.myhandle,
but you get the idea.



Wrap-up


[size=1.2em]
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”.

[size=1.2em]
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.

[size=1.2em]
You might also want to check out ourInsider’s
Guide to Python Interviewingfor suggestions on interview questions that can help identify Python experts.

[size=1.2em]
We hope you’ve found the pointers in this article helpful and welcome your feedback.


中文翻译版:http://www.csdn.net/article/2014-05-12/2819716-Top-10-Mistakes-that-Python-Programmers-Make

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-371003-1-1.html 上篇帖子: Python 学习入门(36)—— @property属性 下篇帖子: Python-Matplotlib安装及简单使用
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表