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 [ ]: