Python: Habits that give away inexperience

Dump Python Newbie Norms

Pravash
8 min readJan 16, 2023

We all know that Python is really vast and popular language in the technology industries. And also it is one of the niche skills to have. Its really good to have knowledge in Python and also work on its various modules.

But there are couple of habits which I believe one should have to change in order to be an expert and at the same time writing codes efficiently and effectively. I will also brief about like why one should not be using those and what alternatives one can use.

Some of the things might be an issue and many of the things people might already know. So nevertheless this things will help you to get better or catch any such habits you still hanging on to. So, lets get started

1. Using import *

importing everything from a module, makes your namespace untidy with variables. So, only import the things which you need.

# importing everything
from itertools import *


# importing the required fucntion
from itertools import count

Another thing I wanted to add, at time of importing, import the module not the function. This is to make it much clearer like from where that function is getting called.

# importing fuction
from package.module import function
function()


# importing module
from package import module
module.function()

2. Not following pep8

Well This won’t affect the code run time, but it's better to follow this coding standard. Its basically a style guide for making your code readable.

def function_pep8:
x = 1
lst = [1, 2, 3]

3. Using print statements instead of log

You need to stop using the print literal to keep track of the code.

Instead use logging module of the python.
As it has many features like you can setup logging with your own custom format, can also set the logging level etc.

# using print
def some_func():
print("stage 1 complete")
print("stage 2 processing")
print("stage 2 complete")

# use logging
import logging
def some_func():
log.info("stage 1 complete")
level = Logging.DEBUG
fmt = '[% (Levelname)s] %(asctime)s - %(message)s'
logging.basicConfig(Level=level, format=fmt)

4. Creating own counter variable

You can directlty use “enumerate” to do that for you, instead of declaring a variable with default value and adding 1 to it at the end of the loop.

# using own counter variable
lst = [1, 2, 3]
i = 0
for × in l:
i += 1


# using enumerate
lst = [1, 2, 3]
for i, val in enumerate(lst):
pass

5. Use of default mutable arguments

Sometimes this can be problematic as if you refer to the below example, every call to the function is sharing the same list.

# default mutable arguments
def append(n, l=[]):
l.append(n)
return l

## o/p
l1 = append (0) # [0]
l2 = append (1) # [0, 1]

Instead, what you can do is declare that value with None, and then check whether it is None and set the default value there.

# setting default to None
def append(n, l=None):
if l is not None:
l.append(n)
return l

## o/p
l1 = append(0) # [0]
l2 = append(1) # [1]

6. Not knowing items() in dictionary

If you want to get the values while lopping over the keys, then its better to use “.items()”. This will loop over the items in dictionary which are key-value pair.

# looping through keys
d = {"val1": 1, "val2": 2, "val3": 3}
for key in d:
val = d[key]


# looping through items
d = {"val1": 1, "val2": 2, "val3": 3}
for key, val in d.items():
pass

7. Use Comprehensions when required

Comprehensions are really good, But some times it makes code less readable.


return [
sum(a[n * i + k] * b[n * k + j] for k in range (n))
for i in range (n)
for j in range (n)
]

So, not every loop should be turned into comprehension. Sometimes knowing what that piece of logic is doing is more important.

c = []
for i in range(n):
for j in range(n) :
entry = sum(a[n * i + k] * b[n * k + j] for k in range (n))
c.append(entry)
return c

8. Not using zip

Lets take an example, you want to have a synchronizing iterator to get the corresponding element, if you are using 2 lists. So most of the cases we use range(len()).

lst1 = [1, 3, 5]
lst2 = [2, 4, 6]
for i in range(len(lst2)):
lst1_v = lst1[i]
lst2_v = lst2[i]

So, there’s a better way to do this is using ‘zip’. And you can also use ‘enumerate’ if you want to use the index.

# using zip
lst1 = [1, 3, 5]
lst2 = [2, 4, 6]
for x, y in zip(lst1, lst2):
pass


# using enumerate to get the index
lst1 = [1, 3, 5]
lst2 = [2, 4, 6]
for ind, (x, y) in enumerate(zip(lst1, lst2)):
pass

9. Checking if bool() or if len()

There’s nothing wrong about it, Its just they are same as checking for that variable itself.

# checking with bool and len
if bool(x):
pass
if len(x) != 0:
pass


# directlty check for the value
if x:
pass

10. Using range(len())

If you are looking for values, then why to loop over the indexes.

lst = [1, 2, 31]
for i in range(len(lst)):
val = a[i]

Instead directly loop over that list. It's much easier to read.
And if you want to use the index, then you can use ‘enumerate’.

# looping through underlying container
lst = [1, 2, 31]
for val in lst:
print(val)


# using enumerate
lst = 11, 2, 3]
for index, val in enumerate(lst):
pass

11. Looping over keys of the dictionary

You don’t have to use “.keys()” to loop over the dictionary. You can directly use the dictionary variable as that’s the default for that.

# using .keys()
d = {"val1": 1, "val2": 2, "val3": 3}
for key in d.keys():
pass


# using the default dictionary varaible
d = {"val1": 1, "val2": 2, "val3": 3}
for key in d:
pass

12. Checking type() with ‘==’

There’s no problem using this, but in some cases we can’t and therefore we should be aware of this. It works well with the built in class type.

Lets say, in the example below, namedtuple is a tuple so does the Point class is, But its not the built-in tuple. The reason is inheritance, as the class Point is a subclass.

Point = namedtuple('Point', ['×', 'y'])
p = Point(1, 2)

if type(p) == tuple:
print(p)

Instead its safe to use ‘isinstance’ keyword for comparison.

if isinstance(p, tuple):

13. Not using tuple unpacking

If you want to get the values from a tuple in different variables, then you can do that in single line of code, instead of assigning each new variable separately.

# getting values separately
tup1 = (1, 2)
x = tup1[0]
y = tup1[1]


# using tuple unpacking
tup1 = (1, 2)
x, y = tup1

14. Using bare except clause

This is like using the except clause without handling the required exception. For example when you hit ctrl-c, bare exception will going to catch that.

# bare except block
try:
s = input("Input a number: ")
× = int(s)
break
except:
print("Not a number, try again")

So, instead try to catch that specific exception which you think you are expecting and that going to be thrown.

# using right exception
try:
s = input("Input a number: ")
× = int(s)
break
except ValueError:
print("Not a number, try again")

15. Using time() for timing the code

Just to let you know time().time() is not to check the performance of your code. It wont give you the correct time.

import time
def checking_performance():
start_time = time.time()
time.sleep(1)
end_time = time.time()
print(end_time - start_time)

Instead use “perf_counter()”. This will give you the most accurate way for calculating how much time taken by the code to run.

from time import perf_counter
def checking_performance():
start_time = perf_counter()
time.sleep(1)
end_time = perf_counter()
print(end_time - start_time)

16. Using try and finally instead of context manager

This is similar to closing file, like if you are working on making a connection. Also we can use with statement for the same for closing the connection.

# manually closing conection
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
s.sendall('Hello, world')
finally:
s.close()


#using with statement
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
s.sendall('Hello, world')

17. Manual String formatting

By this what I meant is putting everything with ‘+’ sign.

Instead we can use f string. As they are more readable with minimal syntax.

# using + sign  
print("My name is " + " name ")


# using f string
print(f"My name is {name}")

18. Learn to package your code

If you are importing something, python check for it in your system path. And also adds the directory of the file being run. So sometimes it usually works.

But in case if you have multiple scripts that are not in the same directory, then things get worse.

from some_module import your_function

So, learn how to package your code and install it in the current environment and then us it in import.

from package.some_module import your_function

19. Manually closing files

This is the first thing many of us learn while sarting to work with files — opening a file, write into it and close it.

# manualyy closing
f = open (your_filename, "w")
f.write("hello this is python")
f.close()

The problem with that is if it throws exception, then the file is never be closed. So, its better to use with statement. So this will close the file even if it throws exception.

# using with sttement
with open(your_filename, "w") as f:
f.write("hello this is python")

20. Not using main() function

Its a common practice to do in Python.
Modules should be importable, So, when you import a file, you don’t want to accidentally execute a bunch of codes. Its better to put that into a separate function.

That’s the reason we add main() function inside “if” namespace

def main():
pass

if __name__ == '__main__':
main()

This indicate that its a executable file. There’s another reason of writing it in this way as to avoid availability of variables globally.

21. Lexical Scoping

If you have a nest python function, it call variables out side of its own scope, but it should actually never assign to those variables. But the issue is python resolves this lexical scope at compile or interpret time. And that’s what it determines which variables are going to be accessible.

Lets take below example, function - func(), has a locally declared variable ‘y’, then I have an internal function - nested_function(), and also have a for loop calling the internal function.

def func():
y: int = 3

def nested_function():
print(y)

for val in range(3):
nested_function()

def main():
func()

if __name__ == '__main__':
main()

When I run this code it prints that int value three times, which is right.

So now if I declare the same variable ‘y’ (like y = 1) inside ‘nested_function()’, then that variable ‘y’ is not same as the variable ‘y’ we have in ‘func()’ because I am in another scope, So thats leads to an error.

And also in the for loop, if I replace the ‘val’ with ‘y, So this ‘y’ is local to the function and it will override the ‘y’ in ‘func()’.

def func():
y: int = 3

def nested_function():
print(y)
y = 1 # This will anyway throw error as its another scope, so it won't find the variable

for y in range(3): # This will overide the variable 'y' in function 'func()'
nested_function()

def main():
func()

if __name__ == '__main__':
main()

So one should be always aware of this while accessing variable outside of your scope.

I believe this article will help those who are actually new or still writing codes in python like a beginner and wanted to get better.

Connect with me on LinkedIn

--

--

Pravash

I am a passionate Data Engineer and Technology Enthusiast. Here I am using this platform to share my knowledge and experience on tech stacks.