Home > Article > Backend Development > Ten common mistakes Python programmers make
No matter in the process of studying or working, people will make mistakes. Although Python's syntax is simple and flexible, there are still some big pitfalls. If you are not careful, both beginners and experienced Python programmers may stumble. This article shares 10 common mistakes with you. Friends in need can refer to them
Common mistake 1: Wrongly using an expression as the default parameter of a function
In Python, we can set a default value for a parameter of the function, so that This parameter becomes optional. While this is a nice language feature, it can also lead to some confusing situations when the default value is a mutable type. Let’s take a look at the following Python function definition:
>>> def foo(bar=[]): # bar is an optional parameter. If the value of bar is not provided, it defaults to [],
... bar.append("baz") # But we will see later that this line of code will cause problems.
... return bar
A common mistake Python programmers make is to take it for granted that every time a function is called, if no value is passed in for the optional parameter, then the optional parameter will be set to the specified value. the default value. In the above code, you may think that repeatedly calling the foo() function should always return 'baz', because by default, every time the foo() function is executed (the value of the bar variable is not specified), the bar variable is set to [ ] (that is, a new empty list).
However, the actual running result is like this:
>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
> ;>> foo()
["baz", "baz", "baz"]
Isn't it strange? Why is the default value "baz" added to the existing list every time the foo() function is called, instead of creating a new empty list?
The answer is that the setting of the default value of the optional parameter will only be executed once in Python, that is, when the function is defined. Therefore, only when the foo() function is defined, the bar parameter will be initialized to the default value (that is, an empty list), but every time the foo() function is called thereafter, the bar parameter will continue to be initialized with the original value. of that list.
Of course, a common solution is:
>>> def foo(bar=None):
... if bar is None: # or if not bar:
... bar = []
... bar.append("baz")
... return bar
...
>>> foo()
["baz"]
>>> foo()
[" baz"]
> :
... x = 1
...
>>> class B(A):
... pass
...
>>> class C(A):
.. . pass
...
>>> print A.x, B.x, C.x
1 1 1
This result is normal.
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
Well, the result is as expected.
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
In Python language, class variables are processed in the form of dictionaries and follow the method parsing order (Method Resolution Order, MRO). Therefore, in the above code, since there is no attribute x in class C, the interpreter will look for its base class (although Python supports multiple inheritance, in this example, the base class of C is only A) . In other words, C does not have its own x attribute that is independent of A and truly belongs to it. Therefore, referencing C.x actually refers to A.x. If the relationship here is not handled properly, it will lead to the problem in the example.
Common mistake 3: Incorrectly specifying the parameters of the exception block (exception block)
Please look at 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
IndexError: list index out of range
The problem with this code is that the except statement does not support specifying exceptions in this way. In Python 2.x, you need to use the variable e to bind the exception to the optional second parameter to further view the exception. Therefore, in the above code, the except statement does not catch the IndexError exception; instead, it binds the exception that occurs to a parameter named IndexError.
To correctly catch multiple exceptions in an except statement, you should specify the first parameter as a tuple, and then write the exception type you want to catch in the tuple. Also, to improve portability, use the as keyword, which is supported in both Python 2 and Python 3.
>>> try:
... l = ["a", "b"]
... int(l[2])
... except (ValueError, IndexError) as e:
.. . pass
...
>>>
Common Mistake 4: Wrong Understanding of Variable Name Parsing in Python
Variable name parsing in Python follows the so-called LEGB principle, that is, "L: local scope; E : The local scope of def or lambda in the upper structure; G: global scope; B: built-in scope" (Local, Enclosing, Global, Builtin), search in order. Doesn’t it look simple? However, there are actually some special features in the way this principle takes effect. Speaking of this, we have to mention the following common Python programming errors. Please look at the code below:
>>> x = 10
>>> def foo():
... ;> foo()
Traceback (most recent call last):
File "
File "
UnboundLocalError: local variable 'x' referenced before assignment
What went wrong?
The above error occurs because when you assign a value to a variable in a certain scope, the variable is automatically regarded as a local variable of the scope by the Python interpreter and will replace any variable with the same name in the upper scope. variable.
It is precisely because of this that the code that started out well appears, but after adding an assignment statement inside a function, an UnboundLocalError occurs. No wonder it surprises many people.
Python programmers are especially likely to fall into this trap when working with lists.
Please look at the following code example:
>>> lst = [1, 2, 3]
>>> def foo1():
... lst.append(5) # Not here Question
...
>>> foo1()
>>> lst
[1, 2, 3, 5]
>>> lst = [1, 2, 3]
>>> def foo2():
... lst += [5] # ... But something is wrong here!
...
>>> foo2()
Traceback (most recent call last):
File "
File "
UnboundLocalError: local variable 'lst' referenced before assignment
eh? Why does function foo1 run normally but error occurs in foo2?
The answer is the same as the previous example, but a little more elusive. The foo1 function does not assign a value to the lst variable, but foo2 does. We know that lst += [5] is just the abbreviation of lst = lst + [5]. From this we can see that the foo2 function is trying to assign a value to lst (therefore, it is considered a variable in the local scope of the function by the Python interpreter) . However, the value we want to assign to lst is based on the lst variable itself (at this time, it is also considered a variable in the local scope of the function), which means that the variable has not been defined yet. That's when the error occurred.
Common mistake 5: Changing the list while traversing the list
The problem with the following code should be very 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 "
IndexError : list index out of range
Removing elements from a list or array while iterating over it is something that any experienced Python developer will be aware of. But while the above example is obvious, experienced developers are likely to inadvertently make the same mistake when writing more complex code.
Fortunately, the Python language incorporates many elegant programming paradigms that, if used correctly, can greatly simplify your code. Another advantage of simplifying the code is that it is less likely to cause the error of deleting elements when traversing the list. One programming paradigm that can do this is list comprehensions. Moreover, list comprehensions are particularly useful in avoiding this problem. Let’s re-implement the function of the above code using list comprehensions:
>>> 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: Not understanding how Python binds variables in closures
Please look at the following code:
>>> def create_multipliers( ):
... return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
... print multiplier(2)
...
You may I think the output result should be like this:
However, the actual output result is:
I’m shocked!
This result occurs mainly because of the late binding mechanism in Python, that is, the value of the variable in the closure will only be queried when the internal function is called. Therefore, in the above code, each time the function returned by create_multipliers() is called, the value of variable i will be looked up in the nearby scope (and by that time, the loop has ended, so the variable i is finally assigned value is 4).
To solve this common Python problem, you need to use some hacks:
>>> 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
Please note! We use default parameters here to implement this lambda anonymous function. Some might think it's elegant, some might think it's clever, and still others might scoff. However, if you are a Python programmer, you should know about this solution regardless.
Common Mistake 7: Circular dependencies between modules
Suppose you have two files, a.py and b.py, which reference each other, as shown below:
a.py file The code in the file:
import b
def f():
return b. First, we try to import the a.py module:
The code runs fine. Maybe this is unexpected. After all, we have the problem of circular references here, so there must be problems, right?
The answer is that the mere presence of circular references does not in itself cause problems. If a module is already referenced, Python can prevent it from being referenced again. But if each module tries to access functions or variables defined by other modules at the wrong time, you're likely to get into trouble.
So back to our example, when we import the a.py module, there will be no problem when it references the b.py module, because when the b.py module is referenced, it does not need to access the a. Any variables or functions defined in the py module. The only reference to module a in the b.py module is the call to the foo() function of module a. But that function call occurs in the g() function, and the g() function is not called in either the a.py or b.py module. So, no problem will arise.
But what if we try to import the b.py module (that is, without referencing the a.py module before):
>>> import b
Traceback (most recent call last):
File "< ;stdin>", line 1, in
File "b.py", line 1, in
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'
Oops. The situation is not good! The problem here is that in the process of importing b.py, it tries to reference the a.py module, and the a.py module then calls the foo() function, which then tries to access the b.x variable. But at this time, the b.x variable has not been defined, so the AttributeError exception occurred.
There is a very simple way to solve this problem, which is to simply modify the b.py module and only reference a.py inside the g() function:
x = 1
def g():
import a # This will be evaluated only when g() is called
print a.f()
Now if we import the b.py module, there will be no problems:
>>> 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: Module naming and Python standard library module name conflict
One of the great advantages of the Python language is its own powerful standard library. However, because of this, if you don't pay careful attention, you may give your module the same name as Python's own standard library module (for example, if there is a module in your code called email.py, Then this will conflict with the module of the same name in the Python standard library)
This is likely to cause difficult problems for you. For example, when importing module A, if module A tries to reference module B in the Python standard library, but because you already have a module B with the same name, module A will mistakenly reference module B in your own code. , instead of module B in the Python standard library. This is also the cause of some serious mistakes.
Therefore, Python programmers should take extra care to avoid using the same names as Python standard library modules. After all, it is much easier to change the name of your own module than to propose a PEP proposal to change the name of an upstream module and have the proposal pass.
Common Mistake 9: Failure to resolve the differences between Python 2 and Python 3
Suppose you have the following code:
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()
If it is Python 2, then the code runs normally:
$ python foo.py 1
key error
1
$ python foo.py 2
value error
2
But now, let’s switch to Python 3 and run it again:
$ python3 foo.py 1
key error
Traceback (most recent call last):
File "foo. py", line 19, in
bad()
File "foo.py", line 17, in bad
print(e)
UnboundLocalError: local variable 'e' referenced before assignment
What the hell is going on? What's going on? The "problem" here is that in Python 3, exception objects are not accessible outside the scope of the except block. (The reason for this design is that otherwise, its reference loop will remain in the stack frame until the garbage collector runs and the reference is cleared from memory.)
One way to avoid this problem is to Maintain a reference to the exception object outside the scope of the except code block so that the exception object can be accessed. The following code uses this method, so the output results in Python 2 and Python 3 are consistent:
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()
Run the code under Python 3:
$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2
Great!
Common Mistake 10: Wrong use of del method
Suppose you write the following code in the mod.py file:
import foo
class Bar(object):
...
def __del__(self):
foo. After cleanup(self.myhandle)
, you perform the following operations in the another_mod.py file:
import mod
mybar = mod.Bar()
If you run the another_mod.py module, an AttributeError exception will occur.
Why? Because when the interpreter ends, the global variables of the module will be set to None. Therefore, in the above example, foo has been set to None before the __del__ method is called.
To solve this somewhat tricky Python programming problem, one of the ways is to use the atexit.register() method. In this case, when your program completes execution (that is, when the program exits normally), the handler you specify will run before the interpreter is closed.
Applying the above method, the modified mod.py file may look 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 supports cleanly calling any necessary cleanup functions when the program terminates normally. Obviously, in the above example, the foo.cleanup function will decide how to handle the object bound to self.myhandle.
Overview
Python is a powerful and flexible programming language that provides many programming mechanisms and paradigms that can greatly improve work efficiency. But as with any software tool or language, if you have limited understanding or appreciation of the language's capabilities, you can sometimes be hindered rather than benefited. As a proverb goes, “Thinking you know enough can put you or others in danger.
Continuously familiarize yourself with some of the subtleties of the Python language, especially the 10 common mistakes mentioned in this article, will It will help you use the language effectively while avoiding some of the more common mistakes