10 errors that Python programmers often make
Python is an explanatory, object-oriented, and advanced programming language with dynamic semantics. It has a built-in advanced data structure that combines the advantages of dynamic types and dynamic binding, making it attractive for rapid application development, it can also be used as a script or glue language to connect to existing components or services. Python supports modules and packages to encourage modularization and code reuse.
About this article
The easy-to-learn syntax of Python may make Python developers-especially those programming beginners-ignore some of its nuances and underestimate the capabilities of the language.
In view of this, this article lists top 10 lists, enumerating errors that are sometimes hard to be captured by senior Python developers.
Common Error 1: misuse an expression as the default value of a function parameter
Python allows default optional values for function parameters. Although this is a major feature of the language, it may lead to confusion in changing the default value. For example, let's take a look at the definition of this Python function:
>>> 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 error is that the optional parameter is set to the default value when the function does not provide the optional parameter for each call. In the code above, for example, people may want to call foo () repeatedly (that is, the bar parameter is not explicitly specified) and always return 'Baz' because every time foo () it is assumed that (the bar parameter is not set) the bar is set to [] (an empty list) during the call ).
But let's take a look at what will happen in this case:
>>> foo() ["baz"]>>> foo() ["baz", "baz"]>>> foo() ["baz", "baz", "baz"]
Yeah? Why do I append the default value "baz" to the existing list every time foo () is called instead of creating a new list?
The answer is that the default value of the function parameter is only evaluated once-when the function is defined. Therefore, the bar parameter is its default value (an empty list) during initialization, that is, when foo () is first defined, but when foo () is called (that is, if the bar parameter is not specified, the original initialized parameters of the bar will be used.
The following is a common 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"]
Common Error 2: incorrect use of class variables
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
For general use.
1
2
3
>>> B. x = 2
>>> Print A. x, B. x, C. x
1 2 1
Well, try again.
1
2
3
>>> A. x = 3
>>> Print A. x, B. x, C. x
3 2 3
What $ % #! &?? We only changed a.x. Why did C. x change?
In Python, class variables are processed as Dictionaries internally and follow the commonly referenced method parsing sequence (MRO ). Therefore, in the code above, because the x attribute in class C is not found, it will look up its base class (although Python supports multiple inheritance, but in the above example only ). In other words, class C does not have its own x attribute, which is independent of class. Therefore, C. x is actually A reference of A. x.
Common error 3: specify an incorrect parameter for counter t
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 "
", line 3, in
IndexError: list index out of range
The problem here is that the distinct T statement does not accept the exception list specified in this way. In Python 2. in 'X', use the syntax 'failed T exception'. e is to bind an Exception object to the Second Optional parameter (e in this example) for later use. Therefore, in the above example, the IndexError exception is not caught by the except t statement, but is thrown when it is bound to a parameter named IndexError.
The correct way to capture multiple exceptions in a distinct T statement is to specify the first parameter as a tuple containing all exceptions to be caught. In addition, the as keyword should be used for code portability, because both Python 2 and Python 3 support this syntax:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
Common Error 4: does not understand the Python scope
Python performs resolution based on LEGB. LEGB is short for Local, Enclosing, Global, and Built-in. It looks like "seeing Wen Zhiyi", right? In fact, there are some notes in Python. let's take a look at the following code:
>>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File "
", line 1, in
File "
", line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment
What's wrong here?
The above problem occurs because when you assign a value to a variable in the scope, Python will automatically treat it as a local variable in the current scope, which will hide the variable with the same name in the external scope.
Many people are surprised that they get an UnboundLocalError when they add a value assignment statement somewhere in the function body of the code that can run normally before. (You can learn more here)
This problem is more common when developers use lists. See the following example:
>>> Lst = [1, 2, 3] >>> def foo1 ():... lst. append (5) # No problem...> foo1 () >>> lst [1, 2, 3, 5] >>> lst = [1, 2, 3] >>>> def foo2 ():... lst + = [5] #... but there is a problem! ...> Foo2 ()
Traceback (most recent call last ):
File" ", Line 1, in
File" ", Line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
Hmm? Why does foo2 report an error while foo1 does not?
The reason is the same as in the previous example, but it is more elusive. Foo1 does not assign values to the lst, but foo2 does. You must know that lst + = [5] is the abbreviation of lst = lst + [5]. We try to assign values to lst (Python regards it as a local variable ). In addition, the value assignment operation for lst is based on the lst itself (this is once again treated as a local variable by Python), but it is not defined yet. Therefore, an error occurred!
Common error 5: modify a List during iteration)
The problem in the following code should be quite 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" ", Line 2, in
IndexError: list index out of range
Removing elements from a List or array during iteration is a well-known error for any experienced developer. Although the above example is very obvious, many advanced developers do not mean it in more complex code.
Fortunately, Python contains a large number of simple and elegant programming examples that can greatly simplify and refine code if used properly. The advantage is that you can get more simplified and streamlined code, and avoid bugs such as modifying a List during iteration in the program. An example is list comprehensions ). In addition, the list comprehensions is particularly useful for this problem. by modifying the implementation above, you can get an excellent code:
>>> 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 Error 6: I don't understand how Python binds variables in closures.
Let's look at the example below:
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
You may want to obtain the following output results:
0
2
4
6
8
But the actual result is:
8
8
8
8
8
Surprised!
This occurs because the variable used in the closure is assigned a value only when the function is called. Therefore, in the above code, when the returned function is called, Python will search for the value of I in the scope of the function when it is called (at this time, the loop has ended, so I is assigned the final value -- 4 ).
The solution is 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
Here, we use the default parameter to generate an anonymous function to achieve the desired result. Some people say this method is clever, some say it is hard to understand, and others hate it. However, if you are a Python developer, it is important to understand this behavior.
Common error 7: create a circular dependency module
Let's assume that you have two files, a. py and B. py, which are referenced by each other, as shown below:
A. py:
import b def f(): return b.x print f()
B. py:
import a x = 1 def g(): print a.f()
First, let's try to introduce a. py:
>>> Import
1
It works normally. This may be strange to you. After all, we did introduce a circular dependency module here. we speculate that this will cause problems, right?
The answer is that in Python, it is no problem to introduce only one circular dependency module. If a module has been introduced, Python will not introduce it again. However, problems may occur when each module accesses functions and variable positions in other modules.
So back to our example, when we introduce. in py, then introduce B. py will not cause any problems, because when it is introduced, B. py does not require. define anything in py. The only reference in B. py is to call a. f (). But that call occurs in g (), and neither a. py nor B. py call g (). So it runs normally.
But what happens if we try to introduce B. py? (Do not introduce a. py before this), as shown below:
>>> import b
Traceback (most recent call last ):
File" ", Line 1, in
File "B. py", line 1, in
Import
File "a. py", line 6, in
Print f ()
File "a. py", line 4, in f
Return B. x
AttributeError: 'module' object has no attribute 'x'
Ah. Something went wrong! The problem here is that B. python tries to introduce. py, but. py calls f (), while f () tries to access B. x. But B. x is not defined yet. Therefore, an AttributeError error occurs.
At least, it is very easy to solve this problem. you only need to modify B. py to introduce a. py in g:
X = 1 def g (): import a # a print a. f () is introduced only when g () is called ()
Now, when we introduce B again, there is no problem:
>>> 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 Error 8: conflicting with the module name in the Python Standard Library
One of the notable parts of Python is that it has a wide array of modules that can be used out of the box ". However, if you do not pay attention to it, it is easy to see the name conflict between the module you write and the module of the standard library that comes with Python (for example, you may have an email. but this will conflict with the module with the same name in the standard library ).
This may cause a strange problem. for example, you have introduced another module, but this module has to introduce a module in the Python standard library. because you have defined a module with the same name, this will cause this module to introduce your module incorrectly, instead of the module in stdlib. This will cause problems.
Therefore, we must pay attention to this issue to avoid using the same module name as the Python standard library. Modifying the module name in your package is much easier than using Python Enhancement Proposal (PEP) to give Python suggestions to modify the module name in the standard library.
Common Error #9: failed to solve the difference between Python 2 and Python 3
See 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()
Running properly in Python 2:
$ Python foo. py 1
Key error
1
$ Python foo. py 2
Value error
2
But now let's run it in Python 3:
$ 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's wrong? The "problem" is that in Python 3, the exception object is invisible outside of the except code block. (This is because it will save a reference cycle for the stack frames in the memory until the garbage collector runs and removes references from the memory. For more technical details, refer to here ).
One solution is to define a reference to the exception object in the external scope of the jsont code block for access. The following example uses this method, so the final code can run well in Python 2 and Python 3.
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 in Py3k:
$ Python3 foo. py 1
Key error
1
$ Python3 foo. py 2
Value error
2
Normal!
(By the way, our Python Hiring Guide discusses other important differences that need to be known when we migrate code from Python 2 to Python 3 .)
Common Error 10: Misuse of The _ del _ method
Suppose you have a file named calledmod. py:
import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)
And there is a file named another_mod.py:
Import mod
Mybar = mod. Bar ()
You will get an AttributeError exception.
Why? As mentioned here, when the interpreter exits, global variables in the module are set to None. Therefore, in the above example, when _ del _ is called, foo has been set to None.
The solution is to use atexit. register () instead. In this way, when your program ends running (meaning normal exit), the registered handler will be executed before the interpreter exits.
After learning about this, we can modify the above mod. py code to the following:
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 and trustworthy method to clean up before the program exits. Obviously, it is up to foo. cleanup to decide what to do to the object bound to self. myhandle, but this is what you want.
Summary
Python is a powerful and flexible language. it has many mechanisms and language specifications to significantly improve your productivity. Like any other language or software, if you have a limited understanding of its capabilities, this may hinder you, not benefit. As the saying goes, "knowing enough to be dangerous" (note: I think I know enough, but I can do something, but it is not ).
Familiar with some key nuances of Python, such as those mentioned in this article (but not limited to these), can help us better use languages and avoid some common traps.