Python 3 - Orientato agli oggetti

Python è stato un linguaggio orientato agli oggetti sin dal tempo in cui esisteva. Per questo motivo, creare e usare classi e oggetti è decisamente facile. Questo capitolo ti aiuta a diventare un esperto nell'uso del supporto per la programmazione orientata agli oggetti di Python.

Se non hai alcuna esperienza precedente con la programmazione orientata agli oggetti (OO), potresti consultare un corso introduttivo su di esso o almeno un tutorial di qualche tipo in modo da avere una comprensione dei concetti di base.

Tuttavia, ecco una piccola introduzione della programmazione orientata agli oggetti (OOP) per aiutarti:

Panoramica della terminologia OOP

  • Class- Un prototipo definito dall'utente per un oggetto che definisce un insieme di attributi che caratterizzano qualsiasi oggetto della classe. Gli attributi sono membri di dati (variabili di classe e variabili di istanza) e metodi, accessibili tramite notazione a punti.

  • Class variable- Una variabile condivisa da tutte le istanze di una classe. Le variabili di classe sono definite all'interno di una classe ma al di fuori di qualsiasi metodo della classe. Le variabili di classe non vengono utilizzate così frequentemente come le variabili di istanza.

  • Data member - Una variabile di classe o una variabile di istanza che contiene i dati associati a una classe e ai suoi oggetti.

  • Function overloading- L'assegnazione di più di un comportamento a una particolare funzione. L'operazione eseguita varia in base ai tipi di oggetti o argomenti coinvolti.

  • Instance variable - Una variabile definita all'interno di un metodo e che appartiene solo all'istanza corrente di una classe.

  • Inheritance - Il trasferimento delle caratteristiche di una classe ad altre classi che ne derivano.

  • Instance- Un oggetto individuale di una certa classe. Un oggetto obj che appartiene a una classe Circle, ad esempio, è un'istanza della classe Circle.

  • Instantiation - La creazione di un'istanza di una classe.

  • Method - Un tipo speciale di funzione definita in una definizione di classe.

  • Object- Un'istanza univoca di una struttura dati definita dalla sua classe. Un oggetto comprende sia membri di dati (variabili di classe e variabili di istanza) che metodi.

  • Operator overloading - L'assegnazione di più di una funzione a un particolare operatore.

Creazione di classi

L' istruzione class crea una nuova definizione di classe. Il nome della classe segue immediatamente la parola chiave class seguita da due punti come segue:

class ClassName:
   'Optional class documentation string'
   class_suite
  • La classe ha una stringa di documentazione, a cui è possibile accedere tramite ClassName.__doc__.

  • Il class_suite consiste di tutte le istruzioni dei componenti che definiscono i membri della classe, gli attributi dei dati e le funzioni.

Esempio

Di seguito è riportato un esempio di una semplice classe Python:

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)
  • La variabile empCount è una variabile di classe il cui valore è condiviso tra tutte le istanze di a in questa classe. È possibile accedervi come Employee.empCount dall'interno della classe o dall'esterno della classe.

  • Il primo metodo __init __ () è un metodo speciale, chiamato costruttore di classi o metodo di inizializzazione che Python chiama quando crei una nuova istanza di questa classe.

  • Dichiari altri metodi di classe come funzioni normali con l'eccezione che il primo argomento di ogni metodo è self . Python aggiunge l' argomento self alla lista per te; non è necessario includerlo quando si chiamano i metodi.

Creazione di oggetti istanza

Per creare istanze di una classe, chiamate la classe utilizzando il nome della classe e passate qualsiasi argomento accetti il suo metodo __init__ .

This would create first object of Employee class
emp1 = Employee("Zara", 2000)
This would create second object of Employee class
emp2 = Employee("Manni", 5000)

Accesso agli attributi

Si accede agli attributi dell'oggetto utilizzando l'operatore punto con oggetto. È possibile accedere alla variabile di classe utilizzando il nome della classe come segue:

emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Ora, mettendo insieme tutti i concetti -

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)


#This would create first object of Employee class"
emp1 = Employee("Zara", 2000)
#This would create second object of Employee class"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Quando il codice sopra viene eseguito, produce il seguente risultato:

Name :  Zara ,Salary:  2000
Name :  Manni ,Salary:  5000
Total Employee 2

Puoi aggiungere, rimuovere o modificare attributi di classi e oggetti in qualsiasi momento:

emp1.salary = 7000  # Add an 'salary' attribute.
emp1.name = 'xyz'  # Modify 'age' attribute.
del emp1.salary  # Delete 'age' attribute.

Invece di utilizzare le normali istruzioni per accedere agli attributi, è possibile utilizzare le seguenti funzioni:

  • Il getattr(obj, name[, default]) - per accedere all'attributo dell'oggetto.

  • Il hasattr(obj,name) - per verificare se un attributo esiste o meno.

  • Il setattr(obj,name,value)- per impostare un attributo. Se l'attributo non esiste, verrebbe creato.

  • Il delattr(obj, name) - per eliminare un attributo.

hasattr(emp1, 'salary')    # Returns true if 'salary' attribute exists
getattr(emp1, 'salary')    # Returns value of 'salary' attribute
setattr(emp1, 'salary', 7000) # Set attribute 'salary' at 7000
delattr(emp1, 'salary')    # Delete attribute 'salary'

Attributi di classe incorporati

Ogni classe Python continua a seguire gli attributi incorporati ed è possibile accedervi utilizzando l'operatore punto come qualsiasi altro attributo -

  • __dict__ - Dizionario contenente lo spazio dei nomi della classe.

  • __doc__ - Stringa di documentazione della classe o nessuna, se non definita.

  • __name__ - Nome della classe.

  • __module__- Nome del modulo in cui è definita la classe. Questo attributo è "__main__" in modalità interattiva.

  • __bases__ - Una tupla possibilmente vuota contenente le classi di base, nell'ordine in cui sono presenti nell'elenco delle classi di base.

Per la classe precedente proviamo ad accedere a tutti questi attributi:

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)

emp1 = Employee("Zara", 2000)
emp2 = Employee("Manni", 5000)
print ("Employee.__doc__:", Employee.__doc__)
print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__ )

Quando il codice sopra viene eseguito, produce il seguente risultato:

Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {
   'displayCount': <function Employee.displayCount at 0x0160D2B8>, 
   '__module__': '__main__', '__doc__': 'Common base class for all employees', 
   'empCount': 2, '__init__': 
   <function Employee.__init__ at 0x0124F810>, 'displayEmployee': 
   <function Employee.displayEmployee at 0x0160D300>,
   '__weakref__': 
   <attribute '__weakref__' of 'Employee' objects>, '__dict__': 
   <attribute '__dict__' of 'Employee' objects>
}

Distruzione di oggetti (raccolta dei rifiuti)

Python elimina automaticamente gli oggetti non necessari (tipi incorporati o istanze di classe) per liberare lo spazio di memoria. Il processo mediante il quale Python recupera periodicamente i blocchi di memoria che non sono più in uso è definito Garbage Collection.

Il garbage collector di Python viene eseguito durante l'esecuzione del programma e viene attivato quando il conteggio dei riferimenti di un oggetto raggiunge lo zero. Il conteggio dei riferimenti di un oggetto cambia al variare del numero di alias che puntano a esso.

Il conteggio dei riferimenti a un oggetto aumenta quando gli viene assegnato un nuovo nome o viene inserito in un contenitore (elenco, tupla o dizionario). Il conteggio dei riferimenti dell'oggetto diminuisce quando viene eliminato con del , il suo riferimento viene riassegnato o il suo riferimento esce dall'ambito. Quando il conteggio dei riferimenti di un oggetto raggiunge lo zero, Python lo raccoglie automaticamente.

a = 40      # Create object <40>
b = a       # Increase ref. count  of <40> 
c = [b]     # Increase ref. count  of <40> 

del a       # Decrease ref. count  of <40>
b = 100     # Decrease ref. count  of <40> 
c[0] = -1   # Decrease ref. count  of <40>

Normalmente non noterai quando il garbage collector distrugge un'istanza orfana e ne recupera lo spazio. Tuttavia, una classe può implementare il metodo speciale __del __ () , chiamato distruttore, che viene richiamato quando l'istanza sta per essere distrutta. Questo metodo può essere utilizzato per pulire le risorse non di memoria utilizzate da un'istanza.

Esempio

Questo distruttore __del __ () stampa il nome della classe di un'istanza che sta per essere distrutta -

#!/usr/bin/python3

class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   def __del__(self):
      class_name = self.__class__.__name__
      print (class_name, "destroyed")

pt1 = Point()
pt2 = pt1
pt3 = pt1
print (id(pt1), id(pt2), id(pt3))   # prints the ids of the obejcts
del pt1
del pt2
del pt3

Quando il codice sopra viene eseguito, produce il seguente risultato:

140338326963984 140338326963984 140338326963984
Point destroyed

Note- Idealmente, dovresti definire le tue classi in un file separato, quindi dovresti importarle nel tuo file di programma principale usando l' istruzione import .

Nell'esempio precedente, supponendo che la definizione di una classe Point sia contenuta in point.py e non ci sia altro codice eseguibile in esso.

#!/usr/bin/python3
import point

p1 = point.Point()

Eredità di classe

Invece di iniziare da zero, puoi creare una classe derivandola da una classe preesistente elencando la classe genitore tra parentesi dopo il nome della nuova classe.

La classe figlia eredita gli attributi della sua classe genitore e puoi usare quegli attributi come se fossero definiti nella classe figlia. Una classe figlia può anche sovrascrivere membri di dati e metodi dal genitore.

Sintassi

Le classi derivate sono dichiarate in modo molto simile alla loro classe genitore; tuttavia, dopo il nome della classe viene fornito un elenco di classi base da cui ereditare:

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Optional class documentation string'
   class_suite

Esempio

#!/usr/bin/python3

class Parent:        # define parent class
   parentAttr = 100
   def __init__(self):
      print ("Calling parent constructor")

   def parentMethod(self):
      print ('Calling parent method')

   def setAttr(self, attr):
      Parent.parentAttr = attr

   def getAttr(self):
      print ("Parent attribute :", Parent.parentAttr)

class Child(Parent): # define child class
   def __init__(self):
      print ("Calling child constructor")

   def childMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.childMethod()      # child calls its method
c.parentMethod()     # calls parent's method
c.setAttr(200)       # again call parent's method
c.getAttr()          # again call parent's method

Quando il codice sopra viene eseguito, produce il seguente risultato:

Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200

In modo simile, puoi guidare una classe da più classi genitore come segue:

class A:        # define your class A
.....

class B:         # define your calss B
.....

class C(A, B):   # subclass of A and B
.....

È possibile utilizzare le funzioni issubclass () o isinstance () per controllare le relazioni di due classi e istanze.

  • Il issubclass(sub, sup) La funzione booleana restituisce True, se la sottoclasse data sub è effettivamente una sottoclasse della superclasse sup.

  • Il isinstance(obj, Class)La funzione booleana restituisce True, se obj è un'istanza della classe Class o è un'istanza di una sottoclasse di Class

Metodi di sostituzione

Puoi sempre sovrascrivere i metodi della tua classe genitore. Uno dei motivi per sovrascrivere i metodi del genitore è che potresti volere funzionalità speciali o diverse nella tua sottoclasse.

Esempio

#!/usr/bin/python3

class Parent:        # define parent class
   def myMethod(self):
      print ('Calling parent method')

class Child(Parent): # define child class
   def myMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.myMethod()         # child calls overridden method

Quando il codice sopra viene eseguito, produce il seguente risultato:

Calling child method

Metodi di sovraccarico di base

La tabella seguente elenca alcune funzionalità generiche che puoi sovrascrivere nelle tue classi:

Sr.No. Metodo, descrizione e chiamata di esempio
1

__init__ ( self [,args...] )

Costruttore (con eventuali argomenti opzionali)

Chiamata di esempio: obj = className (args)

2

__del__( self )

Distruttore, elimina un oggetto

Chiamata di esempio: del obj

3

__repr__( self )

Rappresentazione di stringa valutabile

Chiamata di esempio: repr (obj)

4

__str__( self )

Rappresentazione di stringa stampabile

Chiamata di esempio: str (obj)

5

__cmp__ ( self, x )

Confronto di oggetti

Chiamata di esempio: cmp (obj, x)

Operatori di sovraccarico

Supponiamo di aver creato una classe Vector per rappresentare i vettori bidimensionali. Cosa succede quando usi l'operatore più per aggiungerli? Molto probabilmente Python ti sgriderà.

Tuttavia, potresti definire il metodo __add__ nella tua classe per eseguire l'addizione vettoriale e quindi l'operatore più si comporterebbe come previsto -

Esempio

#!/usr/bin/python3

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

Quando il codice sopra viene eseguito, produce il seguente risultato:

Vector(7,8)

Dati nascosti

Gli attributi di un oggetto possono o meno essere visibili al di fuori della definizione della classe. È necessario denominare gli attributi con un doppio carattere di sottolineatura e quegli attributi non saranno quindi direttamente visibili agli estranei.

Esempio

#!/usr/bin/python3

class JustCounter:
   __secretCount = 0
  
   def count(self):
      self.__secretCount += 1
      print (self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print (counter.__secretCount)

Quando il codice sopra viene eseguito, produce il seguente risultato:

1
2
Traceback (most recent call last):
   File "test.py", line 12, in <module>
      print counter.__secretCount
AttributeError: JustCounter instance has no attribute '__secretCount'

Python protegge questi membri modificando internamente il nome per includere il nome della classe. È possibile accedere ad attributi come object._className__attrName . Se sostituisci l'ultima riga come segue, allora funziona per te -

.........................
print (counter._JustCounter__secretCount)

Quando il codice sopra viene eseguito, produce il seguente risultato:

1
2
2