Le classi: slot e controllo dell'input

Introduzione

A volte, per questioni di performance e di risparmio di risorse, si può desiderare di non utilizzare un dizionario per immagazzinare gli attributi di un oggetto, bensì un insieme di campi predefiniti, detti slot.

Se nella definizione della classe scriviamo un'istruzione come la seguente, definiamo l'elenco degli slot. Non sarà possibile, durante l'esecuzione, aggiungere altri attributi (nell'esempio che segue, l'elenco è una tupla, non una lista o un dizionario, e pertanto è immutabile; ma la regola varrebbe comunque).

__slots__ = ('filename', 'width', 'height', 'filetype', 'quality')

Già che ci siamo, vedremo nell'esempio che segue che è possibile definire anche i tipi associati a ciascuno slot, in modo da poter gestire un input controllato. Inoltre, visto che la tupla di slot è iterabile, possiamo definire una funzione che prende in considerazione tutti i campi per fare l'input dei dati di un oggetto.

La classe Picture

Nel file picture.py definiamo la nostra classe per gestire un'immagine.

from basic_io import *

class Picture():
    __slots__ = ('filename', 'width', 'height', 'filetype', 'quality')
    # __slots__ è una parola chiave che definisce l'elenco degli attributi 
    # di una classe in modo vincolante

    __attributeTypes = {
        'filename': str,
        'width': int,
        'height': int,
        'filetype': str,
        'quality': float
        }
    # __attribuiteTypes è un dizionario privato in cui memorizzo i tipi
    # associati a ciascun attributo

    __attributeLabels = {
        'filename': 'nome del file',
        'width': 'larghezza',
        'height': 'altezza',
        'filetype': 'tipo di file',
        'quality': 'qualità'
        }
    # __attributeLabels è un dizionario privato in cui memorizzo i tipi
    # associati a ciascun attributo

    # costruttore
    def __init__(self, filename='', width=320, height=240, filetype='JPEG', quality=1.0):
        self.filename = filename
        self.width = width
        self.height = height
        self.filetype = filetype
        self.quality = quality

    def getCompleteDescription(self):
        """Return a complete description of the picture as a string
        filled with all attributes' names and values.
        """
        fields=[]
        for attrName in self.__slots__:
            DisplayName = str(self.__attributeLabels[attrName])
            AttributeValue= str(getattr(self, attrName))
            # il metodo getattr serve per accedere al valore di un
            # attributo avendo il suo nome
            # getattr(self, 'width') corrisponde a self.width
            fields.append('%s: %s' % (DisplayName, AttributeValue))
        return ', '.join(fields)
        # il metodo join applicato ad una stringa fa sì che la stringa
        # venga usata come "collante" per unire tutti gli elementi della
        # lista che ha come argomento

    def __setattr__(self, name, value):
        assert(isinstance(value, self.__attributeTypes[name]))
        super().__setattr__(name, value)

    def inputFields(self):
        print("*** INSERIMENTO DATI PER UNA IMMAGINE ***")
        for field in self.__slots__:
            self.inputField(field)

    def inputField(self, field):
        datatype = self.__attributeTypes[field]
        message = 'Inserisci un valore per "%s": ' % self.__attributeLabels[field]
        data=checked_input(message, datatype)
        self.__setattr__(field, data)

    def showFields(self):
        print("*** VISUALIZZAZIONE DATI PER UNA IMMAGINE ***")
        for attrName in self.__slots__:
            self.showField(attrName)

    def showField(self, attrName):
        '''Prints out a list of field names and values'''
        print('%s --> %s' % (
            self.__attributeLabels[attrName],
            str(getattr(self, attrName))
            ))


if __name__=="__main__":
    img1=Picture()
    img1.inputFields()
    print(img1.getCompleteDescription())
    img1.showFields()

La funzione checked_input

Nel file si importa il modulo basic_io, che contiene il codice seguente (piccola variante rispetto a quello presentato precedentemente, che consentiva l'input controllato di un numero, e che qui invece è generica ed effettua la verifica per qualsiasi classe, tentando la conversione):

def checked_input(message, constraint_class):
    """Permette l'inserimento di un valore in maniera controllata.

    Chiede in input una stringa usando *message* come prompt, poi
    tenta la conversione nella classe *constraint_class* e
    restituisce il valore convertito.
    Se viene inserito un valore non valido, viene richiesto di
    nuovo l'inserimento.
    """
    while(True):
        v=input(message)
        try:
            n = constraint_class(v)
            return n
        except ValueError as err:
            print("Sembra che tu non abbia inserito un valore valido…")

La classe PictureBox

A questo punto, se vogliamo gestire un elenco di immagini, possiamo pensare di metterle in una lista, o meglio ancora di creare una nostra versione personalizzata di lista (classe derivata). Vediamo un esempio, nel quale, già che c'eravamo, abbiamo ridefinito i metodi append e insert per controllare che i valori aggiunti alla lista siano di tipo Picture, e il metodo __setitem__ per far sì che un elemento possa essere cambiato o con un altro elemento di tipo Picture oppure con None:

from Picture import *

class PictureBox(list):
    def showPictures(self):
        for i in range(len(self)):
            # NB len(self) ha senso in questo caso, visto che self è una lista
            if isinstance(self[i], Picture):
                # controlliamo, potrebbe essere None
                print(self[i].getCompleteDescription())

    def append(self, item):
        # ridefinizione del metodo append (se si vuole essere sicuri che non
        # si possano aggiungere elementi non di tipo Picture)
        assert(isinstance(item, Picture))
        super().append(item) # uso il metodo della classe padre

    def insert(self, position, item):
        # ridefinizione del metodo insert (se si vuole essere sicuri che non
        # si possano aggiungere elementi non di tipo Picture)
        assert(isinstance(item, Picture))
        super().insert(position, item) # uso il metodo della classe padre       

    def __setitem__(self, key, value):
        # ridefinizione del metodo __setitem__ (se si vuole essere sicuri che non
        # si possano cambiare elementi con oggetti non di tipo Picture, a meno
        # che non li si imposti a None, che invece consideriamo cosa lecita)
        assert(value is None or isinstance(value, Picture))
        super().__setitem__(key, value) # uso il metodo della classe padre


if __name__=='__main__':
    mybox=PictureBox()

    n = checked_input("Quante immagini vuoi inserire? ", int)

    for i in range(n):
        pic=Picture()
        pic.inputFields()
        mybox.append(pic)   

    mybox.showPictures()

Un esempio di interazione con l'utente potrebbe essere questo:

Quante immagini vuoi inserire? 3
*** INSERIMENTO DATI PER UNA IMMAGINE ***
Inserisci un valore per "nome del file": gatto.jpg
Inserisci un valore per "larghezza": 320
Inserisci un valore per "altezza": 240
Inserisci un valore per "tipo di file": JPEG
Inserisci un valore per "qualità": 0.8
*** INSERIMENTO DATI PER UNA IMMAGINE ***
Inserisci un valore per "nome del file": cane.jpg
Inserisci un valore per "larghezza": 800
Inserisci un valore per "altezza": 600
Inserisci un valore per "tipo di file": JPEG
Inserisci un valore per "qualità": 0.7
*** INSERIMENTO DATI PER UNA IMMAGINE ***
Inserisci un valore per "nome del file": pulce.png
Inserisci un valore per "larghezza": 16
Inserisci un valore per "altezza": 16
Inserisci un valore per "tipo di file": PNG
Inserisci un valore per "qualità": 1
Immagine "gatto.jpg", larghezza 320, altezza 240
Immagine "cane.jpg", larghezza 800, altezza 600
Immagine "pulce.png", larghezza 16, altezza 16

results matching ""

    No results matching ""