Articoli Manifesto Tools Links Canali Libri Contatti ?
Linguaggi / Python

Python: uso delle classi

Abstract
In questo secondo articolo illustreremo l'uso delle classi in python; è propedeutica la conoscenza di Java o del C++
Data di stesura: 01/10/2002
Data di pubblicazione: 05/11/2002
Ultima modifica: 04/04/2006
di Giovanni Giorgi Discuti sul forum   Stampa

GLoad

Lo script che presenteremo come esempio, si chiama gload ed è un piccolo programma in grado di testare l'affidabilità di un server web.

GLoad è in grado di inviare un numero X di richieste http quasi contemporanee. Python è un linguaggio interpretato, e non possiede un meccanismo affidabile di sincronizzazione dei thread: per questa ragione, le richieste non possono essere contemporanee e all'aumentare del numero di thread questo fenomeno è osservabile a occhio nudo.

GLoad è composto da due classi:

  • GLoad che è formata dal solo costruttore, e che coordina i vari client web
  • Client che modella un semplicissimo client http
Vediamo come scrivere queste due classi in python.

Definizione delle classi in python

In python le classi sono dichiarate con la direttiva class con una struttura del tipo:
class NomeClasseDerivata(Superclasse):
    <statement-1>
    .
    .
    .
    <statement-N>
All'interno del blocco class è possibile definire i metodi d'istanza.

I metodi sono funzioni, il cui primo parametro è sempre l'istanza della classe di cui si invoca il metodo. Alcuni metodi hanno un particolare significato per python:essi sono riconoscibili perché il loro nome inizia e termina con i caratteri '__' (doppia sottolineatura).

Ad esempio, il costruttore si chiama sempre __init__

Esempio di costruttore

Prendiamo in esame la classe GLoad: questa classe è formata dal solo costruttore, che prende in input due parametri: l'url da aprire e il numero di thread da attivare.

__init__ quindi per prima cosa istanzia un singolo client, in modo da ottenere il contenuto della pagina (che userà per condurre il test). Si noti che mentre tlist è una variabile locale al metodo __init__, self.expectedResult è una variabile d'istanza.

Analizziamo ora il codice dell'altra classe:

  1. class Client(Thread): 
  2.     """ Simulate a Web Client """ 
  3.     def __init__(self,url,silent): 
  4.         # We must call the father: 
  5.         Thread.__init__(self) 
  6.         self.url=url 
  7.         self.silent=silent 
  8.     def run(self): 
  9.         self.error=0 
  10.         self.text="" 
  11.         if not self.silent: 
  12.             print self,"[Running]" 
  13.         try: 
  14.             f = urllib.urlopen(self.url) 
  15.             self.text=f.read() 
  16.             f.close() 
  17.             if not self.silent: 
  18.                 print self,"[END]", len(self.text) 
  19.         except IOError: 
  20.             self.error=1 
  21.             if not self.silent: 
  22.                 print self,"[ERR]" 
  23.              
  24.     def isOk(self): 
  25.         if(self.error==1): 
  26.             return 0 
  27.         return 1 
  28.  
  29.     def isResultMatch(self,textExpected): 
  30.         return self.text == textExpected 
  31.  
  32.     def getResult(self): 
  33.         return self.text 

Uso dell' ereditarietà

La classe Client deriva dalla classe Thread dichiarata nel package threading i cui simboli sono tutti importati con l'istruzione from threading import *. Come in Java, la classe Client implementa un metodo run() che è chiamato da python quando il thread è fatto partire.

Il costruttore di Client si preoccupa di chiamare il metodo della superclasse Thread prima di fare qualsiasi cosa: questo garantisce che il thread sia inizializzato correttamente. Il costruttore in python si occupa anche di inizializzare e dichiarare le variabili d'istanza url e silent. Il metodo run() non fa altro che richiedere l'url e inserirne il contenuto nella variabile self.text. In caso di errore, il blocco try...except si occupa di gestire la condizione di errore. La sintassi di questo blocco è assai simile a quella usata in Java. Per i dettagli si rimanda alla documentazione di python.

Il metodo isResultMatch(stringa) è usato dalla classe GLoad per verificare che la risposta del server web sia corretta. Questo perché alcuni server, in caso di picchi di carico possono presentare una pagina di errore 500 o una pagina statica personalizzata dallo webmaster. Questi server rispondono comunque al chiamante, senza errori di I/O. Altri server invece, possono non rispondere o chiudere la socket dopo aver accettato la connessione.

Attraverso il metodo isOk() è possibile sapere se la richiesta è andata a buon fine.

Il metodo getResult() consente di ottenere il testo ritornato dalla richiesta HTTP ed è usato dalla classe GLoad per inizializzare self.expectedResult.

Differenze tra Python e gli altri linguaggi ad oggetti

Tutti i metodi in python, se fossero scritti in C++ sarebbero dichiarati virtual. Questo è vero anche per il linguaggio Java.

In C++ le funzioni virtual sono dichiarate nella classe padre e ridefinite nella classe derivata. Poi a runtime le sottoclassi implementano nel modo più appropriato i metodi virtual (si parla di pure polymorphism).

Python supporta una forma "ridotta" di ereditarietà. Se si dichiara una classe nel seguente modo:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>
La regola usata per cercare un riferimento all'attributo di classe è del tipo da sinistra-a-destra. Per cui se un attributo non è trovato in DerivedClassName, è cercato prima in Base1, poi in Base2 e infine in Base3. Questo significa che, contrariamente a quanto succede in C++ non è possibile avere un conflitto sullo scope di un metodo! Infatti in C++, se il metodo pippo() è dichiarato sia in Base1 che in Base2, il compilatore richiede di risolvere l'ambiguità usando l'operatore ::.

Information Hiding

Python non prevede il concetto di identificatore "private"; c'è però una debole convenzione nominale. Un identificatore che inizia con '__' è considerato privato. Python si occupa di "rinominare" questi identificatori. Per cui, per esempio se nella classe PGP dichiariamo __chiavePrivata la variabile è rinominata in _PGP__chiavePrivata.

Questa regola è una semplice sostituzione e funziona sia per i metodi che per le variabili d'istanza.

Il vantaggio di questo metodo è che si garantisce che queste variabili non possono essere confuse con altre ereditate dalle superclassi. Purtroppo è possibile accedere a _PGP__chiavePrivata: basta conoscerne il nome.

Questo meccanismo di protezione è tra i più deboli che esistano. In Smalltalk80, che non ha un meccanismo per dichiarare metodi privati, è però impossibile accedere alle variabili d'istanza dall'esterno. Questo perché in programmazione orientata agli oggetti, l'integrità dello stato dell'oggetto deve essere mantenuta!

Se infatti chiunque può modificare gli attributi di un oggetto senza controllo è necessario affidarsi alla disciplina dei programmatori e sperare che non si introducano bug. Questo è sicuramente uno dei punti più deboli di python.

L'arcano float

Molti si chiederanno perché abbia scritto
  1. print "Success%", ((noerrors/float(load))*100) 
invece di
  1. print "Success%", ((noerrors/load)*100) 
Purtroppo in python le variabili sono di default INTERE. Siccome noerrors e load sono intere, l'operatore di divisione NON ritorna un valore float ma l'arrotonda ad un intero! Siccome per ovvie ragioni il rapporto di cui sopra è sempre in [0,1] l'espressione risultante è del tipo (0*100) oppure (1*100). Affinchè il valore percentuale sia corretto, è necessario forzare la conversione di una delle due variabili a float, attraverso la funzione float().

Conclusioni

Python offre un buon supporto alla programmazione ad oggetti. Si osserva però che tale supporto non è marcato come in altri linguaggi come C++, Java, Ruby e Smalltalk. Per questa ragione, pur essendo possibile scrivere progetti di medie dimensioni in python, si deve riflettere accuratamente prima di considerarne l'uso in progetti con un discreto numero di sviluppatori da coordinare.

In quest'ultimo caso infatti, framework e linguaggi con concetti come interfacce, template, information hiding possono risultare più appropriati.

Concludendo, python rappresenta una buona alternativa rispetto agli script di shell o a perl (ove non siano richieste le caratteristiche peculiari di quest'ultimo).

Informazioni sull'autore

Giovanni Giorgi, classe 1974. Dopo il diploma di liceo Classico, si è laureato in Informatica nel febbraio 2000, e attualmente lavora nel campo del software finanziario (trading on line, soluzioni web).
Appassionato di linguaggi di programmazione, si interessa anche di politica e letteratura.

È possibile consultare l'elenco degli articoli scritti da Giovanni Giorgi.

Altri articoli sul tema Linguaggi / Python.

Risorse

  1. Il sorgente del codice illustrato nell'articolo.
    http://www.siforge.org/articles/2002/11/python-classes/gload.py (3Kb)
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.