Topic: Il mio primo modulo...  (Letto 485 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline domi84

  • python unicellularis
  • *
  • Post: 42
  • Punti reputazione: 0
    • Mostra profilo
Il mio primo modulo...
« il: Gennaio 03, 2018, 16:08 »
Tanto per cominciare buon anno a tutti!!
Un po' di anni fa ho cominciato a studiare python come hobby, nel tempo ho imparato un po' ad usare pandas e seaborn, a lavoro ho scritto qualche script, solo per curiosità. Finchè alcuni colleghi mi hanno chiesto di poter usare gli scripts. Ho quindi messo insieme gli scripts e l'ho riscritto per poter essere usato anche da altri. Da lì a github il passo è stato breve ed e' stata la spinta giusta per imparare come creare un pacchetto python, caricarlo su pypi, usare git, pytest e image hashing. Non ho scritto molto sul forum, ma ho trovato risposte a tante domande, quindi un sentito: GRAZIE!!!  :birrame:

Il codice è qui:
https://github.com/domenico-somma/Papillon
Se vi va di dare un occhio al codice, consigli e critiche sono sempre ben accettI (tenete conto che lo faccio per hobby, non di lavoro...e sono sicuro che uno di voi riscirebbe a riscriverlo meglio in mezz'ora, mentre io ci ho impiegato settimane...ma (ancora una volta) il mio obiettivo era solo imparare.
Non so se sarà mai usato da qualcuno...vedremo :fingers-crossed:
Ciao

P.S. Scusate se non ho risposto agli ultimi post, ma (credo) con la nuova veste grafica era saltata la notifica delle risposte.

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.447
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Il mio primo modulo...
« Risposta #1 il: Gennaio 06, 2018, 13:43 »
Sei *sicuro* di volere suggerimenti? Perche' quello che scrivi sembra, datemi suggerimenti, ma da quello che scrivi dopo non sembra davvero che ti interessi.
Guarda, aperto brevemente il codice, richiuso immediatamente. Non saprei davvero dove cominciare. Capisco che ci e' voluto un sacco di lavoro ma... forse se avessi seguito una best practice ogni tanto avresti fatto prima.

Temo anche che sia Python 2. Come dire... comincia a guardare la PEP8 (e capiscine lo spirito).
Ma sono in difficolta' nell'aiutarti. E' chiaro che non hai un background di software engineering... lo dici anche. Potrebbe essere che non sia davvero rilevante metterlo a posto secondo i crismi. La maggior parte del codice fa troppo o troppo poco: ci sono intere funzioni, notevolmente complicate, che non servono a nulla.

Vabbe', mi prendo lo sbattimento, vada come vada.

1. _FPKM funzione inutile completamente. Buona parte della complessita' e' dovuta al fatto che vuoi che funzioni con liste e stringhe (ma *solo* liste e stringhe). Non si scrive roba cosi'. Si spera che quando hai una variabile sia vagamente noto il tipo. Cioe' se e' una stringa e' una stringa. Se e' una collezione di stringhe e' una collezione di stringhe (ed e' molto piu' comodo avere qualcosa che funziona con *ogni* iterabile che qualcosa che funziona solo con stringhe). In pratica comoda non e' comoda, semplice non e' semplice. Obietterai che a te e' comodo cosi'. Insisto: se hai pezzi di codice dove una variabile puo' essere indifferentemente una stringa o una lista di stringhe, hai un problema a monte. Non ha senso. Se invece non lo hai, allora non ti serve quella funzione. Questo senza nemmeno toccare il fatto che una funzione che fa da "toggle" non ha nessun senso. E' la cosa piu' scomoda nel mondo reale. Ma tu ragioni a botte di ipython... quindi veniamo al numero 2.

2. quell'import di Ipython e' atroce. Mi stai dando side effect globali solo perche' importo il tuo modulo? Non scherziamo. Questa roba in OOP si fa ben diversamente. Tipicamente quando importo qualcosa non voglio side effect. Si certo... e' comodo quando sei dentro ipython... ma mi sembra che tu volessi scrivere una libreria. Il che vuole dire che e' chi ti chiama a dirti come devi comportarti (e usa oggetti, non stato globale). Per me quelle poche righe sarebbero sufficienti a non considerare nemmeno il modulo, anche se fosse qualcosa che mi serve. Dovrei comunque riscrivermelo o  fare porcate per impedire al modulo di fare porcate.

3. Non usare *mai* type(jjj) == qualcosa. C'e' scritto grosso come una casa nella PEP8. Se non e' scritto grosso abbastanza, ingrandisci il font. Perche' dovrebbe uscire dallo schermo e tatuarsi in ogni synapse. Se pensi ad oggetti, e' anche ovvio perche'. Se poi realizzi il fatto che python e' un linguaggio dinamico dove e' comune il duck typing, dovrebbe essere ancora piu' ovvio. In generale non vai molto avanti finche' non hai capito, interiorizzato e masterizzato SOLID. Se capissi cosa sono i principi SOLID *davvero* e rileggessi ogni pezzo del tuo codice pensando, principio per principio, a quanti ne stai violando, riusciresti a riscriverlo facilmente e da solo. E questo si applica anche per il resto. resta il fatto che e' davvero improbabile che tu abbia bisogno di determinare i tipi: stai solo scrivendo codice complicato perche' pensi che sia piu' facile da usare. :)

In termini di SOLID, (1) viola almeno SRP, OCP, LSP, e parzialmente DIP (ma questo va via se metti via gli altri 3). Non violi ISP solo perche' stiamo parlando di una funzione, quindi davvero non si applica... ora, se proprio fosse indispensabile quella funzione, applicando i principi di cui sorpra arriveresti ad un'architettura che consente a quella funzione di operare in modo semplice. Poi rimarrebbe la questione se davvero sarebbe un buon design. E se lo fosse, vorrebbe dire che il corrente e' ancora peggio di quello che mi e' parso da una rapida lettura.

I test apprezzo tantissimo e non sai quanto che li hai scritti. Sono una delle cose che puo' darti illuminazione.

Per esempio... partiamo dal primo test, quello su test_functions_FPKM. Sfortunatamente stai usando un antipattern (assertion roulette, se interessa: http://xunitpatterns.com/Assertion%20Roulette.html). Se lo avessi scritto a modo (e se usi pytest, non hai bisogno di cacciare tutto in una classe che non e' una classe ma una collezione di test scorrelati) e avessi davvero coperto tutti i casi rilevanti, ti saresti accorto che ci vuole un delirio di codice da scrivere.

Questa quantita' di codice e' necessaria perche' la funzione e' troppo complicata: la specifica dice che deve funzionare con due tipi (ma solo quei due tipi, quindi non puoi nemmeno farti salvare da LSP) e a seconda di quello che trovi, deve fare due cose diverse. Tanto per dirne una... cosa succede se hai una lista di liste di stringhe? Non lo hai testato. Non dovrebbe funzionare. Eppure, in un certo senso funziona. La docstring non e' in sintono con il codice... etc etc etc. Semplifica.

E questo si applica ad ogni singolo aspetto del tuo codice. Tipo il costruttore della classe principale e' un esempio da manuale di cosa *non* deve fare un costruttore. Ti manca struttura. Per una roba cosi' complicata hai bisogno di un Builder o qualcosa che ne faccia le veci. Non un __init__. Qui stai violando OCP, SRP... molto probabilmente anche DIP.


Mi fermo qui... se vado avanti per tutto il modulo scrivo un libro.


Offline domi84

  • python unicellularis
  • *
  • Post: 42
  • Punti reputazione: 0
    • Mostra profilo
Re:Il mio primo modulo...
« Risposta #2 il: Gennaio 07, 2018, 02:35 »
Citazione
Sei *sicuro* di volere suggerimenti? Perche' quello che scrivi sembra, datemi suggerimenti, ma da quello che scrivi dopo non sembra davvero che ti interessi.
Sicurissimo! Le critiche sono sempre ben accette.

Citazione
forse se avessi seguito una best practice ogni tanto avresti fatto prima.
In realtà ero molto più focalizzato sul farlo funzionare (il codice che scrivo non sempre funziona al primo tentativo). Comunque le due che ho provato a seguire sono https://gist.github.com/sloria/7001839 e la PEP8 (non sto dicendo che le ho seguite a menadito, per carità. Solo che ci ho dato una lettura e ho cercato di seguirle). Se pensi che ce sia qualcuna in particolare che mi possa essere utile dimmelo pure (ti prego, non dirmi tutte le PEP, non voglio fare il pigro, ma sono quasi un migliaio......)

Citazione
La maggior parte del codice fa troppo o troppo poco: ci sono intere funzioni, notevolmente complicate, che non servono a nulla.
Fin'ora la regola che ho seguito è stata: "devo fare questa operazione più di una volta? Ne faccio una funzione". Intuisco cosa intendi quando dici che fanno troppo o troppo poco, ma non ho (ancora) un metro di valutazione adeguato a capirlo.

Citazione
1. _FPKM funzione inutile completamente. Buona parte della complessita' e' dovuta al fatto che vuoi che funzioni con liste e stringhe (ma *solo* liste e stringhe). Non si scrive roba cosi'. Si spera che quando hai una variabile sia vagamente noto il tipo. Cioe' se e' una stringa e' una stringa. Se e' una collezione di stringhe e' una collezione di stringhe (ed e' molto piu' comodo avere qualcosa che funziona con *ogni* iterabile che qualcosa che funziona solo con stringhe). In pratica comoda non e' comoda, semplice non e' semplice. Obietterai che a te e' comodo cosi'. Insisto: se hai pezzi di codice dove una variabile puo' essere indifferentemente una stringa o una lista di stringhe, hai un problema a monte. Non ha senso. Se invece non lo hai, allora non ti serve quella funzione. Questo senza nemmeno toccare il fatto che una funzione che fa da "toggle" non ha nessun senso. E' la cosa piu' scomoda nel mondo reale.

Come hai capito il tutto nasce da una serie di script scritti su ipython che hanno formato uno scheletro sul quale ho scritto il resto del codice. Perchè ho scritto quella funzione in quel modo? Boh!!! Non ricordo neanche quando l'ho scritta. So a cosa serve ovviamente, deve funzionare con liste di stringhe. Come la scriverei ora:

def FPKM(name_list):
    if name_list[0].endswith("_FPKM"):
        return [name[:-5] for name in name_list]
    else:
        return [name+"_FPKM" for name in name_list]


Perchè non l'ho cambiata prima? Non ci avevo fatto caso...man mano che facevo prove, scrivevo i test e altri pezzi di codice notavo cose scritte male e cercavo di scriverle meglio...quella funzione ha sempre funzionato e non ci ho fatto mai caso (com'è quella frase?! Non riparare una cosa finchè funziona). Non mi fraintendere, non sto dicendo che sia scritta bene, sto solo dicendo che ad uno esperto come te salta subito all'occhio, mentre ad uno come me ci devi andare a far caso.

Citazione
2. quell'import di Ipython e' atroce. Mi stai dando side effect globali solo perche' importo il tuo modulo? Non scherziamo. Si certo... e' comodo quando sei dentro ipython... ma mi sembra che tu volessi scrivere una libreria. Il che vuole dire che e' chi ti chiama a dirti come devi comportarti (e usa oggetti, non stato globale). Per me quelle poche righe sarebbero sufficienti a non considerare nemmeno il modulo, anche se fosse qualcosa che mi serve.
Immaginavo. In effetti ero indeciso se lasciarlo o no. Siccome qualsiasi cosa ho letto con ipython usano %matplotlib avevo pensato di lasciarlo come try. Il modulo che ho scritto nasce come una (specie) di alternativa a questo https://bioconductor.org/packages/release/bioc/html/cummeRbund.html in R ... era solo un mio: "Vediamo se riesco a farlo in python..." ...anche perchè ero più invogliato a farne uno nuovo in python che imparare ad usare bene R, non riuscivo a fare tutto quello che volevo e R mi dà sui nervi. Quindi l'idea è che chi usa il modulo vuole plottare alcuni grafici da qui ipython e %matplotlib. Non pensavo ci fossero side effect (ovviamente non li conosco, ma se dici che ci sono mi fido), ed ovviamente non vale la pena di usarlo. Chi lo vorrà aggiungerà la riga di codice direttamente in ipython.

Citazione
Non usare *mai* type(jjj) == qualcosa. C'e' scritto grosso come una casa nella PEP8. Se non e' scritto grosso abbastanza, ingrandisci il font. Perche' dovrebbe uscire dallo schermo e tatuarsi in ogni synapse. Se pensi ad oggetti, e' anche ovvio perche'. Se poi realizzi il fatto che python e' un linguaggio dinamico dove e' comune il duck typing, dovrebbe essere ancora piu' ovvio.
Ho fatto un po' di ricerce sul forum e mi sono uscite delle discussioni illuminanti su type(jjj) == qualcosa , isinstance e duck typing...rileggerò (e scriverò) il codice in modo diverso ora. Grazie. A mia discolpa posso dire che non sono l'unico a fare ancora questo errore  :) e devi ammettere che in PEP8 non è così grande....ma ho recepito il messaggio!

Citazione
In generale non vai molto avanti finche' non hai capito, interiorizzato e masterizzato SOLID. Se capissi cosa sono i principi SOLID *davvero* e rileggessi ogni pezzo del tuo codice pensando, principio per principio, a quanti ne stai violando, riusciresti a riscriverlo facilmente e da solo. E questo si applica anche per il resto. resta il fatto che e' davvero improbabile che tu abbia bisogno di determinare i tipi: stai solo scrivendo codice complicato perche' pensi che sia piu' facile da usare. :)

In termini di SOLID, (1) viola almeno SRP, OCP, LSP, e parzialmente DIP (ma questo va via se metti via gli altri 3). Non violi ISP solo perche' stiamo parlando di una funzione, quindi davvero non si applica...
Mai sentita prima 'sta roba...ho trovato cose molto interessati a riguardo...domani mi metto a studiare.

Citazione
I test apprezzo tantissimo e non sai quanto che li hai scritti. Sono una delle cose che puo' darti illuminazione.
Meno male...almeno una cosa :embarrassed:

Citazione
Per esempio... partiamo dal primo test, quello su test_functions_FPKM. Sfortunatamente stai usando un antipattern (assertion roulette, se interessa: http://xunitpatterns.com/Assertion%20Roulette.html). Se lo avessi scritto a modo (e se usi pytest, non hai bisogno di cacciare tutto in una classe che non e' una classe ma una collezione di test scorrelati) e avessi davvero coperto tutti i casi rilevanti, ti saresti accorto che ci vuole un delirio di codice da scrivere.
Devo ammettere che i test sono l'ultima cosa che ho studiato e ci devo tornare...

Citazione
Tipo il costruttore della classe principale e' un esempio da manuale di cosa *non* deve fare un costruttore. Ti manca struttura. Per una roba cosi' complicata hai bisogno di un Builder o qualcosa che ne faccia le veci. Non un __init__.
More info su questo sarebbero gradite...non ho trovato molto in giro a riguardo.

Citazione
Mi fermo qui... se vado avanti per tutto il modulo scrivo un libro.
Grazie dei consigli, avrò da studiare per le prossime settimane. E proverò a migliorare il codice e i test anzichè aggiungere nuove funzioni.

Offline domi84

  • python unicellularis
  • *
  • Post: 42
  • Punti reputazione: 0
    • Mostra profilo
Re:Il mio primo modulo...
« Risposta #3 il: Gennaio 07, 2018, 10:53 »
Giusto per completezza, l'ho riscritto così:

def _FPKM(name_list):
    """Either append or remove '_FPKM' to a string or an iterable.
    Return a string or a list respectively"""
    try:
        if name_list.endswith("_FPKM"):
            return name_list[:-5]
        else:
            return name_list+"_FPKM"
    except AttributeError:
        if name_list[0].endswith("_FPKM"):
            return [name[:-5] for name in name_list]
        else:
            return [name+"_FPKM" for name in name_list]

Non perchè non sappia se la mia variabile è una stringa o una lista, ma perchè concettualmente fa la stessa cosa su entrambi i tipi. L'alternativa sarebbe scrivere due funzioni:


def _FPKM(name):
        if name.endswith("_FPKM"):
            return name[:-5]
        else:
            return name+"_FPKM"

def _FPKM2(name_list)
        if name_list[0].endswith("_FPKM"):
            return [name[:-5] for name in name_list]
        else:
            return [name+"_FPKM" for name in name_list]


e usare nel codice la prima per le stringhe e la seconda per le liste.

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.447
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Il mio primo modulo...
« Risposta #4 il: Gennaio 13, 2018, 21:34 »
In realtà ero molto più focalizzato sul farlo funzionare (il codice che scrivo non sempre funziona al primo tentativo).

Il codice di "nessuno" funziona al primo colpo. Ovvero, piu' sei esperto e piu' lo fa... pero' il punto e' che c'e' sempre la volta che si fa un errore (anche sapendo esattamente cosa succede istruzione per istruzione). E' il motivo per cui piu' sei esperto e piu' (in teoria) resisterai alla tentazione di non scrivere i test. Personalmente ogni qual volta ho provato a farne a meno, mi sono trovato a doverli scrivere perche' trovavo bachi. Non ti preoccupare da questo punto di vista. La soluzione e' comunque semplice scrivi tanto codice e testalo per bene. Il resto (da questo punto) viene da se.

Poi c'e' la classica curva sulla complessita' del codice che scrivi: all'inizio si tendono ad usare costrutti molto semplici e codice poco idiomatico. Poi si comincia a prendere confidenza con le altre parti del linguaggio: si fa un periodo che culmina con codice molto idiomatico, eccessivamente terso, relativamente difficile da mantenere e soprattutto da capire al primo colpo. Infine ci si rende conto dell'abuso in questa direzione e ci si limita ad usare la roba piu' impestata solo quando e' davvero necessario. Se immagini l'equilibrio come una linea retta, di solito (per lo meno per quelli che hanno buoni istinti) tenderai ad avere una sininusoidale che converge sempre di piu' ad approssimare la retta. Tutto questo e' normale: tienilo d'occhio (perche' essenzialmente vuoi andare ad approssimare nel modo di cui sopra), ma non ti preoccupare troppo. Anche perche' nessuno ti puo' insegnare dove sta la famosa linea retta.

Citazione
Comunque le due che ho provato a seguire sono https://gist.github.com/sloria/7001839 e la PEP8 (non sto dicendo che le ho seguite a menadito, per carità. Solo che ci ho dato una lettura e ho cercato di seguirle). Se pensi che ce sia qualcuna in particolare che mi possa essere utile dimmelo pure (ti prego, non dirmi tutte le PEP, non voglio fare il pigro, ma sono quasi un migliaio......)

No, per carita'. La maggior parte delle PEP sono in realta' per aggiungere/documentare feature al linguaggio. Non e' che facciano male lette, ma hanno una funzione piuttosto diversa. Non necessariamente ti serve *ora* per migliorare il codice. Se hai dubbi su una specifico costrutto del linguaggi, per esempio, puoi volere leggere la PEP relativa. Ma fine.

Le cose che segui sono ottime. Devi solo continuare ad insistere. Poi oggettivamente ci sono questioni di diverso peso. Che so... il consiglio su active_elements vs. elements_active (al di la che non mi convince del tutto), e' chiaramente meno importante che, per dire, non usare type (dalla PEP8).

Comunque ti ci vuole un linter. Per i consigli puramente meccanici e stilistici, il tuo editor di testo di deve sottolineare le violazioni. Ad occhio ci si perde cose.

Ovviamente per le cose piu' concettuali questa cosa non funziona e devi pensarci attivamente.

> Come hai capito il tutto nasce da una serie di script scritti su ipython che hanno formato uno scheletro sul quale ho scritto il resto del codice.

Perche'... sono familiare con il codice di persone che non hanno background di software ma che vengono da altre discipline. Di solito manca la big picture: manca architettura e struttura. Ci sono funzioni che evidentemente non vuoi in una libreria o in un programma, ma possono essere comode in modo interattivo. L'esempio tipico e' matplotlib e simili: da un punto di vista del design dell'api e' terribile. Pero' alla gente e' piaciuta cosi' perche' di solito uno ci si approccia pasticciando in una roba interattiva.

In generale in una libreria vuoi meno side-effect possibili. Vuoi che tutto faccia poco perche' e' piu' componibile e predicibile (gli errori infilati dentro il cuore di un programma sono ben difficili da trovare). Viceversa se sei in una repl ispezioni passo passo quello che succede e vedi subito se qualcosa non torna. Ma probabilmente ti rompe scrivere un sacco di codice tutte le volte: un programma viene letto (ed eseguito) molte piu' volte che scritto. Una roba interattiva... viene scritta molte piu' volte che altro.

> Perchè ho scritto quella funzione in quel modo? Boh!!! Non ricordo neanche quando l'ho scritta. So a cosa serve ovviamente, deve funzionare con liste di stringhe. Come la scriverei ora:

Ed e' meglio. Io comunque eliminerei il fatto che sia bistabile: e' meglio avere cose idempotenti. Se hai una funzione idempotente, e sai che non ha bachi (perche' l'hai testata a modo -- ok, sai che non dovrebbe avere bachi) semplifica tutto. Se hai

xs = foo(ys)

non ti poni il problema di cosa era ys. Sai che xs rispettera' quello che foo prescrive. Per lo stesso motivo e' meglio che quando qualcosa non funziona scoppi in modo spettacolare piuttosto che andare avanti silenziosamente. Questo ti permette di dire che se stai eseguendo codice dopo xs che usa xs... xs e' la cosa giusta. Altrimenti o non stai eseguendo quel codice perche' sei esploso (ma le eccezioni a prima vista le stai usando in modo appropriato -- ci possono essere un paio di suggerimenti, ma non credo sia critico in questo momento). Se hai una funzione bistabile diventa tutto rilevante. Lo stato esatto in cui era ys quando lo hai passato e tutto quello che succede dopo.

E' essenzialmente simile al motivo per cui si preferisce roba immutabile a roba mutabile.

Citazione
Perchè non l'ho cambiata prima? Non ci avevo fatto caso...man mano che facevo prove, scrivevo i test e altri pezzi di codice notavo cose scritte male e cercavo di scriverle meglio...quella funzione ha sempre funzionato e non ci ho fatto mai caso (com'è quella frase?! Non riparare una cosa finchè funziona). Non mi fraintendere, non sto dicendo che sia scritta bene, sto solo dicendo che ad uno esperto come te salta subito all'occhio, mentre ad uno come me ci devi andare a far caso.

Solo perche' mi e' capitato un po' di volte alle 4 di notte che qualcosa in produzione non funziona e il codice scritto "male" mi ha reso determinare il problema piu' doloroso. Se scopri che una cosa ti fa male, impari a riconoscerla ed evitarla.

Citazione
Citazione
2. quell'import di Ipython e' atroce. Mi stai dando side effect globali solo perche' importo il tuo modulo? Non scherziamo. Si certo... e' comodo quando sei dentro ipython... ma mi sembra che tu volessi scrivere una libreria. Il che vuole dire che e' chi ti chiama a dirti come devi comportarti (e usa oggetti, non stato globale). Per me quelle poche righe sarebbero sufficienti a non considerare nemmeno il modulo, anche se fosse qualcosa che mi serve.
Immaginavo. In effetti ero indeciso se lasciarlo o no. Siccome qualsiasi cosa ho letto con ipython usano %matplotlib avevo pensato di lasciarlo come try. Il modulo che ho scritto nasce come una (specie) di alternativa a questo https://bioconductor.org/packages/release/bioc/html/cummeRbund.html in R ... era solo un mio: "Vediamo se riesco a farlo in python..." ...anche perchè ero più invogliato a farne uno nuovo in python che imparare ad usare bene R, non riuscivo a fare tutto quello che volevo e R mi dà sui nervi. Quindi l'idea è che chi usa il modulo vuole plottare alcuni grafici da qui ipython e %matplotlib. Non pensavo ci fossero side effect (ovviamente non li conosco, ma se dici che ci sono mi fido), ed ovviamente non vale la pena di usarlo. Chi lo vorrà aggiungerà la riga di codice direttamente in ipython.

Guarda quello che fai: import. Questo tecnicamente ha gia' side effect in se e per se, ma se quello che importi e' fatto bene, non ci sono problemi. Cioe', il problema e' che crei una dipendenza non necessaria in questo caso: la crei per modo di dire perche' hai avuto al buona idea (a questo punto) di sopprimere la faccenda se non lo trovi. Ok.
Poi pero' fai una configurazione globale su ipython. Quindi io mi troverei che a prescindere che io usi roba del tuo modulo, se lo importo o non lo importo ho behavior diversa su cose completamente separate. Immaginati una pull request che toglie una dipendenza al tuo codice (che so.. non lo usiamo piu'). Ecco... roba su ipython potrebbe funzionare diversamente. Secondo me importare il tuo modulo dovrebbe "solo" rendermi accessibile il codice che hai scritto. Nient'altro.

Citazione
Mai sentita prima 'sta roba...ho trovato cose molto interessati a riguardo...domani mi metto a studiare.

Ok: essenzialmente hai scoperto una disciplina intera, che potremmo chiamare software engineering (nel senso, ha tanti nomi a seconda dell'aspetto, ma essenzialmente si tratta di software engineering). Molto interessante. E' anche una delle cose che cambiano maggiormente nel tempo. Viene spessa insegnata male, usando concetti di anni prima. Si trova anche tanta roba pessima. Biosgna un po' capire dove leggere.

Il problema, poi, e' che finche' non si e' scritto un po' di software, non si capisce davvero di che si tratta. Si finisce per seguire ricette in modo zelota oppure per evitare tutta la baracca perche' non si vede davvero il punto delle cose. E', se vuoi, una "professione". Con quello che ne consegue in termini di acquisizione dell'esperienza.

Nota: consiglio caldissimamente di leggere e informarsi. Premetto anche che molta roba sembra assurda. Ma alla fine determinati autori ci prendono *tanto* e nel dubbio conviene seguire il consiglio. Capendo bene quando lo consigliano e cercando di capire il perche'.

Io insisto: se devi sapere una e una sola cosa di tutta la disciplina, secondo me e' "SOLID". I 5 articoli originali di Bob Martin sono free e puoi leggerli. Sono tosti. Ha scritto anche un bellissimo libro sull'argomento che e' un po' meno terso (eccellente lettura... se dovessi compare solo un libro...). La questione e' poi quanto vuoi investire su questa cosa (specie in termini di tempo). Cioe', per me e' un mestiere. Bella forza che ci investo. Per te e' uno strumento che puoi usare per fare altro (a meno che tu non decida di cambiar carriera). Probabilmente una comprensione sommaria dei fondamenti sono un buon investimento se devi scrivere codice.

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.447
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Il mio primo modulo...
« Risposta #5 il: Gennaio 14, 2018, 11:04 »


Citazione
Devo ammettere che i test sono l'ultima cosa che ho studiato e ci devo tornare...

Guarda... e' normale. Ci vanno anni (ma tipo una decina) prima di capire veramente come funziona la baracca. Meno se scrivi tantissimo codice, sei super severo con te stesso *e* trovi il modo di avere qualcuno di bravo davvero che ti fa le crs.

Ci sarebbe anche il suggerimento di fare TDD. Che e' anche buono ed ovvia al problema che il codice che scrivi non ha utenti reali della sua API (o per lo meno non utenti che si possono rendere conto di difetti e, nel caso, suggerirti migliorie). Tu che scrivi i test sei probabilmente la persona che si trovera' ad esporre piu' spigoli dell'API. Che va bene.
Il punto e'... come rendersi conto che qualcosa e' uno spigolo?

Stai imparando tutto quanto, quindi alla fine non vedo soluzione che non sia iterativa. Capisci meglio come vanno scritti i test, quindi ti accorgi che vecchi test che hai scritto non vanno tanto bene, e ti rendi conto che il motivo e' che l'api/la gestione dell'errore non e' fatta a modo, quindi puoi andare a studiare un po' per capire dove stai sbagliando, la metti a posto, quindi puoi mettere a posto i test e nel frattempo mentre rifattorizzi vedi che anche il nuovo modo di scriverli potrebbe essere migliorato con le cose che hai imparato sul design delle API, etc etc etc.

Citazione
> Tipo il costruttore della classe principale e' un esempio da manuale di cosa *non* deve fare un costruttore. Ti manca struttura. Per una roba cosi' complicata hai bisogno di un Builder o qualcosa che ne faccia le veci. Non un __init__.
More info su questo sarebbero gradite...non ho trovato molto in giro a riguardo.

Hint: si chiama __init__ perche' non e' nemmeno un vero costruttore in senso stretto. Quello che dovrebbe fare e' inizializzare l'oggetto.

Per intenderci... la creazione dell'oggetto deve dipendere da dove e' il file sul file system? Verrebbe da dire di no... una volta che e' in memoria, chettefrega. Quindi e' assurdo che sia nel costruttore. Quello appartiene ad un altro metodo/funzione. Etc etc etc. Impara solid e applicalo. Sara' piu' chiaro.

Offline domi84

  • python unicellularis
  • *
  • Post: 42
  • Punti reputazione: 0
    • Mostra profilo
Re:Il mio primo modulo...
« Risposta #6 il: Gennaio 24, 2018, 22:32 »
Citazione
Comunque ti ci vuole un linter. Per i consigli puramente meccanici e stilistici, il tuo editor di testo di deve sottolineare le violazioni. Ad occhio ci si perde cose.
E ci credo...non sapevo cosa fosse. O meglio credevo Spyder lo usasse, invece segnala proprio le cose grossolane e bisogna lanciarlo a parte.
Molto utile. Mi ha segnalato un sacco di schifezze che avevo scritto.

Citazione
Probabilmente una comprensione sommaria dei fondamenti sono un buon investimento se devi scrivere codice.
Infatti e’ quello che sto facendo al momento. Credo di aver capito SRP (effettivamente sembra una cosa ovvia ora). Prima il moduto aveva solo una classe enorme, ora sono 2 più piccole, ho una funzione costruttore...ma ho già capito che probabilmente servono più classi e mi sono appuntato tanti aggiustamenti da fare.

Ho cambiato il codice perchè questo unito alle cose che aveva sottolineato liter mi sembrava abbastanza per dare una ripulita. Magari c’è una seconda persona al mondo che sta usando il modulo e non mi sembrava giusto lasciarlo com’era.

Credo di aver capito anche DIP, anche questo sembra ovvio ora. Al momento il mio modulo dipende direttamente da seaborn e pandas. Se per qualche ragione volessi usare matplotlib o numpy (o altro) semplicemente non va. Il fatto è che non ho la più pallida idea di come si crei una interfaccia astratta. Devo ammettere che al momento non ne sento l’urgenza, ma vedrò quello che riesco a studiare/fare a riguardo.

Abbastanza nebulose mi sono ancora il resto dei (S)OLI(D) . La difficoltà maggiore e’ che non ho trovato nessuno che tratti I principi facendo degli esempi con Python (certo, I principi sono generali e funzionano anche con altri linguaggi ma sarebbe più facile avere esempi in python). Continuo a studiare...vediamo dove arrivo.

Citazione
Ed e' meglio. Io comunque eliminerei il fatto che sia bistabile: e' meglio avere cose idempotenti. Se hai una funzione idempotente, e sai che non ha bachi (perche' l'hai testata a modo -- ok, sai che non dovrebbe avere bachi) semplifica tutto.

[ironico] Eh, certo, come no!  [/ironico]
Ci ho messo parecchio a capire cosa volevi dire. Capito. Annotato. Appena ho un po’ di tempo libero mi ci metto.

Citazione
Stai imparando tutto quanto, quindi alla fine non vedo soluzione che non sia iterativa.

Si, infatti, al momento non vedo altra strada.
Grazie mille dei consigli! Ora ho tanto da studiare/lavoro da fare.

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.447
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Il mio primo modulo...
« Risposta #7 il: Aprile 02, 2018, 12:51 »

Credo di aver capito anche DIP, anche questo sembra ovvio ora. Al momento il mio modulo dipende direttamente da seaborn e pandas. Se per qualche ragione volessi usare matplotlib o numpy (o altro) semplicemente non va. Il fatto è che non ho la più pallida idea di come si crei una interfaccia astratta. Devo ammettere che al momento non ne sento l’urgenza, ma vedrò quello che riesco a studiare/fare a riguardo.

Si allora... in linea di principio hai ragione. Ora, avere una dipendenza *diretta* da Pandas non e' un problema (se usi Pandas). Il problema e' quanto capillare e' la dipendenza. Se tutto quello che riguarda Pandas fosse limitato al componente che effettivamente fa i conti, questo sarebbe ok. L'idea e' che il resto dell'applicazione dovrebbe essere indipendente da Pandas.

Dopo di che, spesso e volentieri non si vuole essere cosi' puliti perche' e' un costo. Insomma, bisognerebbe valutare caso per caso. Per applicare DIP a quanto sopra, quello che dovresti fare e' astrarre la parte che fa i conti, sicche' la parte che decide quali conti fare veda semplicemente oggetti business su cui applica operazioni relativamente di alto livello e poi l'implementazione di questo fa le cose con Pandas.

Questo caso specifico e' una buona idea su una applicazione di una certa dimensione, ma fa andare alle stelle i costi su una cosa piu' piccola. Potresti scoprire che il layer di astrazione e' quasi grosso come il resto dell'applicazione. D'altra parte... se hai una parte che fa plotting (e solo plotting) e una che fa conti... potrebbe essere una buona idea usare DIP nel mezzo, visto che potresti avere facilmente diversi engine di plotting -- potenzialmente anche per gestire diverse versioni di matplotlib, se la retrocompatibilita' e' importante -- e questo diventa banale.

Citazione
Abbastanza nebulose mi sono ancora il resto dei (S)OLI(D) .

Guarda... LSP e' "facile facile" e "molto complesso" allo stesso tempo. E' facile perche' ti dice chiaramente quando si applica. Parla di ereditartieta': se non hai ereditarieta' non ti tange davvero. E' facile perche' e' ovvio: se A e' un sottotipo di B, il programma deve funzionare "allo stesso modo" passando A dove ci si aspetta B.

Ora, prova a negare questa proprieta' e vedi che sarebbe un problema. "Ho un sottotipo A di B che quando uso al posto di B fa si che il programma non funziona piu' in modo atteso." Non sarebbe comodo no?

Fondamentalmente Liskov ti dice come corollario che se hai due oggetti che non sono intesi per essere sostituibili, allora non devono essere in una relazione di ereditarieta'. Se usi l'ereditarieta' e' perche' *vuoi* potere passare A al posto di B senza preoccupartene. In altre parole vuoi che lo specifico sottotipo sia un dettaglio implementativo. Spesso questo vuole anche che ci sia qualche dp creational (che so... una factory) che gestisce la selezione del tipo.

Difficile perche'... tante cose apparentemente semplici non sono cosi' semplici (o non si devono fare) perche' violerebbero Liskov.

ISP e' un'altro principio naturale, ma si comincia a vedere il problema quando si fa sul serio. Essenzialmente ti dice di controllare le dipendenze. Per esempio se tu stessi scrivendo una webapp, ti direbbe che se anche supporti sia Postgres che MySQL dovrebbe fare in modo tale che i vari moduli non richiedano dipendenze da entrambi i driver, ma essenzialmente se usi solo uno dei due ti basta uno dei due driver. Questa e' una semplificazione grossolana, ma ti fa vedere al tipo di conseguenze assurde con cui ti trovi se non lo rispetti. Questo ti e' ostico perche' non hai davvero il problema di fare software per altri. In generale scrivi il tuo software e lo "usi" dallo stesso posto in cui lo scrivi. Tutti questi problemi in un certo senso non ti toccano. Toccherebbero chi dovesse usare il tuo sw. E' una buona idea abituarsi a rispettarlo. Basta fare le cose giuste e avere un po' di attenzione.

OCP e' se vuoi quasi semplice come SRP, ma e' piu' ambiguo. Nel senso che non e' ovvio cosa sia una extension e cosa sia una modification. Non e' definibile in modo "matematico". Sostanzialmente e' una cosa che ti deriva da avere capito quale sia il dominio applicativo (per cui capisci quali sono cose su cui puoi avere estensione). Ma in generale, se sei estremamente rigoroso nell'applicare SRP e ISP (quindi tutti i tuoi oggetti hanno una sola responsabilita' e tutte le tue interfacce sono molto coese) hai di fatto anche individuato quale sia l'area di estensione. Se hai anche DIP applicato a modo, di fatto una buona parte di OCP si risolve scrivendo oggetti compatti che fanno una sola cosa e ad usare diverse implementazioni per fare cose diverse, che rende ancora piu' facile lavorare con OCP.

> La difficoltà maggiore e’ che non ho trovato nessuno che tratti I principi facendo degli esempi con Python (certo, I principi sono generali e funzionano anche con altri linguaggi ma sarebbe più facile avere esempi in python). Continuo a studiare...vediamo dove arrivo.

E vedi... questa e' una falsa impressione. Il fatto che Python non diventi doloroso come Java (semplicemente meno mantenibile) quando non applichi a modo SOLID e' l'unico svantaggio. Ma e' come dire... e' peggio perche' va meno male... che sebbene sia vero e' anche un po' buffo.

Questi sono principi talmente generali che, *oggettivamente* si applicano perfino a linguaggi che non sono ad oggetti. Si applicano perfino in fase di definizione dell'architettura (dove invece che "oggetto" hai "componente" o "servizio"). Non puo' essere un problema la mancanza specifica di esempi in Python (non dovrebbe esserlo). Vuole solo dire che devi scrivere codice e validarlo mentalmente ogni volta per imparare ad usare questi principi.

Offline domi84

  • python unicellularis
  • *
  • Post: 42
  • Punti reputazione: 0
    • Mostra profilo
Re:Il mio primo modulo...
« Risposta #8 il: Novembre 11, 2018, 19:15 »
Scusa se non ho risposto prima, ma sono stato preso da altre cose...
Grazie delle spiegazioni sul SOLID.
Durante le varie vacanze di quest'anno ho messo mano  un paio di volte al codice che avevo scritto all'inizio. Credo di poter riassumere tutte le modifiche che ho fatto con un tuo suggerimento nel primo post:

... etc etc etc. Semplifica.

Mi sono accorto che più semplificavo, più trovavo codice che era inutile e al contempo più era semplice aggiungere nuove funzioni...
E non ho ancora finito  ;) (aaahhh...ad averci più tempo)

Grazie ancora.