Python Prompt

Interactive

You can perform all operation interactively from the Python or IPython prompt. By default, it will output the result, so there is no need for some print command.


In [ ]:
# Multiplication 
2 * 3

In [ ]:
# Division (floating point - default behavior in python3)
8 / 3

In [ ]:
# integer (floor) division
8 // 3

In [ ]:
# Multiplication of strings
'abc' * 5

In [ ]:
# Power operator
3 ** 4

In [ ]:
# Modulo (remainder)
13 % 2

everything after the # symbol is a comment

Defining variables

Python variables are dynamically typed and upcasting is happening:


In [ ]:
a = 3     # int
b = 3.    # float
print(a, type(a))
print(b, type(b))

Upcasting is the mechanismn which transforms the type of a variable when needed:


In [ ]:
a = 1
print(a, type(a))

In [ ]:
a = a*0.1
print(a, type(a))

In [ ]:
c = a + b
print(c, type(c))

In [ ]:
boolean = True
print(boolean, type(boolean))

Types can also be used to explicitly cast variables.

convert an integer to a float:


In [ ]:
a = float(3)
print(a, type(a))

convert a string to an integer:


In [ ]:
a = int('3')
print(a, type(a))

convert a float to a string:


In [ ]:
a = str(3.3)
print(a, type(a))

To recover memory, all Python variables can be deleted. However this is hardly ever necessary because Python is a managed language with its own garbage collector.


In [ ]:
del(c, boolean)

In [ ]:
print(c)   # raises an exception

Some names can not be used with Python

Main Python objects

Everything in python is an object, with attributes and methods. You can access them with a . after the variable name.

Using IPython a tab will show you the list of attributes and methods of a given object:


In [ ]:
a = 1  # Even int are objects
print(type(a))

In [ ]:
print(a.real) # access the real attribute
print(a.imag) # access the imaginary attribute

In [ ]:
# define a simple function
def foo():
    """a dummy function that returns 1"""
    return 777

print(foo())

Intrisic Python List [ ]

A list in Python is a set of ordered objects. It can be created empty:


In [ ]:
l = []
print(type(l))

# another equivalent syntax for creating a list
ll = list()
print(type(ll))

and later populated:


In [ ]:
l.append('abxy')    # using the append method of python list object
print(l)

or its elements can be defined at once:


In [ ]:
l = [10, 5.,'cc','dd','ff']
print(l)

As you see, an intrinsic python list can mix different types

You can also remove the last element using the 'pop' method:


In [ ]:
last_element = l.pop() # it can also be used as l.pop() without the assignement
print(last_element)
print(l)

Accessing elements is simple, remember python is 0-indexed


In [ ]:
l[2] # Accessing the third element (python is 0-indexed)

In [ ]:
l[-2] # Accessing the second-last element

In [ ]:
l[1] = True # setting the value of the second element
print(l)

Slicing, i.e. taking a subsample of a list, it is very powerful in python. The syntax is list[start : stop : step] where start and stop are indices and step is a number of elements to jump ahead. By default, start = 0, stop = end and step = 1

NOTE: the stop argument is the index of the last element that is not included in the slice.


In [ ]:
l[2:]  # from 3rd element until the end

In [ ]:
l[:2] # from the start up to the 3rd element (not included)

In [ ]:
l[:-2] # from start to the end minus 2

In [ ]:
l[1::2] # select every other elemnt starting from the second element

In [ ]:
l[::-1] # will reverse the list (same as l[-1:-5:-1])

Concatenation of list is done using the + operand


In [ ]:
a = [1, 2]
b = [3, 4]
c = a + b + [10, 11, 4]
print(c)

Tuples ( )

A 'tuple' is a special type of object, useful to pack and unpack variables together; it is mostly used in returning arguments of functions. Tuples are immutable (i.e. they are read-only, once they are created, they can not be modified).


In [ ]:
a = (2, 'a', 10.1) # a way to simply pack ...
print(a)

In [ ]:
index, name, value = a # ... and unpack several values together
print(index)
print(name, value)

In [ ]:
a[1] = 5  # this will raise an exception since tuples are immutable

Dictionaries { }

Dictionaries are associative arrays, kind of un-ordered lists, that you can access using keys, close to IDL struct. Keys can be strings, numbers or objects.


In [ ]:
mydict = {'first': 1, 'second':[2, 3], 2: 23}
print(mydict)

In [ ]:
mydict['first'] # will access the element with key first

In [ ]:
mydict.keys() # will return the list of keys

In [ ]:
mydict.values() # will returns of the values

In [ ]:
mydict['fourth'] = 4 # will add a new key to the dictionnary
print(mydict)

dictionaries can be created also by passing arguments to dict()


In [ ]:
my_other_dict = dict(first=1, second=[2, 3], third=['a', 'b', 'c'])
print(my_other_dict)

NOTE ordered dictionaries do exist in Python. They can be found in the module collections https://docs.python.org/3/library/collections.html

Collections

dict() vs OrderedDict


In [ ]:
from collections import OrderedDict
my_ordered_dict = OrderedDict()      # create an ordered dict instance
my_ordered_dict['first'] = 1
my_ordered_dict['second'] = [2, 3]
my_ordered_dict['third'] = ['a', 'b', 'c']
print('ordered:', my_ordered_dict)
print('default', my_other_dict)

Strings " " or ' '

Strings in Python act as lists of characters.


In [ ]:
my_str = 'abc'
print(my_str[0], my_str[2])

In [ ]:
my_new_str = mystr + 'efg'
print(my_new_str)

There are two ways of including a variable into a string (more info here)

  • the % method (deprecated) [don't use it in production code]
  • the format method

The former is more concise but the latter as many more advantages. A good comparison between new and old formatting can be found here


In [ ]:
name = 'Tom'
age = 46
size = 1.783

Using the % method, you must specify the type of the variable withing the string, e.g. %s for a str, %f for a float, etc.


In [ ]:
s = "%s is %d years old and is %.2f m tall"  # for every number, the precision can be specified ; two decimals here
print(s % (name, age, size))

The .format() method is aware of the variable type and does the casting.


In [ ]:
s2 = "{} is {} years old and is {:.2f} m tall"
print(s2.format(name, age, size)) # Tuple unpacking

which is equivelant to:


In [ ]:
s2 = "{name} is {age} years old and is {size:.2f} m tall"
print(s2.format(name='Tom', size=1.783, age=46))

It can work with dictionaries keys


In [ ]:
dct = {'name': 'Tom',
       'age': 46,
       'size': 1.78}

s3 = "{name} is {age} years old and is {size:1.2f} m tall, but let's recall his age : {age}"
 
print(s3.format(**dct))  # dictionary unpacking

joining strings is easy


In [ ]:
print(' '.join(['a', 'b', 'xyz']))
print('-'.join(['a', 'b', 'xyz']))

Copying variables

All variables are objects in python and assignement only gives a reference. Thus there is a small subtility compared to other programming language:


In [ ]:
a = [1, 2, 3] # a gets its own reference (identity) in memory
b = a         # beware that you are assigning references in memory here, so b points to a content

print('{}\n{}'.format(id(a), id(b))) # actual memory references, they are the same

changing an entry in a will change the same entry in b as they are effectively the same object

official documentation for id can be found here


In [ ]:
a[2] = 4   # thus changing a...
print(a, b) # ... will change b

In [ ]:
a = a + [2, 3, 4] # Changing the full object will create a new id, 
print(a, b)

To avoid this behavior you can copy an object using the copy module (see later for explanation on module)


In [ ]:
# This will import the copy module, see later for explanation
import copy
c = copy.copy(a)
a[0] = 99
print('', a, '\n', c)

Python offer a easy was to dump and reload (serialize) variable to disk, it is provided by the pickle or cPickle module, whose usage is similar to the SAVE/RESTORE command from IDL


In [ ]:
import pickle
a, b = 1, 2.

pickle.dump((a, b), open('myfile.pickle', 'wb'))

In [ ]:
!ls *.pickle

In [ ]:
(c, d) = pickle.load(open('myfile.pickle', 'rb'))
print(c)
print(d)

Logical Operations and Loops

Logical operations


In [ ]:
a = 2
b = 4

print(a == b) # The equality test will return a boolean

In [ ]:
print(a > b, a >= b)

In [ ]:
print(a < b, a <= b)

In [ ]:
print(a != b)

You can also use the in statement to test if an element is present in a list


In [ ]:
print(1 in [1, 2, 3])
print('a' in ['a','b','c'])
print('z' in 'abc')

In [ ]:
print ('z' not in 'abc' ) # not is used to inverse the test

True vs False vs None


In [ ]:
print(True is True)

In [ ]:
print(True is False)

In [ ]:
print(True is None)

In [ ]:
print(False is None)

Ifs and Block of code

Blocks of instructions are defined by an indentation. Indentation can be made of white spaces or tabs, but you can not mix the two.

It is best pratice to use a multiple of 4 spaces by convention


In [ ]:
if a == 2:
    print('Yes') # Define a block of code with 4 withe space

Of course, several tests can be combined all at once


In [ ]:
a, b = 1, 5

if (a == 1) and (b >=4):
    print('Good')
else:
    print('Not so good')

There is no case statement in Python, but you can use the elif structure


In [ ]:
if a == 1:
    print('No')    
elif a == 2:
    print('Yes 2')
else:
    print('A lot more')

Loops

Loops can be made in Python with for or while statements


In [ ]:
i = 0
while i < 3:
    print(i)
    i += 1

A for loop in Python uses objects it can iterate on, like lists or strings, or generators.


In [ ]:
words = ['cool', 'powerful', 'readable']  # a list
for word in words:                        # the for statement use list to go through
    print("python is " + word)

In [ ]:
for char in 'abc':                        # Remember strings are list of characters
    print(char.upper())

In [ ]:
# generators
print(range(10))     # range is a generator, a python function which return a list of int.
print(range(5,10))
print(range(3,9,3))

In [ ]:
for i in range(2, 8, 2):     # range can be used with the for statement to loop over indexes
    print(i, 'Hello')

Several lists can be combined in a for loop (up to the lowest length)


In [ ]:
words = ['cool', 'powerful', 'readable']
superwords = ['fun', 'stable', 'easy']
for word1, word2 in zip(words, superwords):
    print( "Python is " + word1 + " and " + word2 )

Using the enumerate function, you can also generate an index for a given list


In [ ]:
for i, word in enumerate(words):               # you may indexes over the list as well as the items
    print("%i - python is %s" % (i, word))

Functions and Modules and Classes

Defining functions

Function are really usefull when you need to repeat a task several times. They can have two types of arguments

  • required argument, which needs to be present at the call of the function
  • optional arguments which must have a default value

You can call the function either with the values of the arguments in the declaration order, or use keyword declaration without specific order, however non-keyword arguments should always come before the keyword arguments.


In [ ]:
def do_nothing(): # simplest function ever with empty body.. doesn't do anything and returns None
    pass

In [ ]:
print(do_nothing())

In [ ]:
print(do_nothing.__name__)

In [ ]:
def disk_area(radius, pi=3.14):
    return pi*radius**2

In [ ]:
print(disk_area(2))
print(disk_area(2, pi=3.14159))
print(disk_area(pi=3.14, radius=2))

In [ ]:
arguments = {'pi': 3.14, 'radius': 2}  # Function arguments can be packed into a dictionnary
print(disk_area(**arguments))       # and unpacked at the function call

also functions are objects with attributes. You can access these attributes and even add attributes at runtime:


In [ ]:
print('The name of the function is "%s"' % disk_area.__name__)

Add an attribute to the function:


In [ ]:
# this raises an exception as the attribute 'info' does not exist
print(disk_area.info)

add the attribute info


In [ ]:
disk_area.info = 'this function computes the area of the area of a circle'
print(disk_area.info)

Function documentation a.k.a docstrings


In [ ]:
def foo2(a, b, x=None, y=2, z=[], *args, **kwargs):
    """This is the docstring of the function. There are several ways to
    document the parameters. For this function, we use the sphinx convention.
    
    :param a: This is the first positional argument. It is for ....
    :param b: This is the seconds positional arugmet. It must be a ..
    :param str x: A keyword argument for .... its default value is None
    :param int y: A keyword argument for ...
    :param iterable z: yet another keyword argument...
    :param args: arguments list
    :param kwargs: keyword arguments list
    """
    print('a = ', a)
    print('b = ', b)
    print('y = ', y)
    print('z = ', z)
    print('args = ', args)
    print('kwargs = ', kwargs)

In [ ]:
foo2?

In [ ]:
foo2(1, 2, x='table', z=['g', 'h'], k1='fff', k2='iii')

More explanation of the usage of args and kwargs can be found here

Importing existing modules

A module is a series of usefull functions or classes put together, by you or other. It is very simple to import modules into Python, in order to use the functions it provides. They are several ways to import a module. Each module provide their own namespace, i.e. a way to recognize them. It is always a good idea to keep all module functions into their own namespace to avoid conflict and allow good tracability.

For example, the default python math module provide mathematical functions. We will see later that other module provide similar functions. It is then a good idea to keep the namespace distinct to know who's who.


In [ ]:
import math                     # Import all the math module in its own namespace
print(math.sqrt(2))

In [ ]:
import math as m                # Import all the math module and name it as m
print(m.sqrt(2))

In [ ]:
from math import sqrt as mysqrt # Import only the math.sqrt function as mysqrt
print(mysqrt(2))

In [ ]:
# This should be avoided unless you know exactly what you are doing
from math import *    # Import all the math functions into the current namespace (could have name conflict)
print(sqrt(2))

simple IO

open a file and write some stuff to it


In [ ]:
fobj = open('myfoo.txt', 'w')
for word in 'i hope it is not raining outside'.split():
    fobj.write('{}\n'.format(word))
fobj.close()

In [ ]:
!cat myfoo.txt

open the file back (in read mode) and print its content


In [ ]:
with open('myfoo.txt') as fobj_read:
    for line in fobj_read:
        print(line)

the with statment safely opens and closes the file when pickling is finished. It is called a context manager (for more info click here)

Help on functions / modules


In [ ]:
import math
print(math.__doc__) # will print the docstring of the module

In [ ]:
help(math)         # will produce a nice output of the help
?math             # will do the same in IPython

In [ ]:
help(math.sqrt) # will print the help of the math.sqrt function

Writing your own modules / scripts

Each file defines a module namespace. Thus a disk.py file defines the disk module. You can see a module as a container for functions and/or variables.


In [ ]:
%%file disk.py
# When need to use a double value for pi, found in the math module
from math import pi

def area(radius=1.):              # By default the radius is 1.
    return pi*radius**2

def length(radius):
    return 2*pi*radius

In [ ]:
import disk

In [ ]:
print(disk.area()) # By default we said the radius would be 1
print(disk.area(2)) # This call the area function from the loaded disk module

In [ ]:
from disk import area as disk_area
print(disk_area(2)) # This call the disk_area function imported from the disk module

A script is a serie of more or less complex python commands that you can easily run


In [ ]:
%%file disk_script.py

import disk as mydisk
from disk import area as disk_area

print(mydisk.area(2))
print(disk_area(3))

In [ ]:
%run disk_script.py        # runs the script in IPython

In [ ]:
import disk                # This import a module
import disk_script         # Will import a script as a module and thus 
                           # run its content (on the first import only), 
                           # but this does not actually really make sense...

In [ ]:
print(disk_script.mydisk.area(2)) # and leads to syntax like that

In [ ]:
import importlib
importlib.reload(disk) # is needed if you want to re-import a modified version of an already imported module

Classes

The simplest class


In [ ]:
# define the class
class myclass():
    pass

# create an instance
foo_instance = myclass()
print(type(foo_instance))

A more useful class that defines some attributes


In [ ]:
class myclass():
    x = 1  # this is a class attribute
    y = 2  # this is another class attribute
print(myclass.x)
print(myclass.y)

An even more useful class that defines methods


In [ ]:
class myclass():
    
    x = 1            # class attribute
    y = 2            # another class attribute

    def func1():
        print('The value of x is:', myclass.x)

    def func2(self):
        print('The value of x is:', self.x)

myclass.func1()             # call the method
foo_instance = myclass()    # create an instance
myclass.x += 1              # increment the class attribute x
foo_instance.func2()        # print the incremented attribute value

The class attrubute value is shared between the instance and the class itself.

So far we have been using classes as namespaces that provide access to their own attributes and methods. Next we will define classes with controlled initialization where eacy instance has its own attributes.


In [ ]:
class myclass(object):  # inherit object

    def __init__(self):
        self.x = 1      # attribute created upon creating an instance
        self.y = 2      # another attribute created upon creating an instance
        print('created a new instance of myclass', self)

    def func1(self):
        print('The value of x is:', self.x)
    
    def increment_x(self):
        self.x += 1

In [ ]:
my_instance = myclass()  # create an instance

In [ ]:
# do a method call on the instance
my_instance.func1()

In [ ]:
my_instance.increment_x()
my_instance.func1()

In [ ]:
print('The attributes and method of the class are', dir(myclass))