About Python
Python is an interpretive, object-oriented, high-level programming language with dynamic semantics. It has built-in advanced data structures that combine the benefits of dynamic typing and dynamic binding, making it attractive in rapid application development and being able to connect existing components or services as a script or glue language. Python supports modules and packages, thereby encouraging the modularity and code reuse of the program.
About this article
Python's easy-to-learn syntax may make Python developers – especially those who are programming beginners – overlook some of its subtleties and underestimate the language's capabilities.
For this reason, this article lists a "Top 10" list, enumerating errors that even advanced Python developers sometimes struggle to catch.
Common Error 1: Abusing an expression as the default value for a function parameter
Python allows you to provide default optional values for the parameters of a function. While this is a great feature of language, it can cause some confusion about the default values that are variable. For example, consider 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 is problematic, as we'll see ... ... Return bar
A common mistake is to assume that an optional parameter is set to the default specified value when the function does not provide an optional argument call at a time. In the above code, for example, one might want to always return ' Baz ' when calling Foo () repeatedly (i.e. not explicitly specifying the bar parameter), since the bar is set to [] (that is, an empty list) every time foo () is called (without setting the bar parameter).
But let's see what happens when we do this:
>>> foo () ["Baz"]>>> foo () ["Baz", "Baz"]>>> foo () ["Baz", "Baz", "Baz"]
Yes? Why does the default value "Baz" be appended to the existing list each time Foo () is called instead of creating a new list?
The answer is that the default value of the function parameter is evaluated only once-at the time of the function definition. Therefore, when the bar parameter is initialized for its default value (that is, an empty list), that is, Foo () is first defined, but when Foo () is called (that is, when the bar parameter is not specified), the bar's original initialized parameters will continue to be used.
The following is a common workaround:
>>> 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: Using class variables 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
Regular use.
1
2
3
>>> b.x = 2
>>> print a.x, b.x, c.x
1 2 1
Well, try it again.
1
2
3
>>> a.x = 3
>>> print a.x, b.x, c.x
3 2 3
What $%#!&??? We only changed the a.x, why c.x also changed?
In Python, class variables are handled internally as dictionaries, which follow the commonly referenced method resolution order (MRO). So in the above code, because the X attribute in class C is not found, it will look up its base class (although Python supports multiple inheritance, but only a in the example above). In other words, Class C does not have its own x attribute, which is independent of a. Therefore, c.x is actually a reference to a.x.
Common error 3: Specifying the wrong parameters for except
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): File "
", line 3, in
indexerror:list index out of Range
The problem here is that the except statement does not accept a list of exceptions that are specified in this way. Instead, in Python 2.x, using syntax except Exception, E is to bind an exception object to the second optional parameter (in this case, E) for later use. So, in the above example, Indexerror This exception is not captured by the except statement, but is bound to a parameter named Indexerror.
The correct way to catch multiple exceptions in a except statement is to specify the first parameter as a tuple containing all the exceptions to catch. Also, to use the AS keyword for code portability, because Python 2 and Python 3 support this syntax:
>>> try: ... L = ["A", "B"] ... Int (l[2]) ... except (ValueError, Indexerror) as E: ... Pass ... >>>
Common Error 4: Do not understand the scope of Python
Python is based on LEGB to parse, LEGB is the abbreviation of Local, enclosing, Global, built-in. It looks like "see the text," right? In fact, there are some areas in python that you need to be aware of, first look at the following code:
>>> x = ten >>> def foo (): ... x + = 1 ... Print x ... >>> foo ( Traceback): File "
", line 1, in
File "
", Line 2, in foo unboundlocalerror:local variable ' x ' referenced before assignment
What's the problem here?
The above problem occurs because when you assign a value to a variable in the scope, Python automatically takes it as a local variable of the current scope, hiding the variable with the same name in the outer scope.
Many would be surprised to get a unboundlocalerror error when they add an assignment statement somewhere in the function body of the previously functioning code. (You can learn more here)
Especially when developers use lists, this problem is more common. Take a look at 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's a problem here! ... >>> Foo2 ()
Traceback (most recent):
File " ", line 1, in
File " ", Line 2, in Foo
unboundlocalerror:local variable ' LST ' referenced before assignment
Well? Why Foo2 error, and foo1 no problem?
The reason is the same as the previous one, but more elusive. Foo1 did not assign a value to LST, and Foo2 did. To know that LST + = [5] is the abbreviation for LST = LST + [5], we try to assign a value to the LST (Python treats him as a local variable). In addition, our assignment to LST is based on the LST itself (which is once again referred to as a local variable by Python), but it is not defined at this time. So error!
Common error 5: Modifying a list when iterating
The problem in the following code should be quite obvious:
>>> odd = lambda x:bool (x% 2) >>> numbers = [n for n in range] >>> for i in rang E (Len (Numbers)): ... If Odd (Numbers[i]): ... Del Numbers[i] # bad:deleting item from a list while iterating over it ...
Traceback (most recent):
File " ", line 2, in
Indexerror:list index out of range
When iterating, deleting elements from a list or array is a well-known mistake for any experienced developer. Although the above example is obvious, many advanced developers are not intentionally involved in more complex code.
Fortunately, Python contains a number of simple and elegant programming paradigms that, if used properly, can greatly simplify and refine the code. The benefit is a simpler and leaner code that can be better avoided in a program where a bug like a list is modified when the iteration occurs. One such example is a recursive list (lists comprehensions). Also, a recursive list comprehensions is particularly useful for this issue by changing the implementation above to get an excellent piece of code:
>>> odd = lambda x:bool (x% 2) >>> numbers = [n for n in range] >>> numbers[:] = [N for n with numbers if not odd (n)] # Ahh, the beauty of it all >>> numbers [0, 2, 4, 6, 8]
Common error 6: Do not understand how Python binds a variable in a closure
Look at 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 want to get the following output:
0
2
4
6
8
But the actual results are:
8
8
8
8
8
It's amazing!
This happens because of the late binding behavior in Python-variables used in closures are only assigned when the function is called. So, in the above code, at any time, when the returned function is called, Python looks for the value I corresponds to in the scope of the function being called (at this point, the loop is over, so I is assigned the final value--4).
The solution has a bit of hack flavor:
>>> 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 parameters to generate an anonymous function to achieve the results we want. Some people say that this method is very ingenious, some people say it is difficult to understand, others hate this practice. However, if you are a Python developer, it is important to understand this behavior.
Common ERROR 7: Creating a circular dependency module
Let's assume that you have two files, a.py and b.py, that they reference each other as follows:
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 a
1
can work correctly. It may be that you feel very strange. After all, we did introduce a loop-dependent module here, and we figured that would be a problem, wouldn't it?
The answer is that in Python, it's no problem to just introduce a loop-dependent module. If a module has been introduced, Python is not going to introduce it again. However, depending on the location of the functions and variables in each module to access other modules, you are likely to encounter problems.
So, back to our example, when we introduce a.py, the introduction of b.py does not create any problems, because when introduced, b.py does not need to define anything in a.py. The only thing in b.py that references a.py is to call A.F (). But that call occurred in G (), and G () was not called in a.py and b.py. So it runs normally.
But what happens if we try to introduce b.py? (Before this is introduced a.py), as follows:
>>> Import B
Traceback (most recent):
File " ", line 1, in
File "b.py", line 1, in
Import a
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 '
Oh, yes. It's a problem! The problem here is that in introducing b.py, Python tries to introduce a.py, but a.py calls F (), and F () has an attempt to access b.x. But at this point b.x is not yet defined. So a attributeerror exception occurred.
At the very least, it's easy to fix the problem by simply modifying the b.py to introduce a.py in G ():
x = 1 def g (): import a # will only introduce a print a.f () 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 En D 1 # printed A second time, this one was our call to ' G '
Common Error 8: Naming conflicts with modules in the Python standard library
One of the admirable parts of Python is that it has a rich module for us to "out of the box". However, if you do not have the conscious attention, it is easy to appear the module you write and Python comes with the standard library of the module between the naming conflict problem (for example, you may have a module called email.py, but this will be in the standard library with the same name module conflict).
This can lead to a strange problem, for example, you introduce another module, but this module introduces a module in the Python standard library, because you define a module with the same name, it will cause the module to introduce your module incorrectly, not the module in Stdlib. This is going to be a problem.
Therefore, we must pay attention to this issue in order to avoid using the same module name as in the Python standard library. Modifying the module name in your package is much easier than modifying the module name of the standard library with Python enhancement proposal (PEP) to make suggestions to Python.
Common error #9: Failed to resolve differences between Python 2 and Python 3
Take a look at 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 ()
Run normally 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):
File "foo.py", line +, in
Bad ()
File "foo.py", line.
Print (e)
Unboundlocalerror:local variable ' e ' referenced before assignment
What's the problem? The "problem" is that in Python 3, the exception object is not visible outside of the except code block. (The reason for this is that it will hold a reference cycle to the in-memory stack frame until the garbage collector runs and clears the reference from memory.) For more technical details, please refer to here).
One workaround is to define a reference to the exception object in the outer scope of the except code block for access. The following example uses this method, so the final code works 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!
(Incidentally, our Python hiring guide discusses some of the other important differences we need to know when we migrate code from Python 2 to Python 3 o'clock.) )
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 a Attributeerror exception.
Why is it? Because, as this is said, when the interpreter exits, the global variables in the module are set to none. So, in the example above, when __del__ is called, Foo has been set to none.
The workaround is to use Atexit.register () instead. In this way, when your program finishes executing (meaning a normal exit), your registered handler executes before the interpreter exits.
With this in view, we can change the code above mod.py 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 neat and trustworthy way to do some cleanup work before the program exits. Obviously, it is up to Foo.cleanup to decide what to do with the objects bound on Self.myhandle, but that's what you want.
Summarize
Python is a powerful and flexible language that has many mechanisms and language specifications to significantly improve your productivity. As with any other language or software, if you have limited knowledge of its capabilities, it is likely to hinder you rather than benefit. As the saying goes, "knowing enough to being dangerous" (the translator notes that it is self-sufficient to know enough to do something, but it is not).
Familiarity with some of the key nuances of python, such as those mentioned in this article (but not limited to these), can help us to better use the language and avoid some common pitfalls.