According to you, what are the most common Python programming mistakes that programmers may commit while coding? Well, there are some fundamental coding mistakes that some of us get used to making. Here, we’ll guide you on how to spot these and improve your code quality. But it’s fair to understand the backdrop of these problems.
Surprisingly, studies reveal that most of these mistakes happen to be the side effects of common misconceptions carried from past programming experiences. For example, while learning to code, it goes without realizing that you made a few mistakes. Sometimes, you aren’t aware that you’re committing errors and leaving gaps in your programs.
It also reflects that you are taking it easy. Whereas learning to program is a tough task, everyone will accept who’s done it.
But the good part is that you can learn from the mistakes. You can take them as opportunities to grow. So, you shouldn’t be ashamed if you made one. In fact, each mistake leaves an important lesson to learn that you carry till you become an expert. And, a good programmer never runs away, instead accepts them as milestones in his path to development.
Nonetheless, in this article, we’ve laid down a few of the Python programming mistakes and tried to give a solution for each of them.
Common Python Programming Mistakes to Avoid.
To begin with, you can go through the following checklist to help you avoid basic Python programming mistakes. It lists some of the key elements of a program/application and lays down a few points for improvement.
- Identifiers: Make sure all your identifiers are meaningful. Avoid single letters and names like temp/flag.
- Modularization: Split up logic using functions and classes. Don’t reinvent a library routine.
- Formatting: Be careful while indenting your code in Python. Use spaces instead of tabs and follow a consistent indentation pattern.
- Comment Vs. Docstring: Python supports Docstring. It’s more convenient than using traditional comments. Make sure all your functions have a Docstring.
- Code Analysis: Run a tool like PyLint on your code. It helps catch low-hanging fruits like undefined vars, basic typos, unused code, etc.
- Unit Tests: Don’t miss to unit test your code. Use test modules like <unittest> or <unittest.mock> and deliver a dev-tested code.
- Code Profiling: Never guess the unknown, instead find them. Add modules like <timeit> or <cprofile> to locate hidden issues in your code.
If you are a keen learner and wish to excel in Python programming, then do follow the below two posts as well.
- Top 30 Essential Python Coding Tips Every Programmer Should Know
- Top 12 Essential Python Code Optimization Tips for Seasoned Programmers
Let’s now review the common mistakes and the actions you should take to fix them.
1. Ignorant of Python Scoping Rules (LEGB).
If you aren’t aware of Python scoping rules, then there is a high probability of you making mistakes. It’s because Python uses a little different approach for scoping variables than other programming languages. For example, it allows accessing the variables declared inside loops or if statements from outside. It could be a bit confusing for someone coming from a C/C++ background.
Here is a sneak overview of the Python Scoping Rules a.k.a. LEGB.
- L – stands for Local. It encompasses (identifier/variable) names specified within a function (using def or lambda) and not declared using the global keyword.
- E – stands for Enclosing function locals. It includes a name from the local scope of any/all enclosing functions (for example, using def or lambda).
- G – refers to Global entities. It includes names operating at the top level of a module file or defined using the global keyword.
- B – refers to Built-ins. It spans names that are preassigned as built-in names such as print, input, open, etc.
The LEGB rule specifies the following order for namespaces, meant to be used for searching the names.
Local -> Enclosed -> Global -> Built-in.
So, if a particular <name->object> mapping isn’t available in the local namespaces, it’ll then be looked up in the enclosed scope. If it doesn’t succeed, then Python will move on to the global namespace, and carry on searching the Built-ins. If it fails to find the name in any namespace, then a NameError will be raised.
To understand LEGB rules in detail, consider the below example. It showcases the practical usage and impact of the Python scoping rules. In this example, we’ve used four functions to demonstrate the application of scoping rules in Python.
LEGB Example.
1. Function: <access_local()> – It uses a local variable named “token” (which also exists in the global namespace) and initializes it with some value. Then, it queries the local and global namespaces to confirm its presence in both of them. And finally, print the “token” variable to make sure that it isn’t referencing the global variable.
2. Function: <access_enclosed()> – It has a for loop and initializes the token variable inside the loop. Then, it checks the global namespace that includes the token variable too. Next, it prints the value of the token variable, which is the value set in the enclosed for-loop. It proves that variables defined in the enclosed scope have a higher precedence than the global variables.Function: <access_global()>
3. Function: <access_global()> – In this function, first, we are confirming the presence of the token variable in the global namespace. And then, printing its value which remains the same as we’d set at the onset i.e. at the global level.
4. Function: <id()> – Here, we’ve created our own definition of the built-in “id()” function. And, as per the LEGB rules, built-ins have the least precedence. So whenever we call the “id()” function, Python will refer to the one available in the global namespace.
5- NameError – As said above, the use of an undefined variable throws the NameError. You can see that happening with the last statement of the below code. In that line, we tried to print “token1” which resulted in the error.
Sample Code.
token = 'global' def access_local(): token = 'local' if 'token' in locals() and 'token' in globals(): print("Yes, token is in both local and global scope.") print("But value of token used is = (" + token + ")\n") def access_global(): if 'token' in globals(): print("Yes, token is in global scope.") print("Value of token used is = (" + token + ")\n") def access_enclosed(): test = 1 for test in range(5): token = 'enclosed' pass if 'token' in globals(): print("Though, token is in global scope.") print("But value of token used is = (" + token + ")\n") def id(token): return 1 access_local() access_enclosed() access_global() print("%s = %d\n" % ("token length", id(token))) print(token1)
Here is the output of the above Python code. To interpret the below result, please refer to the description given in the example.
Python 2.7.10 (default, Jul 14 2015, 19:46:27) [GCC 4.8.2] on linux Yes, token is in both local and global scope. But value of token used is = (local) Though, token is in global scope. But value of token used is = (enclosed) Yes, token is in global scope. Value of token used is = (global) token length = 1 Traceback (most recent call last): File "python", line 27, in <module> NameError: name 'token1' is not defined
2. Misconceive Identity as Equality.
Another common mistake that Python programmers commit is mistaking <is> for <equality> while comparing integers. Since Python is used to cache integers, they may not be aware of this error.
To grasp this concept, let’s consider the following two examples.
Example-1.
In the first example below, we’ve used two variables named <sum> and <add>. And each of them stores the sum of two integers. Then, we are comparing the two variables with the equality (==) operator. It’ll return true as both the variables hold the same value. Next, we test them using the identity (“is”) operator, but that too returns true. The reason is Python allocated the same address for both of them. You can confirm it from their id values printed at the end.
However, the programmer didn’t realize how come the two distinct operations (“==” and “is”) yield the same result. And made the mistake unknowingly.
Python 2.7.10 (default, Jul 14 2015, 19:46:27) [GCC 4.8.2] on linux sum = 10 + 15 => None add = 5 + 20 => None sum == add => True sum => 25 add => 25 sum is add => True id(sum) => 25625528 id(add) => 25625528
However, it’s going to cost him in the next example.
Example-2.
In this example, we’ve considered long integers to use. The catch here is that Python only caches integers between -5 to 256. Whereas the large numbers do occupy their separate boxes to sleep.
Hence, matching large integers with the identity (“is”) operator wouldn’t yield the same result as you saw in the previous example.
300 + 200 is 500 => False 300 + 200 == 500 => True
The takeaway here is that the programmers should pay attention to the concept first before making blind use of any constructs.
However, you can read more on how Python deals with integers and voice any doubts in the comment box.
3. Irrational use of Anti-patterns in your code.
In general, an anti-pattern is a design approach to a commonly occurring problem that tentatively solves it but with possible side-effects.
Here, we are discussing a few Python anti-patterns that programmers may tend to use while coding.
3.1. Use of Java-styled getter and setter functions.
It’s often in Java termed as a best practice to define get/set functions for accessing members of a class. And, you can see this pattern being applied in applications using the Java Hibernate Framework.
On the contrary, such use of functions in Python leads to extra code with no real benefit.
Anti-pattern example: Implement a Python class in Java-style.
What’s best for Java does not eventually be the same for Python. So if you are from a Java background, you must cautiously think about the way things work in Python.
class Employee(object): def __init__(self, name, exp): self._name = name self._exp = exp # Java-style getter/setter def getName(self): return self._name def setName(self, name): self._name = name def getExp(self): return self._exp def setExp(self, exp): self._exp = exp emp = Employee('techbeamers', 10) print("Employee-1: ", emp.getName(), emp.getExp()) emp.setName('Python Programmer') emp.setExp(20) print("Employee-2: ", emp.getName(), emp.getExp())
Approach 1: How should you do it in Python?
In Python, it’s right to access or manipulate a class member directly. And, usually, the use of protected or privates is scarce in Python. The members in Python are also public by default until you prefix them using <_> or <__>. This way, you can just emulate them to behave like protected (with _) or private (with __). Python obfuscates the names of variables starting with the <_> or <__> prefix to alienate them from the code outside the class.
You should see the code below after we remove the get/set functions.
class Employee(object): def __init__(self, name, exp): self.name = name self.exp = exp emp = Employee('techbeamers', 10) print("Default: ", emp.name, emp.exp) emp.name = 'Python Programmer' emp.exp = 20 print("Updated: ", emp.name, emp.exp)
Approach 2: Use built-in <property> to work like get/set functions.
In some situations, when it’s mandatory to hide the members, then you can use the property decorators to achieve getter/setter functionality.
That’s how you can modify your code.
class Employee(object): def __init__(self, exp): self._exp = exp @property def exp(self): return self._exp @exp.setter def exp(self, value): self._exp = value @exp.deleter def exp(self): del self._exp emp = Employee(10) print("default: ", emp.exp) emp.exp = 20 print("Updated: ", emp.exp)
3.2. Irregular use of Spaces with Tabs.
The PEP 8 guidelines affirm that Python code should consistently use four spaces for indentation and probit using tabs. However, it’s just a piece of rules that no standard Python engine enforces. But that’s the way you should follow to make your code manageable and error-free.
Anti-pattern example: Spaces mixed with tabs.
Here is a piece of Python code holding a class indented with tabs and two methods, one uses spaces and the other one has tabs for indentation. The code runs fine on execution but misses the PEP 8 guidelines.
# indented with tabs class Sample: def record_sample(): # indented with spaces print("Recored the sample!") def list_sample(): # indented with tabs print("Listed the sample!")
Refactored: Convert Tabs to Spaces.
The solution is to refactor your code to convert the tabs into spaces. There are many ways to do it.
1. You can edit the settings of your text editor and set it to insert four spaces instead of a tab.
2. If you are on Linux and using VIM, then use the <:retab> command to do the job for you. It’ll swap the tab with no. of spaces defined in the tab settings.
3. You can also run the script <reindent.py> for auto-indentation. You can find it under the path <Python install dir>Tools\Scripts\reindent.py>.
# indented with spaces class Sample: def record_sample(): print("Recored the sample!") def list_sample(): print("Listed the sample!")
3.3. Underutilization of Python’s exception block.
While learning or adapting to a new language, we do consider walking through the essentials but walk over the extras.
However, we shouldn’t overlook a topic something like exceptions. Knowing and utilizing exceptions can make your application work even in exceptional conditions.
Sometimes, we get to use them but in a way that’s never going to help us. Let’s look at one such example followed by a solution that guides on implementing exceptions efficiently.
Anti-pattern: Not using exceptions at all.
Below is an example of weak error handling. It’s just confirming an obvious fact. But overlooking the following conditions.
- What if “debug.log” exists, but there comes some error while removing it? The code will abort without any informative message from the application.
- You won’t want to see your code dying on a step that doesn’t impact the rest of the execution.
import os # Testing the obvious, while overlooking the EAFP principle. if os.path.exists("debug.log"): os.remove("debug.log")
EAFP is a common slang often used by Python programmers. It stands for <easier to ask for forgiveness than permission>. It expresses a notion of using exceptions for handling errors relating to undefined variables or files etc.
Solution: Use try-except to avert any eventuality.
Here is the same code wrapped in a try-except block. It’s now in a format as per the EAFP convention. Interestingly, the except clause is set to show the befitting error message.
import os try: os.remove("debug.log") #Raised when file isn't available. except Exception, OSError: print (str(OSError)) #Output #[Errno 2] No such file or directory: 'debug.log'
3.4. Return inconsistent type values from functions.
You should check if your function is returning a value of a type that its caller doesn’t expect. If it does, then better update that condition to raise an exception. Otherwise, the caller would always have to verify the type before processing it further.
You should avoid writing such code as it leads to confusion and increases complexity. Consider the below example and refer to the solution given next.
Anti-pattern: Returning in-variant types.
In the below example, the function get_error_message() returns error messages corresponding to an error code. But in the case of a non-existent error code, it returns None. It leads to ambiguous code which is hard to maintain. And the caller will have to check it explicitly.
def get_error_message(code): if code == 200: return "ok" elif code == 404: return "not found" else: return None status = get_error_message(403) if status is None: print("Unknown error.") else: print("The status is {}".format(status))
Solution: Raise an exception for unknown values.
The ideal approach for handling unexpected conditions is by employing a try-except block and raising an appropriate exception. It also fits in such conditions because the function won’t return any data. So instead of returning any invalid or unknown value, it throws an exception.
You can refer to the code below which is the updated version of the above example. Now it’s much cleaner and doesn’t require checking for an additional data type.
def get_error_message(code): if code == 200: return "ok" elif code == 404: return "not found" else: raise ValueError try: status = get_error_message(403) print("The status is {}".format(status)) except ValueError: print("Unknown error.")
3.5. Incorrect type checking.
Sometimes, the programmers use to call <type()> in their code to compare the datatypes. Instead, they should use <isinstance> for type checking.
This method does even have the ability to identify a derived class object. Hence, it is the best choice for type-checking.
Anti-pattern: Weak type-checking example.
The below code would fail to match the type of <emp> with the Employee class. Though, the programmer would have thought that it would work.
class Employee(object): def __init__(self, name): self.name = name class Engineer(Employee): def __init__(self, name, exp, skill): Employee.__init__(self, name) self.exp = exp self.skill = skill emp = Engineer("Python Programmer", 4, "Python") # Bad if type(emp) is Employee: print("object emp is a Employee")
Solution: Strong type-checking example.
Here is the right way to perform type checking of an object.
class Employee(object): def __init__(self, name): self.name = name class Engineer(Employee): def __init__(self, name, exp, skill): Employee.__init__(self, name) self.exp = exp self.skill = skill emp = Engineer("Python Programmer", 4, "Python") # Good if isinstance(emp, Employee): print("object emp is a Employee")
4. Imports leading to circular dependency.
In Python, import is also an executable statement. Each import clause leads to the execution of a corresponding module. Also, any function or a class embedded in a module doesn’t come to life until the related code (in def or class) gets executed.
Hence, importing a module recursively may cause a circular dependency in your program. For example, let’s assume we’ve two modules mod1 and mod2.
The mod1 has an import call to load mod2. It contains the following code.
# module mod1 import mod2 def mod1_func(): print("function in mod1")
To understand the reason for circular dependency, let’s imagine the following sequence of code.
1. You load the mod1 from your main program. The main program will then read the mod1 and process it. Since it’s loading the mod2 on the top, Python will get on to reading it next.
2. Till this point, Python has got in both <mod1> and <mod2> under the sys.modules object list. But <mod1> still hasn’t received any definition because Python is currently executing the <mod2> module.
3. Now, to make a case of circular dependency, let’s add an “import mod1” statement into the mod2 module. So while executing the “import mod1” call, Python will reference back to the empty mod1 object.
4. In this situation, any call to mod1 entities (def or class) from mod2 would result in failures.
# module mod2 import mod1 # The below call would fail as a circular dependency side-effect. mod1.mod1_func()
Solution.
There can be two most likely solutions to the above problem.
1. Modify the program to eliminate the recursive imports. You can offload some functionality to a new module.
2. Another approach could be to displace the affected imports (mod2) to the tail of the calling module (i.e. mod1).
Hence, relocating the “import mod2” call towards the EOF in module mod1 will resolve the circular dependency issue.
5. Misusing the <__init__> method.
Just like constructors in C++, you’ve <__init__> method in Python. It automatically gets called when Python allocates memory to a new class object. The purpose of this method is to set the values of instance members for the class object.
And it’s not a good practice to explicitly return a value from the <__init__> method. It implies that you want to deviate from the purpose of this method. If that is the case, then better you choose a different method or define a new instance method for what you wish to accomplish.
Let’s establish the above facts with some examples.
Example: Misusing the <__init__> method.
In this example, the code is trying to return the work experience of an employee from the <__init__> method. But It’ll result in an error “TypeError: __init__() should return None”.
class Employee: def __init__(self, name, workexp): self.name = name self.workexp = workexp self._avgsal = workexp*1.5*100000 # causes "TypeError: __init__() should return None". return self._avgsal emp = Employee("Python Programmer", 10)
Example: Adding a new property to fix <__init__> error.
To solve the above issue, we’ll move the desired logic to a different instance method. You can call this method once the class object is ready with initialization.
class Employee: def __init__(self, name, workexp): self.name = name self.workexp = workexp self._avgsal = workexp*1.5*100000 @property # Relocated the logic for returning work exp to a new method. def getAvgSal(self): return self._avgsal emp = Employee("Python Programmer", 10) print(emp.getAvgSal)
So that were a few Python programming mistakes and their solutions we wanted to share with you. However, the list is too big to fit in one post. So we’ll keep posting the useful 🙂 programming mistakes in the future as well.
Summary- Common Python Programming Mistakes to Avoid.
Hello, we believe this post has a lot for you to learn and apply in your daily work. You may not use them directly, but you can still avoid making such mistakes in your code.
Finally, if you’ve any such “Python programming mistakes” to share, then let the world know about it.
Also, if you liked the stuff discussed here, then don’t mind sharing it further.
Help us grow and grow with us.
Best,
TechBeamers