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.

[ ]: