Topic: opinione su procedura popolamento entry  (Letto 93 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline alealex

  • python unicellularis
  • *
  • Post: 3
  • Punti reputazione: 0
    • Mostra profilo
opinione su procedura popolamento entry
« il: Dicembre 04, 2018, 14:41 »
Buongiorno, colgo l'occasione con questo post per presentarmi:  sono un "lettore" da diverso tempo e sono un appassionato di Python da 10 anni ormai. Sto realizzando la conversione per la fatturazione elettronica da un gestionale e mi sono affidato a Python + Tkinter.

Scrivo più che una domanda, una richiesta di conferma che la logica usata sia corretta...

Ovvero, il concetto di popolare Entry in questo modo, cioè aggiungere in una lista
tutte le istanze delle caselle di testo (Entry Tkinter) e caricarle con un ciclo
è corretto secondo voi? Cioè non occupa troppa memoria mettere in una lista le istanze?

scrivo un estratto del codice:

  
import types
  ....
  def aggiungiDati(self):

    # nsDati è una lista definita nelle altre funzioni della classe contenente dati
    nsDati = self.nsDati
   
    # dataEntry è una lista contenente le caselle di testo (definite in altre funzioni della classe)
    # l'elemento vuoto è per saltare un campo nel database in modo da far corrispondere
    # numero del campo con numero della casella di testo ("trucco" realizzato per comodità
    # che comunque NON riguarda la domanda)
    dataEntry = [self.nomeEntry,'',self.cfEntry,self.piEntry,self.capEntry,self.cittaEntry,
                 self.provEntry,self.indirizzoEntry,self.numCivicoEntry,self.datiDichEntry,
                 self.cfDichEntry,self.caDichEntry]

    for x in enumerate(dataEntry):
      if isinstance(x[1], types.InstanceType):
        x[1].insert(0, nsDati[x[0])     


Chiedo perchè inizialmente ho perso tempo per trovare il modo di utilizzare i nomi delle
istanze avendo paura di usare troppa memoria e caricarle come stringhe nella lista (vedi codice a fine post) però poi richiamandole ovviamente non essendo istanze non si poteva "trasformarle" (non esiste un modo, vero?)

Grazie per la vostra opinione

di seguito Il concetto a cui ho lavorato all'inizio.

#Per capirci era: creo una lista con tutte stringhe
    dataEntry = ['self.nomeEntry','','self.cfEntry','self.piEntry','self.capEntry','self.cittaEntry',
                 'self.provEntry','self.indirizzoEntry','self.numCivicoEntry','self.datiDichEntry',
                 'self.cfDichEntry','self.caDichEntry']                 
# e richiamo la stringa dalla lista spacciandola per istanza (non ho trovato però la funzione che lo fa)
   # dataEntry[1].insert(...) --> sarebbe self.cfEntry.insert ma self.cfEntry in questo caso è una stringa

Offline Markon

  • python sapiens sapiens
  • *
  • moderatore
  • Post: 4.103
  • Punti reputazione: 5
    • Mostra profilo
    • Neolithic
Re:opinione su procedura popolamento entry
« Risposta #1 il: Dicembre 04, 2018, 18:51 »
Ciao! Dipende un po' da ciò che ti serve fare. Per esempio, potresti usare un observable, così che sta ai singoli widget di chiedere aggiornamenti, non viceversa. In pratica definisci un oggetto che contiene una lista inizialmente vuota di oggetti da notificare quando avviene un evento. Gli observers hanno accesso all'oggetto observable e si registrano ad esso, e definiscono un metodo che l'observable chiamerà quando avviene l'evento.  In questo modo non devi fare hardcoding di stringhe o altro, e dai la possibilità agli oggetti di decidere a quali eventi registrarsi.

Per rispondere alla tua domanda se la lista non diventa troppo grande. Non so quanti widget hai nella tua applicazione, ma ad ogni modo ciò che inserisci nella lista sono references, non una copia degli oggetti, bensì una copia dei references (così non puoi sovrascrivere i widget a runtime per errore) .
« Ultima modifica: Dicembre 04, 2018, 18:55 da Markon »

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 2.821
  • Punti reputazione: 9
    • Mostra profilo
Re:opinione su procedura popolamento entry
« Risposta #2 il: Dicembre 04, 2018, 21:36 »
Il tuo codice è... non bello da vedere, su molti livelli. In primo luogo, non preoccuparti: non è certo una lista che occupa memoria in Python. C'era già qualcuno che un po' di giorni fa (non ricordo bene) qui sul forum si meravigliava che una lista occupa meno spazio della somma degli oggetti che contiene. La risposta è che una lista non contiene "davvero" gli oggetti che ci metti dentro, ma solo i loro riferimenti (un array di puntatori, all'incirca). Quindi prima che una lista pesi davvero sulla memoria bisogna che contenga (molte) migliaia di elementi. E sicuramente, molto prima di arrivare a preoccuparti del peso della lista dovrai preoccuparti del peso degli oggetti già esistenti che ci vuoi mettere dentro, no?
Secondo, il "trucco" strano che usi per saltare una colonna del database è davvero bruttino. Le query servono proprio per selezionare le colonne che ti servono, e volendo anche nell'ordine che ti servono. Ok, certe volte vuoi tirare su dei dati in più perché magari ti servono da un'altra parte, ma anche così... non so, puoi usare dei dizionari o delle named tuples invece che delle semplici tuple ordinate per riferirti alle colonne... tra l'altro se non sbaglio tutti i driver di database possono restituire delle named tuples e/o dei dizionari, quindi non è che devi fare troppo lavoro.
Terzo, se proprio vuoi usare il tuo trucco, allora passare per isinstance proprio non ha senso. Una stringa vuota testa per False mentre l'istanza di una entry Tk direi che testa per True, quindi tutto il giro complicato che fai dovrebbe essere semplicemente fattibile con qualcosa come for n, entry in enumerate(dataEntry): if entry: etc etc.
Quarto, in effetti non è un'intuizione sbagliata quella di usare una lista di entry e ciclare su questa per inserire i valori pescati dal database (incrociando le dita che siano nell'ordine giusto...). Ma tieni presente che *non* stai migliorando neanche di un millimetro il design del tuo codice. Che tu faccia una cosa terra-terra come:

entry_0.Value = db_row[0]
entry_1.Value = db_row[1]
entry_2.Value = db_row[2]
...etc

Oppure una cosa taaaaanto cool come

entries = [entry_0, entry_1, entry_2, etc etc]
for n, entry in enumerate(entries):
    entry.Value = db_row[n]

in realtà entrambe le versioni sono ugualmente brutte perché mantengono comunque un accoppiamento forte tra il "model" (il database, diciamo) e la "view" (le entry, diciamo) della tua applicazione. La seconda versione è solo fisicamente più breve da scrivere, ma anzi francamente è preferibile la prima versione che è più esplicita e leggibile. La seconda versione introduce un ulteriore elemento di fragilità dovuto al fatto che l'ordine delle colonne del database deve essere lo stesso delle entry nella lista. (Ok, come dicevo almeno questo è superabile usando dizionari o named tuples per i valori pescati dal database, e organizzando le entry in un dizionario invece che in una lista...)
Il problema di fondo però è che dovresti disaccoppiare model e view implementando un qualche tipo di 3-tier architecture. Ora, Markon ti consiglia di implementare degli Observer, e sicuramente è una strada possibile e raccomandabile in generale, ma va detto che in un gui framework (per di più in una semplice scheda anagrafica) di solito gli eventi scorrono nel verso opposto (è la view che emette eventi di aggiornamento "diretti" al model, mentre non capita quasi mai che il model si aggiorni da solo per qualche ragione e abbia bisogno di comunicarlo alla view) e un gui framework ha già un suo sistema di eventi che gestisce il normale flusso previsto degli aggiornamenti. Per di più è abbastanza improbabile che ogni singola entry, indipendentemente, abbia necessità di emettere e ricevere eventi. Di solito vuoi che l'utente compili la scheda, e poi l'aggiornamento avviene tutto in una volta quando si fa clic su "ok". Anche se avessi, per dire, una scheda anagrafica composta di varie entry, e collegata a una seconda view "a lista" - ecco, questo sarebbe forse un caso in cui un Observer si potrebbe prendere in considerazione: ma anche in questo caso, tu non vuoi (di solito) che la lista si aggiorni non appena l'utente modifica una singola entry (il nome, diciamo). Vuoi aspettare che l'utente abbia compilato la scheda e premuto su "ok": solo allora la lista si aggiorna. Ma allora non è proprio necessario che la lista sia un Observer. Forse basta che un oggetto intermedio (un "controller", come si dice) riceva l'evento del framework alla pressione del pulsante, e reagisca sincronizzando la lista.
In ogni caso, qualunque sia l'architettura specifica che finirai per implementare, dovresti appunto cominciare a pensare in termini di qualche oggetto intermedio (un "controller") che collega e media tra il model e la view. Ora, se tutto questo per te (come immagino) è arabo, allora non ti resta che metterti a studiare un po' (un bel po') di OOP. Nel frattempo, puoi provare a correggere le cose più evidenti del codice che hai scritto...

Offline alealex

  • python unicellularis
  • *
  • Post: 3
  • Punti reputazione: 0
    • Mostra profilo
Re:opinione su procedura popolamento entry
« Risposta #3 il: Dicembre 04, 2018, 22:21 »
Grazie a entrambi per le risposte, ne farò tesoro sicuramente. Immaginavo (ho "visto" stampando il contenuto) però grazie a voi ho avuto la conferma che fosse solo un riferimento all'oggetto e non l'oggetto stesso.

Per la programmazione a oggetti diciamo che ne ho sentito parlare  :D

Il discorso del "miscuglio" tra database e gui è per via della non editabilità. La funzione scritta nel post scrive solo i dati nelle Entry, all'avvio, nessun evento. L'utente non può in nessun modo editare nessun dato (ho usato Entry anzichè Label per dare la possibilità di copiare il dato in altri punti esterni alla gui) può solo selezionare quali fatture convertire scegliendole da una Treeview (ttk) quindi non dovendo preoccuparmi di nulla, ho "mischiato" le cose. Leggo->scrivo->aspetto selezione e conferma.
Il driver del database.... non è un driver: legge da un file di testo, per questo non ho nessun aiuto in tal senso (sono i famosi gestionali fatti a foglio di calcolo...)

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 2.821
  • Punti reputazione: 9
    • Mostra profilo
Re:opinione su procedura popolamento entry
« Risposta #4 il: Dicembre 05, 2018, 10:34 »
Mah sì, è chiaro che per gli scenari semplici uno è sempre tentato di usare scorciatoie... e non c'è nulla di male fin quando sai che sono scorciatoie ad hoc. Mettere in piedi un'architettura robusta è qualcosa che puoi anche non fare... finché scrivi il codice una volta sola. L'importante è capire che, se mai dovessi riadattare, modificare, espandere quel codice, allora faresti meglio a buttarlo via e riscrivere tutto daccapo meglio, invece di provare a modificare quello che hai già scritto.
Dopo di che, anche se non hai un database puoi comunque aprire il file di testo con csv e ottenere dizionari, per dire. Ma soprattutto ti converrebbe davvero portare tutto in un database. I gestionali fatti coi fogli di calcolo sono inutilizzabili.

Offline alealex

  • python unicellularis
  • *
  • Post: 3
  • Punti reputazione: 0
    • Mostra profilo
Re:opinione su procedura popolamento entry
« Risposta #5 il: Dicembre 05, 2018, 23:57 »
Scrivo la struttura "modificata" dopo i vostri consigli, solo per concetto (senza codice)

(conver è il nome del programma, conversione)


class ConverUI(Frame):
  # classe interfaccia grafica
  def __init__(self, master=None):
    Frame.__init__(self, master)
    # costruzione interfaccia grafica
    .....
    # Istanza classe controller
    self.datiCtrl = ConverCtrl()

    # richiama i dati di default
    self.datiIntestazione
    .....
  def datiIntestazione(self):
    # richiama i dati di default dal metodo nsDati della classe controller
    dati = self.datiCtrl.datiDefault()
    # aggiorna widget
    self.updateWidget(dati)

 def updateWidget(self, dati):
    dataEntry = [self.nomeEntry,self.cfEntry,self.piEntry,self.capEntry,self.cittaEntry,
                 self.provEntry,self.indirizzoEntry,self.numCivicoEntry,self.datiDichEntry,
                 self.cfDichEntry,self.caDichEntry]
    for n, entry in enumerate(dataEntry):
        if entry:
          entry.insert(0, dati[n])

class ConverCtrl:
  # classe controller -> oggetto intermedio
  def init(self):
   ....

  def datiDefault(self):
    #richiama i dati di default dalla classe database e li memorizza in una lista ordinata per i widget della view
    return dati
  ...


E' migliorato un pochino stilisticamente?
« Ultima modifica: Dicembre 06, 2018, 00:25 da alealex »

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 2.821
  • Punti reputazione: 9
    • Mostra profilo
Re:opinione su procedura popolamento entry
« Risposta #6 il: Dicembre 06, 2018, 10:22 »
Mah, suppongo che sia un miglioramento. Per lo meno adesso non si passa da isinstance. La cosa buffa è che adesso però è scomparsa la stringa vuota dalla lista, quindi anche il test "if entry" non ha più senso (la lista contiene *solo* entry, a questo punto). Per il resto, è lo stesso impianto di prima e quindi ha gli stessi problemi di prima, che uno può benissimo decidere di ignorare, come già discusso.
A parte questo, darei comunque una bella ripulita al tutto a colpi di Pep8.