Python Zen in practice
Explanation of Python Zen with examples
Hello everyone !
I’m glad to see you here. Hope you love Python as much as I do.
If you are practicing Python in your job/study/hobby , you saw the Zen of Python written by Tim Peters. If you didn’t, now is the most suitable time to do it.
Let’s post them here initially. On a snippet below you can see the original output if you execute in Python interpreter: >>> import this
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
In this article I will make a detailed overview of each rule with explanation and examples of their fulfilment and violation. Hope it will help you as much as it helped me to improve my code.
Beautiful is better than ugly
Python is a beautiful programming language. It has rules and standards, but many of these standards flow from logic of usage like there are no need in using else
with return
after if
block with return
statement, we can just use if>return1>return2
because interpreter won’t execute return2 if our condition in if
is true. Other rules come from comfort of visual perception like maximum length of line no more than 100 characters.
If you want to follow this rule, investigate and learn PEP8 via this link. PEP — Python Enhancement Proposals. 8th part of this guideline refers to a style and visual part of a code. Also read this fresh (oct. 2020) article.
Note: both examples below are working, but question for you: which one do you like more ?
Violation example:
def LongCamelCaseFuncNameDoSomeStuff(arg1, arg2, arg3, arg4, arg5):
if (arg1 | arg2):
return 1;
elif (arg4 > arg5):
return 2;
else:
return arg3;
localVar = map(lambda x,y,z,a,b,c: x+y+z+a+b+c, [1], [2], [3], [4], [5], [6]);
return localVar
Fulfilment example:
def define_time(length, velocity):
return length / velocity if velocity else 0
def calculate_time_for_batch(lengths_array, velocities_array):
time_array = map(
define_time,
lengths_array,
velocities_array
)
return time_array if sum(time_array) else []
Explicit is better than implicit
“If something might be misunderstood, it will be misunderstood” — repeat it yourself each time you look at your code and think: “Will it be obvious to my colleagues the same as to me ?” and remove all doubting parts with clear ones.
Violation example:
def main_function_of_program(argument_first, argument_second):
result_first = argument_first + argument_second
results_in_general = []
if result_first > 0:
results_in_general.append(result_first)
else:
results_in_general.append(0)
another_action = argument_first * argument_second
if another_action > results_in_general[0]:
results_in_general.append(another_action)
else:
results_in_general.append(0)
sum_of_results = 0
for value in results_in_general:
sum_of_results += value
return sum_of_results
Fulfilment example:
def get_sum_of_values_in_two_arrays(array_a, array_b):
return sum(array_a) + sum(array_b)
def create_integers_array_from_string(string):
integers_array = []
for char in string:
if char.isdigit():
integers_array.append(int(char))
return integers_array
def strings_digits_sum(string_a, string_b):
int_array_a = create_integers_array_from_string(string_a)
int_array_b = create_integers_array_from_string(string_b)
sum_of_int_a_b = get_sum_of_values_two_arrays(
int_array_a, int_array_b
)
print(sum_of_int_a_b)
if __name__ == "__main__":
strings_digits_sum("hello123", "222goodbye") # 12
strings_digits_sum("abc1", "def22") # 5
strings_digits_sum("abc", "abc") # 0
Simple is better than complex.
How to make your life easier ? Make it simple. How to make life of your colleagues easier ? Simplify your code.
If you have to choose between complex and simple solution for the same problem, choose the simple one. Next code examples will perform the same task and will return the same result, just evaluate the complexity.
Violation example:
def define_how_many_letters_are_digits(string):
alphabet = "abcdefghijklmnopqrstuvwxyz"
digits = "0123456789"
count_of_digits = 0
for char in string:
char_is_letter = None
if char.lower() in alphabet:
char_is_letter = True
elif char in digits:
char_is_letter = False
if isinstance(char_is_letter, bool):
if not char_is_letter:
count_of_digits += 1
return count_of_digits
Fulfilment example:
Two examples both simple but first is using list comprehension and second one is the classic one.
# example 1 via comprehension
def define_how_many_letters_are_digits_1(string):
return len([1 for char in string if char.isdigit()])
# example 2 classic for loop
def define_how_many_letters_are_digits_2(string):
count_of_digits = 0
for char in string:
if char.isdigit():
count_of_digits += 1
return count_of_digits
Complex is better than complicated.
This sounds like a tautology, but these two things are different. We have many problems in our world that have complex nature. For example is to write a neural network in Python. Is it complex ? — Yes. Should the solution be complicated ? — No. There are a lot of linear algebra and gradient computations while training AI, but the solution should not be 1M rows of spaghetti code with a lot of inefficient algorithms doing simple task. It should be easy to understand for a specialist who have expert knowledge in AI.
Let’s have a look for a simpler example: get a sum of pairwise product for two lists of integer numbers (dot product). It is complex task, but should it be complicated ?
Complicated (violation example):
def dot_product(list_a, list_b):
list_of_products = []
for a, b in zip(list_a, list_b):
list_of_products.append(a * b)
sum_of_list = 0
for product in list_of_products:
sum_of_list += product
return sum_of_list
Complex (fulfilment example):
def dot_product(list_a, list_b):
return sum(map(lambda a, b: a*b, list_a, list_b))
Flat is better than nested.
This time the main idea is closely connected to if-elif-else
blocks. There are two main types of conditions schemes: chained(flat) and nested. Chained conditions is when you have one or more conditions and each of them can exclude all others if it’s true. Nested conditions is when you have conditions inside of your other conditions like a decision tree structure.
So, if it’s possible better simplify your code from nested to flat conditions. Next examples solves the same problem but first one is nested and second one is flat/chained.
Violation example (nested):
a = 10
b = 5
if a > b:
if len(c) > a:
print(1)
else:
print(2)
Fulfilment example (flat/chained):
a = 10
b = 5
if b < a < len(c):
print(1)
else:
print(2)
Sparse is better than dense.
What is sparse and dense in code ? Sparse code is similar to single responsibility principal (or SRP). Each block/function/class/row should be responsible for execution of one simple(relatively) task. Dense code is a total opposite situation to SRP like “many responsibilities for one piece of code” situation.
In case of sparse code you will catch errors much more easier and also unit tests will be easier to write. SRP is cool, follow it.
Violation example (two examples of dense code):
# first example is bad
def print_sum_of_positive_int_numbers_in_mixed_list(array):
numbers = [value for value in array if isinstance(value, int)]
positive = [value for value in numbers if value > 0]
sum_of_values = sum(positive)
print("Sum of positive numbers is %s . Thank you." % sum_of_values)# but this one even worse
def print_sum_of_positive_int_numbers_in_mixed_list(array):
print("Sum of positive numbers is %s . Thank you." %
sum([value for value in
[value for value in array if isinstance(value, int)]
if value > 0]))
Fulfilment example (sparse code for the same problem)
def get_integers_from_list(array):
return [value for value in array if isinstance(value, int)]
def get_greater_than_zero_integers_from_list(array):
return [value for value in array if value > 0]
def get_sum_of_array_values(array):
return sum(array)
def print_information(value):
print("Sum of positive numbers is %s . Thank you." % value)
def print_sum_of_positive_int_numbers_in_mixed_list(array):
numbers = get_integers_from_list(array)
positive = get_greater_than_zero_integers_from_list(numbers)
sum_of_values = get_sum_of_array_values(positive)
print_information(sum_of_values)
Readability counts.
This rule is a gathering of all rules connected with PEP8 and code visual component. Just for your notice: it really counts for you and your colleagues. If you are doing a task, ask yourself two questions: Will it be the same easy to understand your code for … 1) You in 3 month after abandoning it ? 2) For your colleagues right now ?
Readability = explicit naming + simple solutions + sparse structure
Imagine a situation. You can’t solve a problem and google a solution. You have found 2 solutions. One is simple but hard to read, second one is complex but easy to read. Which one do you use ?
Please, use explicit names, construct your structures wisely for certain purpose, avoid unnecessary data mixing, use keywords instead of ampersands (and, or instead of |, &) etc. Just read PEP8 again and more books with good Python code. You will be grateful for yourself in future, trust me. To write an unreadable code is like a bad habit, of course it’s easier to name your data X, Y
instead of input_variables, desirable_output
, but in future you will hate this code while fixing bugs instead of doing it fast.
It’s not a math paper, it’s a good story covered in code.
Violation example:
VarA = 10
elemedf_fd_dict_A = { "a": "hello", "v": 2
,"c": 1}
elemedf_fd_dict_A[VarA] = \
"elemet!"
Fulfilment example:
char, index = "C", 3 # using tupleschar_indices_dict = {
"A": 1,
"B": 2,
"C": 3,
char: index
} # simple meaning for dict is char:char_index without any comments
Special cases aren't special enough to break the rules.
There is a difference between “follow the rules” and “strive to follow the rules”. Python Zen is not a constitution of a country. It’s not strict and nobody will punish you by a jail sentence for violating one of the rules.
But as a professional, you should follow these rules to be a better developer.
Here are no examples, just remember it.
Although practicality beats purity.
Well, it is a conflict with a previous one rule. Like “always follow the rules except some cases …”
If you have a choice between to follow the rules and to make a good, checked by time and practice solution, you better to choose second option.
Rules are changing every day. Quality of your work is only your responsibility and it happens right now.
Errors should never pass silently.
It means you should never pass the errors or cover in try-except
potentially dangerous blocks of code with some print()
functions.
Catch each error, crash your code, but be sure it won’t happen again.
Violation :
def division_function(a, b):
try:
return a / b
except:
pass # or print("Hey bro, don't divide on 0, please")
finally:
return 0
Fulfilment example:
def division_function(a, b):
try:
return a / b
except ZeroDivisionError as error:
raise error
Unless explicitly silenced.
Even for exceptions we have exceptions in our rules.
If we catch an error and don’t want to stop the program when it’s really important (remember: practicality beats purity !)
Fulfilment example:
def division_function(a, b):
result = 0
try:
result = a / b
except ZeroDivisionError as error:
result = error[0]
finally:
return result
if __name__ == "__main__":
value_a, value_b = 5, 0
division = division_function(value_a, value_b)
if division == "division by zero":
pass
# here we can send error message to developer
else:
print("Division result: %s" % division)
In the face of ambiguity, refuse the temptation to guess.
As a developer I have seen a lot of code and will see much more. So do you if you are a developer too. How much do you have a situation when you see a block of a code and try to guess what does it perform ? I think it was an often temptation.
This rule means: if you don’t know for 101% how does code work — break it into simple parts and combine the puzzle to understand the whole picture. Do not guess. It’s not a TV show with supernatural vision into future. It’s your duty as a developer to find all the needles in a haystack.
Violation example:
def huge_function(arg1, arg2): # where is a function description ?
component_1 = function_huge_2(arg1) # you skip to debug it
component_2 = function_huge_3(arg2) # you skip to debug it
component_result = sqrt(component_1+component_2)
return component_result + 1 # where is an explanation ?
Fulfilment example:
def my_custom_formula(arg1, arg2):
"""
Custom function to calculate
how ... <here is explanation>
<also raw formula>
:param arg1: integer, 0 < arg1 < 10
:param arg2: float, 0 < arg2 < 1
:return: square root of sum of
argument 1 in power of 10 and
second argument tangent.
"""
component_1 = function_huge_2(arg1) # pow to 10
component_2 = function_huge_3(arg2) # tangent
component_result = sqrt(component_1+component_2)
return component_result + 1 # see method docstring
There should be one-- and preferably only one --obvious way to do it.
Well, each problem has only one perfect variant of solution. It’s true.
If you think long enough and try more solutions you will find a perfect one.
“When you first start off trying to solve a problem, the first solutions you come up with are very complex, and most people stop there. But if you keep going, and live with the problem and peel more layers of the onion off, you can often times arrive at some very elegant and simple solutions.” — Steve Jobs
Remember, solution it’s not just a way of performing a task. Because ways are many. Solution is only one. Let’s look for one example. How to create a list in Python ?
my_list = [1, 2, 3, 4, 5]
my_list_2 = [i for i in range(1, 6)]
my_list_3 = list(range(1, 6))
my_list_4 = [i for i in other_list]
...
But what if out task is not just to create a list, but to create a numeration list if we know only start and end values ? list(range(start, end+1))
is the best. But what is the best solution to create a custom list with variable number of arguments like function create_list(1, “dog”, 2, False, “apple”, 0)
? in this way next solution is the best:
def create_list(*args):
return list(args) # without list() it's a tupleprint(create_list(1, “dog”, 2, False, “apple”, 0))
# [1, “dog”, 2, False, “apple”, 0]
In this you need to understand: specify your problem and make a good formulation and you will find the best solution.
Although that way may not be obvious at first unless you're Dutch.
Ha-ha, it’s a reference. Guess to who ? That’s him. Because for him I suppose all the solutions in Python are obvious for obvious reasons.
But jokes away. Main idea of this rule is: think more than once. As it was said by Steve Jobs, perfect solution doesn’t come first. First of all come complex ones. Think more.
Now is better than never
Means if you have an idea of optimisation or how to make best solution do it now. Implement your ideas, test them and find proofs that they are the best. Don’t spend too much time planning and pre-optimising.
Although never is often better than *right* now
But think about your solution. Some “optimisations” are redundant. Remember YAGNI (You Aren’t Gonna Need It) principle also.
If the implementation is hard to explain, it's a bad idea
Try to break your idea explanation into simple parts like 2 x 2 = 4. If your idea can not be such factorised, it means your idea is bad and not optimal. Or you have lack of knowledge to understand it. In case of absence of simple explanation you can’t be sure for your idea.
If the implementation is easy to explain, it may be a good idea
Opposite to the previous one rule. You can really good explain your idea like. 2 x 2 , but does it mean that your idea is good ? Probably, you may be just a good story teller or have strong knowledge in your domain.
But always try to simplify your explanations ! It’s a one of the keys to success.
Namespaces are one honking great idea -- let's do more of those!
It is kind of an interesting rule formulation. Great article in help to understand this rule.
A namespace is a mapping from names to objects.A namespace is a mapping from names to objects. — Python.org
Namespace is a way to organize storage of variables in your code, modules etc. Let’s see for example.
variable = 10
print(id(variable)) # 4464290896
variable = variable + 10 # variable = 20
print(id(variable)) # 4464291216
print(id(20)) # 4464291216
It means id(variable) = id(20)
and it’s kind of nice.
But how it works ? You need to understand that namespaces are like huge dicts of mapping names to objects, and those objects have their own dicts of namespaces. It’s like module → class → function → local variables
.
Conclusion
You have seen 19 rules of Python Zen. And now you know more about Python, its philosophy and how to make your code better and become a better version of yourself in developer way. You may interpret each rule as you can, it’s a zen. We don’t know what Tim Peters really wanted to put in it, only we can ask him personally.
Good luck !
With best wishes,
Bondarenko K. , machine learning engineer