Object-Oriented Programming¶

da PowerShell -- jupyter nbconvert --to html notebook.ipynb¶

Il secondo significato di OOP è programmazione con gerarchie di classi, ovvero famiglie di classi che ereditano metodi e attributi l'una dall'altra.¶

Impareremo a raggruppare (gerarchie) e fare in modo che le Classi ereditino dalle padri.¶

Class Hierarchy and Inheritance¶

Una gerarchia di classi è una famiglia di classi strettamente correlate, organizzate in modo gerarchico. Un concetto chiave è l'ereditarietà, che significa che le classi figlie possono ereditare attributi e metodi dalle classi padre. Una strategia tipica consiste nello scrivere una classe generale come classe base (o classe padre) e poi lasciare che i casi speciali siano rappresentati come sottoclassi (classi figlie). Questo approccio può spesso risparmiare molta digitazione e duplicazione del codice. Come di consueto, introduciamo l'argomento esaminando alcuni esempi.¶

Classi per linee rette e parabole¶

In [13]:
import numpy as np

class Line:                   # Classe base o Parent class
    def __init__(self, c0, c1):  
        self.c0, self.c1 = c0, c1  
    def __call__(self, x):  
        return self.c0 + self.c1*x  
    def table(self, L, R, n):  
        # Return a table with n points for L <= x <= R.  
        s = ''  
        for x in np.linspace(L, R, n):  
            y = self(x)  
            s += f'{x:12g} {y:12g}\n'  
        return s 

class Parabola(Line):                # Parabola è una sottoclasse di Line - necessita suo c2 - una sua __call__
    def __init__(self, c0, c1, c2):  # eredita sttributi e metodi - perché sia parabola dobbiamo aggiungere codice
        super().__init__(c0, c1)     # usa il costruttore di Line per salvare c0, c1  
        self.c2 = c2                 # aggiunge c2
    def __call__(self, x):  
        return super().__call__(x) + self.c2*x**2

p = Parabola(1, -2, 2)  
p1 = p(2.5)  
print(p1)  
#print(p.table(0, 1, 3)) 

"""
Class → progetto
Object → cosa concreta
Base class → classe generale (Line)
Subclass → versione specializzata (Parabola)
Inheritance → la subclass eredita dati e metodi
super() → riusa il codice della classe base
Vantaggio → meno codice, più ordine, più riuso
"""
from Line_Parabola import Line, Parabola

l = Line(-1,1)
print(isinstance(l,Line))
print(isinstance(l,Parabola))

p = Parabola(-1,0,10)
print(isinstance(p,Parabola))
print(isinstance(p,Line))
print(issubclass(Parabola,Line))
print(issubclass(Line,Parabola))
print(p.__class__ == Parabola)
print(p.__class__.__name__)
8.5
True
False
True
True
True
False
True
Parabola

Se invece Parabola è "base class" e Line è "subclass" (è una parabola con c2=0)¶

In [15]:
class Parabola:  
    def __init__(self, c0, c1, c2):  
        self.c0, self.c1, self.c2 = c0, c1, c2  
    def __call__(self, x):  
        return self.c2*x**2 + self.c1*x + self.c0  
    def table(self, L, R, n):  
        """Return a table with n points for L <= x <= R."""  
        s = ''  
        for x in linspace(L, R, n):  
            y = self(x)  
            s += '%12g %12g\n'% (x, y)  
        return s  
        
class Line(Parabola):  
    def __init__(self, c0, c1):  
        super().__init__(c0, c1, 0)

Example - Classes for Numerical Differentiation¶

In [17]:
from math import exp, sin, pi  

class Derivative:  
    def __init__(self, f, h=1E-5):  
        self.f = f  
        self.h = float(h)  
    def __call__(self, x):  
        f, h = self.f, self.h #make short forms  
        return (f(x+h) - f(x))/h

def f(x):  
    return exp(-x)*sin(4*pi*x)  
    
dfdx = Derivative(f)  
print(dfdx(1.2)) 
-3.239208844119101

However, numerous other formulas can be used for numerical differentiation, for instance¶

Codice molto ripetuto¶

In [23]:
from math import exp, sin, pi  

class Forward1:
    def __init__(self, f, h=1E-5):
        self.f, self.h = f, h

    def __call__(self, x):
        f, h = self.f, self.h
        return (f(x+h) - f(x))/h

class Central2:
    def __init__(self, f, h=1E-5):
        self.f, self.h = f, h

    def __call__(self, x):
        f, h = self.f, self.h
        return (f(x+h) - f(x-h))/(2*h)

class Central3:
    def __init__(self, f, h=1E-5):
        self.f, self.h = f, h

    def __call__(self, x):
        f, h = self.f, self.h
        return (-1/6*f(x+2*h) + f(x+h) - 1/2*f(x) - 1/3*f(x-h))/h

class Central4:
    def __init__(self, f, h=1E-5):
        self.f, self.h = f, h

    def __call__(self, x):
        f, h = self.f, self.h
        return (4./3)*(f(x+h) - f(x-h)) /(2*h) - (1./3)*(f(x+2*h) - f(x-2*h))/(4*h)

class Central6:
    def __init__(self, f, h=1E-5):
        self.f, self.h = f, h

    def __call__(self, x):
        f, h = self.f, self.h
        return (f(x+h)-f(x-h))*3/(4*h) - (f(x+2*h)-f(x-2*h))*3/(20*h) + (f(x+3*h)-f(x-3*h))/(60*h)

def f(x):  
    return exp(-x)*sin(4*pi*x)  
    
dfdx = Central2(f)  
print(dfdx(1.2)) 
dfdx = Central6(f)  
print(dfdx(1.2)) 
-3.2391005667389834
-3.2391005760525977

Codice compattato mediante eredità¶

In [9]:
from math import exp, sin, pi, cos 
import numpy as np

class Diff:
    def __init__(self, f, h=1E-5):
        self.f, self.h = f, h

class Forward1(Diff):
    def __call__(self, x):
        f, h = self.f, self.h
        return (f(x+h) - f(x))/h

class Central2(Diff):
    def __call__(self, x):
        f, h = self.f, self.h
        return (f(x+h) - f(x-h))/(2*h)

class Central3(Diff):
    def __call__(self, x):
        f, h = self.f, self.h
        return (-1/6*f(x+2*h) + f(x+h) - 1/2*f(x) - 1/3*f(x-h))/h

class Central4(Diff):
    def __call__(self, x):
        f, h = self.f, self.h
        return (4./3)*(f(x+h) - f(x-h)) /(2*h) - (1./3)*(f(x+2*h) - f(x-2*h))/(4*h)

class Central6(Diff):
    def __call__(self, x):
        f, h = self.f, self.h
        return (f(x+h)-f(x-h))*3/(4*h) - (f(x+2*h)-f(x-2*h))*3/(20*h) + (f(x+3*h)-f(x-3*h))/(60*h)

def f(x):  
    return exp(-x)*sin(4*pi*x)  

# istanziazione
d = Central6(f)
# valutazione della derivata
val = d(1.2)
#print(val)

mycos = Central4(sin) 
mycos(pi)

h = [1.0/(2**i) for i in range(5)] 
ref = cos(pi/4)
for h_ in h:
    f1 = Forward1(sin,h_); c2 = Central2(sin,h_); c6 = Central6(sin,h_) 
    e1 = abs(f1(pi/4)-ref)
    e2 = abs(c2(pi/4)-ref)
    e6 = abs(c6(pi/4)-ref)
    print(f'{h_:1.8f} {e1:1.10f} {e2:>1.10f} {e6:>1.10f}')
1.00000000 0.4371522985 0.1120969417 0.0041569044
0.50000000 0.2022210836 0.0290966823 0.0000751720
0.25000000 0.0952716617 0.0073427121 0.0000012182
0.12500000 0.0459766451 0.0018399858 0.0000000192
0.06250000 0.0225501609 0.0004602661 0.0000000003

Example - Classes for Numerical Integration¶

In [40]:
import numpy as np

class Integrator:
    def __init__(self, a, b, n):
        self.a, self.b, self.n = a, b, n
        self.points, self.weights = self.construct_method()

    def construct_method(self):
        raise NotImplementedError('no rule in class %s' % \
            self.__class__.__name__)

    def integrate(self, f):
        s = 0
        for i in range(len(self.weights)):
            s += self.weights[i]*f(self.points[i]) 
        return s

    def vectorized_integrate(self, f):
        # f must be vectorized for this to work
        return dot(self.weights, f(self.points))

class Trapezoidal(Integrator): 
    def construct_method(self):
        h = (self.b - self.a)/float(self.n - 1) 
        x = np.linspace(self.a, self.b, self.n)
        w = np.zeros(len(x))
        w[1:-1] += h
        w[0] = h/2; w[-1] = h/2
        return x, w

class Midpoint(Integrator):
    def construct_method(self):
        a, b, n = self.a, self.b, self.n # quick forms 
        h = (b-a)/float(n)
        x = np.linspace(a + 0.5*h, b - 0.5*h, n)
        w = np.zeros(len(x)) + h
        return x, w

class Simpson(Integrator):
    def construct_method(self):
        if self.n % 2 != 1:
            print("n=%d must be odd, 1 is added" % self.n)
            self.n += 1
        x = np.linspace(self.a, self.b, self.n)
        h = (self.b - self.a)/float(self.n - 1)*2 
        w = np.zeros(len(x))
        w[0:self.n:2] = h*1.0/3
        w[1:self.n-1:2] = h*2.0/3
        w[0] /= 2
        w[-1] /= 2
        return x, w

def f(x): 
    return x*x

simpson = Simpson(0, 2, 101) 
print(simpson.integrate(f))

trapez = Trapezoidal(0,2,101) 
print(trapez.integrate(f))
2.6666666666666674
2.666800000000001
In [ ]: