Generalità su "Class"¶

In [9]:
from math import exp

class Barometric:  
    def __init__(self, T):         # metodo detto "costruttore" della classe
        self.T = T #K              # tutti questi self.* sono "attributi"
        self.g = 9.81 #m/(s*s)  
        self.R = 8.314 #J/(K*mol)  
        self.M = 0.02896 #kg/mol  
        self.p0 = 100.0 #kPa  
        
    def value(self, h):            # metodo "value"
        return self.p0 * exp(-self.M*self.g*h/(self.R*self.T))

b1 = Barometric(T=245)             # una istanza di Barometric (oggetto)
p1 = b1.value(2469)                # metodo per calcolare la formula
print(p1)
70.86738432067067
In [21]:
from math import sin, exp, pi
from numpy import linspace
n = 3

def make_table(f,tstop,n):
    for t in linspace(0,tstop,n):
        print(t,f(t))

def g(t):
    return sin(t)*exp(-t)

class Barometric:  
    def __init__(self, T):         # metodo detto "costruttore" della classe
        self.T = T #K              # tutti questi self.* sono "attributi"
        self.g = 9.81 #m/(s*s)  
        self.R = 8.314 #J/(K*mol)  
        self.M = 0.02896 #kg/mol  
        self.p0 = 100.0 #kPa  
        
    def value(self, h):            # metodo "value"
        return self.p0 * exp(-self.M*self.g*h/(self.R*self.T))

b1 = Barometric(T=245)             # una istanza di Barometric (oggetto)
p1 = b1.value(2469)                # metodo per calcolare la formula
make_table(g,2*pi,n)               # invio di una funzione ordinaria

make_table(b1.value,2*pi,n)        # invio di un metodo di classe
0.0 0.0
3.141592653589793 5.292178668034404e-18
6.283185307179586 -4.573915527954357e-19
0.0 100.0
3.141592653589793 99.95619273000563
6.283185307179586 99.91240465078029

Classi di Python più generali¶

Gli attributi possono essere definiti ovunque, anche fuori della Classe¶

In [ ]:
m = MyCalss(p1,p2,...)
m.new_attr = p3

Protected Class Attributes¶

Per un esempio informatico più classico di una classe Python, consideriamo una classe che rappresenta un conto bancario. Gli attributi naturali di una classe di questo tipo saranno il nome del titolare, il numero di conto e il saldo, e possiamo includere metodi per depositi, prelievi e stampa di informazioni sul conto.¶

In [40]:
class BankAccount:  
    def __init__(self, first_name, last_name, number, balance):  
        self._first_name = first_name  # _first_name è una convenzione x dire dati riservati
        self._last_name = last_name    # da non essere usati al di fuori della Classe
        self._number = number  
        self._balance = balance     # saldo
        
    def deposit(self, amount):     # versamento 
        self._balance += amount  
        
    def withdraw(self, amount):    # prelievo
        self._balance -= amount  
        
    def print_info(self):  
        first = self._first_name; 
        last = self._last_name  
        number = self._number; 
        bal = self._balance  
        s = f'{first} {last}, {number}, balance: {bal}'  
        print(s) 

a1 = BankAccount('John', 'Olsson', '19371564761', 20000)
a2 = BankAccount('Liz', 'Olsson', '19371564761', 20000)
a1.deposit(1000)
a1.withdraw(4000)
a2.withdraw(10500)
a1.withdraw(3500)
print("a1’s balance:", a1._balance)
a1.print_info()
a2.print_info()
a1’s balance: 13500
John Olsson, 19371564761, balance: 13500
Liz Olsson, 19371564761, balance: 9500

Special Methods¶

In [1]:
from math import exp
   
class Barometric:  
    def __init__(self, T):         # metodo detto "costruttore" della classe, è un metodo speciale
        self.T = T #K              # tutti questi self.* sono "attributi"
        self.g = 9.81 #m/(s*s)  
        self.R = 8.314 #J/(K*mol)  
        self.M = 0.02896 #kg/mol  
        self.p0 = 100.0 #kPa  
        
    def __call__(self, h):         # è un metodo speciale
        return self.p0 * exp(-self.M*self.g*h/(self.R*self.T))

baro = Barometric(245)             # una istanza di Barometric
p = baro(2469)                     # posso calcolare col formalismo di una funzione
print(p)
70.86738432067067

Special Methods for Printing¶

Facciamo costruire dalla Classe un stringa da stampare -- metodo speciale "str"¶

In [15]:
from math import exp
   
class Barometric:  
    def __init__(self, T):         # metodo detto "costruttore" della classe, è un metodo speciale
        self.T = T #K              # tutti questi self.* sono "attributi"
        self.g = 9.81 #m/(s*s)  
        self.R = 8.314 #J/(K*mol)  
        self.M = 0.02896 #kg/mol  
        self.p0 = 100.0 #kPa  
        
    def __call__(self, h):         # è un metodo speciale
        return self.p0 * exp(-self.M*self.g*h/(self.R*self.T))

    def __str__(self):             # è un metodo speciale per costruire stringa comprensibile
        return f'p0 * exp(-M*g*h/(R*T)); T = {self.T}'

b = Barometric(245)             # una istanza di Barometric
b(2469)                         # posso calcolare col formalismo di una funzione e stampare
print(b)
p0 * exp(-M*g*h/(R*T)); T = 245

Special Methods for Mathematical Operations¶

In [ ]:
c = a+b #c = a.__add__(b)  -- a, b istanze della Classe
c = a-b #c = a.__sub__(b)  
c = a*b #c = a.__mul__(b) 
c = a/b #c = a.__div__(b)  

c = a**e #c = a.__pow__(e)

# These methods can return an object of the same type as the operands. 
# Similarly, there are special methods for comparing objects,as follows:  
a == b #a.__eq__(b)  
a != b #a.__ne__(b)  
a < b  #a.__lt__(b)  
a <= b #a.__le__(b)  
a > b  #a.__gt__(b)  
a >= b #a.__ge__(b)
In [160]:
from math import exp

class Barometric:  
    def __init__(self, T):         # metodo detto "costruttore" della classe, è un metodo speciale
        self.T = T #K              # tutti questi self.* sono "attributi"
        self.g = 9.81 #m/(s*s)  
        self.R = 8.314 #J/(K*mol)  
        self.M = 0.02896 #kg/mol  
        self.p0 = 100.0 #kPa  
        
    def __call__(self, h):         # è un metodo speciale
        return self.p0 * exp(-self.M*self.g*h/(self.R*self.T))

    def __str__(self):  
        return f'p0 * exp(-M*g*h/(R*T)); T = {self.T}'

    def __repr__(self):
        # Return code for regenerating this instance.
        return f'Barometric({self.T})'

b = Barometric(271)
print(b)
print(repr(b))
b2 = eval(repr(b))      # repr(b) è un oggetto = repr(b2)
print(b2)
repr(b) == repr(b2)
p0 * exp(-M*g*h/(R*T)); T = 271
Barometric(271)
p0 * exp(-M*g*h/(R*T)); T = 271
Out[160]:
True
In [46]:
class A:  
    def __init__(self, value):
        self.v = value
a = A(2)
#dir(a)
print(a.__doc__)
print(b.__dict__)        # contiene tutti gli attributi - è un Dizionario
print(a.v)
print(a.__module__)
None
{'T': 271, 'g': 9.81, 'R': 8.314, 'M': 0.02896, 'p0': 100.0}
2
__main__

Automatic Differentiation of Functions¶

In [61]:
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 x**3

dfdx = Derivative(f)
print(dfdx(2))

from math import *
df = Derivative(sin)
x = pi
print(df(x))
12.000060000261213
-0.9999999999898844

Test Functions for Classes¶

In [114]:
def test_Derivative():  
    #The formula is exact for linear functions, regardless of h  
    f = lambda x: a*x + b  
    a = 3.5; b = 8  
    dfdx = Derivative(f, h=0.5)   # la derivata non contiene x -- sempre verificata 
    diff = abs(dfdx(4.5) - a)
    assert diff < 1E-14, 'bug in class Derivative, diff=%s'% diff

print(test_Derivative())
0.0
None
In [122]:
def test_Derivative_quadratic():
    f = lambda x: x**2 + 3*x + 1
    df_exact = lambda x: 2*x + 3

    dfdx = Derivative(f, h=1e-6)    # se h=1e-5 fallisce e python solleva una eccezione (tipo errore, ma non lo è)
    x0 = 4.5

    diff = abs(dfdx(x0) - df_exact(x0))
    assert diff < 1e-6, f"Errore troppo grande: {diff}"

test_Derivative_quadratic()

Example - A Polynomial Class¶

In [166]:
class Polynomial:
    def __init__(self, coefficients):
        self.coeff = coefficients

    def __call__(self, x):
        return sum(a * x**i for i, a in enumerate(self.coeff))

    def __add__(self, other):                    # somma di polinomi
        if not isinstance(other, Polynomial):
            return NotImplemented

        n = max(len(self.coeff), len(other.coeff))
        new_coeff = [0] * n

        for i in range(n):
            a = self.coeff[i] if i < len(self.coeff) else 0
            b = other.coeff[i] if i < len(other.coeff) else 0
            new_coeff[i] = a + b

        return Polynomial(new_coeff)

    def __mul__(self, other):                    # moltiplicazione di polinomi
        if not isinstance(other, Polynomial):
            return NotImplemented

        n = len(self.coeff)
        m = len(other.coeff)
        new_coeff = [0] * (n + m - 1)

        for i in range(n):
            for j in range(m):
                new_coeff[i + j] += self.coeff[i] * other.coeff[j]

        return Polynomial(new_coeff)

    def differentiate(self):                    # derivata del polinomio
        if len(self.coeff) <= 1:
            return Polynomial([0])

        new_coeff = [
            i * self.coeff[i]
            for i in range(1, len(self.coeff))
        ]
        return Polynomial(new_coeff)

    def __str__(self):                          # scrittura come String visualizzabile
        terms = []
        for i, a in enumerate(self.coeff):
            if a == 0:
                continue

            sign = "-" if a < 0 else "+"
            a = abs(a)

            if i == 0:
                term = f"{a}"
            elif i == 1:
                term = "x" if a == 1 else f"{a}x"
            else:
                term = f"x^{i}" if a == 1 else f"{a}x^{i}"

            terms.append((sign, term))

        if not terms:
            return "0"

        first_sign, first_term = terms[0]
        s = first_term if first_sign == "+" else f"-{first_term}"

        for sign, term in terms[1:]:
            s += f" {sign} {term}"

        return s


p1 = Polynomial([1,-1])
print(p1)
p2 = Polynomial([0, 1, 0, 0, -6, -1])
print(p2)

p3 = p1 + p2
print(p3) 

p4 = p1*p2
print(p4) 

dp2 = p2.differentiate()
#print(dp2.coeff)
print(dp2)
1 - x
x - 6x^4 - x^5
1 - 6x^4 - x^5
x - x^2 - 6x^4 + 5x^5 + x^6
1 - 24x^3 - 5x^4
In [ ]: