Topic: Valutazione tipi di variabile, ne parliamo un po'?  (Letto 128 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline nuzzopippo

  • python neanderthalensis
  • ****
  • Post: 279
  • Punti reputazione: 0
    • Mostra profilo
Valutazione tipi di variabile, ne parliamo un po'?
« il: Settembre 10, 2020, 13:47 »
Saluti a Voi :)

Pur non interessandomi molto a tkinter, ogni tanto lo riprendo in mano, al momento sto approfittando di un periodo di "stanca" per proseguire un argomento già toppato in altri post tipo questo ed in parte anche proseguito, tipo qua, in sostanza sto provando  a progettare un generico oggetto "tabella" da utilizzarsi poi in GUI tkinker.

Ora sto provando a progettare un processo, opzionale, di valutazione dei tipi di dati, ed allo stato sto prevedendo che tale valutazione venga effettuata da un oggetto di supporto alla tabella che, a richiesta, fornisca una "riga" di widget configurata, per colori, allineamenti e fonts, secondo parametri impostati, la tabella si occuperà di geometria, popolamento e binding.

Riguardo alla "definizione" delle tipologie di widget, per la valutazione, mi appoggio ad un dizionario che associa a delle "categorie" di dati vari sub-classamenti di widget
	data_types = {'text': DCEntry,        # casella di testo DCEntry allineato a destra
  'numeric': DCEntry,     # casella di testo DCEntry allineato a sinistra
  'date': DCEntry,   # casella di testo DCEntry allineato centrato
  'boolean': DCCheck,     # casella di spunta
  'image': DCCanvas,      # PhotoImage DCCanvas
  'collection': DCCombo,  # Lista, tupla o set DCCombo
  'label': DCLabel        # testo fisso DCLabel allineato a destra
}


e le chiavi del dizionario definiranno una lista definente i controlli da utilizzarsi, tale definizione può essere fornita direttamente dall'esterno oppure può esserne invocata la
definizione fornendo una lista di "oggetti" rappresentanti le tipologie dei dati da
rappresentare.

def evaluate_types(self, data):
'''
Esegue la valutazione di una riga dati per determinare il tipo di
controllo da utilizzare.

parametri : data - una lista di oggetti rappresentanti lo schema dei
                   dati da trattare.
'''
types = []
for i in len(data):
var = data[i]
if 'bool' in type(var):
types.append('boolean')
elif 'int' in type(var) or 'float' in type(var):
types.append('numeric')
elif 'datetime' in type(var):
type.append('date')
elif 'list' in type(var) or 'tuple' in type(var) or 'set' in type(var):
types.append('collection')
elif 'tkinter.PhotoImage' in type(var):
types.append('image')
else:
types.append('text')
self._style[controls][types] = types


... e qui mi son fermato a riflettere un po', in particolare inserendo la valutazione di "datetime", essendo la stessa una classe (come per altro PhotoImage) e non una tipologia builtin.
In effetti, pensandoci, non necessariamente una data proviene da un "datetime" (c'è anche "time" p.e, ol altro), così come una immagine non necessariamente è una "tkintr.PhotoImage"

... tale considerazione mi fa venire il dubbio che il tentativo di "generalizzazione" che sto cercando di fare sia troppo semplicistico, pur essendo ragionevolmente convinto che "funzionerebbe" in linea di massima.

Riferendo ai tipi di dato da valutare, in genere, Voi cosa ne pensate di un approccio di tal tipo?, pensate che vi siano ulteriori da tener presenti o migliori approcci?

Grazie per l'attenzione, ciao :)

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 3.083
  • Punti reputazione: 9
    • Mostra profilo
Re:Valutazione tipi di variabile, ne parliamo un po'?
« Risposta #1 il: Settembre 11, 2020, 21:16 »
Mah... non mi sembra semplicistico... anzi, potresti portare ancora più avanti quello che hai cominciato, senza troppi problemi.

Cominciamo col dire che il concetto di "tipo" di dato in Python è piuttosto semplice. Banalmente, in Python tipo=classe. I tipi sono classi, e le classi sono tipi. Quando crei un oggetto (un'istanza di una classe), allora il "tipo" di quell'oggetto è semplicemente la classe da cui hai derivato l'oggetto. Questo vale per i "tipi" predefiniti (che sono *classi*, né più né meno), per qualsiasi classe importata dalla libreria standard o da pacchetti esterni, per qualsiasi classe che ti scrivi tu.

>>> type(3)
<class 'int'>
>>> d = datetime.datetime.now()
>>> type(d)
<class 'datetime.datetime'>
>>> class Klass: pass
...
>>> f = Klass()
>>> type(f)
<class '__main__.Klass'>
>>>

Come vedi, tutti questi tipi iniziano con "class..." perché in effetti è quello che sono: delle classi.

Una volta capito che i "tipi" non sono strutture in qualche modo separate dal resto del linguaggio, ma sono semplici classi, allora bisogna ricordare che le classi in Pyhton sono pur sempre "first class object", ovvero possono essere passate in giro come normali valori. Quindi, come hai già scoperto, puoi fare cose come ficcare i nomi delle classi in un dizionario, senza troppi problemi:

>>> TYPEMAP = {'foo': int, 'bar': datetime.datetime, 'baz': Klass}


E fin qui, non ci sono troppi problemi. Poi invece inizia il tuo vero problema... che stai cercando di serializzare dei valori. E una classe in Python non ha un serializzatore, ha solo un costruttore. Ora, tante volte capita che il costruttore è abbastanza gentile da servire anche come (de)serializzatore:

>>> int('-42.12')
-42.12

Ma non puoi aspettarti che questo succeda sempre, anzi in genere non basta. Per esempio, per de-serializzare un datetime.datetime dovresti usare datetime.datetime.strptime... Quindi in generale non puoi aspettarti che, usando l'esempio precedente, questo funzioni per de-serializzare "alla cieca":

>>> TYPEMAP = {'myint': int, 'mydate': datetime.datetime, 'baz': Klass}
>>> def deserialize(item, mytipe):
        return TYPEMAP[mytipe](item)

>>> deserialize('123', 'myint')  # finché è un intero... ancora te la cavi
123
>>> deserialize('12/9/2012', 'myint') # ma un tipo più complicato ovviamente fallisce
TypeError: an integer is required (got type str)

Tuttavia, "de-serializzare alla cieca" è un obbiettivo perfettamente ragionevole. Quello che dovresti fare è, invece di affidarti semplicemente ai costruttori, scriverti tante piccole funzioni-factory de-serializzatrici, da usare nei diversi casi. Nei casi più semplici, ovviamente, non c'è bisogno di nessuna funzione perché puoi affidarti al costruttore.

>>> def make_datetime(item):
        return datetime.datetime.strptime(item, '%d/%M/%Y')

>>> TYPEMAP = {'myint': int,     # qui basta il costruttore
                   'mydate': make_datetime}  # qui invece ci vuole la nostra funzione specifica

>>> def deserialize(item, mytipe):
        return TYPEMAP[mytipe](item)

>>> deserialize('123', 'myint')
123
>>> deserialize('12/09/1212', 'mydate')
datetime.datetime(1212, 1, 12, 0, 9)

A questo punto la de-serializzazione "alla cieca" funziona senza problemi. Dentro le diverse funzioni-factory uno può metterci la logica che vuole, eventuale validazione compresa (anche se questo aspetto è comunque una cosa separata dalla pura e semplice de-serializzazione).

Ovviamente lo stesso discorso si può fare a rovescio per la serializzazione, ovvero prendere un oggetto e conservarlo in una stringa.

(Tutto questo discorso si applica poi ai soprattutto ai tipi custom complessi, perché se uno deve solo trattare tipi molto semplici, forse allora basta json... Senza contare che esiste comunque anche pickle per serializzare oggetti arbitrari).

Offline nuzzopippo

  • python neanderthalensis
  • ****
  • Post: 279
  • Punti reputazione: 0
    • Mostra profilo
Re:Valutazione tipi di variabile, ne parliamo un po'?
« Risposta #2 il: Settembre 12, 2020, 12:20 »
Ti ringrazio dell'interessante intervento, @Ric.

Interessante principalmente riguardo alle "funzioni-factory de-serializzatrici", tipologia di aspetto che, lo confesso, ho sempre affrontato in maniera un po' "caotica" in passate esperienze, molto basata sul "bisogno del momento" e su cui dovrei cercare di migliorare il mio approccio mentale.

Dal nome, mi sembrerebbe che il pattern "factory" centri qualcosina, l'ho incontrato cercando altro ma non ho ancora "guardato", sarà bene che mi faccia qualche idea in merito.

Ciao :)

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 3.083
  • Punti reputazione: 9
    • Mostra profilo
Re:Valutazione tipi di variabile, ne parliamo un po'?
« Risposta #3 il: Settembre 12, 2020, 17:49 »
mah, non c'è molta magia dietro "factory"... una funzione-factory è una funzione che, a partire dai parametri, restituiscono oggetti fatti e finiti.
Se scrivi classi complesse, e hai bisogno di se/deserializzare spesso, di solito conviene includere queste funzioni-factory direttamente all'interno della classe, come classmethod. Per esempio, questo è ciò che fa datetime.datetime... guarda sulla documentazione tutti i metodi del tipo datetime.datetime.from***, o datetime.datetime.strptime... sono tutti classmethod, e lavorano tutti come factory.

Offline nuzzopippo

  • python neanderthalensis
  • ****
  • Post: 279
  • Punti reputazione: 0
    • Mostra profilo
Re:Valutazione tipi di variabile, ne parliamo un po'?
« Risposta #4 il: Settembre 13, 2020, 18:43 »
mah, non c'è molta magia dietro "factory"... una funzione-factory è una funzione che, a partire dai parametri, restituiscono oggetti fatti e finiti. ...

Si, certo, non è che il concetto, di per se, sia così "difficile", forse è anche "troppo" facile e, magari, si finisce ad applicarlo con una certa disinvoltura, cosa che mi capita (quel "caotico" precedente).

Dato che al momento sono "fuori sede" e non ho facilmente disponibili i mezzi ordinari, tra cui internet (avrei cercato factory), mi son divertito un po' a pensare su quanto hai esposto applicato al "DCEntry" ("vorrebbe" stare per Data Show Controll Entry: troppo lungo!), da me applicato per TRE contesti diversi, e chiedermi : come applicherei a 'sto coso i suggerimenti di @Ric?

Ovviamente, la risposta trovata era già contenuta nei suggerimenti dati : dato che "tutto è un oggetti", parametri di istanza e magari una stringa di formattazione e vai ...
Va da se che le simulazioni "al volo", su di un pseudo-widget,  mi hanno subito chiarito che comunque bisogna pensarci su, già ai setter e getter non mi è riuscito di applicare i decoratory "@property" e "@<proprietà>.setter", credo di averne capito la logica del perché non vanno (inizializzazione contemporanea) ma devo approfondire, e rassegnarmi ad un "set_value()" - "get_value()" ... non ho considerato una memorizzazione del valore effettivo, dato che potrebbe anche essere editato.
Qualora a qualcuno possa interessare la simulazione che ha funzionato :

>>> def data_serializer(value, formatter):
if not formatter: formatter = '%d/%m/%Y'
return value.strftime(formatter)

>>> def data_de_serializer(text, formatter):
if not formatter: formatter = '%d/%m/%Y'
try:
d = datetime.datetime.strptime(text, formatter)
except:
d = ''
return d

>>> class my_widget:
def __init__(self, functions, formatter=''):
self.format = formatter
self.func = functions
self.data = ''
def get_value(self):
return self.func[1](self.data, self.format)
def set_value(self, val):
self.data = self.func[0](val, self.format)
def get(self):
return self.data


>>> funcs = (data_serializer, data_de_serializer)
>>> w = my_widget(funcs)
>>> ora = datetime.datetime.now()
>>> w.set_value(ora)
>>> w.get()
'13/09/2020'
>>> tempo = w.get_value()
>>> tempo
datetime.datetime(2020, 9, 13, 0, 0)


... Ovviamente è solo un ragionare estemporaneo, non so se utilizzerò un tale metodo effettivamente, utilizzare "locale" e formati numerici complica leggermente le cose, non molto, certo, va anche così ma 'na spulciata sulle factory la do comunque.

Ciao :)

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 3.083
  • Punti reputazione: 9
    • Mostra profilo
Re:Valutazione tipi di variabile, ne parliamo un po'?
« Risposta #5 il: Settembre 14, 2020, 22:40 »
beh, per prima cosa copriamo le basi... non ha nessun senso scrivere cose come "if not formatter...".
Banalmente scrivi def data_serializer(value, formatter="%d/%m/%Y"):.
Dopo di che, @property eccetera sono un discorso completamente diverso, qui non c'entra.

Poi, non mi è chiaro che cosa stai cercando di fare... un widget "astratto" in grado di concretizzarsi in diversi widget a seconda dei tipi di dati da visualizzare? Può essere un'idea (anche se con l'esperienza ti accorgerai che nella maggior parte dei casi è solo sovra-ingegnerizzazione). Però allora scriviti tanti diversi widget, in grado ciascuno di trattare il suo tipo di dato, e poi una funzione-factory per restituire il widget voluto a seconda della circostanza.


class UnWidgetPerLeDate:
    #...
    def getvalue: # ...
    def setvalue: # ...

class UnWidgetPerINumeri:
    #...
    def getvalue: # ...
    def setvalue: # ...

class UnWidgetPerIlTesto:
    #...
    def getvalue: # ...
    def setvalue: # ...

MAP = {"date": UnWidgetPerLeDate; "int": UnWidgetPerINumeri, "text": UnWidgetPerIlTesto}
def select_display(mytipe, value):
    widget = MAP[mytipe]()
    widget.setvalue(value)
    return widget

o qualcosa del genere.


Offline nuzzopippo

  • python neanderthalensis
  • ****
  • Post: 279
  • Punti reputazione: 0
    • Mostra profilo
Re:Valutazione tipi di variabile, ne parliamo un po'?
« Risposta #6 il: Settembre 15, 2020, 08:36 »
beh, per prima cosa copriamo le basi... non ha nessun senso ...
:embarrassed: hai ragione, è imbarazzante quante volte non mi vengano in mente accorgimenti banali che uso abitualmente, il fatto che "pensavo ad altro" non giustifica.

Poi, non mi è chiaro che cosa stai cercando di fare... un widget "astratto" in grado di concretizzarsi in diversi widget a seconda dei tipi di dati da visualizzare? Può essere un'idea (anche se con l'esperienza ti accorgerai che nella maggior parte dei casi è solo sovra-ingegnerizzazione). Però allora scriviti tanti diversi widget, in grado ciascuno di trattare il suo tipo di dato, e poi una funzione-factory per restituire il widget voluto a seconda della circostanza.
Si può essere un'idea ...

Premetto che, forse, il mio precedente post è stato un po' inopportuno, dato che al momento sto, essenzialmente, esplorando alla ricerca di nuove idee.
La scrittura "di tanti diversi widget" (sub-classamenti, in realtà) l'ho già provata, mi porta ad una "esplosione" di codice cui poi è più complesso applicare "varianti", il codice precedente è una simulazione a linea di comando di un widget che faccia l'inverso, cioè un singolo widget che agisca diversamente con diversi tipi di dato, tale diversità di azione "verrebbe" determinata dall'oggetto creatore, tramite un metodo-factory, diciamo, non ho esposto successivi tentativi fatti su "my_widget" con interi, float e testo, oltre che con "formatter" diretti vari e "locale", per non creare confusione.

La fantomatica "tabella" tkinter che sto cercando di abbozzare è, sostanzialmente, un esercizio di studio, non avendo intenzione di utilizzare tkinter, cerco solo di acquisire idee e tecniche di approccio.
L'idea è cercare di definire un controllo "tabella", composto di base da pochi tipi widget, essenzialmente capace di auto-configurarsi su tipologie dati "ordinari" (che, so, una lista o un record da un database) ma che anche fornisca una API che lo renda, per quanto possibile, configurabile "a piacere".
Un tal tipo di tabella definita per esigenze specifiche sono in grado di farla, anche se non con tecniche concise quali le Tue esemplificazioni (cui spesso non arrivo), sto cercando di astrarre un pochino e definire un componente "riciclabile" ... ed, ovviamente, tutto parte dalla valutazione dei dati, motivo del post.
« Ultima modifica: Settembre 15, 2020, 08:50 da nuzzopippo »

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 3.083
  • Punti reputazione: 9
    • Mostra profilo
Re:Valutazione tipi di variabile, ne parliamo un po'?
« Risposta #7 il: Settembre 15, 2020, 22:47 »
>  al momento sto, essenzialmente, esplorando alla ricerca di nuove idee.
Mah... non è probabilmente la cosa migliore in questo caso. Se non ti fermi su uno scenario concreto, il rischio è che cerchi di generalizzare troppo...

> un controllo "tabella", composto di base da pochi tipi widget, essenzialmente capace di auto-configurarsi
...ecco, appunto. Difficilmente questa è una buona idea. Se lo fosse, un "meta-widget" così esisterebbe già. Ma la verità è che la programmazione di GUI è fatta soprattutto di casi particolari, e questo è la sua croce e delizia. Se cerchi di astrarre troppo, di fattorizzare troppo, ti perdi un sacco di dettagli che in genere costituiscono il "sale" della tua GUI.
Per esempio, tu potresti pensare a un "meta-widget" che può "concretizzarsi" in una casella di testo oppure in una lista, a seconda del parametro ("lista" o "casella_di_testo") che passi al costruttore. E va bene... solo che prima o poi scoprirai che a volte vuoi una lista con selezione multipla, a volte no. A volte vuoi una casella di testo con sfondo giallo, a volte una con le scritte rosse. E tutto questo il tuo "meta-widget" non te lo può dare, a meno di non complicarlo pazzescamente.

Detto questo, in ogni caso, se (se!) riesci a trovare un preciso ambito applicativo, con dei limiti ben definiti, allora questo tipo di operazioni può avere un significato. Mi ricordo che tanti tanti anni fa avevo costruito un "plugin" per wxPython che creava un'interfaccia per impostare dei report. Poteva essere applicato a qualsiasi applicazione wxPython che avesse bisogno di generare molti report, e funzionava "automagicamente" a partire da una lista di parametri configurabili. In pratica tu dicevi quali parametri volevi per il tuo report, e il tipo dei parametri (testo, bool, data, lista...). Il mio plugin ti creava una finestra che permetteva all'utente di inserire quei parametri. Ciascun parametro era renderizzato a seconda del suo tipo (per esempio, se un parametro richiedeva l'immissione di un bool, era renderizzato con un checkbox; se chiedeva di scegliere una voce da una lista, allora veniva mostrato un combo-box... etc.). Quando l'utente aveva impostato tutti i parametri del report, cliccava sul pulsante "report!" della finestra, e il programma generava automativamente il report, passando i parametri raccolti a una query sul database (query che ovviamente dovevi scriverti tu... almeno questo). Raccolti i dati della query, poteva poi generare il report in vari output (a video, in excel, etc.).
Tutto questo era piuttosto raffinato... per esempio mi ricordo che c'era il modo per impostare il comportamento dei "null", per ricalcolare i valori di un parametro in base alla scelta di un altro parametro, e così via.
Questo era un "meta-widget" molto complesso, che in effetti faceva una cosa più o meno come quella che stai pensando tu: a partire da una serie di configurazioni, "si auto-disegnava da solo" in molti modi complessi. Tuttavia restava comunque all'interno di un recinto ben definito: disegnava sempre quel tipo di finestra, con quel tipo di caselle e pulsanti. Se l'utente voleva uno sfondo rosso per la finestra, questo non era possibile.  Il plugin aveva in mente uno scenario ben preciso, ovvero i tanti report "banali" e ripetitivi di cui ogni applicazione "gestionale" ha bisogno, e che richiedono di solito una marea di codice noioso e lungo. Ma se avevi bisogno di un report più sofisticato, dovevi comunque scrivertelo a mano. Anzi, a dire il vero mi ricordo che avevo fatto già fin troppi sforzi per supportare scenari complessi, e alla fine l'API si era complicata non poco. Comunque adesso non lo ricordo benissimo, era qualcosa come dieci anni fa...

La cosa importante è che non era nato da un esercizio astratto, ma da un'esigenza concreta... stavo sviluppando un gestionale e ogni due giorni mi chiedevano di prevedere un report nuovo, sempre "quasi" uguale ma con due parametri un po' diversi dal precedente, e così via... a un certo punto mi sono annoiato e ho scritto un sistema per generare automaticamente questo tipo di finestre per l'immissione dei parametri.

Offline nuzzopippo

  • python neanderthalensis
  • ****
  • Post: 279
  • Punti reputazione: 0
    • Mostra profilo
Re:Valutazione tipi di variabile, ne parliamo un po'?
« Risposta #8 il: Settembre 16, 2020, 08:08 »
Il parere dell'esperienza e della capacità mi è sempre ben accetto, grazie di avermelo fornito, lo terrò in gran conto.

Per altro, certamente "astrarre" porta complessità ed ancor di più "automatizzare", seppur epidermicamente ne sono consapevole.

... E va bene... solo che prima o poi scoprirai che a volte vuoi una lista con selezione multipla, a volte no. A volte vuoi una casella di testo con sfondo giallo, a volte una con le scritte rosse. E tutto questo il tuo "meta-widget" non te lo può dare, a meno di non complicarlo pazzescamente.
:D ... giuro, il codice che segue è stato impostato prima dell'avvio di questo post

Uno dei "DCcontrolli"
class DCEntry(tk.Entry):
    ''' Entry "speciale" per tabella. '''
    def __init__(self, x, y, bg_c=None, fg_c=None, bg_rs_c=None,
                 *args, **kwargs):
        self.x = x
        self.y = y
        self.bg_c = bg_c        # background color
        self.fg_c = fg_c        # foreground color
        self.bg_rs_c = bg_rs_c  # riga sel background color
        super().__init__(*args, **kwargs)
        self._update_colours()

    def updatecolours(self):
        ''' Se impostati, configura i colori del widget. '''
        if self.bg_c: self.configure(bg=self.bg_c)
        if self.fg_c: self.configure(fg=self.fg_c)

    def select(self):
        if self.bg_s_c:
            self.configure(bg=self.bg_rs_c)

    def unselect(self):
        if self.bg_c:
            self.configure(bg=self.bg_c)

tutti i prototipi sono impostati così.

colorazioni determinate da un oggetto apposito (lo chiamo "TableStyler") che prevede un dizionario di dizionari per le impostazioni di visualizzazione, con impostazioni di defaults
    def _set_defaults(self):
        '''
        Imposta alle condizioni di default.
        '''
        style = {}
        voice = {}
        voice['mode'] = 'auto'
        voice['types'] = []
        style['controls'] = voice
        voice = {}
        voice['alternate'] = True
        voice['h_bg'] = 'LightGrey'        # background intestazioni
        voice['h_fg'] = 'black'            # foreground intestazioni
        voice['bg_odd'] = 'white'          # background righe dispari
        voice['bg_even'] = 'comsilk'       # background righe pari
        voice['bg_select'] = 'turquoise1'  # background riga selezionata
        voice['fg_data'] = 'black'         # colore del testo
        style['colours'] = voice
        voice = {}
        voice['headers'] = None    # font intestazioni - non valorizzato
        voice['data'] = None    # font ordinario - non valorizzato
        style['fonts'] = voice

        self._style = style

Impostabili anche esternamente (fornendo un dizionario) o singolarmente, tale "TableStyler" fornirebbe all'oggetto superiore (ancora non definito) una riga di controlli che poi verrebbero "posizionati".

Prospettiva complessa? Certamente si, mi son fermato a "pensare" sulle 300 righe di codice con TableStyler ancora fortemente incompleto e un "Table" neanche iniziato ... comunque, non è una complessità che poi mi spaventi.

Detto questo, in ogni caso, se (se!) riesci a trovare un preciso ambito applicativo, ...
Ecco, questa era una problematica cui non avevo pensato ... la rappresentazione tabellare è un metodo che ho usato spesso in ciò che ho fatto con altri linguaggi, tutto li, dato che in tkinter non c'è, contrariamente a wx, ho pensato che realizzare un meta-oggetto del genere per tale framework, magari dotato di una certa "elasticità", possa essere un buon esercizio ed occasione di apprendere e fissare concetti "nuovi", anche in caso di un eventuale fallimento, tutto qua.