Commandments
The Supreme Committee for the Doctrine of Coding has ruled important Commandments you shall follow.
If you accept their wise words, you shall become a true Python Jedi.
WARNING: if you don’t follow the Commandments, you will end up in Debugging Hell!
I COMMANDMENT
You shall write Python code
Who does not writes Python code, does not learn Python
II COMMANDMENT
Whenever you insert a variable in a for
cycle, such variables must be new
If you defined the variable before, you shall not reintroduce it in a for
, because doing so might bring confusion in the minds of the readers.
So avoid such sins:
[1]:
i = 7
for i in range(3): # sin, you lose variable i
print(i)
print(i) # prints 2 and not 7 !!
0
1
2
2
[2]:
for i in range(2):
for i in range(5): # debugging hell, you lose the i of external cycle
print(i)
print(i) # prints 4 !!
0
1
2
3
4
4
0
1
2
3
4
4
[3]:
def f(i):
for i in range(3): # sin, you lose parameter i
print(i)
print(i) # prints 2, not the 7 we passed!
f(7)
0
1
2
2
III COMMANDMENT
You shall never ever reassign function parameters
Never perform any of these assignments, as you risk losing the parameter passed during function call:
[4]:
def sin(my_int):
my_int = 666 # you lost the 5 passed from external call!
print(my_int) # prints 666
x = 5
sin(x)
666
Same reasoning can be applied to all other types:
[5]:
def evil(my_string):
my_string = "666"
[6]:
def disgrace(my_list):
my_list = [666]
[7]:
def delirium(my_dict):
my_dict = {"evil":666}
For the sole case when you have composite parameters like lists or dictionaries, you can write like below IF AND ONLY IF the function description requires to MODIFY the internal elements of the parameter (like for example sorting a list in-place or changing the field of a dictionary.
[8]:
# MODIFY my_list in some way
def allowed(my_list):
my_list[2] = 9 # OK, function text requires it
outside = [8,5,7]
allowed(outside)
print(outside)
[8, 5, 9]
[9]:
# MODIFY dictionary in some way
def ok(dictionary):
dictionary["my field"] = 5 # OK, function text requires it
[10]:
# MODIFY instance in some way
def fine(class_instance):
class_instance.my_field = 7 # OK, function text requires it
On the other hand, if the function requires to RETURN a NEW object, you shall not fall into the temptation of modifying the input:
[11]:
# RETURN a NEW sorted list
def pain(my_list):
my_list.sort() # BAD, you are modifying the input list instead of creating a new one!
return my_list
[12]:
# RETURN a NEW list
def crisis(my_list):
my_list[0] = 5 # BAD, as above
return my_list
[13]:
# RETURN a NEW dictionary
def torment(my_dict):
my_dict['a'] = 6 # BAD, you are modifying the input dictionary instead of creating a new one!
return my_dict
[14]:
# RETURN a NEW class instance
def desperation(my_instance):
my_instance.my_field = 6 # BAD, you are modifying the input object
# instead of creating a new one!
return my_instance
IV COMMANDMENT
You shall never ever reassign values to function calls or methods
WRONG:
my_function() = 666
my_function() = 'evil'
my_function() = [666]
CORRECT:
x = 5
y = my_fun()
z = []
z[0] = 7
d = dict()
d["a"] = 6
Function calls like my_function()
return calculations results and store them in a box in memory which is only created for the purposes of the call, and Python will not allow us to reuse it like it were a variabile.
Whenever you see name()
in the left part, it cannot be followed by the equality sign =
(but it can be followed by two equals sign ==
if you are doing a comparison).
V COMMANDMENT
You shall never ever redefine system functions
Python has several system defined functions. For example list
is a Python type: as such, you can use it for example as a function to convert some type to a list:
[15]:
list("ciao")
[15]:
['c', 'i', 'a', 'o']
When you allow the forces of evil to take the best of you, you might be tempted to use reserved words like list
as a variable for you own miserable purposes:
[16]:
list = ['my', 'pitiful', 'list']
Python allows you to do so, but we do not, for the consequences are disastrous.
For example, if you now attempt to use list
for its intended purpose like casting to list, it won’t work anymore:
list("ciao")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-c63add832213> in <module>()
----> 1 list("ciao")
TypeError: 'list' object is not callable
In particular, we recommend to not redefine these precious functions:
bool
,int
,float
,tuple
,str
,list
,set
,dict
max
,min
,sum
next
,iter
id
,dir
,vars
,help
VI COMMANDMENT
You shall use return
command only if you see written RETURN in function description!
If there is no return
in function description, the function is intended to return None
. In this case you don’t even need to write return None
, as Python will do it implicitly for you.
VII COMMANDMENT
You shall also write on paper!
If staring at the monitor doesn’t work, help yourself and draw a representation of the state sof the program. Tables, nodes, arrows, all can help figuring out a solution for the problem.
VIII COMMANDMENT
You shall never ever reassing self
!
Never write horrors such as this:
[17]:
class MyClass:
def my_method(self):
self = {'my_field':666} # SIN
Since self
is a kind of a dictionary, you might be tempted to write like above, but to external world it will bring no effect.
For example, let’s suppose somebody from outside makes a call like this:
[18]:
mc = MyClass()
mc.my_method()
After the call mc
will not point to {'my_field':666}
[19]:
mc
[19]:
<__main__.MyClass at 0x7f4d9423b210>
and will not have my_field
:
mc.my_field
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-26-5c4e6630908d> in <module>()
----> 1 mc.my_field
AttributeError: 'MyClass' object has no attribute 'my_field'
Following the same reasoning, you shall never reassign self
to lists or others things:
[20]:
class MyClass:
def my_method(self):
self = ['evil'] # YET ANOTHER SIN
self = 666 # NO NO NO
IX COMMANDMENT
You shall test!
Untested code does not work by definition. For ideas on how to test it, have a look at Errors and testing
X COMMANDMENT
You shall never ever add nor remove elements from a sequence you are iterating with a for
!
Falling into such temptations would produce totally unpredictable behaviours (do you know the expression pulling the rug out from under your feet ? )
Do not add, because you risk walking on a tapis roulant that never turns off:
my_list = ['a','b','c','d','e']
for el in my_list:
my_list.append(el) # YOU ARE CLOGGING COMPUTER MEMORY
Do not remove, because you risk corrupting the natural order of things:
[21]:
my_list = ['a','b','c','d','e']
for el in my_list:
my_list.remove(el) # VERY BAD IDEA
Look at the code. You think we removed eveything, uh?
[22]:
my_list
[22]:
['b', 'd']
O_o'
Do not even try to make sense of such sorcery - nobody can, because it is related to Python internal implementation.
Our version of Python gives this absurd result, yours may give another. Same applies for iteration on sets and dictionaries. You are warned.
If you really need to remove stuff from the sequence you are iterating on, use a while cycle or first make a copy of the original sequence.
[ ]: