Buggy Python Code: The 10 Most Common Mistakes That Python Developers Make

About Python

Python is a deciphered, object-arranged, significant level programming language with dynamic semantics. Its significant level implicit information structures, joined with dynamic composing and dynamic official, make it extremely appealing for Rapid Application Development, just as for use as a scripting or paste language to interface existing segments or administrations. Python underpins modules and bundles, consequently reassuring project particularity and code reuse.

About this article

Python's straightforward, simple to-learn sentence structure can delude Python engineers – particularly the individuals who are more current to the language – into missing a portion of its nuances and disparaging the intensity of the various Python language. 

In light of that, this article presents a "main 10" rundown of fairly unobtrusive, harder-to-get botches that can nibble even some further developed Python designers in the back. 

(Note: This article is proposed for a further developed crowd than Common Mistakes of Python Programmers, which is designed more for the individuals who are fresher to the language.)


Common Mistake #1: Misusing expressions as defaults for function arguments

Python allows you to specify that a function argument is optional by providing a default value for it. While this is a great feature of the language, it can lead to some confusion when the default value is mutable. 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


A common mistake is to think that the optional argument will be set to the specified default expression each time the function is called without supplying a value for the optional argument. In the above code, for example, one might expect that calling foo() repeatedly (i.e., without specifying a bar argument) would always return 'baz', since the assumption would be that each time bar is called (without a bar argument specified) bar is set to [] (i.e., a new empty list).

But let’s look at what actually happens when you do this:

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

Huh? For what reason did it continue affixing the default estimation of "baz" to a current rundown each time foo() was called, as opposed to making another rundown each time?

The more advanced Python programming answer is that the default value for a function argument is only evaluated once, at the time that the function is defined. Thus, the bar argument is initialised to its default (i.e., an empty list) only when foo() is first defined, but then calls to foo() (i.e., without a bar argument specified) will continue to use the same list to which bar was originally initialised.

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

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

Makes sense.

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

Yes, again as expected.

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

What the $%#!&?? We just changed A.x. For what reason did C.x change as well?

In Python, class factors are inside taken care of as word references and follow what is regularly alluded to as Method Resolution Order (MRO). So in the above code, since the quality x isn't found in class C, it will be gazed upward in its base classes (just A in the above model, despite the fact that Python underpins numerous legacies). All in all, C doesn't have its own x property, free of A. In this manner, references to C.x are in reality references to A.x.This causes a Python issue except if it's taken care of appropriately. Study class credits in Python.

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

Suppose you have the following code:

The issue here is that the except from proclamation doesn't take a rundown of exemptions indicated as such. Or maybe, In Python 2.x, the grammar aside from except Exception, e is utilized to tie the exemption to the discretionary second boundary determined (for this situation e), so as to make it accessible for additional investigation. Thus, in the above code, the IndexError special case isn't being gotten by the except from articulation; rather, the exemption rather winds up being bound to a boundary named IndexError.

The correct method to get numerous exemptions in an except from proclamation is to determine the principal boundary as a tuple containing all special cases to be gotten. Additionally, for greatest transportability, utilize the as catchphrase, since that punctuation is upheld 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

Python scope goal depends on what is known as the LEGB rule, which is shorthand for Local, Enclosing, Global, Built-in. Appears to be sufficiently direct, isn't that so? Indeed, really, there are a few nuances to the way this works in Python, which carries us to the normal further developed Python programming issue beneath. Think about the accompanying:

>>> 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

What’s the problem?

The above blunder happens in light of the fact that, when you make a task to a variable in a degree, that variable is naturally considered by Python to be neighbourhood to that extension and shadows any also named variable in any external degree.

Many are accordingly shocked to get an UnboundLocalError in already working code when it is adjusted by including a task proclamation some place in the body of a capacity. (You can peruse more about this here.)

It is especially normal for this to entangle engineers when utilizing records. Think about the accompanying model:

>>> 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

Huh? For what reason did foo2 bomb while foo1 ran fine? 

The appropriate response is equivalent to in the earlier model issue however is in fact more inconspicuous. foo1 isn't making a task to lst, while foo2 is. Recollecting that lst += [5] is truly only shorthand for lst = lst + [5], we see that we are endeavoring to allocate an incentive to lst(accordingly assumed by Python to be in the neighborhood scope). Notwithstanding, the worth we are hoping to dole out to lst depends on lst itself (once more, presently dared to be in the neighborhood scope), which has not yet been characterized. Blast.

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

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[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 range

Erasing a thing from a rundown or cluster while repeating over it is a Python issue that is notable to any accomplished programming engineer. However, while the model above might be genuinely self-evident, even progressed designers can be unexpectedly nibbled by this in code that is considerably more intricate. 

Luckily, Python fuses various exquisite programming ideal models which, when utilized appropriately, can bring about essentially disentangled and smoothed out code. A side advantage of this is less complex code is less inclined to be chomped by the unintentional cancellation of-top notch thing while at the same time emphasizing over-it bug. One such worldview is that of rundown appreciations. Additionally, list perceptions are especially valuable for keeping away from this particular issue, as appeared by this substitute usage of the above code which works impeccably:

>>> 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

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)
...

You might expect the following output:

0
2
4
6
8

But you actually get:

8
8
8
8
8

Shock! 


This occurs because of Python's late restricting conduct which says that the estimations of factors utilized in terminations are gazed toward the time the internal capacity is called. So in the above code, at whatever point any of the returned capacities are called, the estimation of i is gazed upward in the encompassing degree at the time it is called (and by at that point, the circle has finished, so i has just been alloted its last estimation of 4). 

The answer for this regular Python issue is somewhat 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

Presto! We are exploiting default contentions here to create unknown capacities so as to accomplish the ideal conduct. Some would call this rich. Some would call it unobtrusive. Some disdain it. Yet, in case you're a Python engineer, it's essential to comprehend regardless.

Common Mistake #7: Creating circular module dependencies

Let’s say you have two files, a.py and b.py, each of which imports the other, as follows:

In a.py:

import b

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

And in b.py:

import a

x = 1

def g():
    print a.f()

First, let’s try importing a.py:

>>> import a
1

Worked fine and dandy. Maybe that shocks you. All things considered, we do have a round import here which probably ought to be an issue, shouldn't it?

The appropriate response is that the simple presence of a roundabout import isn't all by itself an issue in Python. In the event that a module has just been imported, Python is keen enough not to attempt to re-import it. Notwithstanding, contingent upon where every module is endeavoring to get to capacities or factors characterized in the other, you may to be sure run into issues. 

So getting back to our model, when we imported a.py, it had no issue bringing in b.py, since b.py doesn't need a single thing from a.py to be characterized at the time it is imported. The main reference in b.py to a is the call to a.f(). Yet, that call is in g() and nothing in a.py or b.py conjures g(). So life is acceptable. 

In any case, what occurs in the event that we endeavor to import b.py (without having recently imported b.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'

Oh goodness. That is bad! The issue here is that, during the time spent bringing in b.py, it endeavors to import a.py, which thusly calls f(), which endeavors to get to b.x. However, b.x has not yet been characterized. Consequently the AttributeError special case.


At least one solution to this is quite trivial. Simply modify b.py to import a.py within g():

x = 1

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

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

One of the delights of Python is the abundance of library modules that it accompanies "out of the crate". In any case, therefore, in case you're not deliberately staying away from it, it isn't so hard to run into a name conflict between the name of one of your modules and a module with a similar name in the standard library that ships with Python (for instance, you may have a module named email.py in your code, which would be in struggle with the standard library module of a similar name). 

This can prompt intense issues, for example, bringing in another library which in goes attempts to import the Python Standard Library variant of a module in any case, since you have a module with a similar name, the other bundle erroneously imports your rendition rather than the one inside the Python Standard Library. This is the place terrible Python mistakes occur. 

Care should, accordingly, be practiced to abstain from utilizing similar names as those in the Python Standard Library modules. It's route simpler for you to change the name of a module inside your bundle than it is to document a Python Enhancement Proposal (PEP) to demand a name change upstream and to attempt to get that affirmed.

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

Consider the following file 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()

On Python 2, this runs fine:

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

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

What has simply occurred here? The "issue" is that, in Python 3, the special case object isn't open past the extent of the except from block. (The purpose behind this is, else, it would keep a reference cycle with the stack outline in memory until the trash specialist runs and cleanses the references from memory. More specialized insight regarding this is accessible here). 

One approach to evade this issue is to keep up a reference to the special case object outside the extent of the except from block with the goal that it stays open. Here's a form of the past model that utilizes this method, accordingly yielding code that is both Python 2 and Python 3 neighborly:

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 this on Py3k:

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

Yesss!

(Unexpectedly, our Python Hiring Guide talks about various other significant contrasts to know about when relocating code from Python 2 to Python 3.)

Common Mistake #10: Misusing the __del__ method

Let’s say you had this in a file called mod.py:

import foo

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

And you then tried to do this from another_mod.py:

import mod
mybar = mod.Bar()

You’d get an ugly AttributeError exception.

Why? Since, as announced here, when the translator closes down, the module's worldwide factors are good to go to None. Thus, in the above model, at the point that  __del__is summoned, the name foo has just been set to None. 

An answer for this fairly further developed Python programming issue is use atexit.register(). That way, when your program is done executing (while leaving ordinarily, that is), your enrolled controllers are commenced before the mediator is closed down

With that understanding, a fix for the above mod.py code may 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)

This usage gives a perfect and dependable method of calling any required cleanup usefulness upon typical program end. Clearly, it's up to foo.cleanup to choose how to manage the item bound to the name self.myhandle, however you get the thought.

Wrap-up

Python is an incredible and adaptable language with numerous components and ideal models that can extraordinarily improve profitability. Similarly as with any product instrument or language, however, having a restricted comprehension or valuation for its capacities can here and there be a greater amount of a hindrance than an advantage, leaving one in the notorious condition of "understanding a thing or two". 

Acclimating oneself with the key subtleties of Python, for example, (however in no way, shape or form restricted to) the reasonably progressed programming issues brought up in this article, will help enhance utilization of the language while dodging a portion of its more normal mistakes. 

You may likewise need to look at our Insider's Guide to Python Interviewing for proposals on inquiries addresses that can help recognize Python specialists

We trust you've discovered the pointers in this article accommodating and welcome your criticism.

Comments

Popular posts from this blog

Some Tips to Learn Programming and How To Improve Your Programming Skills

Tips to Create Java Assignments within Time Period | Stuck in Java Programming Assignment