Python Course, lesson 2

Advanced pure python

Contents:

  • Programming paradigmes in Python
    • Imperative programming
    • Structural programming
    • Functional programming
    • Object oriented programming
  • Error handling

Programming paradigmes in Python

Overview

  • Imperative programming
    • Use sequentially ordered statements to change program state
  • Structural programming
    • Structure your code in modules
  • Functional programming
    • Write you code as functions.
    • Pure functional programming: No statements only functions without side effects
  • Object- oriented programming
    • Distribute you code into classes

Programming paradigmes in Python

Imperative programming

Use sequentially ordered statements to change program state, as in iPython

Pro:

  • Fast
  • Easy for small programs

Con:

  • Chaotic for larger programs
  • Difficult to test`
  • Difficult to reuse code between programs
  • Difficult to reuse code in program -> Code duplication -> BAD!!!

Programming paradigmes in Python

Example Prime list generator

To simplify the example, 2 is ignored.


In [ ]:
n = 100
primes = []
for p in range(3,n):

In [ ]:


In [ ]:
n = 100
primes = []
for p in range(3,n):
    for x in range(2,p):
        if p%x==0:
            break
    else:
        primes.append(p)
print primes

In [ ]:
n

Programming paradigmes in Python

Structural programming

Structure your code in modules and packages

Pro:

  • Easy to reuse code between programs
  • Easier to reuse code in program

Con:

  • You need to create and switch between multiple files(modules)
  • Difficult to test`

In [ ]:


In [ ]:
import prime_module

In [ ]:
print prime_module.primes
print prime_module.__file__

Programming paradigmes in Python

Structural programming - packages

package = folder containing (possibly empty) __init__.py

Current working directory (cwd):

  • file: prime_module.py [function: "def prime_list(n)..."]
  • folder: my_package
    • file: __init__.py [empty]
    • file: prime_module.py [function: "def prime_list(n)..."]
  • folder: my_folder
    • file: prime_module.py [function: "def prime_list(n)..."]

In [ ]:
from my_package import prime_module
print prime_module.primes
print prime_module.__file__

In [ ]:
from my_folder import prime_module

Programming paradigmes in Python

Functional programming

Write you code as functions. Pure functional programming: No statements only functions without side effects

Pro:

  • Easy to reuse code between programs
  • Easy to reuse code in program
  • Very easy to test as a function given the same input always produces the same output

Con:

  • Chaotic for large programs if not structured into modules
  • No state -> results cannot be reused -> slow (depending on intelligence of compiler/interpreter)

In [ ]:
n = 100
primes = []
for p in range(3,n):
    for x in range(2,p):
        if p%x==0:
            break
    else:
        primes.append(p)
print primes

In [ ]:
def prime_list(n):
    primes = []
    for p in range(3,n):
        for x in range(2,p):
            if p%x==0:
                break
        else:
            primes.append(p)
    return primes
print prime_list(100)

In [ ]:
def prime_list(n):
    primes = []
    for p in range(3,n):
        if is_prime(p):
            primes.append(p)
    return primes

def is_prime(p):
    for x in range(2,p):
        if p%x==0:
            return False
    else:
        return True
print prime_list(100)

In [ ]:
def prime_list(n):
    return [p for p in range(3,n) if is_prime(p)]

def is_prime(p):
    return all([p%x for x in xrange(2,p)])
    
print prime_list(100)

In [ ]:
def prime_list(n):
    return [p for p in range(3,n) if all([p%x for x in xrange(2,p)])]

    
print prime_list(100)

Programming paradigmes in Python

Functional programming - functions

  • map(function, sequence) -> list (python 3: generator)
    • Apply function to each element in sequence
  • filter(function, sequence) -> list (python 3: generator)
    • Filter sequence to contain elements where function(element)==True only
  • reduce(function, sequence) -> object
    • Reduce sequence to single value by a function f(x,y) -> z, i.e. f(s[0], f(s[1], f(s[3], s[4])))
    • Ex: reduce(sum, [2,4,6]) -> sum(2, sum(4, 6)) -> sum(2,10) -> 12

In [ ]:
def prime_list(n):
    return [p for p in range(3,n) if is_prime(p)]

def is_prime(p):
    return all([p%x for x in range(2,p)])
    
print prime_list(100)

In [ ]:
def prime_list(n):
    def add(a,b):
        return a+b
    return reduce(add, filter(is_prime, range(3,n)))

def is_prime(p):
    def not_div(x):
        return p%x>0
    return all(map(not_div,range(2,p)))
    
print prime_list(100)

list vs generator

  • List
    • All elements generated at once
  • Generator (lazy list)
    • Elements generated when needed

List


In [ ]:
def my_list(n):
    l = []
    i = 0
    while i<n:
        l.append(i)
        i+=1
    return l

print my_list(10)

In [ ]:
def my_list(n):
    l = []
    for i in range(n):
        l.append(i)
    return l

print my_list(10)

In [ ]:
def my_list(n):
    return [x for x in range(n)]

print my_list(10)

In [ ]:
def my_list(n):
    return range(n)

print my_list(10)

In [ ]:
my_list = range
print my_list(10)

Generator


In [ ]:
def my_generator(n):
    i = 0
    while i<n:
        yield(i)
        i+=1
    
print my_generator(10)

In [ ]:
for i in my_generator(5):
    print i

In [ ]:
def my_generator(n):
    for in xrange(n):
        yield(i)
    
print my_generator(10)

In [ ]:
def my_generator(n):
    return (x for x in xrange(n))
print my_generator(10)

In [ ]:
my_generator = xrange
print my_generator(10)

In [ ]:
def test(range_, exp=30):
    for x in range_(2**exp):
        if x==3:
            break

In [ ]:
#test with generator
%timeit test(xrange)

In [ ]:
#test with list
%timeit test(range,30)

[x for x in ...] -> List

(x for x in ...) -> Generator

Python 2:

  • xrange: Generator
  • range: List

Python 3 (+ python 2 when using template.py)

  • xrange: Gone!!!
  • range: Generator
  • list(range(x)): List

Programming paradigmes in Python

Functional programming - Anonyous lambda functions

lambda arg_0,...,arg_n : <function body>


In [ ]:
def pow(x,e=2):
    return x**e
print pow(4)

In [ ]:
#refactor
pow = lambda x: x**2
print pow(4)

In [ ]:
#refactor
print (lambda x: x**2)(5)

In [ ]:
#example
print  map(lambda x: x**2, range(4))

Exercise: Lazy prime list generator

Modify generator_map to be a generator version of the of the built-in map function


In [ ]:
def generator_map(f,seq):
    return map(f,seq) #Modify this line

#Test
assert not isinstance(generator_map(lambda x : x**2, [1,2,3,4]), list)
for a,b in zip(generator_map(lambda x : x**2, [1,2,3,4]), [1,4,9,16]):
    assert a==b
assert a==b==16
print "Yeah!"

In order to benefit from generator_map being a generator, is_primes must return as soon as any number that is divisible by x is found.

  • Modify is_prime_any to use any instead of all
  • Note how it is tested without code duplication!!!

In [ ]:
def is_prime_all(p):
    return all(map(lambda x : p%x, xrange(2,p)))

def is_prime_any(p,map_=map):
    return all(map_(lambda x : p%x, xrange(2,p))) #Modify this line

for p in range(3,100):
    for map_ in [map, generator_map]:
        assert is_prime_all(p)==is_prime_any(p, map_)
  • Finish is_prime_generator2 to be a lambda function analougue to is_prime_generator2

In [ ]:
def prime_list(n, is_prime_func=is_prime_all):
    return map(is_prime_func, range(3,n))


def is_prime_generator1(p):
    return is_prime_any(p, generator_map)

#Your generator lambda function
is_prime_generator2 = lambda p ...



assert prime_list(100)==prime_list(100, is_prime_generator1)
assert prime_list(100)==prime_list(100, is_prime_generator2)

Speed test

  • What do you expect
  • Run test below
  • Was the result as expected
  • Try other values of n

In [ ]:
n=1000
%timeit prime_list(n)
%timeit prime_list(n, lambda p : is_prime_any(p, generator_map))

Programming paradigmes in Python

Object-oriented programming

Organize code in classes

Pro:

  • Cohession: State and method can be organized in coherrent objects
  • Encapsulation: Implementation details can be hidden for users by private fields and methods (Limited support)
  • Decoupling: Programs can be split into decoupled classes, that interacts via a simple interface (Limited support)
  • Inherritance: Extend and change existing classes (instead of copy/paste/modify = code duplication = Bad!!!)

  • Testing may be easy

Con:

  • Some overhead for simple programs

In [1]:
class Prime(object):
    def __init__(self, n, last_number):
        self.n = n
        self.last_number = last_number

    def last_prime(self):
        return self.n

    def __str__(self):
        return "I am: %2d, prime: %s,\tprev prime: %s" % (self.n, 
                                                          bool(isinstance(self, Prime)), 
                                                          self.last_number.last_prime())


class NonPrime(object):
    def __init__(self, n, last_number):
        self.n = n
        self.last_number = last_number

    def last_prime(self):
        return self.last_number.last_prime()

    def __str__(self):
        return "I am: %2d, prime: %s,\tprev prime: %s" % (self.n, 
                                                          bool(isinstance(self, Prime)), 
                                                          self.last_number.last_prime())

class Two(object):
    def __init__(self):
        self.n = 2
        self.last_number = None

    def last_prime(self):
        return self.n

    def __str__(self):
        return "I am:  2, prime: True,\tprev prime: --"




is_prime = lambda n : all([n%x for x in range(2,n)])

numbers = [Two()]

for n in range(3,13):
    if is_prime(n):
        numbers.append(Prime(n, numbers[-1]))
    else:
        numbers.append(NonPrime(n, numbers[-1]))

for n in numbers:
    print n


I am:  2, prime: True,	prev prime: --
I am:  3, prime: True,	prev prime: 2
I am:  4, prime: False,	prev prime: 3
I am:  5, prime: True,	prev prime: 3
I am:  6, prime: False,	prev prime: 5
I am:  7, prime: True,	prev prime: 5
I am:  8, prime: False,	prev prime: 7
I am:  9, prime: False,	prev prime: 7
I am: 10, prime: False,	prev prime: 7
I am: 11, prime: True,	prev prime: 7
I am: 12, prime: False,	prev prime: 11

In [2]:
#refactor
class Number(object):
    def __init__(self, n, last_number):
        self.n = n
        self.last_number = last_number

    def info(self, prev_prime):
        return "I am: %2d, prime: %s,\tprevious prime: %s" % (self.n, 
                                    bool(isinstance(self, Prime)), 
                                    prev_prime)
        
    def __str__(self):
        return self.info(self.last_number.last_prime())
    
class Prime(Number):
    def last_prime(self):
        return self.n

class NonPrime(Number):
    def last_prime(self):
        return self.last_number.last_prime()

    
class Two(Prime):
    def __init__(self):
        Prime.__init__(self, 2,None)

    def __str__(self):
        return self.info("--")



is_prime = lambda n : all([n%x for x in range(2,n)])

numbers = [Two()]

for n in range(3,13):
    if is_prime(n):
        numbers.append(Prime(n, numbers[-1]))
    else:
        numbers.append(NonPrime(n, numbers[-1]))

for n in numbers:
    print n


I am:  2, prime: True,	previous prime: --
I am:  3, prime: True,	previous prime: 2
I am:  4, prime: False,	previous prime: 3
I am:  5, prime: True,	previous prime: 3
I am:  6, prime: False,	previous prime: 5
I am:  7, prime: True,	previous prime: 5
I am:  8, prime: False,	previous prime: 7
I am:  9, prime: False,	previous prime: 7
I am: 10, prime: False,	previous prime: 7
I am: 11, prime: True,	previous prime: 7
I am: 12, prime: False,	previous prime: 11

In [ ]:

Exercise Magic number

Make a class MagicInt that satisfies the assert statements, i.e:

  • Is a int
  • Makes 2 + 2 = 5
  • Result of addition is also a MagicInt object
  • Lets len(magicInt) return the number of digits
  • Has a property prime that returns True the if it is a prime.
  • Allows the user to set the prime property manually

In [ ]:
class MagicInt(...
    
        
#Do not modify code below
two = MagicInt(2)
m127 = MagicInt(127)

# a int
assert isinstance(two, int)

# 2 + 2 = 5
assert two + two == 5
assert two + 3 == 5

#Result of addition is also a MInt object
assert isinstance(two + two, MagicInt)
assert isinstance(two + 3, MagicInt)

# len() return the number of digits
assert len(two)==1
assert len(m127) == 3
assert len(MagicInt(-127)) == 3

# property prime that returns True the if it is a prime
assert m127.prime
assert (two+m127).prime==False

# Allow the user to set the 'prime' property manually
m127.prime = False
assert m127.prime==False

print "Yeah!!! All asserts satisfied"