Coding in Python
Good and Bad Practices of Coding in Python
Python is a high-level multi-paradigm programming language that emphasizes readability. It’s being developed, maintained, and often used following the rules called The Zen of Python or PEP 20.
This article shows several examples of good and bad practices of coding in Python that you’re likely to meet often.
Using Unpacking to Write Concise Code
Packing and unpacking are powerful Python features. You can use unpacking to assign values to your variables:
You can exploit this behavior to implement probably the most concise and elegant variables swap in the entire world of computer programming:
That’s awesome!
Unpacking can be used for the assignment to multiple variables in more complex cases. For example, you can assign like this:
Unpacking can be used for the assignment to multiple variables in more complex cases. For example, you can assign like this:
But instead, you can use more concise and arguably more readable approach:
That’s cool, right? But it can be even cooler:
The point is that the variable with * collects the values not assigned to others.
Using Chaining to Write Concise Code
Python allows you to chain the comparison operations. So, you don’t have to use and to check if two or more comparisons are True:
Instead, you can write this in a more compact form, like mathematicians do:
Python also supports chained assignments. So, if you want to assign the same value to multiple variables, you can do it in a straightforward way:
A more elegant way is to use unpacking:
However, things become even better with chained assignments:
Be careful when your value is mutable! All the variables refer to the same instance.
Checking against None
None is a special and unique object in Python. It has a similar purpose, like null in C-like languages.
It’s possible to check whether a variable refers to it with the comparison operators == and !=:
However, a more Pythonic and desirable way is using is and is not:
In addition, you should prefer using the is not construct x is not None over its less readable alternative not (x is None).
Iterating over Sequences and Mappings
You can implement iterations and for loops in Python in several ways. Python offers some built-in classes to facilitate it.
In almost all cases, you can use the range to get an iterator that yields integers:
However, there’s a better way to iterate over a sequence:
But what if you want to iterate in the reversed order? Of course, the range is an option again:
Reversing the sequence is a more elegant way:
The Pythonic way is to use reversed to get an iterator that yields the items of a sequence in the reversed order:
Sometimes you need both the items from a sequence and the corresponding indices:
It’s better to use enumerate to get another iterator that yields the tuples with the indices and items:
That’s cool. But what if you want to iterate over two or more sequences? Of course, you can use the range again:
In this case, Python also offers a better solution. You can apply zip and get tuples of the corresponding items:
You can combine it with unpacking:
Please, have in mind that range can be very useful. However, there are cases (like those shown above) where there are more convenient alternatives.
Iterating over a dictionary yields its keys:
Iterating over a dictionary yields its keys:
However, you can apply the method .items() and get the tuples with the keys and the corresponding values:
You can also use the methods .keys() and .values() to iterate over the keys and values, respectively.
Comparing to Zero
When you have numeric data, and you need to check if the numbers are equal to zero, you can but don’t have to use the comparison operators == and !=:
The Pythonic way is to exploit the fact that zero is interpreted as False in a Boolean context, while all other numbers are considered as True:
Having this in mind you can just use if item instead of if item != 0:
You can follow the same logic and use if not item instead of if item == 0.
Avoiding Mutable Optional Arguments
Python has a very flexible system of providing arguments to functions and methods. Optional arguments are a part of this offer. But be careful: you usually don’t want to use mutable optional arguments. Consider the following example:
At first sight, it looks like that, if you don’t provide seq, f() appends a value to an empty list and returns something like [value]:
Looks fine, right? No! Consider the following examples:
Surprised? Confused? If you are, you’re not the only one.
It seems that the same instance of an optional argument (list in this case) is provided every time the function is called. Maybe sometimes you’ll want just what the code above does. However, it’s much more likely that you’ll need to avoid that. You can keep away from that with some additional logic. One of the ways is this:
It seems that the same instance of an optional argument (list in this case) is provided every time the function is called. Maybe sometimes you’ll want just what the code above does. However, it’s much more likely that you’ll need to avoid that. You can keep away from that with some additional logic. One of the ways is this:
A shorter version is:
Now, you get different behavior:
In most cases, that’s what one wants.
Avoiding Classical Getters and Setters
Python allows defining getter and setter methods similarly as C++ and Java:
This is how you can use them to get and set the state of an object:
In some cases, this is the best way to get the job done. However, it’s often more elegant to define and use properties, especially in simple cases:
Properties are considered more Pythonic than classical getters and setters. You can use them similarly as in C#, i.e. the same way as ordinary data attributes:
So, in general, it’s a good practice to use properties when you can and C++-like getters and setters when you have to.
Avoiding Accessing Protected Class Members
Python doesn’t have real private class members. However, there’s a convention that says that you shouldn’t access or modify the members beginning with the underscore (_) outside their instances. They are not guaranteed to preserve the existing behavior.
For example, consider the code:
The instances of class C have three data members: .x, ._y, and ._C__z. If a member’s name begins with a double underscore (dunder), it becomes mangled, that is modified. That’s why you have ._C__z instead of .__z.
Now, it’s quite OK to access or modify .x directly:
Now, it’s quite OK to access or modify .x directly:
You can also access or modify ._y from outside its instance, but it’s considered a bad practice:
You can’t access .z because it’s mangled, but you can access or modify ._Cz:
You should avoid doing this. The author of the class probably begins the names with the underscore(s) to tell you, “don’t use it”.
Using Context Managers to Release Resources
Sometimes it’s required to write the code to manage resources properly. It’s often the case when working with files, database connections, or other entities with unmanaged resources. For example, you can open a file and process it:
To properly manage the memory, you need to close this file after finishing the job:
Doing it this way is better than not doing it at all. But, what if an exception occurs while processing your file? Then my_file.close() is never executed. You can handle this with exception-handling syntax or with context managers. The second way means that you put your code inside the with a block:
Using the with block means that the special methods .enter() and .exit() are called, even in the cases of exceptions. These methods should take care of the resources.
You can achieve especially robust constructs by combining the context managers and exception handling.
You can achieve especially robust constructs by combining the context managers and exception handling.
Stylistic Advises
Python code should be elegant, concise, and readable. It should be beautiful.
The ultimate resource on how to write beautiful Python code is Style Guide for Python Code or PEP 8. You should definitely read it if you want to code in Python
Comments
Post a Comment