↑ Follow + star, learn new Python skills every day
Backstage Reply [ Big Gift Package ] Send you a Python Self-Learning Gift Package
Abstract: Python is an easy-to-learn programming language with concise and clear syntax, and a rich and powerful class library. In daily development, developers are prone to make some low-level mistakes. This article summarizes the 10 most common mistakes developers make.
Python is an easy-to-learn programming language with concise and clear syntax, and a rich and powerful class library. Unlike most other programming languages, which use curly braces, it uses indentation to define blocks of statements.
In normal work, Python developers are prone to make some small mistakes, and these mistakes are easy to avoid. This article summarizes the 10 most common mistakes made by Python developers. Let’s take a look at them. I wonder if you have been shot.
1. Abusing expressions as function parameter defaults
Python allows developers to specify a default value for function parameters. Although this is a feature of the language, it can easily lead to confusion when parameters are variable. For example, the following 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 barIn the above code, once the foo() function is called repeatedly (without specifying a bar parameter), it will always return 'bar', because no parameters are specified, then every time foo() is called, it will be assigned [] . Here's a look at the result of doing so:
>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]solution:
>>> def foo(bar=None):
... if bar is None: # or if not bar:
... bar = []
... bar.append("baz")
... return bar
.. .
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]2. Incorrect use of class variables
Take a look at the following example:
>>> class A(object):
... x = 1
...
>>> class B(A):
... pass
...
>>> class C(A):
... pass
...
>>> print Ax, Bx, Cx
1 1 1This makes sense:
>>> Bx = 2
>>> print Ax, Bx, Cx
1 2 1do it again:
>>> Ax = 3
>>> print Ax, Bx, Cx
3 2 3Just changed Ax, why Cx also changed.
In Python, class variables are all handled internally as dictionaries and follow Method Resolution Order (MRO). In the code above, since the attribute x is not found in class C, it looks for its base class (in the above example only A, although Python supports multiple inheritance). In other words, C has no x attribute of its own and is independent of A, so referencing Cx is actually referencing Ax.
3. Specifying incorrect parameters for exceptions
Suppose the code has 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 rangeThe problem here is that the except statement doesn't need this way to specify the list of exceptions. However, in Python 2.x, except Exception,e is usually used to bind the second parameter in the exception so that it can be checked further. Therefore, in the above code, the IndexError exception is not caught by the except statement, and the exception is finally bound to a parameter named IndexError.
The correct way to catch multiple exceptions in one exception statement is to specify the first parameter as a tuple containing all the exceptions that were caught. At the same time, use the as keyword for maximum portability, a syntax supported by both Python 2 and Python 3.
>>> try:
... l = ["a", "b"]
... int(l[2])
... except (ValueError, IndexError) as e:
... pass
...
>> >4. Misunderstanding Python Rule Scope
Python's scope resolution is based on LEGB rules, namely Local, Enclosing, Global, and Built-in. In fact, this parsing method also has some mysteries, see the following example:
>>> 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 assignmentMany people will be surprised that when they add a parameter statement to the body of a working function, an UnboundLocalError will be thrown in the code that was previously working (click here for a more detailed description).
It is very easy for developers to make this mistake when using lists. Take a look at 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 assignmentWhy does foo2 fail while foo1 runs fine?
The answer is the same as in the previous example, but with some subtleties. foo1 is not assigned to lst, but foo2 is assigned. lst += [5] is actually lst = lst + [5], trying to assign a value to lst (thus, assuming Python is in local scope). However, the value we're looking to assign to lst is based on lst itself, which is not yet determined.
5. Modify the traversal list
The following code is obviously wrong:
>>> 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 rangeWhen traversing, it is a very low-level error to delete the list. Anyone with a little experience will not do it.
Modify the above code to execute correctly:
>>> 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]6. How to bind variables in closures
Consider the following example:
>>> def create_multipliers():
... return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
... print multiplier(2)
...The result you expect is:
<code>0
2
4
6
8</code>Actually:
<code>8
8
8
8
8</code>Isn't it very surprising! This happens mainly because of Python's late binding behavior, the variable is used in the closure at the same time as the inner function is calling it.
solution:
>>> 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
87. Create circular module dependencies
Suppose there are two files, a.py and b.py, and import them separately, as follows:
In a.py:
import b
def f():
return bx
print f()In b.py:
import a
x = 1
def g():
print af()First, let's try to import a.py:
<code>>>> import a
1</code>can work well, maybe you will be surprised. After all, we do do a circular import here, shouldn't it be a problem?
The mere existence of a circular import is not a problem with Python itself, if a module is imported, Python will not try to re-import it. According to this, each module may encounter some problems at runtime when trying to access functions or variables.
What happens when we try to import b.py (a.py was not previously imported):
>>> 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 bx
AttributeError: 'module' object has no attribute 'x'There is an error, the problem here is that in the process of importing b.py, it also tries to import a.py, so f() is called, and it tries to access bx. But bx is not defined.
It can be solved like this, just modify the g() function imported by b.py into a.py:
x = 1
def g():
import a # This will be evaluated only when g() is called
print af()Everything works fine whenever I import:
>>> import b
>>> bg()
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'8. Conflicts with Python standard library module names
Python has a very rich module library and supports it "out of the box". Therefore, if not deliberately avoided, naming collision incidents can easily occur. For example, you might have an email.py module in your code that, because of the same name, is likely to conflict with the standard library module that comes with Python.
9. Differences between Python2.x and Python3.x are not handled as specified
Take a look at 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()This works fine in Python 2:
$ python foo.py 1
key error
1
$ python foo.py 2
value error
2But in 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 assignmentsolution:
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 result in Py3k:
<code>$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2</code>There are many notes and discussions about Python 2 and Python 3 that need to be paid attention to when porting code in the Python Recruitment Guide, you can go and have a look.
10. Abusing the __del__ method
For example, here is a file called mod.py:
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)Next, you do the following in the another_mod.py file:
import mod
mybar = mod.Bar()You will get an AttributeError exception.
As for why this exception occurs, click here for details. When the interpreter shuts down, the module's global variables are all set to None. So, in the above example, when __del__ is called, foo has all been set to None.
A good solution is to use atexit.register() instead. By the way, when the program execution finishes, the handler you registered stops working before the interpreter shuts down.
The code to fix the above problem:
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 neat and reliable way to call any function that needs cleanup, provided that the program terminates normally.
Summarize
Python is a powerful and flexible programming language and comes with many mechanisms and patterns to greatly increase productivity. As with any language or software tool, people will have a limited understanding or appreciation of its capabilities, some of which do more harm than good, and some times it will bring some traps. Getting to grips with the nuances of a language and understanding some common pitfalls will help you go further as a developer.
YYDS! One line of Python code can realize data visualization on a large screen
Why can't we make products like JetBrains in China?