Jump to content
  • #3 Introduzione a Python: OOP, Classi, Ereditarietà, Moduli


     Share

    Python è un linguaggio di programmazione multi-paradigma

    In Python il concetto di OOP segue alcuni principi base: ereditarietà, incapsulamento, polimorfismo.

     

    Classi

    Un oggetto è un insieme di dati (variabili) e metodi (funzioni) che agiscono su tali dati; ogni valore è un oggetto (object.__class__); si utilizza il costrutto classe:

    class MyNewClass:
      ''' docstring della nuova classe '''
      pass

    Una classe crea un un nuovo namespace locale in cui sono definiti tutti i suoi attributi (dati o funzioni)

    __doctring__: restituisce la docstring della classe

    pass: pass è un operazione nulla. E' utile come placeholder

    Non appena definiamo una classe, viene creato un nuovo oggetto classe con lo stesso nome, ci consente di accedere ai diversi attributi e di creare un'istanza di nuovi oggetti di quella classe

    #esempio
    class MyClass:
      "docstring della mia classe"
      x=5
      def func(self):
        print("Hello")
        
    print(MyClass.x) 	# Output: 5
    print(MyClass.func)	# Output: <function MyClass.func at 0x000..>
    print(MyClass.__doc__)	# Output: 'docstring della mia classse'


    Creare un nuovo oggetto

    nome_oggetto=nome_classe()

    I metodi di un oggetto sono le funzioni corrispondenti di quella classe

    class MyClass:
      "secondo esempio"
      x=5
      def func(self):
        print("Hello")
    
    obj = MyClass()	# create a new MyClass
    print(MyClass.func)	# Output: <function MyClass.func at 0x000..>
    print(obj.func)	# Output: <bound method MyClass.func of ...>
    obj.func()	# Calling function func() Output: Hello

     

    Self e Metodi di Istanza

    Se eseguo l'istruzione MyClass.func(): TypeError: func() missing 1 reguired positional argument: 'self'

    Invece obj.func() funziona senza argomento

    Ogni volta che un oggetto chiama il suo metodo, l'oggetto stesso viene passato come primo argomento

    obj.func() <-> MyClass.func(obj)

    Chiamare un metodo con un elenco di n argomenti equivale a chiamare la funzione corrispondente con un elenco di argomenti creato inserendo l'oggetto del metodo prima del primo argomento

    E' l'argomento implicito che rappresenta l'oggetto, per convenzione lo chiamiamo self

     

    Costruttore

    Ci sono delle funzioni di classi che iniziano con doppio underscore __

    __init__() viene chiamata ogni volta che viene istanziato un nuovo oggetto di quella classe (inizializzatore)

    class Studente:
      def __init__(self, nome, cognome):
        self.nome=nome
        self.cognome=cognome

    Per creare variabili d'istanza

    studente_uno=Studente("Mario","Rossi")
    studente_due=Studente("Luigi","Verdi")
    >>> print(studente_uno) #__main__.Studente object at 0x00..>

    Variabili di Classe vs. Variabili di Istanza

    class Dog:
      kind='canine'
      def __init__(self, name):
        self.name=name
        
    >>> d = Dog('Fido')
    >>> e = Dog('Buddy')
    >>> d.kind
    'canine'
    >>> e.kind
    'canine'
    >>> d.name
    'Fido'
    >>> e.name
    'Buddy'

    Se lo stesso nome di attributo si verifica sia in un'istanza che in una classe, la ricerca degli attributi da la priorità all'istanza:

    class Warehouse:
      purpose='storage'
      region='west'
    
    >>> w1=Warehouse()
    >>> print(w1.purpose, w1.region)
    storage west
    >>> w2=Warehouse()
    >>> w2.region='east'
    >>> print(w2.purpose, w2.region)
    storage east

     

    Variabili e metodi privati

    C'è una convenzione per indicare nomi che devono essere trattati come privati: __nome

    Il carattere di sottolineatura iniziale __ prima del nome della variabile / funzione / metodo indica che è solo per uso interno

    class A:
      def __init__(self, x, y)
        self.__private=x
        self.public=y
        
      def __prmet(self):
        return self.__private
      def pubmet(self):
        return self.public
      def ppmet(self):
        return self.__private, self.public
      
    >>> a=A("Mario","Rossi")
    >>> a.public
    'Rossi'
    >>> a.pubmet()
    'Rossi'
    >>> a.ppmet()
    ('Mario','Rossi')

    Se si desidera che la classe base abbia delle sottoclassi e che alcuni attributi non siano da loro utilizzati, utilizzare la denominazione con doppi __ iniziali

    Questo richiama l'algoritmo name mangling di Python, in cui il nome della classe è inserito nel nome dell'attributo

    Questo aiuta a evitare name collision degli attributi nel caso le sottoclassi inavvertitamente contengono attributi con lo stesso nome

    Si noti che solo il nome della classe viene utilizzato nel nome modificato, quindi se una sottoclasse sceglie sia lo stesso nome di classe che lo stesso nome di attributo, è ancora possibile ottenere collisioni di nomi.

     

    Ereditarietà (Inheritance)

    Definizione di una nuova classe con modifiche minime o nulle ad una classe esistente, la nuova classe è detta classe figlia (sottoclasse), quella da cui eredita è detta classe genitore (classe base)

    class Persona:
      def __init__(self, nome, cognome):
        self.nome=nome
        self.cognome=cognome
    
    class Stutente(Persona):
      pass
    
    class Studente(Persona):
      def __init__(self, nome, cognome, scuola):
        self.nome=nome
        self.cognome=cognome
        self.scuola=scuola

    Super()

    La funzione super permette di estendere o modificare il metodo di una classe base nella classe figlia che lo eredita

    class Studente(Persona):
      def __init__(self, nome, cognome, scuola):
        super().__init__(nome, cognome)
        self.scuola=scuola
        
    class Docente(Persona):
      def __init__(self, nome, cognome, materie=None):
        super().__init__(nome, cognome)
        if materie is None:
          self.materie=[]
        else:
          self.materie=materie

    Python ha due funzioni built-in:

    isinstance() per verificare il tipo (la classe) dell'istanza

    issubclass() per verificare l'ereditarietà

    Ritornano True o False

     

    Python supporta anche una forma di ereditarietà multipla. Una definizione di classe con più classi di base:

    class DerivedClassName(Base1, Base2, Base3):
      <statement-1>
      .
      .
      <statement-N>

    Parte da Base1 poi (ricorsivamente) nelle classi base di Base1 e, solo se non vi è stato trovato, viene ricercato in Base2, e cosi via.

     

    Overriding

    Una classe può avere un'implementazione differente di un metodo della classe genitore

    class Persona:
      def __init__(self, nome, cognome):
        self.nome=nome
        self.cognome=cognome
        def stampa(self):
          print('genitore')
    
    class Studente(Persona):
      def __init__(self, nome, cognome, scuola):
        super().__init__(nome, cognome)
        self.scuola=scuola
        def stampa(self):
          print("sottoclasse")
          
    >>> Mario=Studente("Mario","Rossi","Dibris")
    >>> Mario.stampa()	# Studente.stampa(Mario)
    sottoclasse

     

    Metodi di Classe

    I metodi degli oggetti che abbiamo visto finora vengono chiamati attraverso il nome dell'istanza della classe (oggetto), che viene passato come parametro self all'interno del metodo.

    I metodi di classe vengono invece chiamati direttamente dalla classe che viene passata come parametro cls all'interno del metodo, cls, come self, è semplice convenzione, si usa il decoratore @classmethod

     

    Generalmente questi metido servono per istanziare una nuova istanza di classe, passando dei parametri diversi rispetto a quelli richiesti dal costruttore.

    class Rectangle:
      def __init__(self, width, height):
        self.width=width
        self.height=height
      def area(self):
          return self.width * self.height
      @classmethod
      def new_square(cls, side_length):
        return cls(side_length, side_length)
    
    square = Rectangle.new_square(5)
    print(square.calculate_area())
        

    Creiamo una classe per creare un dizionario per ogni utente, ed una sottoclasse per creare un dizionario con più informazioni

    Facciamo finta che il e il cognome siano dati come string unica (es 'Paolo-Rossi')

    *args permette di passare 0 o un numero arbitrario di parametri alla funzione

    class Crea_profilo:
      def __init__(self, name, surname):
        self.name=name
        self.surname=surname
      def crea(slef):
        return {"Nome":self.name,"Cognome":self.surname}
      @classmethod
      def from_string(cls, string_persona, *args):
        nome, cognome, = string_persona.split("-")
        return cls(nome, cognome, *args)
    
    class Crea_profilo_età(Crea_profilo):
      def __init__(self, name, surname, age):
        super().__init__(name,surname)
        self.age=age
        
      def crea(self):
        return {"Nome":self.name, "Cognome":self.surname, "Età":self.age}
      #overriding
      
    >>> persona1=Crea_profilo("Mario","Rossi")
    >>> persona2=Crea_profilo.from_string("Paolo-Verdi")
    >>> persona3=Crea_profilo_età.from_string("Luigi-Giallo",54)
    >>> print(persona1.crea())
    {'Nome': 'Mario', 'Cognome': 'Rossi'}
    >>> print(persona2.crea())
    {'Nome': 'Paolo', 'Cognome': 'Verdi'}
    >>> print(persona3.crea())
    {'Nome': 'Luigi', 'Cognome': 'Giallo','Età':54}

     

    Metodi Statici

    I metodi statici si richiamano senza creare un'istanza della classe e pertanto non ricevono un parametro self

    Non possono accedere alla proprietà della classe

    Per i metodi statici si usa il decoratore @staticmethod

    class Dates:
      def __init__(self, date):
        self.date=date
      def getDate(self):
        return self.date
      @staticmethod
      def toDashDate(date):
        return date.replace("/","-")
    
    date=Dates("15-12-2016")
    dateFromDB="15/12/2016"
    dateWithDash=Dates.toDashDate(dateFromDB)
    
    if (date.getDate()==dateWithDash):
      print("Equal")
    else:
      print("Unequal")

     

    Overloading degli operatori

    Python consente di cambiare la definizione degli operatori predefiniti quando applicati a tipi definiti dall'utente

    class Punto:
      ...
      def __add__(self, AltroPunto):
        return Punto(self.x + AltroPunto.x, self.y + AltroPunto.y)
    
    >>> P1=Punto(3,4)
    >>> P2=Punto(5,7)
    >>> P3=P1+P2
    >>> print P3
    (8,11)

    L'espressione P1+P2 è equivalente a P1.__add__(P2)

     

    Dunder Methods

    Molto utili perchè garantiscono il compartimento polimorfico degli operatori

    __init__ e __adds__ sono metodi speciali

    >>> 5+5
    10
    >>> "Py"+"thon"
    'Python'
    >>> int.__add__(5,5)
    10
    >>> str.__add__("Py","thon")
    'Python'

    Tanti altri metodi speciali:

    __new__, __del__, __str__, __bool__, __sub__, __eq__ (uguale a), __ne__ (diverso da), __getitem__ (object[item]), __setitem__ (object[item]=value) ecc.

    Per ogni classe posso definirli in modo diverso

    Per maggiori info consultare la documentazione di Python!

     

    Iterators

    Per la maggior parte degli oggetti container abbiamo fatto il for-loop:

    for element in [1,2,3]:
      print(element)
    for element in (1,2,3):
      print(element)
    for key in {'one':1,'two':2}
      print(key)
    for char in "123"
      print(char)
    for line in open("myfile.txt"):
      print(line, end='')

     

    Iterator protocol

    L'istruzione for chiama iter() sull'oggetto container

    La funzione restituisce un oggetto iteratore (iterator object) che definisce il metodo __next__() che accede agli elementi nel contenitore, uno alla volta

    Quando non ci sono più elementi, __next__() solleva un'eccezione StopIteration che dice al ciclo for di terminare; funzione integrata next()

    >>> s="abc"
    >>> it=iter(s)
    >>> it
    <str_iterator object at 0x0..>
    >>> next(it)
    'a'
    >>> next(it)
    'b'
    >>> next(it)
    'c'
    >>> next(it)
    StopIteration

     

    Usare gli iteratori con le classi

    class Reverse:
      """Iterator for looping over a sequence backwards."""
      def __init__(self, data):
        self.data=data
        self.index=len(data)
      def __iter__(self):
        return self
      def __next__(self):
        if self.index==0:
          raise StopIteration
        self.index=self.index-1
        return self.data[self.index]
      
    >>> rev=Reverse('spam')
    >>> iter(rev)
    <__main__.Reverse ojbect at 0x0..>
    >>> for char in rev:
      print(char)
    m
    a
    p
    s

     

    Moduli (librerie)

    I moduli sono dei file usati per raggruppare costanti, funzioni e classi

    Ci consentono di suddividere e organizzare meglio i nostri progetti

    Python include già una lista estensiva di moduli standard (anche conosciuti come standard library)

    import nome_modulo
    #esempio
    import random

    Possiamo usare le funzioni dir() e help() per esplorare i contenuti del modulo

     

    Importare i moduli

    import modulo	#importare intero modulo
    from modulo import nome	#importare una costante o una funzione specifica
    import modulo as mio_nome	#rinominare il modulo
    >>> import random as rdm
    >>> rdm.randint(1,100)
    41
    from modulo import	#importa tutto il modulo

     

    Creare un modulo

    Qualsiasi file con estensione.py può essere sia eseguito che importato

    In Python non esiste una vera distinzione tra modulo e 'main'

    Per creare un modulo: basta scrivere un normalissimo script python quindi salvato con estensione .py

    # operazioni.py
    def somma(x,y):
      return x+y
    def sottrazione(x,y):
      rturn a-b

    Per importarlo in un altro programma: import operazioni

     

    Package

    Python offre un ulteriore livello di organizzazione: i package. Un package è una raccolta di moduli, che in genere corrisponde alla directory che li contiene

    File vuoto __init__.py

     

     Share


    User Feedback

    Recommended Comments

    There are no comments to display.


×
×
  • Create New...

Important Information

Terms of Use Privacy Policy Guidelines We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.