tag:blogger.com,1999:blog-88013122446427144882026-03-08T14:01:08.849+01:00Python in WindowsAppunti, consigli, guide, tutorial...ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]Blogger44125tag:blogger.com,1999:blog-8801312244642714488.post-2473755328498443522022-10-30T13:13:00.003+01:002022-10-30T13:13:36.754+01:00Python 3.11 è tra noi<p>Puntuale ogni anno, a fine ottobre arriva una nuova versione del nostro linguaggio preferito. L'anno scorso il logo sembrava il <a href="https://www.python.org/downloads/release/python-3108/" rel="nofollow" target="_blank">tatuaggio di una band di motociclisti</a>, quest'anno è perfetto come <a href="https://www.python.org/downloads/release/python-3110/" rel="nofollow" target="_blank">etichetta della candeggina</a>... ci sono molte cose per cui Python si fa apprezzare, e la grafica non è tra queste.&nbsp;</p><p>Ve detto che anche le novità sono molto meno roboanti dell'anno scorso, quando la nuova istruzione "match" aveva fatto molto rumore, se ricordate. Quest'anno l'unica cosa rilevante sembra il <a href="https://peps.python.org/pep-0654/" rel="nofollow" target="_blank">ragguppamento di eccezioni</a>, e la nuova "except *": tutto davvero utile (ed era anche ora), ma certo molto meno impattante.&nbsp;</p><p>A proposito: chi si ricorda il pasticcio della PEP 563 sulla valutazione ritardata delle annotazioni, che doveva entrare già l'anno scorso ed era poi stata rimandata a quest'anno, può mettersi definitivamente l'anima in pace: la PEP 563 <a href="https://mail.python.org/archives/list/[email protected]/message/VIZEBX5EYMSYIJNDBF6DMUMZOCWHARSO/" rel="nofollow" target="_blank">non c'è ancora, e forse non ci sarà mai</a>.&nbsp; <br /></p><p>Anche per quanto riguarda il nostro piccolo specifico, ovvero le novita per Windows, c'è ben poco da raccontare: la cosa più significativa è l'aggiornamento del launcher "py.exe", che d'ora in poi dovrebbe essere capace di interpretare <a href="https://peps.python.org/pep-0514/#company" rel="nofollow" target="_blank">i tag che identificano il distributore</a> di una release, oltre che semplicemente il numero di versione. Questo, in futuro, potrebbe permettere di usare py.exe anche per avviare i Python di altre distribuzioni (Anaconda, etc.), oltre alle versioni "canoniche".&nbsp;</p><p><br /></p><p>Nel mio piccolo, ho aggiornato come di consueto il mio libro <a href="https://leanpub.com/pythoninwindows" rel="nofollow" target="_blank">Python in Windows</a>: di ritorno dopo un periodo di super-lavoro per altre cose, non sono riuscito a fare una revisione molto approfondita questa volta. Mi riprometto di pubblicare una versione "intermedia" tra qualche mese, per seguire le novità sui pacchetti, e così via. Nel frattempo potete scaricare l'aggiornamento gratuitamente, se avete già acquistato il libro (o acquistarlo se ancora non lo avete, naturalmente!).&nbsp;</p><p>Anche la mia <a href="https://pytutorial-it.readthedocs.io" rel="nofollow" target="_blank">traduzione del Tutorial ufficiale</a> procede in sincrono senza perdere un colpo. Potete leggere la versione corrente (Python 3.11) o selezionare la versione che preferite.&nbsp;</p><p>Spero di avere il tempo di tornare a dedicarmi al mondo di Python (e magari anche a questo blog) con più continuità... Nel frattempo, happy coding a tutti!</p><p>&nbsp;</p>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-58243431062594421602022-03-26T13:27:00.003+01:002022-03-26T13:27:31.831+01:00Il tutorial è... sempre più complicato<p>Era da un po' che non aggiornavo la traduzione del tutorial di Python, ma oggi ho dedicato qualche ora a riportarmi in pari. Di solito le modifiche da sincronizzare sono delle piccole correzioni, accorgimenti, migliorie cosmetiche. A volte però capita per le mani qualcosa di più grosso, che ti dà da pensare.&nbsp;</p><p>In questa tornata, il capitolo 8 "Errori ed eccezioni" è stato particolarmente tormentato. Per prima cosa, è stato deciso di rimuovere il consiglio di far derivare tutte le eccezioni di un modulo da una classe-madre comune. Potete vedere il testo rimosso <a href="https://pytutorial-it.readthedocs.io/it/python3.8/errors.html#eccezioni-personalizzate" rel="nofollow" target="_blank">nella vecchia versione 3.8</a>; adesso, dalla 3.9 in avanti non esiste più. La ragione è che in realtà si tratta di una vecchia "buona pratica" che oggi è considerata dubbia. Un problema, <a href="https://bugs.python.org/issue34538#msg324312" rel="nofollow" target="_blank">che è stato fatto notare</a>, è che in realtà si tratta di una pratica ancora largamente adottata nei moduli della libreria standard.&nbsp;</p><p>Un altro problema, che invece <i>non </i>è stato fatto notare, è che il testo rimosso non viene rimpiazzato da nessun altro consiglio. Il problema, molto concreto per un principiante, di come organizzare le eccezioni personalizzate in un modulo, d'ora in poi non trova più risposta nel tutorial.&nbsp;</p><p>Nel mio piccolo, sono sempre stato contrario alla pratica di rimuovere informazioni dal tutorial; mi è anche capitato di far presente la mia opinione nelle giuste sedi, con successo non brillantissimo, diciamo. Il punto è che, già così com'è, il tutorial <a href="https://pythoninwindows.blogspot.com/2020/05/il-tutorial-di-python-e-difficile.html">è un discreto pasticcio</a>... Tuttavia, in attesa di una risistemazione più approfondita, ogni informazione che si toglie è un'informazione che va perduta. E non solo l'informazione in sé, ma proprio il fatto che quell'informazione <i>fosse presente</i>. Se in futuro qualcuno dovesse risistemare quel capitolo, non avrà più modo di sapere che un tempo lì c'era un consiglio pratico su come organizzare le eccezioni (per quanto la soluzione potesse essere discutibile). Il problema non verrà mai più posto, e un consiglio non verrà mai più dato.&nbsp;</p><p><br /></p><p>Per contro qualche giorno dopo, nello stesso capitolo, è stato aggiunto un paragrafo finale sui <a href="https://peps.python.org/pep-0654/" rel="nofollow" target="_blank">gruppi di eccezioni</a>. Si tratta di una nuova feature che dovrà atterrare nella 3.11, e pertanto potete leggere il testo in anteprima solo <a href="https://pytutorial-it.readthedocs.io/it/latest/errors.html#emettere-e-gestire-eccezioni-multiple-non-correlate" rel="nofollow" target="_blank">nella versione futura del tutorial</a>.&nbsp;</p><p>Ora, vi sfido (cioè... vi invito!) a leggere questo nuovo paragrafo. Onestamente, io non l'ho capito. Cioè, l'ho copiato, l'ho tradotto... ma non non l'ho proprio capito. Confesso che non sapevo di questa novità, e il testo del tutorial è stata la mia prima scoperta. Se anche voi non riuscite a orientarvi, non posso biasimarvi. Si tratta di una <i>feature </i>molto avanzata, nata dall'esigenza specifica di un <i>concurrency framework</i> come <a href="https://trio.readthedocs.io/en/stable/" rel="nofollow" target="_blank">Trio</a>. Dopo aver letto la PEP e un po' di altra documentazione, più o meno ho capito di che cosa si tratta.&nbsp;</p><p>Il problema, qui, non è certo che i gruppi di eccezioni siano una cattiva idea (sono una buona idea), o che servano a poco (servono). Ma era proprio necessario introdurli nel tutorial, e in modo così criptico? In pochi giorni, nello stesso capitolo, abbiamo perso un paragrafo che documentava una pratica basilare, e abbiamo acquisito un paragrafo su un concetto oscuro e avanzato. Non mi sembra un buon bilancio per un tutorial, diciamo.&nbsp;</p><p><br /></p><p>La verità è che, purtroppo, il tutorial di Python è una specie di bacheca aperta, dove chiunque o quasi può attaccare il suo biglietto (e spostare, modificare, etc.). Siccome non c'è un piano redazionale coerente né una redazione che vaglia le proposte, evolve giorno per giorno con la trovata del momento. Si procede a colpi di BPO e di discussioni sul <a href="https://bugs.python.org" rel="nofollow" target="_blank">bug tracker</a>, e poi di PR e discussioni su GitHub, caso per caso, finché un <i>core developer</i> autorizza il cambiamento; ma non c'è un piano coerente su che cosa dovrebbe andare nel tutorial, e che cosa no, e perché, e in che modo. Intendiamoci, nella maggior parte dei casi gli autori hanno comunque la coscienza necessaria a mantenere una rotta...<br /></p><p>E poi talvolta invece scappa la mano.&nbsp;</p><p><br /></p>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-46436607680331104422021-10-06T21:20:00.001+02:002021-10-06T21:20:23.855+02:00È arrivato quel periodo dell'anno...<p>&nbsp;... quando un'altra estate se n'è andata, io mi sento <a href="https://pythoninwindows.blogspot.com/2020/10/python-39-e-qui-e-io-sto-invecchiando.html">sempre più vecchio</a> e una nuova versione di Python arriva puntuale, subito prima di Halloween.&nbsp;</p><p>E quindi Python 3.10 è tra noi, accompagnato <a href="https://www.python.org/downloads/release/python-3100/" rel="nofollow" target="_blank">da una specie di logo</a> un po' tamarro (ma ora io dico... c'è rimasto qualcuno che ricorda <a href="https://en.m.wikipedia.org/wiki/File:Python_logo_1990s.svg" rel="nofollow" target="_blank">com'era una volta</a>, senza tanti serpenti?). Nel frattempo Python 3.8 va fuori supporto, e questo significa che adesso ufficialmente non c'è più una versione di Python "viva" che si installa su Windows 7. Il che va benissimo, a essere onesti: Windows 7 è un ferrovecchio, non ha senso usarlo ancora.&nbsp;</p><p>&nbsp;</p><h3 style="text-align: left;">E dunque, questo switch... <br /></h3><p><a href="https://docs.python.org/3/whatsnew/3.10.html" rel="nofollow" target="_blank">Che cosa c'è di nuovo in questa versione?</a> Beh, la cosa enorme è ovviamente la nuova sintassi per "switch", <a href="https://pythoninwindows.blogspot.com/2021/06/tutorial-varie-ed-eventuali.html">come avevo anticipato</a> a proposito della traduzione del Tutorial. Niente da dire, è una feature potenzialmente molto interessante... e anche molto laboriosa e piena di corner case insidiosi. La sua preparazione è stata un parto lungo e travagliato da parte degli sviluppatori, tanto che adesso "switch" è documentato non da una, ma bensì da tre PEP, addirituttura: la <a href="https://www.python.org/dev/peps/pep-0634/" rel="nofollow" target="_blank">PEP 634</a> spiega di che cosa si tratta, la <a href="https://www.python.org/dev/peps/pep-0635/" rel="nofollow" target="_blank">PEP 635</a> cerca di giustificare perché è stato fatto in questo modo, e infine la <a href="https://www.python.org/dev/peps/pep-0636/" rel="nofollow" target="_blank">PEP 636</a> è niente meno che un tutorial che spiega come si usa in pratica tutto l'affare.&nbsp;</p><p>Quando vedete che per una feature ci sono 3 PEP, già capite che c'è stato da discutere (eufemismo). Ma tutta la vicenda è passata attraverso almeno un'altra PEP bocciata e un diluvio di commenti e proposte lungo più di un anno. Si tratta di sicuro della decisione più controversa che è stata presa da quando Guido si è ritirato da BDFL della comunità (e si era ritirato, guarda caso, in gran parte per le conseguenze di un'altra lunghissima discussione, all'epoca era sul walrus operator).&nbsp;</p><p>&nbsp;</p><h3 style="text-align: left;">Anche la PEP-tutorial, ci mancava<br /></h3><p>E qui apro una parentesi. A mia memoria, nessuno aveva mai pensato di usare una PEP solo a scopo di documentazione. Naturalmente quasi tutte le PEP contengono, in qualche modo, della documentazione: ma si tratta di osservazioni molto tecniche, utili al massimo per chi desidera approfondire un argomento e capire le ragioni dietro a certe scelte. Ma addirittura un tutorial in una PEP... è parecchio strano, devo dire. E apre tutta una serie di domande sullo stato effettivo della documentazione di Python che, <a href="https://pythoninwindows.blogspot.com/2020/05/il-tutorial-di-python-e-difficile.html">come ho già avuto modo di notare</a>, talvolta è... problematico.</p><p><br /></p><h3 style="text-align: left;">Quanto al resto, non c'è molto da dire</h3><p>A parte "switch", le novità (giustamente) non sono moltissime. Un punto curioso da notare è che il <a href="https://www.python.org/dev/peps/pep-0617/" rel="nofollow" target="_blank">nuovo parser PEG</a> per la sintassi di Python, introdotto dietro le quinte nella 3.9, sta cominciando a dare i suoi frutti.&nbsp;</p><p>Mettere a punto un nuovo parser è il progetto principale a cui Guido (con Pablo Salgado) si è dedicato quando si è potuto sgravare dei suoi compiti burocratici di BDFL... e si vede che aveva proprio voglia di lavorarci perché pare che sia un gioiellino. Le conseguenze si faranno vedere per molti anni nel futuro, permettendo alla sintassi di Python di arricchirsi, all'occorrenza, superando diverse ristrettezze che affliggevano il vecchio parser.&nbsp;</p><p>Una prima conseguenza è che adesso è possibile andare a capo nei "with" multipli, se si usano le parentesi: in Python 3.8 questo è un errore di sintassi:</p><p></p><pre>with (open("blabla") as f1,&nbsp;<br /> open("blabla") as f2):<br /> pass<br /></pre><p></p><p>&nbsp;In Python 3.9 già funziona ma non è documentato, e adesso in Python 3.10 è diventato ufficiale. <br /></p><p>&nbsp;</p><h3 style="text-align: left;">Oh mamma, non è che arriva Python 4?!</h3><p>Ma forse l'aspetto più curioso di questa nuova versione riguarda una... non-aggiunta, in realtà. La <a href="https://www.python.org/dev/peps/pep-0563/" rel="nofollow" target="_blank">PEP 563</a>, sulla valutazione ritardata delle type annotation, è stata... ritardata (con gioco di parole inevitabile) forse alla 3.11, forse anche dopo. Di per sé la feature proposta è molto utile e anche molto richiesta, tanto in effetti <i>esiste già</i>, addirittura dalla 3.7, ma solo come un'opzione attivabile con "from __future__ import annotations".&nbsp;</p><p>Era stato deciso di rendere definitiva questa feature, ma poi ci si è resi conto che avrebbe rotto troppo codice esistente in giro... e insomma non si sa più bene come procedere. Il fatto è che questa valutazione ritardata è <i>parecchio </i>non-retrocompatibile, in effetti: al punto che all'epoca si era deciso che sarebbe stata introdotta in via definitiva solo in (rullo di tamburi) Python 4.&nbsp;</p><p>Ma nel frattempo non sembra proprio che ci sia un grande appetito per (squillo di trombe) Python 4, dopo tutto il pasticcio epico che era già successo all'epoca di Python 3. Quindi non ci sono piani precisi per una quarta versione del linguaggio, se non l'assicurazione generica che questa volta le modifiche saranno molto, molto più indolori. E però intanto delle feature non-retrocompatibili come la PEP 563 restano nel limbo, e qualcosa bisognerà pur decidere, prima o poi. Staremo a vedere. Nel frattempo, <a href="https://mail.python.org/archives/list/[email protected]/thread/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/" rel="nofollow" target="_blank">qui </a>trovate le ragioni della non-release della feature, se volete approfondire. <br /></p><p><br /></p><h3 style="text-align: left;">Io la mia parte l'ho fatta, comunque</h3><p>Nel mio piccolo, sono lieto di confermare che l'aggiornamento della <a href="https://pytutorial-it.readthedocs.io" rel="nofollow" target="_blank">traduzione del Tutorial</a> è sempre puntuale: adesso la versione per Python 3.10 è quella di default, e tutte le versioni sono in sincrono con gli originali.&nbsp;</p><p>Ho anche pubblicato una nuova versione (la 3) del mio libro <a href="https://leanpub.com/pythoninwindows" rel="nofollow" target="_blank">Python in Windows</a>, aggiornando tutti i riferimenti a Python 3.10, controllando i cambiamenti, cambiando le figure e così via. Ho anche inserito alcune piccole correzioni e qualche nota nuova, grazie anche alle segnalazioni dei lettori (che ci sono! grazie! e mi segnalano i problemi! grazie due volte!).&nbsp;</p><p>Vi invito a scaricare la versione aggiornata, se lo avete già comprato... o a comprarlo, se non lo avete ancora fatto (ehm... non costa poi molto, dai).&nbsp;</p><p>&nbsp;</p><p>(Nel frattempo sto lavorando a una cosa nuova... ma ho ancora paura di parlarne. Forse tra qualche mese. Vedremo.)<br /></p><p><br /></p>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-61135185047887522672021-06-29T18:32:00.006+02:002021-06-29T18:32:47.389+02:00Tutorial, varie ed eventuali<p>Back to <strike>work</strike> have fun! Ho appena terminato un periodo di... lavori forzati, nel senso che un lavoro mi ha assorbito completamente e non sono riuscito a fare nulla di neanche lontanamente pythonico. Adesso le cose sono tornate tranquille, e sto cominciando a riprendere il giro.&nbsp;</p><p>Ci sono un sacco di cose in arretrato... dovrei aggiornare <a href="https://leanpub.com/pythoninwindows" rel="nofollow" target="_blank">il mio libro su Python in Windows</a>, portare avanti <a href="https://leanpub.com/capirewxpython" rel="nofollow" target="_blank">l'altro mio libro su wxPython</a>, tornare a rispondere a qualcuno <a href="https://forumpython.it/" rel="nofollow" target="_blank">sui forum</a>... ma per prima cosa mi sono dedicato a sincronizzare la mia <a href="https://pytutorial-it.readthedocs.io" rel="nofollow" target="_blank">traduzione del tutorial</a>, che era rimasta un po' indietro.&nbsp;</p><h3 style="text-align: left;">Python 3.10 è qui, e anche il suo tutorial!</h3><p>La prima cosa che ho visto (ma me l'aspettavo...) è che nel frattempo <a href="https://www.python.org/downloads/release/python-3100b3/" rel="nofollow" target="_blank">Python 3.10</a> è entrato nella sua fase "beta": questo significa, tra le molte altre cose, che ormai ha un branch dedicato <a href="https://github.com/python/cpython/tree/3.10" rel="nofollow" target="_blank">nella repository</a>, <a href="https://docs.python.org/3.10/" rel="nofollow" target="_blank">nella documentazione</a>, e quindi anche <a href="https://docs.python.org/3.10/tutorial/index.html" rel="nofollow" target="_blank">nel tutorial</a>. Dal punto di vista tecnico, questo significa che il "master" branch adesso contiene il lavoro per il futuro Python 3.11 (e a proposito di quel "master"... ci arrivo tra poco), mentre la 3.10 ha ormai il suo "topic branch" dedicato.&nbsp;</p><p>La mia traduzione segue lo stesso schema, e si arricchisce anche lei di un branch. Adesso, accanto alla documentazione per la <a href="https://pytutorial-it.readthedocs.io/it/python3.9/" rel="nofollow" target="_blank">versione corrente</a>, è possibile già leggere quella per Python 3.10 e quella per Python 3.11 (al momento identica). Come sempre, usate il selettore di ReadTheDocs in basso a destra per cambiare versione. <br /></p><p>Adesso quindi sono 5 le versioni del tutorial coperte dalla traduzione: va detto però che le versioni 3.7 e 3.8 non vengono più modificate da tempo, quindi quelle "calde" restano 3.&nbsp;</p><p>Anche se il mio lavoro di traduzione non ha suscitato l'interesse che mi auguravo (e soprattuto <a href="https://pythoninwindows.blogspot.com/2020/10/chi-vuole-tradurre-la-documentazione-di.html">l'interesse a collaborare</a>!), devo dire che mi dà comunque soddisfazione mantenerla aggiornata e quindi non ho problemi a continuare da solo, per i pochi (pochissimi) interessati. Del resto, il mio sistema di sincronizzazione con Git mi toglie un sacco di fastidi, e anche quest'ultimo aggiornamento particolarmente delicato non mi ha preso più di un paio d'ore.&nbsp;</p><h3 style="text-align: left;">E anche "match" è in arrivo...</h3><p>Una delle novità più attese per Python 3.10 è la <a href="https://www.python.org/dev/peps/pep-0634/" rel="nofollow" target="_blank">PEP 634</a> che introduce un meccanismo di "pattern matching" e una nuova parola chiave: "match", appunto.&nbsp;</p><p>Detto in breve, <i>potrebbe sembrare</i> un equivalente delle istruzioni "di switch" ("case" etc.) di altri linguaggi. In realtà è una struttura molto più sofisticata e potente, che permette di fare veramente delle cose interessanti. Per il momento ho solo dato un'occhiata a qualche articolo, ma quello che ho visto mi ha veramente interessato.&nbsp;</p><p>L'istruzione "match" è documentata nel tutorial, ovviamente solo a partire dalla versione per Python 3.10. Dal momento che anche questo nuovo paragrafo è stato tradotto... beh, credo proprio che sia il primo testo in Italiano che affronta estesamente l'argomento! (Vedete che vantaggi, a seguire la mia repo GitHub!). Potete comodamente farvi un'idea di "match" anche in Italiano, <a href="https://pytutorial-it.readthedocs.io/it/python3.10/controlflow.html#l-istruzione-match" rel="nofollow" target="_blank">leggendo qui</a> la presentazione che ne fa il tutorial. Ma ci sono numerosi altri casi d'uso altrettanto interessanti...</p><h3 style="text-align: left;">Niente più "master", finalmente.</h3><p>Infine, una novità di forma, ma che personalmente saluto con molta soddisfazione. Python ha approfittato del fork del nuovo branch 3.10 per eliminare una volta per tutte il suo "master" branch (la soluzione tecnica era stata <a href="https://discuss.python.org/t/communitys-take-on-changing-master-branch-to-main/4462/3" rel="nofollow" target="_blank">proposta da Zachary Ware</a> ormai due anni fa). Adesso il branch principale della repository di Python <a href="https://github.com/python/cpython" rel="nofollow" target="_blank">si chiama "main"</a>, rinunciando definitivamente a una terminologia involontariamente razzista.&nbsp;</p><p>Questo cambio di nome va incontro anche a una <a href="https://sfconservancy.org/news/2020/jun/23/gitbranchname/" rel="nofollow" target="_blank">policy ufficiale</a> di Git (e quindi GitHub), dove ormai da tempo le nuove repository vengono create con il nome di default "main" e non più "master", e per quelle vecchie <a href="https://github.com/github/renaming" rel="nofollow" target="_blank">si invita a cambiare nome</a>.&nbsp;</p><p>In realtà è da anni che la comunità degli sviluppatori, nel complesso, cerca di liberarsi dalle terminologie "poco sensibili" che erano ormai entrate nell'uso (master/slave, blacklist/whitelist...). Il movimento Black Lives Matters ha accelerato questi cambiamenti, e la comunità Python, come di consueto, è stata una delle più sensibili e reattive sul tema.&nbsp;</p><p>Ora, mi rendo perfettamente conto che da noi c'è sempre questa moda di dire, con un bel po' di spocchia, che queste americanate politically correct non sono davvero importanti, che quello che conta è sempre qualcos'altro (e non si specifica mai che altro), eccetera. In questo periodo di Europei di calcio, abbiamo visto questo esercizio di benaltrismo fin troppo spesso, come sicuramente saprete.&nbsp; <br /></p><p>Ma intanto i simboli sono pur sempre simboli, e se possiamo intervenire perché non farlo? Benissimo quindi la decisione di rinunciare a "master". La repository della mia traduzione non si è ancora adeguata per il momento, perché volevo prima di tutto riportarla in sincrono. Ma nel prossimo futuro, magari tra qualche giorno, conto senz'altro di cambiare anch'io, e lo farò con una piccola dose di soddisfazione personale: non conterà niente, ben altre sono le cose importanti... ma intanto la piccola folla di sette persone (!!) che hanno messo una star sulla mia repo, lo saprà. Ed è già qualcosa, in attesa di tutto il "ben altro". <br /></p><p><br /></p>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]1tag:blogger.com,1999:blog-8801312244642714488.post-10506081883540035912021-03-06T18:45:00.000+01:002021-03-06T18:45:02.241+01:00Arrow lascia lo ZeroVer!<p>&nbsp;...E così siamo arrivati alla <a href="https://arrow.readthedocs.io/en/latest/releases.html" rel="nofollow" target="_blank">versione 1</a> di Arrow! Non che otto anni siano poi molti, eh.&nbsp;</p><p>Arrow è uno dei pacchetti più amati e conosciuti per la gestione delle date/ore in Python. Anche adesso che la libreria standard <a href="https://pythoninwindows.blogspot.com/2020/10/python-39-e-qui-e-io-sto-invecchiando.html">ha fatto passi da gigante</a> in questo settore, Arrow continua a essere una scelta raccomandabile per chi vuole sviluppare applicazioni di <i>calendaring </i>senza impazzire dietro alle complessità dell'algebra delle date, delle timezone e così via. Arrow è apprezzato dai nerd del <i>calendaring</i> come me, ma è anche, più semplicemente, un'API sensata e ragionevole per trattare problemi comuni come "aggiungi 3 settimane" o "all'ora corrispondente di New York". È un pacchetto di grande successo, con 50mila download al giorno e oltre 7000 "star" su GitHub. Viene impiegato come dependency in molte altre librerie, ed è probabilmente installato in migliaia di scenari di produzione.&nbsp;</p><p>Eppure... fino a oggi non aveva mai raggiunto la versione 1.0, e quindi lo stato di "production/stable" su PyPI.&nbsp;</p><p>Che cosa significa? Da un punto di vista tecnico, seguendo le regole del <a href="https://semver.org/" rel="nofollow" target="_blank">semantic versioning</a> (che Python stesso adotta, <a href="https://www.python.org/dev/peps/pep-0440/#semantic-versioning" rel="nofollow" target="_blank">più o meno</a>), significa che Arrow d'ora in poi si impegna a non introdurre API che rompono la compatibilità con le versioni passate, a meno di non saltare con questo alla versione 2.0. Viceversa, finché un software resta nella versione "0.qualcosa", gli utenti sono avvisati che cambiamenti di API non-retrocompatibili potrebbero accadere in qualsiasi momento.&nbsp;</p><p>Da un lato, mantenere il proprio software in uno stato di <a href="https://0ver.org/" rel="nofollow" target="_blank">ZeroVer</a> perenne aiuta la libertà di sviluppo e libera il programmatore dal peso delle promesse passate, permettendogli di cambiare l'API in modo anche radicale, senza troppi pensieri. Dall'altro, i suoi utenti saranno sempre un po' in ansia, ecco.&nbsp;</p><p>Tuttavia lo ZeroVer è quasi la regola, ormai. Il modello imperante della <a href="https://it.wikipedia.org/wiki/Integrazione_continua" rel="nofollow" target="_blank">continuous integration</a> rende il software sempre più "malleabile", indipendente dal vecchio schema delle release. Si privilegia la rapidità di sviluppo sulla stabilità dell'API. Un contro-esempio è la libreria standard di Python, che invece continua a privilegiare la stabilità del codice anche a discapito degli aggiornamenti. Ma proprio per questo è stata <a href="https://news.ycombinator.com/item?id=3913182" rel="nofollow" target="_blank">notoriamente </a>criticata come "il posto dove i moduli vanno a morire".&nbsp;</p><p>Poi certo, anche nel software ZeroVer almeno un "nocciolo" di API più o meno costante, in qualche modo, viene a formarsi, almeno di fatto se non per contratto. Ma tutti abbiamo avuto almeno una volta l'esperienza di dover cambiare anche in profondità il nostro codice per adeguarci a un improvviso cambio di API delle librerie che utilizziamo.&nbsp;</p><p>Alcuni dei pacchetti più famosi nell'ecosistema Python sono ancora ZeroVer, o lo sono stati per anni: <a href="https://pandas.pydata.org/docs/whatsnew/index.html" rel="nofollow" target="_blank">Pandas </a>(dal 2011 a gen. 2020), <a href="https://pypi.org/project/Flask/#history" rel="nofollow" target="_blank">Flask </a>(2010-2018), <a href="https://wheel.readthedocs.io/en/stable/news.html" rel="nofollow" target="_blank">Wheel </a>(2012-oggi), <a href="https://pypi.org/project/scikit-learn/#history" rel="nofollow" target="_blank">Scikit-learn</a> (2011-oggi) e così via. Naturalmente ogni pacchetto ha la sua storia, e non è detto che per forza dietro a ogni ZeroVer ci sia uno sviluppatore che non riesce a decidersi su un'API stabile, anzi. Ma anche in questa epoca di software sempre più fluido, qualche promessa è bene metterla per scritto, ogni tanto.&nbsp;</p><p>Quindi, benvenuto Arrow 1.x. D'ora in poi ti useremo con un pizzico di piacere e di sicurezza in più.&nbsp;</p><p><br /></p>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-29905614763070376172020-10-19T16:15:00.000+02:002020-10-19T16:15:16.922+02:00Chi vuole tradurre la documentazione di Python?<p>Questa Primavera, un po' per sconfiggere l'ozio del Covid, un po' per ricerca personale e un po' per spirito di servizio alla comunità (<a href="https://pythoninwindows.blogspot.com/2020/06/esiste-una-comunita-python-italiana.html">ammesso che poi esista</a>), mi sono messo a <a href="https://pytutorial-it.readthedocs.io" rel="nofollow" target="_blank">tradurre il tutorial ufficiale</a> di Python. Questa esperienza mi ha fatto capire <a href="https://pythoninwindows.blogspot.com/2020/05/il-tutorial-di-python-e-difficile.html">alcune cose</a> interessanti, dal punto di vista della didattica dell'informatica che mi appassiona sempre. Per il resto, non mi immaginavo che avesse un gran successo al botteghino, per così dire.&nbsp;</p><p>E infatti la cosa è finita in larghissima parte ignorata, come da previsioni. Non ho una precisa idea di quante persone abbiano dato un'occhiata alla mia traduzione, ma suppongo che si possano contare sulle dita di due mani. Tuttavia qualche conseguenza c'è stata: non direi proprio che ho fatto delle onde, ma forse una minima increspatura...&nbsp;</p><p>Il punto è che, tanto tanto tempo fa, la comunità di <a href="https://www.python.it/" rel="nofollow" target="_blank">Python.it</a> aveva fatto uno sforzo di traduzione di tutta la documentazione di Python. Devo dire, un gran bel lavoro, all'epoca: poi però la cosa non era mai più stata aggiornata, e alla fine era diventata così vecchia (erano ancora i tempi di Python 2, per capirci) che è stata ritirata. Adesso, dopo il mio lavoro sul tutorial, è nata una <a href="https://www.python.it/forum/thread/5941/traduzione-documentazione/" rel="nofollow" target="_blank">piccola discussione</a> sull'idea di riprendere in mano il progetto di traduzione compessiva.&nbsp;</p><p>La mia posizione è stata più o meno questa: va bene, l'idea mi interessa e se volete posso non solo dare una mano, ma anche mettermi a coordinare un progetto che sicuramente sarà molto impegnativo. <b>Però bisogna fare in modo che duri</b>, questa volta. Il che vuol dire, tra l'altro:</p><ul style="text-align: left;"><li>un progetto che sia possibile mantenere aggiornato nel tempo, sincronizzandolo via via alle modifiche fatte sulla documentazione originale;</li><li>per questa ragione, ogni traduzione deve essere riferita a un documento originale "versionato", fissato in un punto preciso nella cronologia delle modifiche;</li><li>e per questa ragione, occorre impostare un flusso di lavoro ben definito e codificato, senza "fughe in avanti" e "fai-da-te" e "io-ho-fatto-questo-può-andar-bene?", per quanto benintenzionati.</li></ul><div><p>(Per chi ha voglia di approfondire, ho scritto un piccolo <a href="https://gist.github.com/ricpol/2d9df82a8961bd4c61396084725ab6cc" rel="nofollow" target="_blank">documento programmatico</a> che descrive più o meno come avevo pensato di impostare la cosa.)</p><p>La risposta a tutto questo è stata, più o meno: va bene, tu vai avanti e poi vediamo cosa esce fuori. Ok, almeno non proprio un rifiuto a priori, diciamo. Ma il fatto è che io per primo, dopo aver formulato il piano, mi sono incuriosito abbastanza da continuare a lavorarci sopra, fino a mettere a punto una implementazione concreta, basandomi anche sull'esperienza del tutorial.&nbsp;</p><p>Da oggi questo progetto è pronto a partire, e l'ho pubblicato qui:&nbsp;</p><p style="text-align: center;"><span style="font-size: medium;"><a href="https://github.com/ricpol/pydoc-it" rel="nofollow" target="_blank">https://github.com/ricpol/pydoc-it</a></span></p><p>E questo mi porta alla domanda del titolo... qualcuno vuole contribuire? Adesso vedremo se e come reagiranno nell'ambiente di Python.it... per parte mia sono disponibile ad aggiungere qualche traduzione (e migrare il tutorial qui, se il progetto prende piede).&nbsp;</p><p>Ma intanto voglio divulgare questa idea anche ai due-o-tre lettori del blog. Se avete voglia di dare una mano traducendo qualche pagina della documentazione, siete i benvenuti! Nella repository che ho appena linkato è spiegato tutto quanto. Non occorre occuparsi di dettagli amministrativi, conoscere Git, eccetera. Quello che serve adesso sono persone che hanno voglia di dedicare del tempo a tradurre. Serve ovviamente conoscere l'Inglese (bene), l'Italiano (sembra facile...), conoscere l'argomento (almeno un po') e soprattutto una certa precisione nel lavoro.</p><p>Non so se questo progetto riuscirà a decollare, o anche solo a saltellare. Onestamente, non sono molto fiducioso... ma non vedo l'ora di essere sorpreso nel mio pessimismo. Di sicuro è un'idea valida, e per quanto mi riguarda ci metterò tutto l'impegno che serve, anche per supportare chi vorrà contribuire.&nbsp;</p><p>Vediamo come va!</p></div><div><p>&nbsp;</p></div>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]1tag:blogger.com,1999:blog-8801312244642714488.post-72967233968353001132020-10-08T15:25:00.000+02:002020-10-08T15:25:23.311+02:00Il libro su Python in Windows è finito!<p>Ieri sera ho pubblicato su Leanpub l'ultimo aggiornamento del mio libro "<a href="https://leanpub.com/pythoninwindows" rel="nofollow" target="_blank">Python in Windows</a>". Mi ero ripromesso di scrivere ancora due brevi capitoli, uno su come scrivere codice cross-piattaforma e uno per fare la panoramica delle librerie e dei framework più utili in ambiente Windows. Inoltre ne ho approfittato per aggiungere diverse note e aggiornare tutti gli esempi e le figure a Python 3.9.0, che è uscito solo alcuni giorni fa (se questo non è il primo libro al mondo a coprire Python 3.9, voglio vedere chi mi ha battuto!).&nbsp;</p><p>Questo libro è rivolto soprattutto ai principianti che vogliono imparare Python facendo le cose per bene: non è un manuale del linguaggio, ma piuttosto insegna come funziona e come si prepara l'ambiente (Windows) di lavoro e di esecuzione dei programmi. Nella seconda parte, finisce per parlare di cose un pochino più difficili (Git, Sphinx, i test, la distribuzione...) ma sempre cercando di rendere comprensibile l'argomento per il principiante. Potete trovare l'indice e un breve estratto gratuito sulla pagina di Leanpub.&nbsp;</p><p>In questo momento, come dicevo, considero il libro concluso. Naturalmente ci sarebbero ancora diversi argomenti da affrontare: per esempio, la distribuzione sulle PAAS (a cominciare da Azure, naturalmente!), i sistemi di continuous integration (Buildbot, Travis...), e altro ancora. Oppure potrei prendere praticamente qualsiasi capitolo che ho scritto, e approfondire il discorso entrando più nei dettagli... Ma tutto questo finirebbe per snaturare il libro, e non so fino a che punto sia utile.&nbsp;</p><p>Ho comunque intenzione di continuare a rilasciare piccoli aggiornamenti periodici, in modo che il libro non invecchi. Sicuramente potete aspettarvi una nuova edizione tutti gli anni a ottobre, in coincidenza con il rilascio di una nuova versione di Python. Ma probabilmente farò anche degli aggiornamenti "intercalari" in primavera, per controllare se tutti i link sono ancora a posto, se i pacchetti di cui parlo sono stati aggiornati e così via. Come sempre, gli aggiornamenti sono gratuiti per chi ha già comprato il libro.&nbsp;</p><p>&nbsp;</p><h3 style="text-align: left;">E quindi, alla fine, come sta andando il libro?</h3><p>Uhm... maluccio, devo dire. Grazie della domanda.&nbsp;</p><p>Qui devo fare una premessa: il libro è stato scritto questa primavera, durante il lockdown. In quel periodo non avevo praticamente nient'altro da fare, come tutti: scrivere era anche un modo per tenermi occupato, smettere di leggere compulsivamente i siti di news, organizzare il mio tempo. Come tutti, all'epoca. Da questo punto di vista, il libro è stato comunque un successo e non posso lamentarmi.&nbsp;</p><p>La risposta dei lettori però è stata bassissima, e questo un po' mi ha sorpreso. Il mio <a href="https://leanpub.com/capirewxpython" rel="nofollow" target="_blank">libro su wxPython</a> è molto più tecnico e "di nicchia", eppure nel tempo si è conquistato un suo piccolissimo pubblico di lettori: mi aspettavo che un libro dedicato espressamente ai principianti avrebbe trovato facilmente un pubblico maggiore. Invece, almeno finora, il libro è stato acquistato da pochissime persone: poche anche in rapporto alla generale <a href="https://pythoninwindows.blogspot.com/2020/06/esiste-una-comunita-python-italiana.html">asfittica situazione</a> dell'alfabetizzazione informatica in Italia; poche anche in rapporto alla quasi nessuna pubblicità che faccio del mio lavoro (anche perché odio Facebook e detesto YouTube).&nbsp;</p><p>Con senno di poi, forse dovevo aspettarmelo. In realtà è più facile che siano proprio i principianti a <i>non essere</i> interessati a un libro per principianti! Non avrei dovuto dimenticare che la maggior parte di chi non conosce Python <a href="https://pythoninwindows.blogspot.com/2020/03/come-imparare-python-senza-studiare.html">non vuole impararlo, vuole risolvere un problema</a> che ha in quel momento, e in fretta. Da questo punto di vista, forse è più facile vendere un libro più tecnico, dichiaratamente rivolto a un pubblico che invece <i>sa di voler imparare</i> qualcosa sull'argomento. <br /></p><p>Detto questo, sono comunque molto soddisfatto di essere riuscito a portare a termine questo lavoro: può darsi che nel tempo si conquisterà la sua piccola fetta di pubblico... chissà, magari tra cent'anni sarà un classico! E sono soprattutto molto grato e riconoscente ai pochi lettori che lo hanno acquistato, e a tutti quelli che lo faranno in futuro: continuerò ad aggiornarlo per loro.&nbsp;</p><p>Il dubbio che mi viene, piuttosto, è un altro. In questi mesi di semi-paralisi mi sono venute in mente altre piccole idee, progetti <i>dedicati ai principianti</i>. Il problema è che, a questo punto, non so se ne vale la pena: cioè, non so quanto spazio c'è davvero per prodotti di questo tipo... Mah. Ci rifletterò sopra ancora nei prossimi mesi, suppongo.&nbsp;</p><p>&nbsp;</p>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-12114781531807652122020-10-06T10:12:00.002+02:002020-10-08T15:26:05.586+02:00Python 3.9 è qui, e io sto invecchiando :-)<p>E così eccoci arrivati a Python 3.9, ufficialmente già la seconda release ad avere un numero più grande di quanto Python 2 non sia mai arrivato nei suoi 20 anni di vita. Il mondo corre sempre più in fretta e a me sembra di invecchiare allo stesso ritmo.&nbsp;</p><p>Tra la versione 2.4 (novembre 2004) e la 2.5 (settembre 2006) passarono quasi due anni. Per la 2.6 (ottobre 2008), altrettanti. La 2.7 arrivò solo a luglio 2010. Oggi, la versione 3.9 inaugura una <a href="https://www.python.org/dev/peps/pep-0602/" rel="nofollow" target="_blank">cadenza annuale</a> delle release, nientemeno: d'ora in avanti, a ogni ottobre dovremo aspettarci una nuova versione.&nbsp;</p><p>L'altra faccia della medaglia di questa accelerazione, naturalmente, è che le novità per ciascuna release sono di meno: la <a href="https://docs.python.org/3/whatsnew/2.5.html" rel="nofollow" target="_blank">versione 2.5</a>, per dire, introduceva in un botto solo le espressioni condizionali, gli import relativi, i context manager, le coroutine (l'inizio di una lunga strada che avrebbe portato ad asyncio e al supporto per l'esecuzione asincrona)... e un bel po' di feature "minori" (cose tipo any e all...&nbsp; robette, insomma!).&nbsp; Ricordo che all'epoca ogni nuova release richiedeva parecchie settimane per essere "digerita"; per molti aspetti, sembrava un Python diverso ogni volta. Tutto questo, in un linguaggio che era pur sempre considerato molto conservativo sulle novità da adottare (vi ricordate tutta la cautela di "from __future__ import"?).&nbsp;</p><p>&nbsp;</p><p>Oggi, al confronto, le nuove release sembrano dei bugfix e i bugfix quasi non si notano più. Non che mi lamenti... O forse sì. Forse è una mia impressione, ma una volta c'erano delle cesure più nette: ti beccavi un sacco di roba nuova in un colpo solo, ma poi avevi due anni di tempo in cui non succedeva più niente. Adesso tutto evolve probabilmente alla stessa velocità, ma aggiungendo un pezzetto alla volta. Il risultato è che diventa più difficile scrivere codice che mira a una versione specifica del linguaggio. A mitigare il problema, per fortuna, adesso c'è una policy più stringente sulle versioni supportate: la 3.5 esce di scena adesso, e la 3.6 non sarà più supportata dall'anno prossimo. In pratica, un progetto che inizia oggi può tranquillamente mirare solo più alla "forchetta" 3.7-3.9, che sono versioni relativamente omogenee e moderne. Ricordo che, ai tempi della 2.7, occorreva comunque cercare di scrivere codice compatibile all'indietro fino alla 2.4... cosa che francamente diventava impossibile.&nbsp;</p><p>In ogni caso, una cadenza di release più ravvicinata produrrà inevitabilmente una maggiore dispersione delle installazioni: una volta, versioni come la 2.4 erano il "golden standard" che trovavi dovunque, per molti anni; oggi è probabile che molta gente aggiorni in modo discontinuo, e su ogni macchina puoi trovare una versione diversa. Per esempio, non mi è chiaro (o forse, piuttosto, <a href="https://www.python.org/dev/peps/pep-0602/#python-redistribution" rel="nofollow" target="_blank">non è chiaro</a>) come le varie distribuzioni Linux intendono adeguarsi al nuovo ritmo di release che ci sarà d'ora in poi.&nbsp;</p><p><br /></p><h3 style="text-align: left;">Che cosa c'è di interessante nella 3.9.</h3><p>Detto questo, su che cosa vale la pena di soffermarsi, quindi, in questa versione 3.9 di Python?&nbsp;</p><p></p><p>Prima di tutto, le notizie che contano per noi utenti Windows. A partire dalla 3.9, <b>Windows 7 non è più supportato</b>: non cercate di installarlo sul vostro vecchio pc... non funziona! Inoltre, nonostante sia ancora disponibile l'installer a 32 bit, finalmente l'installer di default proposto sarà quello a 64 bit (e direi che era anche ora).&nbsp;</p><p>Per il resto, la novità più evidente è probabilmente il <a href="https://www.python.org/dev/peps/pep-0584/" rel="nofollow" target="_blank">nuovo operatore</a> che fa il lavoro di dict.update per unire due dizionari. Tuttavia la cosa più importante dietro le quinte è sicuramente il nuovo <a href="https://www.python.org/dev/peps/pep-0617/" rel="nofollow" target="_blank">PEG parser</a> per la sintassi del linguaggio: l'ultimo lavoro di Guido è sicuramente una novità fondamentale che determinerà l'evoluzione di Python per gli anni a venire, anche si tratta di una questione molto tecnica di cui gli utenti normali non si accorgeranno neppure.&nbsp;</p><p>Ma l'aspetto che personalmente mi sta più a cuore è l'introduzione del <a href="https://www.python.org/dev/peps/pep-0615/" rel="nofollow" target="_blank">supporto al database IANA</a> delle timezone, che finalmente rende il modulo datetime... utilizzabile in pratica (e dopo 17 anni di vita, direi che forse era anche il caso).&nbsp;</p><p>La storia di "datetime" è lunga e complessa. Il modulo è stato introdotto in Python 2.3, con una architettura volutamente molto astratta e "naive". In pratica propone una classe per rappresentare i momenti temporali (datetime.datetime) che però è... solo una scatola vuota, che mette a disposizione alcune operazioni astratte, e lascia alle implementazioni concrete il compito di rappresentare le date nel mondo reale. Questa limitazione, unita a un API piuttosto farraginosa, ne ha fatto uno dei moduli meno amati della libreria standard: nel tempo sono nate diverse librerie esterne per rappresentare meglio le date nel mondo reale. Fino a qualche tempo fa l'opzione migliore sembrava Pytz, che purtroppo però aveva <a href="https://blog.ganssle.io/articles/2018/03/pytz-fastest-footgun.html" rel="nofollow" target="_blank">notevoli difetti</a> nel calcolo delle timezone. Oggi la cosa migliore è usare invece <a href="https://github.com/dateutil/dateutil" rel="nofollow" target="_blank">Dateutil</a>, che fa le cose per bene.&nbsp;</p><p>Nel frattempo anche datetime nella libreria standard ha subito un lentissimo processo di evoluzione. In Python 3.2 è stata introdotta la classe datetime.timezone (ancora pur sempre un guscio vuoto) per rappresentare una timezone; in Python 3.6 finalmente datetime.datetime ha guadagnato il supporto per l'ora "doppia" che accade in autunno quando si torna all'ora solare. E in tempi recenti Paul Ganssle, che mantiene Dateutil ed è diventato il guru-di-fatto di questi argomenti nella comunità Python, ha spinto moltissimo per l'introduzione definitiva del database IANA delle timezone (detto anche Olsen database) dentro la libreria standard. Questo significa che finalmente anche datetime.datetime, senza ricorrere a librerie esterne, avrà gli strumenti per sapere che il 29 marzo 2020 ore 2:30, da noi in Italia, non c'è mai stato, e cose così: ovvero, per rappresentare correttamente il concetto di "istante temporale" nel mondo reale.&nbsp;</p><p>Sono argomenti che mi hanno sempre affascinato molto, e su cui prima o poi mi piacerebbe scrivere un tutorial. Ma per il momento, il succo è che da questa versione 3.9, datetime può essere usata non solo come infrastruttura astratta per librerie concrete, ma già come strumento di calcolo delle date nel mondo reale. Ovviamente questo non vuol dire che Dateutil, Arrow e un sacco di altre librerie esterne andranno in pensione: possono offrire pur sempre un'API più piacevole da usare, e si concentrano su determinati aspetti dell'implementazione. Ma d'ora in poi dovrebbe essere possibile, almeno in linea di principio, farsi un'applicazione di calendaring completa e corretta senza abbandonare la libreria standard. <br /></p><p><br /></p>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]2tag:blogger.com,1999:blog-8801312244642714488.post-50001754986432497752020-06-21T18:36:00.000+02:002020-06-21T18:36:33.027+02:00Esiste una comunità Python italiana?<div>Ogni anno la Python Sotware Foundation invita a riempire un questionario, il <a href="https://www.jetbrains.com/lp/python-developers-survey-2019/" rel="nofollow" target="_blank">Python Developers Survey</a>, per sentire il polso della comunità globale di sviluppatori che ruota intorno a Python. Era da qualche settimana che avevo scaricato i risultati del 2019, e finalmente mi sono deciso a dargli un'occhiata, soprattutto per quanto riguarda le risposte che sono arrivate dall'Italia. Ecco alcune riflessioni su quello che ho trovato.</div><div><br /></div><div>Per prima cosa, una precisazione importante: s'intende che chi risponde al questionario è solo una minima frazione di chi programma in Python, nel mondo come in Italia. Per rispondere occorre prima di tutto sapere che il questionario <i>esiste</i>; e per questo occorre avere qualche nozione sull'esistenza di una Python Software Foundation, magari seguire un pochino i forum dove il questionario è pubblicizzato, insomma essere esposti anche di sfuggita al mondo che ruota intorno a Python - alla sua <i>comunità</i>. <br /></div><div>Ed è proprio questo l'aspetto più interessante, almeno per me. Non saprei dire fino a che punto i risultati del questionario rispecchiano la realtà di chi <i>programma in Python</i>. Tuttavia, credo che riflettano abbastanza la realtà di chi programma in Python <i>e si interessa anche al mondo Python</i>, almeno un poco. <br /></div><div><br /></div><div>E quindi, noi come siamo messi? Intanto, il numero grezzo: 410 risposte sono targate "Italy". Sono tante, sono poche? Beh, intanto va detto che indicare il Paese non era obbligatorio. Tra le risposte che riportano questa indicazione, diciamo che siamo messi abbastanza male ma non malissimo. Tra i Paesi europei paragonabili al nostro per popolazione, la Polonia ha più risposte delle nostre (530), la Francia ne ha più del doppio (913) e la Germania più del triplo (1411). Però la Spagna è messa come noi (423); e curiosamente il Giappone (207) sta molto peggio. I dati di Stati Uniti (4492), Gran Bretagna (1150), Canada (710) e Australia (397) vanno presi con le molle perché ovviamente gli sviluppatori di lingua inglese sentono meno il bisogno di indicare il Paese di provenienza. <br /></div><div><br /></div><div>Nel compesso, mi sembrano numeri in linea con la complessiva arretratezza informatica del nostro Paese nei confronti dei nostri vicini europei. Tanto per capirci, la Finlandia ha inviato un terzo delle nostre risposte (142) avendo però un decimo della nostra popolazione. Ma credo che più o meno gli stessi rapporti si troverebbero facendo questionari per tutti gli altri linguaggi e tecnologie. Semplicemente, siamo un Paese arretrato e non c'è molto da fare. <br /></div><div><br /></div><div>Una considerazione più specifica riguarda l'età. Apparentemente, la comunità Python in Italia è <i>un po' più vecchia</i>:</div><div style="text-align: left;"><pre>&nbsp; %&nbsp;&nbsp;&nbsp; tutti Italia</pre></div><div style="text-align: left;"><pre>18-20&nbsp;&nbsp;&nbsp; 8&nbsp;&nbsp; 5</pre></div><div style="text-align: left;"><pre>21-29&nbsp;&nbsp;&nbsp; 42&nbsp; 32</pre></div><div style="text-align: left;"><pre>30-39&nbsp;&nbsp;&nbsp; 31&nbsp; 33</pre></div><div style="text-align: left;"><pre>40-49&nbsp;&nbsp;&nbsp; 12&nbsp; 17</pre></div><div style="text-align: left;"><pre>50-59&nbsp;&nbsp;&nbsp; 5&nbsp; 9</pre></div><div style="text-align: left;"><pre>60+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&nbsp;&nbsp; 3</pre></div><div>e usa Python da un po' più tempo: <br /></div><div style="text-align: left;"><pre>&nbsp;&nbsp; %&nbsp; tutti Italia</pre></div><div style="text-align: left;"><pre>&lt;1 anno&nbsp;&nbsp; 21&nbsp; 12</pre></div><div style="text-align: left;"><pre>1-2 anni&nbsp; 23&nbsp; 18</pre></div><div style="text-align: left;"><pre>3-5 anni&nbsp; 30&nbsp; 30</pre></div><div style="text-align: left;"><pre>6-10 anni 16&nbsp; 22</pre></div><div style="text-align: left;"><pre>11+ anni&nbsp; 10&nbsp; 16</pre></div><div>oltre ad avere, in generale, un po' più esperienza di programmazione: <br /></div><div style="text-align: left;"><pre>%&nbsp;&nbsp; tutti&nbsp; Italia</pre></div><div><div style="text-align: left;"><pre>&lt;1 anno&nbsp;&nbsp; 29&nbsp; 19</pre></div><div style="text-align: left;"><pre>1-2 anni&nbsp; 20&nbsp; 21</pre></div><div style="text-align: left;"><pre>3-5 anni&nbsp; 20&nbsp; 20</pre></div><div style="text-align: left;"><pre>6-10 anni 14&nbsp; 14</pre></div><div style="text-align: left;"><pre>11+ anni&nbsp; 17&nbsp; 26</pre></div></div><div><br /></div><div>Questo sembra essere confermato dalla domanda sul sistema operativo usato: <br /></div><div style="text-align: left;"><pre> % tutti Italia</pre></div><div style="text-align: left;"><pre>Linux&nbsp; 46&nbsp; 54</pre></div><div style="text-align: left;"><pre>Windows 31&nbsp; 29</pre></div><div style="text-align: left;"><pre>MacOs&nbsp; 20&nbsp; 16</pre></div><div>e da quella sul provider usato per aggiornare Python: <br /></div><div style="text-align: left;"><pre> % tutti Italia</pre></div><div style="text-align: left;"><pre>apt-get etc.&nbsp; 23 31</pre></div><div style="text-align: left;"><pre>python.org&nbsp; 20&nbsp; 20</pre></div><div><br /></div><div>Lo ripeto ancora: questi numeri probabilmente non rispecchiano la situazione <i>di tutti</i> i programmatori Python, ma solo <i>di quella parte abbastanza coinvolta</i> da sapere dell'esistenza del questionario e da voler perdere del tempo a compilarlo. La <i>comunità</i>, se volete. <br /></div><div><br /></div><div>Per quanto riguarda l'uso di Python nello specifico, in realtà non ho trovato molti scostamenti dalla media. Per lo più Python si usa per fare data analysis, web development e operazioni da sysadmin; quasi il 90% è ormai passato a Python 3; tra i web framework, c'è una leggera prevalenza di Flask su Django; tutti amano NumPy, Pandas e Matplotlib; e tutti sviluppano con Visual Studio Code e PyCharm. In generale, i <a href="https://www.jetbrains.com/lp/python-developers-survey-2019/" rel="nofollow" target="_blank">grafici riassuntivi</a> valgono anche per l'Italia. <br /></div><div>In realtà ho l'impressione che, nonostante Python sia ormai decisamente un linguaggio di massa, <i>gli sviluppatori Python</i> abbiano ancora conservato qualcosa dello spirito di "élite" di dieci-quindici anni fa, e quindi in un certo senso finiscano per somigliarsi un po' tutti nell'adozione di certe tecnologie e pratiche "sane": chi lavora in Python tende a usare container e virtualizzazione, a testare il codice e così via. <br /></div><div>Da questo punto di vista, un programmatore Python italiano non sembra poi molto diverso da uno americano, e questa è senz'altro una buona cosa. <br /></div><div><br /></div><div>Dove invece si nota una differenza, mi sembra, è nel profilo anagrafico di questa comunità. E la comunità italiana sta diventando... come dire... <i>un po' anzianotta</i>, ecco. Si intuisce che si tratta per lo più di persone che hanno imparato Python molti anni fa, nell'ambiente Linux, e che sono rimasti "dentro" il mondo Python da quell'epoca. Il ricambio generazionale è più lento della media degli altri Paesi. <br /></div><div><br /></div><div>Preferisco ripeterlo ancora una volta (la terza!): non sto parlando qui degli sviluppatori Python in generale, ma di quella parte che, oltre a <i>usare </i>Python, ci resta "attaccato" in qualche modo. Parlo di quelli che si interessano all'evoluzione di Python, che seguono le PEP e le nuove versioni, che vanno alle conferenze (almeno virtualmente) e frequentano i forum; parlo di quelli che più o meno sanno chi sono i <i>core developers</i> e hanno idea di quali pacchetti fanno tendenza; di quelli che, magari, <i>restituiscono </i>parte di quanto hanno ricevuto, mettendo a disposizione il loro codice e aiutando i nuovi arrivati. Parlo della <i>comunità</i>, insomma. <br /></div><div><br /></div><div>Ora, questo invecchiamento potrebbe essere una conseguenza della nostra demografia generale dove, semplicemente, i giovani scarseggiano. Potrebbe essere anche il risultato del gigantesco impoverimento economico <i>selettivo e programmato </i>in un Paese che da molti anni ormai sta consistentemente dirottando le risorse dalle fasce più giovani verso le fasce più anziane (e votanti) della popolazione. Forse oggi i giovani che si accostano a Python lo fanno solo più con un atteggiamento predatorio, interessati ad acquisire rapidamente dei gettoni di competenza convertibili in opportunità di lavoro; e non hanno tempo ed energie per affezionarsi, dedicare del tempo al linguaggio, restituire... fare <i>comunità</i>, appunto. <br /></div><div><br /></div><div>Ma poi, è così importante avere una community Python italiana? Dopo tutto, guidiamo la macchina tutti i giorni senza essere appassionati di motori. Ma <i>tutte </i>le community sono piccole in rapporto al numero totale di utenti: non è questo il punto. Piuttosto, a giudicare da questi dati (opinionabili, ci mancherebbe) sembra che la community Python italiana sia un po' più piccola e un po' più fragile rispetto alla media. E questo è un problema? <br /></div><div>Francamente sì, a maggior ragione in un Paese disperatamente non anglofono come il nostro. Intanto perché c'è differenza tra essere semplici utenti di una tecnologia ed essere, potenzialmente, protagonisti del suo sviluppo: e solo una comunità ampia di persone interessate e consapevoli può produrre, eventualmente, degli sviluppatori di questo tipo. <br /></div><div>E poi soprattutto per un discorso culturale. Noi possiamo, e dobbiamo!, invocare più risorse del governo, chiedere corsi universitari migliori e così via. Ma solo una comunità estesa e attiva può moltiplicare quantità e qualità degli sviluppatori Python, in un modo che nessun corso universitario potrà mai fare. E questo a sua volta andrà a vantaggio del mercato, delle aziende che avranno più scelta, degli sviluppatori che avranno modo di imparare di più, di farsi conoscere di più. Andrà a vantaggio dell'alfabetizzazione informatica dell'Italia e della qualità e quantità di software prodotto nel nostro Paese. <br /></div><div><br /></div><div>Potrebbe valerne la pena. <br /></div><div><br /></div>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]1tag:blogger.com,1999:blog-8801312244642714488.post-33319373672397429452020-06-17T15:33:00.003+02:002020-06-17T15:33:59.022+02:00Un indovinello su print<div>Mentre scrivevo perché <a href="https://pythoninwindows.blogspot.com/2020/05/non-usate-print.html">non bisogna insegnare a usare print</a>, mi era venuto in mente un esempio divertente che poi ho dovuto tagliare per non allungare troppo il discorso. Lo propongo qui, come una specie di appendice al discorso su <i>print</i>. <br /></div><div><br /></div><div>Potete proporlo come un indovinello, se volete. Viene meglio se avete davanti una shell di Python aperta. Scrivete questo nella shell (sono sicuro che non è la prima volta che lo vedete): <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; print('hello world')</pre></div><div style="text-align: left;"><pre>hello world</pre></div><div>E adesso chiedetevi, rapidamente: in Python 3, <i>print </i>è una funzione, giusto? E allora qual è il valore di ritorno di "print('hello world')"? <br /></div><div><br /></div><div>C'è una probabilità abbastanza alta che abbiate pensato: il valore di ritorno è appunto "hello world". E guarda caso, avete pensato la risposta sbagliata. <br /></div><div>Vorrei subito chiarire una cosa importante: questa <i>non è</i> una delle tante domande a trabocchetto su un aspetto molto oscuro di Python. Dare la risposta sbagliata qui rivela una <i>profonda </i>incomprensione di che cosa fa davvero <i>print</i>. Ora, se siete un principiante che ha appena imparato le funzioni in Python, è perfettamente naturale cadere nella trappola: anzi, mi meraviglierebbe sentire la risposta giusta da un principiante. <br /></div><div>Ma se avete appena finito di scrivere un tutorial o registrare l'ennesimo corso su YouTube... forse vi conviene fare una riflessione. <br /></div><div><br /></div><div>Se siete uno studente e avete voglia di metterci un po' di pepe, provate a fare la domanda al vostro prof. Se volete contestualizzare meglio il problema (o forse... confondere ancora di più le idee), fate il confronto con altre funzioni, <i>sempre nella shell</i>... ricordate: dovete provare queste cose nella shell, altrimenti non è esattamente lo stesso. Poi chiariremo perché. <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; abs(-42)&nbsp;&nbsp;&nbsp; # una funzione predefinita</pre></div><div style="text-align: left;"><pre>42</pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; def mysum(a, b): &nbsp;&nbsp; # una funzione scritta da voi</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return a + b</pre></div><div style="text-align: left;"><pre></pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; mysum(40, 2)</pre></div><div style="text-align: left;"><pre>42</pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; print('hello world')&nbsp;&nbsp; # la funzione "print"</pre></div><div style="text-align: left;"><pre>hello world</pre></div><div>Se il valore di ritorno di "abs(-42)" è "42", allora il valore di ritorno di "print('hello world')" è "hello world", giusto? Se riuscite a metterla nel modo più ingenuo possibile, così che il prof non sospetti niente, è probabile che si sbaglierà anche lui. <br /></div><div><br /></div><div>A questo punto, se volete, potete fermarvi un attimo a pensare qual è la risposta giusta. Se avete letto il mio articolo sull'uso di "print", forse siete già sulla buona strada. <br /></div><div>Vediamo un po'. <br /></div><div><br /></div><div>Per prima cosa, in Python non esiste la distinzione tra funzione e procedura, come in Pascal. Una funzione restituisce sempre qualcosa, in ogni caso. Se la funzione non ha un punto di "return" esplicito, allora restituisce comunque l'oggetto "None". Ovvero, scrivere una funzione senza "return" è come scrivere la stessa funzione e terminarla con "return None". <br /></div><div><br /></div><div>E qui c'è un inghippo interessante. Python <i>non scrive mai "None" nella shell interattiva</i>. Se eseguite nella shell una funzione che restituisce "None", allora Python non scrive <i>nulla</i>: <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; def f():&nbsp;&nbsp; # questa funzione restituisce None</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pass</pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; f()</pre><pre>&lt;nulla!&gt;</pre></div><div>anche, ancora più semplicemente: <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; a = None</pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; a</pre></div><div style="text-align: left;"><pre>&lt;nulla!&gt;</pre></div><div>Se volete "vedere" in qualche modo il "None", potete forzare la conversione a stringa: <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; str(f())</pre><pre>'None'</pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; str(a)</pre></div><div style="text-align: left;"><pre>'None'</pre></div><div>questo, come vedremo alla fine, ci porta a un altro trucchetto buffo. <br /></div><div><br /></div><div>Ma intanto, perché Python, nella shell interattiva, si vergogna così tanto a scrivere "None"? <br /></div><div>Bisogna capire che restituire "None" è abbastanza comune: ci sono molte funzioni predefinite (e metodi di oggetti predefiniti) che lo fanno. In particolare, tutte i metodi che <i>modificano sul posto</i> un oggetto mutabile restituiscono "None": <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; a = [1, 2, 3]</pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; a.append(4)</pre></div><div style="text-align: left;"><pre>&lt;nulla!&gt;</pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; str(a.append(4))</pre></div><div style="text-align: left;"><pre>'None'</pre></div><div>Questa è una precisa scelta di design di Python, ed è anche una trappola frequente per i principianti:</div><div style="text-align: left;"><pre>&gt;&gt;&gt; b = a.append(4)</pre></div><div style="text-align: left;"><pre>&gt;&gt;&gt; b&nbsp; # ci aspettiamo [1, 2, 3, 4] ma invece...</pre></div><div style="text-align: left;"><pre>&lt;nulla!&gt;</pre></div><div></div><div>In altri linguaggi le funzioni che modificano oggetti sul posto restituiscono l'oggetto modificato: per esempio, nella shell di Ruby</div><div style="text-align: left;"><pre>&gt; a = [1, 2, 3]</pre></div><div style="text-align: left;"><pre>&gt; b = a.append(4)</pre></div><div style="text-align: left;"><pre>&gt; b</pre></div><div style="text-align: left;"><pre>=&gt; [1, 2, 3, 4]</pre></div><div><br /></div><div>E adesso ci avviciniamo al dunque. Operazioni come la modifica di un oggetto sul posto sono dei <i>side-effect</i>: la funzione sta facendo qualcosa, ma questo qualcosa non riguarda il valore di ritorno restituito. Piuttosto che eseguire il side-effect <i>e inoltre</i> restituire l'oggetto modificato, Python preferisce mantenere le cose separate: se fa un side-effect (come modificare l'oggetto), allora restituisce "None". Come dicevo, è una scelta di design. <br /></div><div><br /></div><div>Ed eccoci al dunque. Se avete letto l'articolo su <i>print</i>, già lo avrete capito: la funzione <i>print </i>emette un messaggio nello standard output, e questa operazione <i>è un side-effect</i>. E quindi, per una scelta di design, <i>la funzione print emette il messaggio nello standard output, e poi restituisce None</i>. Ed ecco la risposta al nostro indovinello. <br /></div><div><br /></div><div>Ed ecco anche perché la shell preferisce non scrivere "None": perché altrimenti per ogni <i>print </i>dovrebbe scrivere entrambe le cose: <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; print('hello world')</pre></div><div style="text-align: left;"><pre>hello world</pre></div><div style="text-align: left;"><pre>None</pre></div><div>E capite che non è proprio bello da vedere.</div><div>Il problema qui è che la modalità interattiva di Python è un po' fuorviante, in effetti. Scrive nello stesso posto (la shell) sia i messaggi dello standard output, sia i valori della valutazione di un'espressione. Ed ecco perché è facile confondersi tra un valore di ritorno pubblicato nella shell<br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; abs(-42)</pre></div><div style="text-align: left;"><pre>42</pre></div><div>e un side-effect nello standard output (che è sempre la shell!)</div><div style="text-align: left;"><pre>&gt;&gt;&gt; print('hello world')</pre></div><div style="text-align: left;"><pre>hello world</pre></div><div>Ed ecco anche perché è meglio non usare <i>print </i>nella shell, ed è meglio non mostrare <i>print </i>ai principianti finché non è assolutamente necessario, come dicevo nell'articolo precedente. <br /></div><div><br /></div><div>Se volete vedere il valore di ritorno di <i>print</i>, potete convertire in stringa, come abbiamo fatto prima:</div><div style="text-align: left;"><pre>&gt;&gt;&gt; str(print('hello world'))</pre></div><div style="text-align: left;"><pre>hello world</pre></div><div style="text-align: left;"><pre>'None'</pre></div><div>Ecco in effetti la "doppia scrittura": vediamo sia il side-effect sia il valore di ritorno. <br /></div><div><br /></div><div>E questo ci porta all'ultima piccola bizzarria che vi avevo promesso. Siccome <i>print </i>utilizza dietro le quinte proprio la funzione "str" per convertire a stringa ciò che deve pubblicare nello standard output, potete usare usare <i>print </i>invece di "str" per vedere il valore di ritorno. <br /></div><div><br /></div><div>Quindi potete divertirvi a chiedere al vostro prof di Python perché <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; print('hello world')</pre></div><div style="text-align: left;"><pre>hello world</pre></div><div>ma <br /></div><div style="text-align: left;"><pre>&gt;&gt;&gt; print(print('hello world'))</pre></div><div style="text-align: left;"><pre>hello world</pre></div><div style="text-align: left;"><pre>None</pre></div><div><br /></div><div>Fatemi sapere che cosa risponde...</div><div><br /></div><div><br /></div>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]2tag:blogger.com,1999:blog-8801312244642714488.post-13145097637390920032020-05-30T16:25:00.001+02:002020-05-30T16:28:25.613+02:00Non usate print!<div> E soprattutto, non insegnate a usare <i>print</i>. Questa è una comunicazione di pubblica utilità per chi crede di poter insegnare Python ai principianti. <br /> </div> <div><br /></div> <div> Se dovessi dire qual è il singolo elemento di Python che vedo più frainteso, sbagliato, insegnato male, usato in modo pericoloso: non avrei esitazioni, vince <b>print</b> a mani basse. <br /> </div> <div><br /></div> <div> Sospetto che <i>print</i> sia in cima a una curiosa classifica: è l'istruzione (funzione in Python 3) con il più basso rapporto di utilizzo professionisti/principianti: ti insegnano a usarla di continuo, ma poi non la vedi quasi mai nel codice serio che leggi in giro. Forse in questa classifica se la gioca con <i>input</i>, altra croce e (poca) delizia dei corsi per principianti. <br /> </div> <div> Ai bei tempi di Python 2, <i>print</i> era anche una insospettabile fonte di terrore su Windows: quando rinominavi il tuo file in ".pyw" per sopprimere l'odiatissima <a href="https://pythoninwindows.blogspot.com/2019/11/aiuto-la-finestrella-nera.html">finestrella nera</a>, un <i>print</i> qualsiasi dimenticato nel codice causava un'eccezione non gestita con conseguente crash e mani nei capelli. Questo in Python 3 non succede più - peccato!, era sempre un'occasione di farci bella figura a costo zero, a "divinare" sui forum la causa dell'errore. <br /> </div> <div><br /></div> <div> Resta il fatto che <i>print</i> è sempre il cocco della maestra di Python. Purtroppo non c'è libro o corso che non ne abusi allegramente. Dovrei provare a contare quante volte compare <i>print</i> nelle prime cento pagine di un manuale qualsiasi di Python: credo che sarebbe un buon indicatore della qualità (inversa) di quel manuale. </div> <div> Non mi è chiarissimo perché <i>print</i> sia così popolare tra chi si mette in testa di insegnare Python. Può darsi che sia l'influenza di <i>print('hello world')</i>: "guardate com'è facile Python, non vi viene già voglia di seguire il mio corso?" <br /> </div> <div> Ma credo piuttosto che sia una questione di banale sciatteria didattica. <br /> </div> <div> <h3 style="text-align: left;"> L'effetto collaterale di usare <i>print</i>, è <i>print</i>. </h3> </div> <div> Qual è il problema con <i>print</i>, insomma? In sostanza: che <i>print</i> scrive nello <i>standard output</i> e questo è per definizione un <a href="https://it.wikipedia.org/wiki/Effetto_collaterale_(informatica)" rel="nofollow">side-effect</a>. E i side-effect, gli effetti collaterali, sono un problema complesso che non bisognerebbe infilare alla chetichella nelle prime pagine di un manuale per principianti. <br /> </div> <div><br /></div> <div> Intendiamoci, Python è pur sempre un linguaggio procedurale, che maneggia i side-effect nel normale flusso di esecuzione del programma: per modificare il valore di una variabile globale usiamo la stessa sintassi che usiamo per una variabile locale, e così via. Le operazioni di input/output sono astratte in manipolazioni di oggetti intercambiabili tra loro: una funzione può indifferentemente aggiungere righe a un file o a una lista. Insomma, non abbiamo paura dei side-effect; non siamo Haskell. <br /> </div> <div><br /></div> <div> Tuttavia, <i>proprio perché</i> è così facile produrre un side-effect in Python, bisogna essere doppiamente cauti. E in particolar modo, è <i>responsabilità dell'insegnante</i> non introdurre inavvertitamente delle storture che poi restano radicate nella testa degli allievi.&nbsp; </div> <div> I motivi per cui bisogna maneggiare con cura i side-effect sono noti e non è questa la sede per un discorso approfondito. Sono difficili da capire: il loro effetto intrinsecamente non-locale influenza a distanza codice che in quel momento non hai sotto gli occhi, rendendo faticoso seguire il flusso del programma. Sono difficili da testare: non vanno d'accordo con il modello degli <i>unit test</i>, che si basa sul principio dell'isolamento funzionale delle unità di codice, principio che ovviamente i side-effect violano allegramente. Sono difficili da gestire nell'ambiente di esecuzione, e quindi difficili da debuggare: gli stati globali condivisi rendono l'esito dipendente dall'ordine di esecuzione delle varie unità che vi accedono, <i>race condition</i> comprese. <br /> </div> <div><br /></div> <div> Ma il vero problema non è tanto che i side-effect sono difficili: molte cose sono difficili e comunque utili, in Python come nella vita. <br /> </div> <div> Il problema è che il principiante <i>non può sapere</i> che i side-effect sono difficili; e non può sapere che <i>print</i> fa qualcosa di concettualmente molto intricato. <br /> </div> <div> La responsabilità dell'insegnante è di non dare in mano all'allievo una pistola carica, senza avvertirlo che è carica e senza spiegargli quali sono i pericoli potenziali di puntarsela su un piede e tirare il grilletto. <br /> </div> <div><br /></div> <div> Annuncio di pubblica utilità: chi si accinge a scrivere l'ennesimo tutorial o a registrare l'ennesimo corso su YouTube, dovrebbe cortesemente passare dieci minuti a riflettere sulla frase precedente. Grazie dell'attenzione. <br /> </div> <div> <h3 style="text-align: left;"><i>Print</i> nella shell: inutile.</h3> </div> <div> Molti corsi iniziano dalla shell: è una buona idea. Anzi, in genere il guaio è che non restano sulla shell abbastanza a lungo. <br /> </div> <div>Ma allora perché molti si sentono obbligati a scrivere</div> <div><pre style="text-align: left;">&gt;&gt;&gt; print(2 + 2)</pre></div> <div><i>nella shell</i>? <br /></div> <div> Come tutti sanno, in modalità interattiva l'interprete di Python funziona come come una <a href="https://it.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop" rel="nofollow">REPL</a>, dove la "P" sta già appunto per "Print". Non c'è alcun bisogno di usare <i>print</i> in una REPL. Ci pensa già la REPL a "stampare". La shell di Python valuta le espressioni "sciolte" (non assegnate a variabili) e ne pubblica il risultato nello <i>standard output</i>. Proprio come farebbe <i>print</i>, insomma: <br /> </div> <div style="text-align: left;"><pre>&gt;&gt;&gt; 2 + 2</pre></div> <div style="text-align: left;"><pre>4</pre></div> <div> Sì, lo sappiamo, è vero: non è proprio la stessa cosa. <i>Print</i> usa "str()" per convertire in stringa, la shell usa invece "repr()". Ma di fatto non vi è alcuna differenza tra le due, almeno per i tipi di base che si spiegano nelle prime lezioni di Python: numeri, liste, tuple, dizionari... stringhe! E qui vi vedo già col ditino alzato: no, per le stringhe è diverso. Ma anche qui: è diverso solo per il gioco degli apici semplici e doppi, e in generale per i caratteri speciali di cui bisogna fare l'<i>escape</i>. In particolare per gli "a-capo". Francamente non mi sembra che ci sia tutta questa urgenza di introdurre le stringhe multi-riga, che oltre tutto sono anche difficili da scrivere nella shell. E in ogni caso si può benissimo spiegare che </div> <div style="text-align: left;"><pre>&gt;&gt;&gt; """hello</pre></div> <div style="text-align: left;"><pre>... world"""</pre></div> <div style="text-align: left;"><pre>'hello\nworld'</pre></div> <div> è una rappresentazione poco <i>user-friendly</i> ma "vedremo in seguito che <i>print</i> ha appunto lo scopo..." eccetera. <br /> </div> <div><br /></div> <div> E a proposito: se sono così bravo da ritardare <i>print</i>, quando poi la spiego ci guadagno che ormai dovrei avere già le basi per spiegare la differenza tra "dato" (valore di un oggetto) e "rappresentazione del dato" (output di quel valore). E specificare che "str()" e <i>print</i> offrono una rappresentazione pensata per gli utenti del programma, mentre "repr()" è rivolta ai programmatori. E vedete quanti frutti posso raccogliere, se solo ho la pazienza di aspettare che maturino. <br /> </div> <div> <h3 style="text-align: left;"><i>Print</i> nei moduli: dannoso.</h3> </div> <div> Quando Python esegue un modulo, d'altra parte, non emette spontaneamente nessun risultato nello <i>standard output</i>. Se vogliamo vedere qualcosa dobbiamo chiederlo esplicitamente; e qui entra in gioco <i>print</i>... o no? </div> <div> Beh... no. Il problema con questo approccio è che porta gli allievi a disseminare i moduli di <i>print</i> "globali", a livello del modulo appunto. <br /> </div> <div><br /></div> <div> E tutte le istruzioni "globali" sono eseguite a <i>import time</i>. <br /> </div> <div><br /></div> <div> Questo vuol dire che i <i>print</i> non sono eseguiti solo quando "fate girare" il modulo come uno script; ma anche quando lo importate in qualche altro modulo. L'istruzione "import" in Python non si limita ad analizzare staticamente il codice di un modulo e "caricare" i nomi che trova: "import" <i>esegue</i> anche il codice a livello del modulo. <br /> </div> <div> Questo è (indovinate) un side-effect, appunto. Si tratta di un side-effect voluto, una scelta di design. Ma è pur sempre un side-effect e andrebbe maneggiato con cura. Il tutorial di Python (che <a href="https://pythoninwindows.blogspot.com/2020/05/il-tutorial-di-python-e-difficile.html">ho tradotto</a> di recente!) <a href="https://pytutorial-it.readthedocs.io/it/python3.8/modules.html#approfondimenti-sui-moduli">mette bene in chiaro</a> che bisognerebbe limitare attentamente il codice a livello di modulo: <br /> </div> <div><br /></div> <div> "<i>Un modulo può contenere istruzioni eseguibili oltre a definizioni di funzioni. Queste istruzioni devono essere intese come un modo di <b>inizializzare </b>il modulo.</i>"<br /> </div> <div><br /></div> <div> Cioè, va bene mettere degli "import", delle definizioni di costanti utili, e cose del genere. Ma un <i>print</i> è solo un side-effect indesiderato e non dovrebbe stare a livello del modulo. Altrimenti, vedrete messaggi surreali che compaiono quando importate il modulo e quindi anche, per esempio, quando lo testate. <br /> </div> <div> Naturalmente questo vale non solo per <i>print</i>, ma per qualsiasi istruzione "globale": e infatti è buona pratica evitarle per quanto possibile. E, <i>manco a dirlo</i>, i corsi e i tutorial e i video su YouTube vanno avanti per un sacco di tempo a proporre agli allievi di mettere istruzioni "globali" nei moduli, <i>prima finalmente di arrivare a spiegare le funzioni</i>. <br /> </div> <div><br /></div> <div> "Ma che importanza ha, poi?", chiede a questo punto il docente sciatto e pigro: "quando si comincia va bene fare così, poi si spiega che però è meglio non mettere i <i>print</i> globali. Come la fai lunga!".&nbsp; <br /> </div> <div> Ma è appunto questo il problema, vedete. Dopo che sei andato avanti per duecento pagine a <i>mostrare</i> agli allievi una cosa sbagliata, diventa un pochino difficile convincerli a smettere. Davvero si insegna con <i>l'esempio</i> e con <i>l'abitudine</i>. Il docente sciatto pensa che si possa iniziare programmando provvisoriamente <i>male</i>, e poi si imparerà a programmare <i>bene</i>. Ma non è così: il progresso va da programmare bene cose semplici, a programmare bene cose più complesse. Se inizi a programmare male, continuerai a programmare male. <br /> </div> <div><br /></div> <div> Prova ne sia che i principianti continuano a riempire i moduli con filastrocche interminabili di istruzioni a livello di modulo, <i>anche quando ormai hanno imparato le funzioni</i>. Nella loro testa, le funzioni servono solo per il codice che bisogna ripetere (chiamare più volte); per tutto il resto, sanno che le istruzioni globali funzionano, perché le usano da molto tempo... e se funzionano, che bisogno c'è di complicarsi la vita? </div> <div><br /></div> <div> Ma questa è una responsabilità dell'insegnante, non del principiante. Ho visto molti corsi in cui addirittura il side-effect del <i>print</i> è una cosa voluta, <i>ricercata</i>: <br /> </div> <div style="text-align: left;"><pre># nel modulo foo.py</pre></div> <div style="text-align: left;"><pre>print('sono foo.py!')</pre></div> <div> Cosi quando poi faccio "import foo", l'output del <i>print</i> mi dice appunto che ho importato "foo". Questa è didattica criminale. <br /> </div> <div><br /></div> <div> La soluzione? Semplicissimo: non spiegate i moduli finché non avete gli strumenti necessari. Restate sulla shell più a lungo; spiegate <i>anche le funzioni </i>nella shell. Solo a quel punto potete introdurre i moduli nel discorso. In questo modo verrà spontaneo organizzare il codice del modulo in funzioni; e non ci sarà bisogno di usare istruzioni "globali", tra cui i <i>print</i>. <br /> </div> <div> Se avete già spiegato le funzioni, potete introdurre i moduli come un modo per raccogliere le funzioni, invece che un modo per raccogliere le istruzioni. A questo punto le istruzioni globali diventeranno un'eccezione, come appunto dovrebbe essere. Se avviate gli allievi sulla buona strada, resteranno sulla buona strada. <br /> </div> <div> <h3 style="text-align: left;"><i>Print</i> nelle funzioni: da evitare.</h3> </div> <div> D'accordo, abbiamo capito che non va bene mettere side-effect come <i>print</i> a livello del modulo. Ma abbiamo anche detto che i moduli hanno bisogno dei <i>print</i> espliciti per visualizzare il loro output. Quindi va bene mettere i <i>print</i> all'interno delle funzioni, giusto? </div> <div><br /></div> <div>Sbagliato. <br /></div> <div><br /></div> <div> Perché non bisogna dimenticare che <i>print</i> è pur sempre un side-effect. Idealmente una funzione dovrebbe essere una struttura isolata che riceve valori come parametri ed emette valori di ritorno. Più ci allontaniamo da questa situazione ideale, più la nostra funzione diventa difficile da testare, da capire, da usare. Ma soprattutto: se stiamo <i>insegnando </i>le funzioni, allora <i>dobbiamo </i>fare ogni possibile sforzo per mettere gli studenti sulla buona strada. Parametri e valori di ritorno: tutto ciò che esce da questo confine è sospetto. Se <i>insegni </i>qualcosa che esce da questo confine, è doppiamente sospetto. <br /> </div> <div> Questo non vale solo per <i>print</i>, ovviamente. Usare "global", ovvero accedere <i>in scrittura</i> a una variabile globale, è una pessima idea. Ma anche accedere <i>in sola lettur</i>a non è proprio ideale: <br /> </div> <div style="text-align: left;"><pre>VAL = 100 # una costante</pre></div> <div style="text-align: left;"><pre>def foo():</pre></div> <div style="text-align: left;"> <pre>&nbsp;&nbsp;&nbsp; return VAL + 1 # accedo in lettura alla costante</pre> </div> <div> Questo naturalmente va bene e si fa spesso in pratica. Ma non va proprio <i>benissimo</i>, e onestamente non lo insegnerei. L'ideale sarebbe che tutto ciò di cui una funzione ha bisogno le venisse passato come argomento. Quindi, perché non approfittarne al momento di spiegare i valori di default? </div> <div style="text-align: left;"><pre>VAL = 100</pre></div> <div style="text-align: left;"><pre>def foo(val=VAL): </pre></div> <div style="text-align: left;"> <pre><span>&nbsp;&nbsp;&nbsp; return val + 1</span></pre> </div> <div> Ma torniamo al nostro <i>print</i>. Il modo peggiore in assoluto di usarlo dentro una funzione è questo: <br /> </div> <div style="text-align: left;"> <pre>def check_age(age): # questo è un crimine</pre> </div> <div style="text-align: left;"> <pre>&nbsp;&nbsp;&nbsp; if age &gt;= 18:</pre> </div> <div style="text-align: left;"> <pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print('sei maggiorenne')</pre> </div> <div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp; else:</pre></div> <div style="text-align: left;"> <pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print('sei minorenne')</pre> </div> <div> Usare <i>print </i>al posto di <i>return </i>è un crimine contro l'insegnamento e andrebbe punito. Di nuovo: non importa se è solo "provvisorio", in attesa di spiegare <i>return</i>. La soluzione è spiegare subito <i>return </i>e usare subito <i>return</i>. <br /> </div> <div> Quante volte avete visto degli orrori del genere in un tutorial, in un corso su YouTube, ovunque? Il "vantaggio" è che in questo modo sembra che la funzione "faccia qualcosa" subito, la si può "usare" immediatamente, si vede subito "il risultato". </div> <div> Io proprio non capisco tutta questa ansia da prestazione. Il problema, non c'è bisogno di dirlo, è che una funzione non si usa così. E poco importa se fai vedere agli allievi che la usi <i>subito</i>, se però fai vedere che la usi <i>male</i>.&nbsp; <br /> </div> <div>La soluzione? Semplice: la shell!</div> <div style="text-align: left;"> <pre>&gt;&gt;&gt; def check_age(age): # nella shell!</pre> </div> <div style="text-align: left;"> <pre>...&nbsp;&nbsp;&nbsp;&nbsp; if age &gt;= 18: </pre> </div> <div style="text-align: left;"> <pre>...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return True</pre> </div> <div style="text-align: left;"> <pre>...&nbsp;&nbsp;&nbsp;&nbsp; else:</pre> </div> <div style="text-align: left;"> <pre>...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return False</pre> </div> <div style="text-align: left;"><pre>...</pre></div> <div style="text-align: left;"> <pre>&gt;&gt;&gt; check_age(42) # guarda mamma, senza print</pre> </div> <div style="text-align: left;"><pre>True</pre></div> <div> Come abbiamo detto, la shell non ha bisogno di <i>print</i>. Se vi serve far vedere una funzione <i>subito </i>e volete anche usarla <i>bene</i>, spiegate le funzioni nella shell e non nei moduli! Semplice, vero? <br /> </div> <div><br /></div> <div> Si chiama didattica. Una cosa a cui forse bisognerebbe pensare <i>prima </i>di mettersi a insegnare. <br /> </div> <div><br /></div> <div> Quando poi volete cominciare a usare i moduli, potete andare avanti ancora a lungo semplicemente importandoli nella shell: </div> <div style="text-align: left;"><pre>&gt;&gt;&gt; import my_module</pre></div> <div style="text-align: left;"> <pre>&gt;&gt;&gt; my_module.check_age(42) # ancora senza print</pre> </div> <div style="text-align: left;"><pre>True</pre></div> <div> Questo, tra l'altro, vi porta già su un terreno contiguo a molte buone pratiche, come l'esecuzione di moduli dalla riga di comando, "sys.argv" e così via. Se incanalate il discorso in questa direzione, i vostri allievi capiranno istintivamente che l'output è una cosa da rimandare il più possibile: si deve chiedere solo all'ultimo momento, quando ce n'è effettivamente bisogno: </div> <div><pre style="text-align: left;">$ python -m my_module 42</pre></div> <div> e cose del genere. Di nuovo, vedete quanti frutti si possono raccogliere se solo si aspetta che maturino. <br /> </div> <div><br /></div> <div> Invece, manco a dirlo, i corsi e i tutorial e i video su YouTube raccolgono sempre <i>subito</i>, il più presto possibile. E siccome le cattive pratiche portano ad altre cattive pratiche, finisce che questi corsi usano <i>print </i>dentro le funzioni <i>anche </i>quando ormai hanno spiegato <i>return</i>. Sempre "provvisoriamente", si capisce. </div> <div style="text-align: left;"><pre>def mydiv(a, b): # terrificante</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp; if b == 0:</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print('non puoi dividere per zero')</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp; return a / b</pre></div><div><i>Print </i>usati per gestire casi particolari, per dire qualcosa <i>mentre </i>si restituisce un risultato... Una variegata galleria degli orrori. <br /></div><div><br /></div><div>Arriva poi il momento in cui, fatti bene i conti e seguite le buone pratiche, davvero occorre emettere qualcosa nello standard output, con un <i>print</i>. (All'inverso, questo succede anche con <i>input</i>, beninteso: non ne parliamo qui per non allungare ulteriormente il discorso.)<br /></div><div style="text-align: left;">Non è sempre sbagliato usare <i>print</i>. Ma se avete messo i vostri allievi sulla strada giusta fin dal principio, quando finalmente arrivate a spiegare <i>print </i>non correte il rischio di mandarli a sbattere. In particolare<i>, print </i>riguarda <i>solo ed esclusivamente</i> l'interfaccia con l'utente del programma. Ora, è davvero necessario spiegare l'interfaccia utente presto nel vostro corso? Potete andare avanti per centinaia di pagine o decine di ore su YouTube senza preoccuparvi di questo aspetto: semplicemente, fate dialogare le funzioni tra loro e chiamatele dalla shell. L'interfaccia utente è un problema separato (e complicato) che può aspettare. <br /></div><div>Se sentite comunque l'impellente necessità di introdurre <i>print </i>"subito", quanto meno <br /></div><ul style="text-align: left;"><li>non parlatene prima di aver spiegato le funzioni,</li><li>non usatelo mai nella shell e nei moduli, a livello "globale",</li><li>non usatelo mai al posto di un <i>return</i>, al posto di un'eccezione, o per gestire un caso particolare.</li></ul><div>Tutto questo, neanche "provvisoriamente". Ma anche se rispettate queste buone pratiche, non basta ancora: <br /></div><div><ul style="text-align: left;"><li>non spargete comunque <i>print </i>nelle funzioni. </li></ul></div><div>Una funzione non dovrebbe restituire un risultato <i>e anche</i> occuparsi della sua interfaccia utente. Questa è una violazione molto grave del <a href="https://it.wikipedia.org/wiki/Principio_di_singola_responsabilit%C3%A0" rel="nofollow">principio di singola responsabilità</a>: se passate ai vostri allievi questa abitudine, anche inavvertitamente e "provvisoriamente", non riusciranno <i>mai più</i> a scrollarsela di dosso. Davvero, credetemi. Ho visto abbastanza principianti devastati da pessimi corsi su YouTube per saperlo. <br /></div><div><br /></div><div>Quello che invece potete far vedere, è come creare una singola funzione ("main"), o comunque poche funzioni ben delimitate, che si occupano in modo specifico di emettere l'output prodotto dalle altre funzioni "pure". <br /></div><div style="text-align: left;"><pre>def check_age(age):</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp; return age &gt;= 18</pre></div><div style="text-align: left;"><pre><br /></pre></div><div style="text-align: left;"><pre>def main(): # qui gestisco l'interfaccia</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp; age = int(input('età...'))</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp; if not check_age(age):</pre></div><div style="text-align: left;"><pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print('non hai l'età')</pre></div><div>Ovviamente non è sempre possibile fare una cosa così pulita. Tuttavia, in primo luogo: un corso fa dozzine di semplificazioni lungo la strada; perché <i>proprio qui</i> non va bene semplificare? In secondo luogo: anche se ci sono casi più complessi, l'importate è aver passato il concetto che l'interfaccia utente dovrebbe essere separata dal codice "di business" del programma. E siccome <i>print </i>fa parte dell'interfaccia utente, allora <i>print </i>non deve stare nel codice "di business". <br /></div><div><br /></div><div>Facile no? Non vi viene voglia di seguire un corso che, per una volta, insegna a fare le cose per bene?<br /></div><div><br /></div>ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-50580654823061568362020-05-25T18:46:00.000+02:002020-05-25T18:47:07.581+02:00Il tutorial di Python è... difficile?!?Ho <a href="https://pytutorial-it.readthedocs.io/" rel="nofollow" target="_blank">tradotto in Italiano</a> il tutorial ufficiale di Python. E mi sono reso conto con una certa sorpresa che... è maledettamente difficile, per un principiante! O forse è proprio <a href="https://pythoninwindows.blogspot.com/2020/03/come-imparare-python-senza-studiare.html" target="_blank">Python a essere difficile</a>, come non mi stanco di dire. Andiamo con ordine.<br /> <br /> Prima di tutto, perché questa traduzione? Il fatto è che sto raccogliendo delle idee e dei materiali per un corso (che non farò mai, di questo passo) e chiaramente il tutorial ufficiale è una tappa obbligatoria. Sapevo che esisteva una vecchissima traduzione su <a href="https://www.python.it/" rel="nofollow" target="_blank">Python.it</a>, ma adesso è stata rimossa perché ormai superata. Ho deciso di farne una nuova perché <i>tradurre </i>è un ottimo modo per <i>capire</i>: ti costringe davvero a seguire riga per riga il ragionamento, a verificare se hai capito davvero, a dubitare e controllare e correggere <i>le tue idee</i> insieme al testo che hai sotto gli occhi.<br /> <br /> Il vantaggio di questa nuova traduzione è che, a differenza di quella vecchia, è <i>aggiornabile</i>: prima di tutto, il sorgente è <a href="https://github.com/ricpol/pytutorial-it" rel="nofollow" target="_blank">ospitato su GitHub</a>, in modo che chiunque possa intervenire o continuare il lavoro anche se io dovessi lasciar perdere un giorno. Poi, la traduzione è fatta direttamente sui sorgenti della <a href="https://github.com/python/cpython/tree/master/Doc/tutorial" rel="nofollow" target="_blank">repository di Python</a>: in questo modo è facile verificare quando un nuovo <i>commit </i>viene aggiunto all'originale e tradurre la parte corrispondente in Italiano, mantenedo le versioni sempre in sincrono. Di più: esistono (attualmente) tre versioni leggermente diverse della traduzione: quella "stabile" per Python 3.8, quella in "pre-release" per Python 3.9 e quella "di sviluppo" corripondente al <i>master branch</i> della repository di Python. Ciascuna di queste versioni può ricevere aggiornamenti indipendenti nell'originale.<br /> Infine, oggi la tecnologia permette di gestire tutto questo in modo molto più semplice e potente: ReadTheDocs compila automaticamente i sorgenti Sphinx, ricavando non solo la versione in Html, ma anche <a href="https://pytutorial-it.readthedocs.io/_/downloads/it/python3.8/pdf/" rel="nofollow" target="_blank">quella in Pdf</a> e in Epub. L'uso di <a href="https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html" rel="nofollow" target="_blank">Intersphinx</a> permette di preservare i link alla documentazione, in modo che si possa usare il tutorial tradotto in modo identico all'originale. La manutenzione di tutto lo stack è praticamente automatica.<br /> <h3> Comincia dal tutorial, dice...</h3> Ma torniamo al tutorial in sé. Devo ammettere che ne avevo una conoscenza superficiale: sicuramente l'ho letto molto tempo fa, ed ero tornato su certi argomenti anche più di recente, ma nel frattempo sono state aggiunge intere nuove sezioni, molte cose sono cambiate. Il compito di tradurlo mi ha costretto a rileggerlo con attenzione con gli occhi del principiante... e devo dire che non è stata un'esperienza piacevolissima, ecco.<br /> Innumerevoli volte, sui forum italiani e inglesi, puoi trovare il consiglio standard: vuoi imparare Python? Comincia dal tutorial. È anche un modo per filtrare le richieste: se non hai letto neanche il tutorial, non venire qui a chiedere come funziona una lista. Ed è giusto così, ci mancherebbe. Il tutorial esiste già, è gratis: leggerlo è il minimo richiesto. S'intende che il principiante resta sempre, immancabilmente, insoddisfatto da questo consiglio. Quello che vuole è la <i>ricettina pronta da copiare</i>: non vuole davvero capire come <i>funziona</i> una lista, vuole che qualcuno gli dica quello che deve <i>fare</i> con una lista per risolvere il suo problema del momento.<br /> <br /> Ma supponiamo invece che davvero il principiante fiducioso si metta di buona lena a leggere il tutorial di Python. La verità è che potrebbe essere un esperienza sgradevole in molti punti. Come minimo, si può dire che il tutorial di Python sia <i>molto</i> più difficile di qualsiasi cosa noi oggi intendiamo come "tutorial". Questo non vuol dire che un principiante non ce la può fare: ma la curva di apprendimento è straordinariamente ripida per gli standard a cui siamo abituati. Anche senza scendere al livello di molti tutorial su YouTube (lo so... lo so: eppure è a <i>questo</i> che il principiante pensa quando si parla di tutorial), prendiamo Django come esempio di eccellenza. Il <a href="https://docs.djangoproject.com/en/3.0/intro/tutorial01/" rel="nofollow" target="_blank">tutorial di Django</a>, o anche quello delle Django Girls (disponibile perfino <a href="https://tutorial.djangogirls.org/it/" rel="nofollow" target="_blank">in Italiano</a>), è un modello: prende un caso d'uso abbastanza interessante, lo sviluppa dal principio, semplifica senza banalizzare e rimanda alla documentazione specifica per gli approfondimenti. Si può argomentare che Django è <i>una</i> tecnologia limitata, per la quale è facile trovare un caso d'uso abbastanza rappresentativo. Ma ci sono anche altri modelli possibili per fare un tutorial.<br /> <h3> Un tutorial o una demo?</h3> La verità è che quello di Python è più uno <i>showcase</i> o una <i>demo</i> di un tutorial vero e proprio. Mette insieme decine di argomenti in un tour-de-force velocissimo, in cui nove volte su dieci ci si deve limitare a <a href="https://pytutorial-it.readthedocs.io/it/python3.8/datastructures.html#set" rel="nofollow" target="_blank">una breve descrizione e un esempio</a>. Ma soprattutto manca di prospettiva, peccato capitale per un tutorial: non si capisce quando un argomento <a href="https://pytutorial-it.readthedocs.io/it/python3.8/modules.html#il-percorso-di-ricerca-dei-moduli" rel="nofollow" target="_blank">è essenziale</a> e quando invece <a href="https://pytutorial-it.readthedocs.io/it/python3.8/modules.html#file-compilati" rel="nofollow" target="_blank">non c'è fretta</a> di impararlo.<br /> Alcune sezioni, come <a href="https://pytutorial-it.readthedocs.io/it/python3.8/classes.html#scope-e-namespace-in-python" rel="nofollow" target="_blank">l'eccellente spiegazione</a> sugli <i>scope</i> e <i>namespace</i>, sembrano messe lì in mancanza di un posto migliore nella documentazione. Altre <a href="https://pytutorial-it.readthedocs.io/it/python3.8/floatingpoint.html" rel="nofollow" target="_blank">sono chiaramente delle FAQ</a> che non dovrebbero stare in un tutorial.<br /> Altre ancora sono evidentemente delle integrazioni perché qualcuno avrà pensato, perché no, di documentare <i>anche nel tutorial</i> una feature <a href="https://pytutorial-it.readthedocs.io/it/python3.8/controlflow.html#parametri-speciali" rel="nofollow" target="_blank">aggiunta di recente</a>. Così però si perde di vista la funzione essenziale del tutorial, quella di orientare il principiante. Non solo: con lo stesso criterio, talvolta invece a nessuno è venuto in mente di includere una feature nel tutorial. Solo che poi passano gli anni e quella feature diventa popolare, addirittura idiomatica: e finisce così che altre parti del tutorial vi fanno riferimento implicito, dimenticandosi che quella feature <i>non è mai stata presentata</i>.<br /> Così si può arrivare a disastri spettacolari come quando, <a href="https://pytutorial-it.readthedocs.io/it/python3.8/inputoutput.html#leggere-e-scrivere-files" rel="nofollow" target="_blank">parlando di file</a>, quasi di sfuggita si dice che "è buona pratica usare <i>with</i> (...). In questo modo il file verrà sempre chiuso al termine delle operazioni (...)". Peccato solo che da nessuna parte si possa trovare una spiegazione dei <i>context manager</i>. Così il tutorial si limita a raccomandare l'uso di <i>with</i> con i file, senza spiegare perché funziona in quel modo: un esempio da manuale di <a href="https://en.wikipedia.org/wiki/Cargo_cult_programming" rel="nofollow" target="_blank">cargo cult programming</a>.<br /> Un altro esempio del genere è la mancanza di una spiegazione esplicita dei booleani, forse perché prima di Python 2.3 <a href="https://www.python.org/dev/peps/pep-0285/" rel="nofollow" target="_blank">non esistevano</a>. Se ne sente la mancanza in paragrafi come quello in cui <a href="https://pytutorial-it.readthedocs.io/it/python3.8/datastructures.html#un-approfondimento-sulle-condizioni" rel="nofollow" target="_blank">si spiegano le condizioni</a> degli <i>if</i>, dando per scontato che il lettore sappia già che cosa vuol dire che un valore è "vero" o "falso".<br /> Un altro esempio ancora, manca una spiegazione di che cosa è il tipo di dati "bytes": a dire il vero qui penso che la ragione sia semplicemente che tutti lo danno per scontato. In ogni caso, il problema è che poi bisogna <a href="https://pytutorial-it.readthedocs.io/it/python3.8/inputoutput.html#metodi-degli-oggetti-file" rel="nofollow" target="_blank">arrampicarsi sugli specchi</a> quando si cerca di spiegare i metodi <i>tell()</i> e <i>seek()</i> di un file.<br /> Alcune assenze, poi, sono veramente difficili da spiegare. Parlando di <a href="https://pytutorial-it.readthedocs.io/it/python3.8/classes.html#ereditarieta" rel="nofollow" target="_blank">ereditarietà</a> e di <a href="https://pytutorial-it.readthedocs.io/it/python3.8/classes.html#ereditarieta-multipla" rel="nofollow" target="_blank">ereditarietà multipla</a>, ogni paragrafo praticamente implora che ci sia una spiegazione di <i>super()</i>, e invece misteriosamente <i>super()</i> non viene mai spiegato. <br /> <br /> Un altro problema è che la logica della demo è per molti aspetti opposta a quella del tutorial. Le sezioni di una demo vanno lette quasi simultaneamente, con molti richiami incrociati. Un tutorial dovrebbe procedere gradualmente: se un argomento richiede la conoscenza di cose più avanzate, o vi si rinuncia del tutto; oppure si anticipa l'indispensabile e si rimanda a futuri approfondimenti. Invece il tutorial di Python è pieno di anticipazioni implicite e collegamenti che mancano. Forse l'esempio più clamoroso è il capitolo <a href="https://pytutorial-it.readthedocs.io/it/python3.8/errors.html" rel="nofollow" target="_blank">sulle eccezioni</a>, che per ragioni misteriose è collocato <i>prima</i> di quello <a href="https://pytutorial-it.readthedocs.io/it/python3.8/classes.html" rel="nofollow" target="_blank">sulle classi</a>. La cosa buffa è che non c'è nessuna particolare ragione per questo ordine: sembra proprio una svista che si perpetua dalla notte dei tempi. Il guaio però è che quando si parla di <a href="https://pytutorial-it.readthedocs.io/it/python3.8/errors.html#eccezioni-personalizzate" rel="nofollow" target="_blank">eccezioni personalizzate</a> senza aver chiaro come funzionano le classi... si va a sbattere forte.<br /> <h3> Chi è il principiante, qui?</h3> Ma forse l'aspetto che mi ha colpito di più mentre traducevo il tutorial sono gli innumerevoli passaggi in cui si dà per scontato che il lettore sia già "uno di noi". Non appena <a href="https://pytutorial-it.readthedocs.io/it/python3.8/introduction.html#stringhe" rel="nofollow" target="_blank">introduce le stringhe</a>, non si fa problemi a dire che "i caratteri speciali sono resi con il <i>backslash</i> di <i>escape</i>": non riesce neanche a concepire un lettore che non sappia già che cosa vuol dire "<i>escape</i>" e perché si deve fare; un lettore che <i>non abbia già fatto</i> quel tipo di operazione in altri contesti.<br /> Ci sono dozzine di piccoli <a href="https://en.wikipedia.org/wiki/Name-dropping" rel="nofollow" target="_blank">name-dropping</a> di questo tipo; e sono irritanti proprio come può esserlo qualsiasi name-dropping che in una conversazione ti fa sentire "fuori dal giro".&nbsp; Ma è proprio questo il punto: il tutorial presuppone che il lettore faccia in qualche modo già parte di un giro. Talvolta immagina che possa avere qualche familiarità con C++ o Pascal (!); occasionalmente salta fuori <a href="https://pytutorial-it.readthedocs.io/it/python3.8/inputoutput.html#vecchio-metodo-di-formattazione" rel="nofollow" target="_blank">un riferimento</a> a una funzione C. Nell'insieme, non c'è niente di irreparabile, beninteso. Tuttavia è evidente che "il principiante" del tutorial di Python non è esattamente quello a cui pensiamo noi.<br /> <br /> Soprattutto, il tutorial dà per scontato che il lettore abbia una <i>radicata consuetudine</i> con la shell Unix. Il punto di vista della shell Unix è talmente invasivo da produrre effetti quasi comici. Il <a href="https://pytutorial-it.readthedocs.io/it/python3.8/interpreter.html" rel="nofollow" target="_blank">capitolo introduttivo</a> è già di una difficoltà allarmante, mentre dettaglia tutti i modi in cui si può usare Python dalla riga di comando e descrive il supporto per GNU Readline (!). Per chiarire il concetto, a GNU Readline (!) è dedicata anche <a href="https://pytutorial-it.readthedocs.io/it/python3.8/interactive.html" rel="nofollow" target="_blank">un'appendice</a>. Parlare di <a href="https://pytutorial-it.readthedocs.io/it/python3.8/modules.html#eseguire-moduli-come-script" rel="nofollow" target="_blank">moduli eseguiti come script</a> richiede solo poche righe, perché non c'è dubbio che i lettori sono già perfettamente a loro agio a eseguire programmi dalla riga di comando. Naturalmente non può mancare una <a href="https://pytutorial-it.readthedocs.io/it/python3.8/stdlib.html#argomenti-della-riga-di-comando" rel="nofollow" target="_blank">descrizione separata</a> di <i>sys.argv</i> e <i>argparse</i>.<br /> Ma personalmente il punto che mi ha fatto più ridere è quando presenta <i>sys</i> e la prima cosa che si sente in dovere di <a href="https://pytutorial-it.readthedocs.io/it/python3.8/modules.html#moduli-della-libreria-standard" rel="nofollow" target="_blank">descrivere</a> è... come cambiare il prompt della shell con <i>sys.ps1</i> e <i>sys.ps2</i>! Voglio dire, è davvero un'informazione essenziale da includere in un tutorial. E nell'esempio relativo non si fa neppure mancare lo sberleffo a Windows, che fa tanto guerrigliero Linux anni '90.<br /> <br /> Parlando di Windows, vi farà piacere sapere che il tutorial... non ne parla. I pochissimi accenni presenti sono evidentemente aggiunti in tempi successivi e servono solo alle indispensabili informazioni di servizio. Ma più in generale il tutorial non menziona mai, neppure una volta, la possibilità di fare programmi Python con interfaccia grafica. Per chi lo ha scritto, è evidente che tutto il mondo di Python e la mentalità dei suoi sviluppatori ruota intorno alla riga di comando.<br /> <br /> Ma più ancora di tutto questo, mi ha colpito l'<i>habitus mentale dell'hacker</i> che trapela di continuo: in molti passaggi si vede che l'autore non si fa problemi a scrivere in modo molto tecnico e sintetico, sfidando il lettore a stargli dietro. Non lo fa apposta, davvero: è solo che bisogna capire che il tutorial è scritto da programmatori per programmatori, non da insegnanti per allievi.<br /> Il passaggio più esilarante in assoluto è <a href="https://pytutorial-it.readthedocs.io/it/python3.8/controlflow.html#stringhe-di-documentazione" rel="nofollow" target="_blank">quando parla delle docstring</a>. Sta spiegando una cosa assolutamente semplice e innocua, ovvero come bisogna formattare le docstring: e di colpo si tuffa in una vertiginosa descrizione tecnica di come si dovrebbe <i>implementare un parser di docstring</i>. Bisogna leggerlo per crederci.<br /> Ma è proprio questo il punto. Per quanto a noi possa sembrare surreale, in realtà per un hacker questo è il modo più semplice di spiegare. Per descrivere le regole di formattazione di un testo, non c'è modo migliore di implementare il parser per quel testo. Questo è il modo che un programmatore userebbe per spiegarsi con un altro programmatore.<br /> Un altro esempio di questo tipo è quando spiega il meccanismo di <i>lookup</i> dei <a href="https://pytutorial-it.readthedocs.io/it/python3.8/classes.html#oggetti-metodo" rel="nofollow" target="_blank">nomi dei metodi</a>, e alla fine propone: "(...) può essere utile dare un'occhiata all'implementazione".&nbsp; Ma ci sono anche altri passaggi del genere, tutti notevoli.<br /> <br /> Come nota di costume un po' malinconica, aggiungo che fanno anche parte di questa mentalità hacker i numerosi riferimenti agli sketch dei Monty Python di cui il tutorial è costellato. Il problema non è solo che appesantiscono inutilmente il codice degli esempi e ormai nessuno li capisce più, al punto che revisori successivi si sentono in dovere di <a href="https://pytutorial-it.readthedocs.io/it/python3.8/whatnow.html#id1" rel="nofollow" target="_blank">spiegare in nota</a> le allusioni. Il vero problema è che oggi, semplicemente, nessuno utilizza più questo tipo di ironia nelle documentazioni tecniche. È una cultura che proviene dell'<i>ethos</i> hacker degli anni '70 e '80, ed è irrimediabilmente legata ai programmatori che si sono fatti le ossa in quell'epoca, come Guido e i core developers originari di Python. Oggi, anche solo scrivere qualcosa come "the_answer = 42" richiede una nota per spiegare il riferimento. <br /> <h3> Comincia dal tutorial, comunque. </h3> E quindi? Dopo averlo tradotto, mi sentirei ancora di consigliare tutorial di Python ai principianti?<br /> <br /> Certamente sì. Si tratta comunque di un <i>corpus</i> di informazioni molto importanti, e per lo più accessibili (nonostante i dubbi che ho espresso fin qui).<br /> Piuttosto, mi sembra che il problema sia <a href="https://pythoninwindows.blogspot.com/2020/03/come-imparare-python-senza-studiare.html" target="_blank">sempre quello</a>: negli anni c'è stato uno slittamento semantico del concetto di "principiante". Il tutorial di Python ha ancora in mente i principianti di vent'anni fa. Oggi, <a href="https://leanpub.com/pythoninwindows" rel="nofollow" target="_blank">nel mio libro</a> io dò per scontato che chi si accosta a Python semplicemente <i>non ha idea di come usare la shell</i>, e dedico i primi due capitoli a chiarire i preliminari.<br /> <br /> Nonostante tutto, credo che il tutorial di Python vada bene così com'è (al netto di un po' di correzioni che magari proporrò). Credo che forse ci sarebbe bisogno di affiancargli <i>un altro tutorial</i> ufficiale, più selettivo e costruito con cura, più moderno e con un approccio più didattico: un tutorial preliminare, diciamo.<br /> Ritengo che i principianti, anche quelli di oggi, possano comunque imparare tantissimo dal tutorial: l'importante è non si aspettino un percorso facilitato. <br /> <br />ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]1tag:blogger.com,1999:blog-8801312244642714488.post-72950362766327133072020-05-11T11:41:00.000+02:002020-05-11T11:41:21.529+02:00Python in Windows, il mio nuovo libro!Ci sono davvero pochi aspetti positivi nei tempi che stiamo vivendo (e a proposito: spero davvero che stiate tutti bene e che finisca presto). Anche io, come tutti, ho cercato di sfruttare al meglio il tempo libero in più dedicandomi a un progetto, anche per stare lontano dall'angoscia dei Tg e dei siti di news. <a href="https://leanpub.com/pythoninwindows" rel="nofollow" target="_blank">Python in Windows</a>, il mio nuovo libro, è il risultato di questi due mesi fuori dal mondo.<br /> <br /> Questo libro è... un po' complicato da spiegare: dalla pagina su Leanpub potete comunque scaricare gratuitamente il capitolo introduttivo, che spiega in dettaglio di che cosa si parla.<br /> Prima di tutto, è la mia particolare versione del "manuale per principianti". Ovvero, si rivolge ai principianti ma senza prenderli in giro. Questo libro non cerca di nascondere le difficoltà sotto il tappeto, non pretende che Python sia <a href="https://pythoninwindows.blogspot.com/2020/03/come-imparare-python-senza-studiare.html" target="_blank">un linguaggio semplice</a> da imparare e da usare. Cerco invece di esporre i problemi con chiarezza, partendo dal principio ovvero dall'indispensabile uso dell'interprete dei comandi (sì, proprio <a href="https://pythoninwindows.blogspot.com/2019/11/aiuto-la-finestrella-nera.html" target="_blank">la finestrella nera</a>). I temi trattati nel libro sono veramente tanti, e chiaramente non c'è spazio per approfondire tutto: questo non è un manuale analitico come il mio altro <a href="https://leanpub.com/capirewxpython" rel="nofollow" target="_blank">libro su wxPython</a>. Tuttavia essere sintetici non deve voler dire <i>banalizzare il problema</i>, il flagello di quasi tutti i libri per principianti che conosco. Presento il caso d'uso più semplice in modo che il lettore possa farsi un'idea del problema generale e della sua importanza, e sia invogliato ad approfondire per conto suo.<br /> <br /> Per quanto riguarda l'argomento, anche questo è un po' complicato. Intanto, <i>non è un corso di Python</i>, non insegna il linguaggio. Può essere letto <i>accanto </i>a un buon manuale, invece. L'idea del libro è la stessa che anima <a href="https://pythoninwindows.blogspot.com/p/blog-page.html" target="_blank">le guide</a> e <a href="https://pythoninwindows.blogspot.com/2019/03/e-adesso-dove-clicco.html" target="_blank">alcuni articoli</a> di questo blog, anche se gli argomenti sono più aggiornati e presentati in modo più approfondito e completo. Ovvero, l'idea che <i>saper usare </i>Python nel contesto del proprio sistema operativo (Windows in questo caso) non sia affatto semplice e automatico, anche quando si conosce il linguaggio in astratto. La verità è che <i>non esiste</i> un libro che ti insegna queste cose: anzi, se ne conoscete uno vi prego di farmelo sapere. È un po' come se a scuola guida insegnassero solo la teoria. Alla fine di un buon manuale di Python, uno potrebbe sapere molte cose sulle metaclassi, ma non avere la più pallida idea di perché "open('miofile')" talvolta funziona e talvolta no. O perché non posso importare un modulo che sta in un'altra "cartella". Di come conviene strutturare un progetto. Di che cos'è un virtual environment. Di come si testa il codice. Di come lo si può distribuire. Di come scegliere un editor. E molte, molte altre cose. <br /> <br /> Questo libro racconta che cosa succede quando Python viene <i>usato</i>, davvero, in Windows. Un titolo che avevo in mente era "Vita quotidiana del programmatore Python su Windows": poi era troppo lungo e ho rinunciato. Ma l'idea è quella. <br /> <br /> Spero che vi piaccia: mi raccomando, fatemi sapere se ci sono cose poco chiare, incomplete, sbagliate, o se ci sono argomenti che secondo voi dovrebbero essere trattati (a parte i due o tre capitoli che ancora devo finire di scrivere, e che saranno pronti tra qualche tempo).<br /> <br /> Nel frattempo, non ho certo dimenticato "Capire wxPython"... anzi, credo che il mio prossimo impegno sarà tornare a scrivere qualche nuovo capitolo per questo libro. Non dovrebbe volerci molto, promesso.<br /> ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-17212000443178440782020-04-19T19:46:00.001+02:002020-04-20T09:19:49.323+02:00Name shadowing e import circolari.Ovvero, ma perché diavolo i manuali, i corsi, i video su YouTube (brr...) e chiunque ha la pretesa di insegnare Python non avverte mai il principiante di questa trappola?<br /> <br /> Sia chiaro: contrariamente a quello che dicono tutti, la realtà è che Python è un linguaggio <a href="https://pythoninwindows.blogspot.com/2020/03/come-imparare-python-senza-studiare.html" target="_blank">molto difficile</a> da imparare. Appena metti il piede fuori dal sentiero tracciato dal tuo libro o dal tuo corso, è praticamente certo che finirai in un burrone. Un manuale non può certo prevedere tutti i variegati modi in cui i suoi lettori possono spararsi in un piede. Quando un manuale prescrive di fare A, e poi B, e poi C... la verità è che il suo autore in quel momento chiude gli occhi, incrocia le dita e prega che a nessuno venga in mente di fare anche D, perché a quel punto il risultato potrebbe essere così distante dal previsto e talmente difficile da spiegare, che il lettore potrebbe bruciare il libro per il nervoso.<br /> <br /> Tuttavia alcune trappole sono talmente ovvie e frequenti che davvero diventa una colpa, non avvisare il principiante almeno di queste. Il name shadowing è una. Basterebbe che ogni manuale avesse una pagina completamente bianca, con una scritta rossa al centro: "FATEVI UN FAVORE..." (ok, ve lo dico alla fine di questo post quale favore dovete farvi, altrimenti non lo leggete). Ora, il principiante non ha bisogno di <i>capire</i> tutti i dettagli del name shadowing, e quando farlo è pericoloso e quando non lo è: francamente, tutti i dettagli spesso non li capisce neanche un programmatore esperto. Io di sicuro no. Non c'è niente da fare: Python è difficile. Tuttavia basta dire al principiante di non farlo, tutto qua. Con il tempo imparerà più cose, ma almeno nel frattempo non casca nella trappola, che poi per tirarlo fuori bisogna fare lo spiegone terrorizzante.<br /> <br /> <h3> Che cos'è un import circolare.</h3> Prima di parlare di name shadowing, facciamo una premessa sugli import circolari, che poi è un'altra trappola frequente. Un import circolare accade quando un modulo A importa il modulo B, che a sua volta importa il modulo A. Per esempio, immaginiamo di avere tre moduli fatti in questo modo:<br /> <br /> <pre><code># file a.py print('io sono A') import b # file b.py print('io sono B') import a # file main.py import a</code></pre> <br /> Adesso, quando invochiamo dalla console <code>python main.py</code>, quello che otteniamo è semplicemente:<br /> <br /> <pre><code>Io sono A Io sono B</code></pre> <br /> Forse questa è già una sorpresa: ci saremmo aspettati un rimpallo infinito tra i due moduli. Ma la verità è che Python tiene traccia dei moduli importati in <code>sys.modules</code>: quando un modulo è già stato importato, non lo importa più una seconda volta. Tuttavia, se invece di invocare "main.py" proviamo con <code>python a.py</code>, otteniamo invece:<br /> <br /> <pre><code>Io sono A Io sono B Io sono A</code></pre> <br /> Ops! Questo dipende dal fatto che, la prima volta, "a.py" non viene <i>importato </i>come un modulo, ma viene <i>eseguito </i>come uno script. E a partire da questa piccola crepa, possiamo scavare una voragine di complessità sugli import circolari, andando a caccia di quando è possibile farli, scoprendo come talvolta basta spostare un import dentro una funzione, catalogando astruserie assortite sull'ordine degli import, e così via all'infinito. Una delle perversioni più semplici è avventurarsi in strani "trielli messicani" mentre si importa con il "from":<br /> <br /> <pre><code># file a.py import b def funct_1(): print('sono a.funct_1') b.funct_3() def funct_2(): print('sono a.funct_2') # file b.py from a import func_2 # import a def funct_3(): print('sono b.funct_3') funct_2() # a.funct_2() # file main.py import a a.funct_1()</code></pre> <br /> Quando eseguiamo <code>python main.py</code> il risultato è terrificante:<br /> <br /> <pre><code>Traceback (most recent call last): (...) ImportError: cannot import name 'func_2' from partially initialized module 'a' (most likely due to a circular import)</code></pre> <br /> Tuttavia, per come funzionano gli import in Python, se avessimo semplicemente scritto <code>import a</code> tutto sarebbe andato a posto. E beninteso, se invece le funzioni fossero state solo due che si chiamano a vicenda, avremmo ottenuto un "banale" <code>RecursionError</code>. <br /> <br /> È necessario per un principiante capire queste trappole? Per niente! Anche un programmatore esperto potrebbe avere difficoltà a districarsi nel codice qui sopra. Gli import di Python sono <i>difficili</i>, per tutti. Ciò che sa il programmatore esperto, tuttavia, e che al principiante andrebbe detto subito, è questo: non usare gli import circolari! Così, puro e semplice. Ci sarà tempo per capire meglio tutte le raffinatezze, le regole, le eccezioni e le buone pratiche. Ma finché non si hanno due lauree in filologia pythonica e pythonologia comparata, è meglio lasciar perdere gli import circolari. Ci si guadagna anche e soprattutto con la nozione che gli import circolari sono comunque una <i>cattiva idea</i>, e si impara a mantenere più organizzato il codice. <br /> <br /> <h3> Il name shadowing.</h3> Quello del name shadowing è un concetto apparentemente distinto: consiste nel sovrascrivere dei nomi già assegnati da altri con i nostri. Una minuscola percentuale di name shadowing è semplicemente proibita per legge: per esempio, scrivere <code>for = 10</code> è SyntaxError perché "for" è una parola riservata. Tuttavia Python è molto democratico: le parole riservate sono pochissime, tutto il resto è terreno di caccia libera. <br /> <br /> Possiamo quindi a scrivere cose come <code>dir = 'ciao'</code>, o <code>int = 25</code>. Ovviamente, molto dipende da <i>dove </i>lo facciamo: nel corpo di una funzione, tutto sommato poco male; a livello del modulo... molto peggio; fare <code>__builtins__.int = 25</code> è proprio criminale (ma per fortuna fuori dalla portata del principiante, si spera).<br /> <br /> Il name shadowing preferito dal principiante, non appena si mette a studiare le liste Python, è scrivere trionfalmente <br /> <br /> <code>list = [1, 2, 3, 4, 5]</code><br /> <br /> Ogni volta che vedo una cosa del genere mi cade un capello e non ricresce più. Ma non ce l'ho con il principiante, ci mancherebbe. Il problema è del libro su cui sta studiando, del corso universitario che sta seguendo del... maledetto video su YouTube che guarda perché è gratis. Perché il libro, il corso, il video non si sono premurati di avvertirlo che <i>i nomi sono importanti</i> (le <i>variabili</i>, se volete). Se un nome è già "occupato", non bisogna usarlo. E se ho già sentito quel nome, vuol dire che probabilmente è già occupato. E ogni volta che io, insegnante o autore di libro o di video su YouTube, introduco un concetto nuovo, devo dire con chiarezza al principiante se questo concetto ha un <i>nome</i>, in Python, e qual è questo nome. Parliamo di "liste"? Le liste sono oggetti che hanno <code>list</code> come tipo: <code>list</code>, ricorda questo nome. Dizionari? Quelli sono <code>dict</code>. E così via.<br /> <br /> Il problema è ovviamente che, qualche tempo dopo aver scritto impunemente <code>list = [1, 2, 3]</code> per qualche volta, il principiante scopre che può fare cose del tipo <code>a = list('hello world')</code>... e naturalmente una volta su due si spara nel piede, e a questo punto si attacca al forum e "aiuto non mi funzia!!!1!!!1!!". Ma di nuovo, la colpa non è sua. Ed è difficile consigliargli di seguire un buon manuale passo-passo, come faccio sempre, quando so benissimo che <i>nessun </i>manuale ha davvero quella pagina bianca con la scritta rossa in centro...<br /> <br /> Ma bastasse. Il secondo name shadowing preferito del principiante, dopo "list", è quello dei nomi dei moduli della libreria standard. Creo un modulo che mette insieme qualche esercizio di matematica... e come lo chiamo? Ma <code>math.py</code> naturalmente! Mi sto esercitando con le date? E perché non chiamarlo <code>datetime.py</code>? I numeri casuali? <code>random.py</code> sembra proprio un nome adeguato, vero?<br /> <br /> Ma bastasse! Il problema è che molto spesso all'interno di quei moduli tu <i>stai già importando</i> "math", "datetime", "random"...&nbsp; perché ovviamente, se vuoi scrivere qualcosa sui numeri casuali, dovrai pure importarlo, quel "random" di Python, nel tuo modulo opportunamente chiamato "random.py". E vedete che qui già si prefigura il problema degli import circolari... ma il fatto è che bisogna sempre ricordare che in Python tutto è un "nome", e tutto è un namespace. <br /> <br /> Ma andiamo con ordine. Se scrivo un modulo, lo chiamo "math.py" e ci metto dentro questo:<br /> <br /> <pre><code># file "math.py" (name shadowing...) import math def funct(x): return math.sqrt(x) if __name__ == '__main__': print(funct(4))</code></pre> <br /> Se adesso eseguo il modulo (<code>python math.py</code>) tutto funziona senza problemi. Ma se invece voglio <i>importare </i>il modulo, per esempio da un "main.py" così:<br /> <br /> <pre><code># file "main.py" import math print(math.funct(4))</code></pre> <br /> ecco che all'improvviso tutto si rompe con un <code>AttributeError</code>, per le leggi inflessibili degli import di Python. E già questo potrebbe comprensibilmente sconcertare il principiante. Inoltre, tutti i gli altri moduli nella stessa directory che dovessero per qualche ragione importare "math" fallirebbero, per la dura legge della <code>sys.path</code>. <br /> <br /> Ma non basta ancora. Se adesso provo a fare una cosa assolutamente analoga, ma questa volta con il modulo "random"...<br /> <br /> <pre><code># file "random.py" (name shadowing...) import random def funct(): return random.randint(1, 6) if __name__ == '__main__': print(funct())</code></pre> <br /> Sembra esattamente la stessa cosa, vero? Solo usando "random" invece di "math". Eppure questa volta, <i>anche quando provo a eseguire </i>il modulo, ottengo un <code>AttributeError</code>. Perché questo scherzo succede con "random", ma non con "math"? Se ripeto il gioco usando "datetime" o "subprocess" (altri name shadowing tra i più gettonati) ottengo di nuovo <code>AttributeError</code>. <br /> <br /> Ma non basta ancora... Se adesso provo a eseguire questi moduli attraverso Idle (con F5, per intenderci), trovo che "math" funziona come prima, "datetime" e "subprocess" falliscono come prima con <code>AttributeError</code>... e invece "random" manda completamente in tilt Idle, con una spettacolare catena di errori (la cosa migliore è avviare Idle dall'interprete dei comandi con <code>python -m idlelib</code> per vederlo).<br /> <br /> Perché "math" funziona (almeno eseguendo il modulo) e gli altri no? E perché "random" dà così fastidio a Idle? Ehm... lo state chiedendo a me? Sapete che così su due piedi non saprei rispondere? Per quanto riguarda "math" il fatto è che non c'è davvero nessun <code>math.py</code> nella libreria standard di Python... "math" è scritto in C, e il nome viene probabilmente iniettato dinamicamente in fase di bootstrap. L'ambiente di esecuzione di Idle, poi, è ancora più complesso. Ma a dire il vero, <i>tutto </i>il sistema di import e di caricamento dei moduli in Python è complesso.<br /> <br /> E non basta, non basta ancora. Fino a poco tempo fa, esempi come questi fallivano con un <code>AttributeError</code> il cui messaggio era semplicemente:<br /> <br /> <pre><code>AttributeError: module 'XXX' has no attribute 'YYY'</code></pre> <br /> <br /> A partire da Python 3.8, per merito di <a href="https://bugs.python.org/issue33237" rel="nofollow" target="_blank">questo ticket</a>, il messaggio di errore è diventato molto più esplicito e ci segnala la (probabile) ragione dell'errore:<br /> <br /> <pre><code>AttributeError: partially initialized module 'XXX' has no attribute 'YYY' (most likely due to a circular import)</code></pre> <br /> Ed eccolo che finalmente viene fuori, il nostro import circolare! Il problema qui è che se facciamo name shadowing di un modulo della libreria standard con il nome del nostro modulo, <i>e quindi proviamo a importare l'originale</i>, stiamo facendo proprio uno di quegli import circolari che abbiamo visto nella prima parte di questo post. E non solo facendo name shadowing con la libreria standard, beninteso: è probabilmente una cattiva idea chiamare "django.py" il nostro esercizio di Django. O chiamare "pygame.py" i nostri esperimenti con Pygame.<br /> <br /> E questo spiega anche perché non occorre veramente sapere nei dettagli come e perché e quando e dove fallisce un import circolare: <i>basta capire che non si deve fare name shadowing</i>, e di colpo tutta una classe di import circolari semplicemente sparisce. (Poi certo, restano quelli che fai apposta, del tipo "modulo A che importa B che importa A".)<br /> <br /> Non ho bisogno di sapere con esattezza quante specie di animali pericolosi vivono nel bosco. Mi basta non andare nel bosco. Certo, poi ci sono i casi in cui invece è importante approfondire. Tuttavia la regola "non fare name shadowing, mai" è semplice da applicare e risolve veramente un sacco di problemi strani. Basta dire al principiante di fare un po' di fatica e inventarsi dei nomi nuovi. La cosa più semplice è aggiungere il prefisso "my" per distinguere: "mymath", "myrandom" e così via.<br /> <br /> Quindi, se io scrivessi mai un manuale di Python, terrei una pagina tutta bianca con una scritta rossa al centro che dice: "fatevi un favore, metteteci un po' di fantasia con i nomi". Ecco, tutto qui.<br /> <br />ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-47482276980379132142020-03-26T19:22:00.001+01:002020-03-26T19:23:11.682+01:00Quattro nuovi capitoli del libro...Ho appena pubblicato un aggiornamento del mio libro <a href="https://leanpub.com/capirewxpython" rel="nofollow" target="_blank">Capire wxPython</a>, con quattro nuovi capitoli... di un certo spessore!<br /> <br /> Due capitoli sono dedicati agli eventi del mouse e della tastiera, e sostanzialmente chiudono la parte dedicata agli eventi, almeno per il momento... fino a quando non prenderò coraggio per scrivere i due/tre difficilissimi capitoli che ancora mancano sulla meccanica dei loop degli eventi.<br /> Parlando degli eventi del mouse mi addentro in alcuni dettagli inconsueti: per esempio, una descrizione di come è possibile manovrare la rotellina... inutile dire che sono cose che trovate solo nel mio libro! Nel capitolo sulla tastiera, invece, parlo estesamente anche del focus e degli eventi di attivazione delle finestre.<br /> <br /> Ma la parte più complicata da scrivere è stata il capitolo "Fornire aiuto all'utente", che descrive i molti modi in cui possiamo appunto inserire indicazioni per l'utente nel nostro programma: dai semplici tooltip alle varie finestre di "About...". La sezione più ampia è però dedicata all'analisi del mini-framework per gestire l'help in linea (la classica "guida" che appare quando premete F1 nei programmi, per capirci).<br /> Sono pagine che mi hanno fatto sudare più del dovuto, un po' perché non conoscevo bene questa parte di wxPython, ma anche perché il sistema conteneva dei fastidiosi bachi che per fortuna sono riuscito a far correggere al volo al buon Robin Dunn, gentile e sollecito come sempre...<br /> Il risultato è questo capitolo (che è il più lungo che ho scritto finora!) contiene alcune tecniche che... non funzionano ancora nella versione ufficiale di wxPython. Se proprio siete ansiosi di provarle, potete scaricare una <a href="https://wxpython.org/Phoenix/snapshot-builds/" rel="nofollow" target="_blank">snapshot build</a>... altrimenti non vi resta che aspettare la prossima release ufficiale tra qualche tempo.<br /> <br /> L'ultimo dei quattro nuovi capitoli è breve (per fortuna!) e riguarda ancora gli eventi: in particolare la necessità che talvolta si ha di tenere sincronizzato lo stato delle voci dei menu e dei pulsanti della barra degli strumenti, o di altri elementi dell'interfaccia. Eventualmente anche tra finestre diverse.<br /> <br /> Come sempre, l'aggiornamento è gratuito per chi ha già comprato il libro. Per gli altri, il prezzo resta sempre quello (vergognosamente basso).<br /> Buona lettura!<br /> <br />ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]2tag:blogger.com,1999:blog-8801312244642714488.post-82715034867653687932020-03-10T22:26:00.002+01:002020-11-15T11:51:08.991+01:00Come imparare Python senza studiare.<i>Come imparare Python senza studiare?</i>, chiedevano scherzosamente qualche giorno fa su <a href="https://forumpython.it/" rel="nofollow" target="_blank">Forumpython.it</a>. Era una battuta, si capisce. Se non fosse... a un livello più profondo, molti principianti sembrano farsi questa domanda sul serio. Se non fosse... a un livello ancora più profondo, potrebbe aver senso davvero, domandarsi come imparare Python senza studiare. Quindi provo a rispondere come se fosse una domanda seria.<br /> <br /> <h3> Si può guidare anche senza patente. </h3> Mettiamola così: è la <a href="https://en.wikipedia.org/wiki/Macintosh_128K" rel="nofollow" target="_blank">maledizione </a>che Steve Jobs ha lanciato sul mondo nel 1984 (<a href="https://en.wikipedia.org/wiki/Nineteen_Eighty-Four" rel="nofollow" target="_blank">coincidenze</a>...) e non possiamo più farci nulla. <br /> Capiamoci: la tendenza a presentare cose difficili come se fossero semplici <a href="https://en.wikipedia.org/wiki/You_Press_the_Button,_We_Do_the_Rest" rel="nofollow" target="_blank">per esigenze commerciali</a> non è certo nata con Steve Jobs e il suo Macintosh. Ma nel nostro piccolo mondo dell'informatica, quello è stato lo spartiacque: il computer nelle case di tutti, il computer accessibile a tutti. Anche e soprattutto <i>cognitivamente</i>, accessibile a tutti. E con la progressiva, capillare diffusione dei sistemi informatici, oggi sembra inevitabile che tutti <i>possano sapere</i> come si usa un computer. Non è scontato che dovesse andare così: anche l'aspirapolvere è molto diffuso e il marketing prova a convincerci che è <a href="https://www.kirby.com/clean-homes-100-years/" rel="nofollow" target="_blank">facile da usare</a>. "Non dovete neppure cambiare il sacchetto!", eppure in genere diamo almeno un'occhiata al libretto di istruzioni per capire come si puliscono le parti interne. Ma il computer, quello è diverso: da quella volta nel 1984, pretendiamo di <i>sapere già</i> come si usa. Deve essere <i>intuitivo</i>, <a href="https://en.wikipedia.org/wiki/Digital_native" rel="nofollow" target="_blank">innato</a> addirittura. Nessuno Stato permette ai suoi cittadini di guidare una macchina senza aver preso la patente: ma <a href="https://xkcd.com/1200/" rel="nofollow" target="_blank">affidiamo ogni cosa</a>, dalla comunicazione politica al conto in banca, a un oggetto estremamente complesso che, per effetto di un marketing concepito in un'epoca lontana, tutti ormai concordiamo essere intuitivo da usare.<br /> <br /> Inevitabilmente questo trend ha investito il mondo della programmazione. Oggi molti nuovi arrivati si chiedono con stupore perché, dal momento che il computer <i>deve </i>essere intuitivo da usare, non può essere anche intuitivo da <i>programmare</i>. Ma attenzione, neanche questa è una cosa nuova. Chi scrive ha accumulato anni di storie dell'orrore a guardare come Excel viene usato da persone normali che non hanno il minimo sospetto dei concetti che manovrano. Tu fai una lista di persone da invitare alla festa, quindi evidenzi in giallo quelli che non sei sicuro se verranno: che problema c'è? Ma "viene/non viene" è un <i>dato</i>, logica di business; "giallo" è <i>formattazione</i>, logica di presentazione. Se vuoi aggiungere un dato, devi usare una colonna a parte e metterci una "x" per chi non viene, una "o" per chi viene e un "?" quando non sei sicuro. <br /> Un foglio di calcolo <a href="https://xkcd.com/2180/" rel="nofollow" target="_blank">semplifica la vita</a> lasciandoci mescolare allegramente dati, formattazione, operazioni sui dati. E perché questo dovrebbe essere <i>sbagliato</i>, se è veloce e <i>funziona</i>?<br /> <br /> <h3> Impara a programmare in 10 secondi.</h3> Tanti anni fa programmare un computer era sinonimo di usare un computer, nel senso che <a href="https://en.wikipedia.org/wiki/ENIAC" rel="nofollow" target="_blank">quei computer</a> erano usati solo da chi li programmava. Ogni generazione di programmatori ha avuto il suo mito dei Bei Tempi Andati quando programmare era una cosa seria. Ancora oggi l'archetipo del <i>real programmer</i> è quello stabilito in due testi di culto, entrambi del 1983 (un anno prima del Macintosh... coincidenze): <a href="https://www.ee.ryerson.ca/~elf/hack/realmen.html" rel="nofollow" target="_blank">Real Programmers Don't Use Pascal</a> e <a href="http://www.cs.utah.edu/~elb/folklore/mel.html" rel="nofollow" target="_blank">The Story of Mel</a>. La cosa interessante è che sono entrambi testi <i>satirici</i>: perché già all'epoca la noiosa supponenza della vecchia guardia nei confronti dei "giovani d'oggi" meritava la presa in giro.<br /> <br /> Oggi, inevitabilmente, i computer diventano "una cosa da programmare" per un numero sempre crescente di persone, che però sono l'esatto opposto del <i>real programmer</i>. Sono ricercatori di biologia che vogliono un modo di indagare i dati di laboratorio. Genitori che montano il loro sistema di domotica. Analisti finanziari. Sociologi interessati a fare sentiment analysis. Impallinati dei big data. Hobbysti: ma non hobbysti <i>del computer</i> come i nerd di Linux negli anni '90. Hobbysti di qualsiasi altra cosa, che oggi usano e programmano il computer, ma non necessariamente per amore del computer e della programmazione.<br /> Queste sono persone che, se <a href="https://pandas.pydata.org/" rel="nofollow" target="_blank">Pandas</a> ci mette più di cinque righe di codice sullo <a href="https://jupyter.org/" rel="nofollow" target="_blank">Jupyter Notebook</a> per trasformare i loro dati in un bel grafico, perdono la pazienza. Sono persone che "stanno studiando Python" e non hanno ancora capito precisamente come funziona un ciclo "for", ma già si chiedono <i>perché</i> è così difficile fare scraping delle statistiche del sito del fantacalcio. Più in generale, sono persone assediate da WhatsApp, distratte da Facebook... vogliono <i>risultati</i> e li vogliono <i>adesso</i>.<br /> <br /> Di nuovo, sia chiaro: questo è sempre successo. Ma è impressionante vedere <i>come</i> e in che quantità e con quale intensità succede oggi, rispetto a vent'anni fa. A questo proposito, la pietra di paragone resta questo <a href="https://norvig.com/21-days.html" rel="nofollow" target="_blank">articolo</a> di Peter Norvig, dal titolo molto celebre "Impara da solo a programmare in 10 anni". Riporta la data del 2001, ma la <a href="https://web.archive.org/web/*/https://norvig.com/21-days.html" rel="nofollow" target="_blank">Wayback Machine</a> lo registra fin dall'inizio del 1998. A rileggerlo oggi, da un lato resta valido nel suo principio generale. Dall'altro, <i>fa tenerezza</i> vedere come è invecchiato.<br /> Allora come oggi, orde di nuovi arrivati correvano a comprare improbabili libri del tipo "Impara Java in 24 ore" nell'illusione che programmare fosse difficile e impegnativo <i>per tutti gli altri</i>: per quelli che non sanno il trucco, per quelli che non hanno comprato e letto il libro magico. <br /> <div style="text-align: left;"> A costoro, Norvig spiega pazientemente che non si diventa bravi a <i>niente</i>, in meno di dieci anni. Suonare uno strumento, dipingere, giocare a tennis, fotografare. Programmare un computer. Sono attività che richiedono dieci anni di apprendistato. Norvig ha ragione, naturalmente.&nbsp;</div> <div style="text-align: left;"> Quello che oggi fa tenerezza, però, è che lui implicitamente assume che queste persone vogliano imparare <i>a essere programmatori</i>, in quelle famose 24 ore. Lo assume perché all'epoca in effetti era così: <i>programmatori</i> e <i>utenti</i> erano ancora, per lo più, comunità separate. I suggerimenti di Norvig sono molto sensati, ma...:<i> "Parla con i programmatori, e leggi i loro programmi; lavora sui progetti di altri programmatori; impara almeno mezza dozzina di linguaggi diversi; capisci quanto tempo impiega un computer a eseguire un'istruzione, a prelevare un dato dalla memoria; fatti coinvolgere in un progetto di standardizzazione (...)".&nbsp;</i></div> <div style="text-align: left;"> <br /></div> ...Ma vi immaginate, oggi, rispondere così a quello che vuole fare scraping del sito del fantacalcio? E badate, non voglio fare ironia. Il fatto è che, rispetto a 20 anni fa, oggi la maggior parte di chi vuole <i>programmare</i> non vuole anche necessariamente <i>fare il programmatore</i>.<br /> Tuttavia, nel merito, le osservazioni di Peter Norvig continuano ad avere lo stesso valore di venti anni fa. Dopo tutto, i computer sono sempre le stesse <a href="https://www.csee.umbc.edu/2012/06/lego-turing-machine/" rel="nofollow" target="_blank">macchine di Turing</a>. E allora che cosa è cambiato nel frattempo, tanto da accostare una platea così vasta ed eterogenea di persone a un mondo che, in essenza, è rimasto esotico e inaccessibile come ai tempi della storia di Mel?<br /> <br /> <h3> La lunga corsa per tornare bambini.</h3> Beh, in parte non c'è bisogno che vi dica <a href="https://ourworldindata.org/internet" rel="nofollow" target="_blank">che cosa è cambiato</a>, per i programmatori come per tutti. Tuttavia i capisaldi dell'architettura del software e delle pratiche di programmazione che seguiamo oggi, erano già lì venti o trenta anni fa: i database relazionali (dal <a href="https://en.wikipedia.org/wiki/IBM_System_R" rel="nofollow" target="_blank">1974</a>), la concorrenza (<a href="https://dl.acm.org/doi/10.1145/365559.365617" rel="nofollow" target="_blank">1965</a>), le architetture client/server (<a href="https://en.wikipedia.org/wiki/ARPANET" rel="nofollow" target="_blank">dagli anni '60</a>), la programmazione a oggetti (<a href="https://en.wikipedia.org/wiki/Simula" rel="nofollow" target="_blank">1962</a>), le metodologie formalizzate di sviluppo (<a href="https://en.wikipedia.org/wiki/Systems_development_life_cycle" rel="nofollow" target="_blank">anni '60-'90</a>) fino all'Agile Manifesto (<a href="https://en.wikipedia.org/wiki/Agile_software_development" rel="nofollow" target="_blank">2001</a>)... La Bibbia dei pattern è del <a href="https://en.wikipedia.org/wiki/Design_Patterns" rel="nofollow" target="_blank">1994</a>; la Bibbia degli algoritmi è degli <a href="https://en.wikipedia.org/wiki/The_Art_of_Computer_Programming" rel="nofollow" target="_blank">anni '70</a>.<br /> Non voglio dire che il mestiere del programmatore non sia cambiato nel tempo. Ma ecco il punto: da un lato, l'uso del computer e le modalità di utilizzo sono progredite molto in fretta verso una grande <a href="https://en.wikipedia.org/wiki/History_of_iTunes" rel="nofollow" target="_blank">semplicità</a> apparente che ne ha favorito la diffusione. Dall'altro, le tecniche di <i>programmazione </i>dei computer non sono progredite neanche lontanamente alla stessa velocità in questa direzione.<br /> <br /> I linguaggi sono certamente diventati più "semplici": Mel il <i>real programmer</i> si rifiutava di usare il compilatore, perché lui sapeva ottimizzare meglio del compilatore. Ma, sorpresa!, proprio per questo motivo il linguggio C (<a href="https://en.wikipedia.org/wiki/C_(programming_language)" rel="nofollow" target="_blank">1973</a>) e il compilatore hanno finito per vincere la partita: il talento dei programmatori come Mel poteva essere speso meglio che a fare elegantissime ottimizzazioni, visto che comunque la crescente velocità dei processori compensava per questi piccoli difetti. Adesso sembra incredibile, ma C era il linguaggio "facile" in quel contesto. E poi, naturalmente, sono nati molti altri linguaggi di alto livello capaci di manipolare astrazioni più complesse, e tra questi anche Python (<a href="https://en.wikipedia.org/wiki/History_of_Python" rel="nofollow" target="_blank">1991</a>).<br /> <br /> E intanto sono nati anche gli "ambienti di programmazione": software che integrano un <i>linguaggio</i> vero e proprio con un <i>programma</i> che dovrebbe rendere più agevole lo sviluppo delle applicazioni. Quando nel 1990 <a href="https://en.wikipedia.org/wiki/Alan_Cooper" rel="nofollow" target="_blank">Alan Cooper</a> vendette a Bill Gates il suo prototipo rivoluzionario di gui-builder, allora chiamato Tripod, Microsoft pensò di integrarlo con il linguaggio Basic. Il risultato fu <a href="https://winworldpc.com/product/microsoft-visual-bas/10" rel="nofollow" target="_blank">spettacolare</a>: potevi costruire il design di una finestra in modo intuitivo, visuale. Le azioni più comuni dietro a ciascun elemento erano già codificate; il resto, potevi scrivertelo da solo con il Basic. A dire il vero il linguaggio non era granché. Ma la parte visuale partorita dal genio di Cooper era rivoluzionaria, e alla fine VisualBasic contribuì in maniera determinante a portare in casa Microsoft quella enorme platea di <i>consumatori</i> che Steve Ballmer tanto desiderava: <a href="https://www.youtube.com/watch?v=Vhh_GeBPOhs" rel="nofollow" target="_blank">developers, developers, developers!</a><br /> VisualBasic, Delphi (<a href="https://winworldpc.com/product/delphi/1x" rel="nofollow" target="_blank">1995</a>) e i loro nipotini hanno contribuito a sfocare la differenza tradizionale tra utenti e programmatori, e tra programmatori professionali e hobbysti. O forse, meglio ancora, a <i>creare</i> la categoria dei programmatori hobbysti, ben distinta dalla cultura hacker degli anni '60 e '70 documentata nel <a href="http://jargon-file.org/" rel="nofollow" target="_blank">Jargon File</a>. Qui la differenza è <a href="http://www.catb.org/~esr/jargon/html/H/hacker.html" rel="nofollow" target="_blank">spiegata bene</a>: un hacker è "<i>una persona che ama esplorare i dettagli dei sistemi programmabili e spingere all'estremo le loro possibilità, dove invece la maggior parte degli utenti preferisce imparare il minimo necessario</i>". <br /> <br /> Questa tendenza a spostare il focus della programmazione sul <i>risultato</i> da raggiungere rapidamente ha fatto molta strada. Da un lato perché i "sistemi programmabili" sono diventati onnipresenti e indispensabili, ma anche talmente complessi che c'è sempre meno tempo per "esplorare i dettagli". Dall'altro perché in ogni caso questo approccio ha avvicinato alla programmazione un numero sterminato di persone, ben oltre le possibilità della cultura selettiva della prima comunità hacker. Infine perché l'industria oggi ha sempre più bisogno di realizzare prodotti complessi in tempi brevi, con processi chiari e condivisi da molti sviluppatori: nessuno oggi può permettersi di perdere le giornate ad analizzare il geniale ma labirintico codice di Mel il <i>real programmer</i>.<br /> <br /> Così, trent'anni dopo VisualBasic, oggi è tutta una proliferazione di drag'n'drop, dashboard, workflows, toolbox, template e plugins. La tipologia di elementi e interazioni che negli anni '90 avremmo usato per descrivere un gioco <a href="https://en.wikipedia.org/wiki/Civilization_(video_game)#/media/File:CivilizationAmigaAGA.png" rel="nofollow" target="_blank">strategico</a> o <a href="https://en.wikipedia.org/wiki/SimCity_(1989_video_game)#/media/File:Micropolis_-_big_city.png" rel="nofollow" target="_blank">gestionale</a>, oggi descrive bene un linguaggio di programmazione (e certi linguaggi di programmazione hanno un <a href="https://www.mendix.com/" rel="nofollow" target="_blank">sito</a> da videogioco anni '90). Per tutto questo, naturalmente!, esiste un acronimo: si chiamano <a href="https://en.wikipedia.org/wiki/Low-code_development_platform" rel="nofollow" target="_blank">LCDP</a> (Low Code Development Platform) e <a href="https://en.wikipedia.org/wiki/No-code_development_platform" rel="nofollow" target="_blank">NCDP</a> (No Code etc. etc.), ma se volete fare i disinvolti potete dire tutto insieme LCNC (Low Code / No Code).<br /> Ma anche questa non è propriamente una novità. Ambienti di programmazione e linguaggi visuali erano in circolazione già vent'anni fa, a partire naturalmente dal progenitore di tutti quanti: <a href="https://hypercard.org/" rel="nofollow" target="_blank">HyperCard</a> (1987). E poi c'erano <a href="https://squeak.org/" rel="nofollow" target="_blank">Squeak</a> (1996), <a href="https://en.wikipedia.org/wiki/Scratch_(programming_language)" rel="nofollow" target="_blank">Scratch</a> (2002) e molti altri. La cosa interessante era che spesso si trattava di piattaforme didattiche, con lo scopo di insegnare ai bambini a programmare in modo interattivo e visualmente stimolante.<br /> In trent'anni, i <i>programmatori</i> sono diventati <a href="https://en.wikipedia.org/wiki/End-user_development" rel="nofollow" target="_blank"><i>utenti</i></a> e poi sono tornati <i>bambini</i>.<br /> <br /> <h3> E Python in tutto questo?</h3> A proposito di didattica dell'informatica: alcuni anni fa la BBC ha lanciato <a href="https://microbit.org/" rel="nofollow" target="_blank">Micro:bit</a>, un vasto programma basato su una scheda hardware dedicata da distribuire nelle scuole. Tra i linguaggi supportati da Micro:bit c'è Scratch, che va ancora alla grande. Ma c'è anche <a href="https://python.microbit.org/v/2.0" rel="nofollow" target="_blank">Python</a>, naturalmente!<br /> Il che ci porta al nostro linguaggio preferito.<br /> <br /> I moderni ambienti LCNC in gran parte espandono la tradizione dei linguaggi non-procedurali di <a href="https://en.wikipedia.org/wiki/Fourth-generation_programming_language" rel="nofollow" target="_blank">quarta generazione</a>, e sono per lo più <a href="https://bubble.io/" rel="nofollow" target="_blank"><i>domain-specific</i></a> o comunque orientati a determinati tipi di <a href="https://monday.com/lang/it" rel="nofollow" target="_blank"><i>business application</i></a>. Python, d'altra parte, è un linguaggio <i>general purpose</i> della generazione precedente ed è paragonabile più che altro a Java o C# (sebbene con la flessibilità di un linguaggio di scripting).<br /> <br /> Di Python è sempre stata lodata la semplicità d'uso; e d'altra parte la sua comunità ha sempre alimentato un <a href="https://www.python.org/community/sigs/current/edu-sig/" rel="nofollow" target="_blank">forte interesse</a> per l'uso di Python nella didattica e una <a href="https://mail.python.org/pipermail/tutor/" rel="nofollow" target="_blank">benevola apertura</a> verso i principianti. Tuttavia, attenzione: la semplicità di Python non è da misurare con il metro <i>dell'utente</i>, ma pur sempre con quello del <i>programmatore</i>. È una semplicità che scaturisce da una eleganza intrinseca, da alcune astrazioni intelligenti (pensate a quante cose sono iterabili e quanto si può fare con un semplice "for"), da una forte <a href="https://xkcd.com/353/" rel="nofollow" target="_blank">espressività</a> e da tutto ciò che è distillato nel suo <a href="https://www.python.org/dev/peps/pep-0020/" rel="nofollow" target="_blank">Zen</a>. La semplicità di Python si capisce meglio per contrasto con linguaggi come <a href="https://www.perlmonks.org/?node_id=752029" rel="nofollow" target="_blank">Perl</a>, non per analogia con ambienti come Delphi.<br /> È una semplicità che richiede pur sempre una <i>forma mentis</i> precedente per essere apprezzata e sfruttata in pieno. Oppure, la pazienza di fare un percorso di apprendimento graduale.<br /> <br /> Questo è l'aspetto che sembra sfuggire ai principianti che si chiedono come imparare Python senza studiare. Il punto è che non sembra loro possibile che fare un sito in <a href="https://www.djangoproject.com/" rel="nofollow" target="_blank">Django</a> debba essere più lento e complicato rispetto, per dire, a fare un sito in <a href="https://www.wix.com/" rel="nofollow" target="_blank">Wix</a>. Oppure, che raccogliere dati con <a href="https://scrapy.org/" rel="nofollow" target="_blank">Scrapy</a> debba richiedere uno sforzo di apprendimento maggiore che con <a href="https://www.parsehub.com/" rel="nofollow" target="_blank">Parsehub</a> o <a href="http://webscraper.io/">Webscraper.io</a>. E così via. Naturalmente non ho idea di quanti di costoro abbiano provato ambienti LCNC e possano fare il paragone con cognizione di causa.<br /> Ma sospetto che almeno una buona parte arriva a Python aspettandosi la versione gratis di Delphi, a dirla tutta.<br /> <br /> Per chiarire: non voglio dire che Python, negli anni, ha fatto "pubblicità ingannevole" di sé. Almeno all'inizio, la platea a cui si rivolgeva era perfettamente in grado di interpretare nel modo corretto gli aspetti di semplicità e usabilità che proponeva. Era gente che veniva da Perl, da Java, ovviamente da C++, tipicamente da Unix e dallo scripting di shell. Alcuni erano hacker.<br /> Ma io sospetto che oggi questa pubblicità, involontariamente, abbia smesso di comunicare la cosa giusta. Forse oggi sarebbe più appropriato ricordare che Python resta comunque un linguaggio di programmazione e non un ambiente visuale, un linguaggio procedurale che non ammette troppe "libere aggregazioni" di elementi, un linguaggio orientato agli oggetti che richiede pur sempre una buona dose di cerimoniale sintattico.<br /> Uno dei tool LCNC di cui abbiamo parlato si propone ai suoi utenti senza tanti giri di parole: "con noi è possibile costruire applicazioni che funzionano, senza tante str***ate". Ecco, se vogliamo usare questo termine, allora io credo che bisognerebbe ricordare: Python richiede pur sempre una modica quantità di str***ate.<br /> <br /> <h3> Vent'anni dopo, le astrazioni continuano a perdere. </h3> Con tutto questo non abbiamo ancora risposto completamente alla domanda iniziale. Che cosa vuol dire, quindi, che Python è "facile", se non è "facile da usare"? Perché non è possibile imparare Python senza studiare, o usare Python senza averlo imparato?<br /> <br /> Ancora una volta conviene prendere spunto da una cosa pensata vent'anni fa. <i>The Law of Leaking Abstractions</i> (La legge delle astrazioni che perdono, gocciolano) è un famoso <a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/" rel="nofollow" target="_blank">articolo</a> di Joel Spolsky del 2002. La riflessione è che noi costruiamo livelli di astrazione via via più potenti e sofisticati perché sia più semplice manovrare gli oggetti sottostanti, che sono primitivi e complicati da usare. E queste astrazioni di solito <i>funzionano</i>, nel senso che davvero rendono i sistemi più facili da usare. Ma inevitabilmente prima o poi capiterà lo scenario in cui cui l'astrazione non tiene più, "ha una perdita" e ti lascia intravedere il complicato groviglio sottostante. E in questi casi a te non resta che scendere al piano di sotto, per così dire, e affrontare di nuovo quegli oggetti primitivi che l'astrazione cercava di semplificare. Il punto fondamentale della riflessione di Spolsky è che <i>tutte</i> le astrazioni "hanno una perdita" da qualche parte.<br /> <br /> Prendiamo Python, per esempio. Quando volete aprire un file di testo, Python vi mette a disposizione un elegante livello di astrazione. Voi scrivete qualcosa come "<code>with open("miofile.txt") as f:</code>" e Python si occupa per voi di tutta la burocrazia dei puntatori C sottostanti. Vi restituisce un oggetto di alto livello, su cui è possibile iterare con un semplice "for", e che potete gestire comodamente con la variabile "f". Non solo: grazie al context manager, quando avete finito Python chiude il file per voi e distrugge anche la variabile dietro le quinte, senza che dobbiate preoccuparvi di deallocare a mano la memoria. Un lavoretto niente male per una sola riga di codice, vero?<br /> Solo che questa astrazione arriva fino a un certo punto, poi comincia a perdere. Python non ha nessun modo di entrare nella vostra testa e capire dove si trova fisicamente il file "miofile.txt" nel file system gestito dal vostro sistema operativo. Tutto quello che può fare è accettare una <a href="https://en.wikipedia.org/wiki/Path_(computing)" rel="nofollow" target="_blank">path</a> alla cieca, e sperare che quella path punti davvero a un file che accetta di essere aperto. E se la path è relativa (come il nostro "miofile.txt"), allora Python la risolve secondo le regole del file system, componendola con la <a href="https://en.wikipedia.org/wiki/Working_directory" rel="nofollow" target="_blank">directory corrente</a>. E a sua volta, la directory corrente non dipende da Python, ma dall'ambiente di esecuzione esterno. E quindi può essere diversa se avete avviato lo script dalla shell, se ci avete fatto doppio clic sopra, se avete premuto "F5" o "Run" dal vostro editor...<br /> Ed ecco che la vostra elegante astrazione vi ha già lasciato a piedi: e infatti, i forum sono pieni di principianti che chiedono "<i>aiuto, non mi trova il file se eseguo da Idle!!!1!1!!!</i>". <br /> <br /> E in un certo senso il principiante non ha torto: ha visto una astrazione e ha pensato che fosse sinonimo di "facile da usare". Ma purtroppo le astrazioni non sono fatte per essere facili da usare: il loro scopo è un altro. Ci arriviamo, sempre accompagnati da Joel Spolsky, tra un minuto.<br /> <br /> Ora, se io dovessi insegnare Python a un principiante, probabilmente all'inizio direi: se vuoi giocare con "open", metti tutti i tuoi file nella stessa directory dello script. E poi all'inizio dello script scrivi qualcosa come "<code>import os, os.path; os.chdir(os.path.abspath(os.path.dirname(__file__)))</code>". Non preoccuparti di che cosa vuol dire, per il momento. Fidati e basta.<br /> Qual è il problema? Avete indovinato: questa è solo <i>un'altra</i> astrazione. Può essere utile per non confondere il principiante, ma prima o poi non c'è niente da fare: non si può usare "open" senza sapere anche qualcosa di come funziona il file system sottostante. "Open" è un'astrazione che perde.<br /> E, d'altra parte, potrei continuare ad aggiungere astrazione dopo astrazione per facilitare il principiante, fino a costruire una vera e propria <a href="https://app.edublocks.org/#Python" rel="nofollow" target="_blank">piattaforma didattica</a>. Ma non sarebbe più possibile usarla per niente di serio.<br /> <br /> E così il principiante che si avvicina a Python in cerca di "come Delphi, ma gratis" impazzisce di gioia quando scopre che esiste <a href="https://doc.qt.io/qt-5/designer-quick-start.html" rel="nofollow" target="_blank">Qt Designer</a>: ma dopo aver giocato un po' con il drag'n'drop, arriva inevitabilmente il momento in cui scopre che dietro questa gradevole astrazione esiste un grande, cattivo GUI framework C++, pieno di concetti complicati come "signal/slot", "loop degli eventi" e così via.<br /> E sappiamo bene che <a href="https://docs.djangoproject.com/en/3.0/intro/tutorial02/#introducing-the-django-admin" rel="nofollow" target="_blank">Django Admin</a> consente di creare "auto-magicamente" un sito di amministrazione per un'applicazione con database. Ma appena andate oltre questa specifica necessità, non vi resta che imparare con pazienza il linguaggio dei template di Django.<br /> Ma poi, intendiamoci: anche creare un'applicazione con Delphi vi espone ben presto a tutto il Pascal sottostante. Anche creare un sito con Wix è facile se restate nell'ambito degli strumenti di Wix: ma se mettete il piede nel punto sbagliato, scoprite che <a href="https://www.wix.com/corvid" rel="nofollow" target="_blank">dietro</a> c'è tutto un framework da programmare a colpi di Javascript. <br /> <br /> La morale dell'articolo di Joel Spolsky sulle astrazioni che perdono è talmente importante che vale la pena riportarla qui per esteso:<br /> "<i>La legge delle astrazioni che perdono significa che tutte le volte che qualcuno salta fuori con nuovo eccitante strumento di generazione del codice che dovrebbe renderci tutti molto più efficienti, sentirete molti che dicono: impara prima a farlo a mano, poi usa il nuovo eccitante strumento per risparmiare tempo. I generatori di codice che pretendono di mascherare la complessità, come tutte le astrazioni, prima o poi perdono; e l'unico modo di affrontare le perdite è imparare come funzionano le astrazioni, e che cosa stanno mascherando. E quindi, le astrazioni possono farci risparmiare tempo nel lavoro, ma non ci faranno risparmiare tempo nell'apprendere.</i>"<br /> <br /> <h3> Si può imparare Python senza studiare?</h3> L'ultima affermazione, per quanto scoraggiante, è semplicemente vera. Lo scopo delle astrazioni non è, come speravamo, permetterci di lavorare <i>senza dover imparare</i>... le str***ate, come si diceva sopra. Lo scopo delle astrazioni è permetterci di lavorare in modo più efficiente e affrontare problemi di complessità via via crescente.<br /> <br /> Questo mette la questione "imparare o no" nella giusta prospettiva. Quaranta o cinquanta anni fa non c'erano grandi alternative. Il livello delle astrazioni era talmente basso (e l'accesso ai computer talmente ristretto) che le strade per diventare programmatori erano pochine: <a href="https://gvanrossum.github.io/Resume.html" rel="nofollow" target="_blank">Guido</a> ha una laurea in Matematica e Computer Science, per capirci. Poi certo, c'erano anche gli <a href="https://en.wikipedia.org/wiki/Jamie_Zawinski" rel="nofollow" target="_blank">autodidatti</a>: ma appunto, erano i tempi degli <i>hacker</i>. E l'etica hacker era definita appunto dalla passione inesauribile per <i>apprendere</i>, prima che per <i>usare</i>. <br /> <br /> Oggi certamente non è più così... ma solo fino a un certo punto, perché poi le astrazioni iniziano a perdere. Ora, io non credo affatto che prima di scrivere la prima riga di Python uno debba avere una laurea in Matematica. Penso che si possa fare parecchia strada senza sapere come si implementa una <a href="https://en.wikipedia.org/wiki/Adjacency_list" rel="nofollow" target="_blank">lista di adiacenza</a>. Penso che molti programmatori Python non avranno mai bisogno di implementarne una, in effetti.<br /> Dirò di più: penso che molti corsi, anche universitari, sono tenuti oggi da docenti che visibilmente hanno scritto "Python" nel programma solo per attirare un po' di gente, ma in realtà hanno sempre insegnato C e vogliono continuare a insegnarlo. E cercano di costringere a martellate dentro Python dei pattern di programmazione che in realtà starebbero meglio in C (e poi ci sono quelli che invece vogliono insegnare Java, naturalmente). E quando gli esercizi che danno agli studenti ogni tanto finiscono nei forum, è tutto un fiorire di "puntatori", e di "array" per dire "liste". Questi docenti non stanno insegnando né C né Python.<br /> <br /> (Detto questo, penso però che sapere come si implementa una lista di adiacenza ti dà un vantaggio competitivo non da poco nel mondo del lavoro, anche come programmatore Python. Ma questo è un altro aspetto, pur molto importante.)<br /> <br /> In primo luogo, credo che al principiante andrebbe chiarito che <i>si possono fare tante cose utili anche senza Python</i>. Per lungo tempo Python è stato una porta per la programmazione rapida ed efficiente: un linguaggio per sviluppare <i>prototipi</i>, per esempio. Oggi spesso non è più così. Fare il prototipo di un sito in Wix in molti casi è più conveniente e rapido che farlo in Django. E, come sempre succede, quel prototipo in molti casi è già pronto per finire, a calci, in produzione. Se basta Wix, allora usate Wix. Oppure: <a href="https://docs.djangoproject.com/en/3.0/topics/forms/" rel="nofollow" target="_blank">Django Forms</a> è molto potente, ma non è facilissimo da usare. Avete provato <a href="https://www.google.com/forms/about/" rel="nofollow" target="_blank">Google Forms</a>? Magari in combinazione con <a href="https://sites.google.com/" rel="nofollow" target="_blank">Google Sites</a>? Se quello che vi serve è un <i>risultato</i>, cercate di capire qual è lo strumento più rapido per ottenere quel risultato. Mantenetevi elastici.<br /> <br /> Se invece volete lavorare con Python, allora prima di tutto dovreste essere disposti a <i>investire del tempo</i> senza la pretesa di ottenere alcun risultato. La cosa peggiore in assoluto è il principiante che a malapena sa che cosa è una funzione e già si mette in testa di scrivere una GUI o un sito web. Anche perché non riesci più a staccarlo dal suo malsano proposito: continuerà in eterno a picchiare a caso sulla tastiera e tempestare di messaggi i forum a ogni riga di codice che, inevitabilmente, "non gli funziona", chiedendo "spunti" (cioè, pezzi di codice da copiare e incollare) e lamentandosi che non lo si "vuole aiutare". Ecco, questa è la <i>forma mentis</i> peggiore di tutte. E anche la più diffusa, guarda un po'. Ed è anche la più naturale, purtroppo: come abbiamo visto, dipende dal fatto che molti principianti pensano che Python debba essere "facile" nel senso di "facile da usare". E come ormai sappiamo, la risposta è no: Python è facile in un senso completamente differente.<br /> <br /> Se quindi volete lavorare con Python e siete disponibili a investirci del tempo senza pretese di risultati immediati, allora è difficile dire <i>quanto poco basta sapere</i> prima di cominciare con Python. Io penso che almeno qualcosa sulla shell del vostro sistema operativo bisognerebbe averla capita. Dopo di che, seguire un buon libro passo-passo dovrebbe bastare a procedere lentamente su un terreno abbastanza solido (il <a href="https://learning-python.com/about-lp5e.html" rel="nofollow" target="_blank">Lutz</a> è sempre una buona idea, anche se in Italiano l'ultima edizione non è disponibile).<br /> Ma la cosa più importante da ricordare è che Python è comunque solo <i>un'astrazione che perde</i>. Appena provate a mettere un piede fuori dal percorso didattico del libro che state seguendo, è <i>normale</i> che finirete su una mina. Non c'è niente di "semplice" in Python. Talvolta c'è più magia nera dietro un "print" Python che in molte righe di C++ (e non parliamo nemmeno di che cosa fa <i>davvero</i> Django dietro le quinte). Tutte le volte che finite su una mina, prendetelo come un segnale che non sapete davvero quello che c'è da sapere sull'argomento. Non incaponitevi a risolvere lo specifico problema su cui vi siete bloccati. Invece, fate un passo indietro e approfondite la vostra conoscenza sull'argomento in generale. Leggete la documentazione, più volte.<br /> <br /> Soprattutto, quando trovate in giro una "ricetta" che risolve il vostro problema, <i>non usatela</i>! Mai! Per nessuna ragione! Invece, <i>studiatela</i>: capite perché funziona, scomponendola pezzo per pezzo. Poi allontanatela dallo sguardo, magari lasciate passare un giorno, e provate a <i>ricostruirla</i> con le vostre forze. Questo è l'unico modo di usare le "ricette" che trovate in giro o che qualche benintenzionato vi passa sul forum. Altrimenti, <i>le "ricette" sono il principale motivo per cui non fate progressi</i> e non ne farete mai.<br /> Il motivo ormai dovrebbe essere chiaro: "risolvere un problema facilmente" e "un linguaggio facile da programmare" non sono sinonimi. Python è la seconda cosa, ma non permette <i>davvero</i> di fare la prima, nel senso che di solito intende il principiante.<br /> <br /> E quindi, per concludere: è possibile <i>imparare</i> Python senza studiare? No. È possibile <i>usare</i> Python per ottenere dei risultati, senza studiare? Temo di no. Ma non è detto che <i>capire</i> non possa essere divertente, molto più di imparare e molto più di usare.<br /> <br /> <br />ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]1tag:blogger.com,1999:blog-8801312244642714488.post-53057988312173876952019-12-28T13:22:00.000+01:002019-12-28T13:22:31.015+01:00WinPackIt, il modo facile per distribuire il vostro programma.Torno sull'argomento "distribuzione" per presentarvi un progettino che ho messo insieme in questi giorni: <a href="https://pypi.org/project/WinPackIt/" rel="nofollow" target="_blank">WinPackIt</a>, il modo (un po' più) facile per distribuire il vostro programma Python su Windows.<br /> <a name='more'></a><br /> <br /> Qualche tempo fa avevo scritto <a href="https://pythoninwindows.blogspot.com/2019/03/distribuire-un-programma-il-modo-facile.html">un post</a> per proporre un'alternativa "un po' alla buona" ai vari packager che si trovano in giro (py2exe, etc.). Quando funzionano, questi tool sono ottimi, intendiamoci. Il problema è che non sempre funzionano come dovrebbero.<br /> La mia idea, banalmente, era di usare le <a href="https://docs.python.org/3/using/windows.html#the-embeddable-package" rel="nofollow" target="_blank">distribuzioni embeddable</a> di Python, che sono disponibili a partire da Python 3.5. In quel post avevo delineato in breve le cose da fare: in sostanza, scaricare e scompattare la distribuzione, installare con Pip i vari pacchetti esterni necessari, copiare i file del vostro programma, fare un po' di aggiustamenti con le path.<br /> A partire da quell'idea, ho scritto un piccolo tool per automatizzare queste operazioni: WinPackIt parte da alcuni dati che voi gli fornite, e vi sforna una cartella "build" bella e pronta per essere consegnata all'utente finale, con dentro il vostro programma e tutto quello che serve per farlo funzionare.<br /> <br /> Potete trovare WinPackIt su <a href="https://pypi.org/project/WinPackIt/" rel="nofollow" target="_blank">PyPI </a>(e quindi potete installarlo normalmente con Pip). Anche se il <a href="https://github.com/ricpol/winpackit" rel="nofollow" target="_blank">codice </a>e la <a href="https://github.com/ricpol/winpackit/blob/master/docs.rst" rel="nofollow" target="_blank">documentazione </a>sono in Inglese, ho pubblicato anche <a href="https://github.com/ricpol/winpackit/blob/master/docs_ITA.rst" rel="nofollow" target="_blank">una versione in Italiano della documentazione</a>.<br /> Usare WinPackIt è facile, ma dovete comunque saper usare la riga di comando, e dovete avere un'idea di quello che state facendo. Il programma che intendete distribuire dovrebbe essere scritto in modo... sensato, come dire. Se all'interno del codice usate delle path assolute per accedere a una risorsa esterna (casi tipici: un database Sqlite, o un file di impostazioni), non potete pretendere che WinPackIt aggiusti le vostre path per voi. Imparate a usare le path relative e tenete le risorse esterne sotto controllo.<br /> Penso che WinPackIt sia un buon modo per distribuire un programma "nel caso facile ma anche più comune": va bene quando dovete far girare uno script o un programma semplice su un computer Windows che non ha Python installato.<br /> <br /> Provatelo, fatemi sapere che cosa ne pensate, e segnalate eventuali (probabili!) bachi...<br /> <br />ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-3877779550241651922019-11-26T12:22:00.001+01:002019-11-26T14:42:38.262+01:00Aiuto, la finestrella nera!Una richiesta tipica del principiante è “come faccio a nascondere la finestrella nera?”. In realtà è una vera ossessione: la “finestrella nera” è in assoluto la cosa più odiata dal principiante. Anche se ho già affrontato l’argomento in alcuni punti <a href="https://pythoninwindows.blogspot.com/p/blog-page.html">della mia guida</a>, provo qui a sviluppare e chiarire meglio il discorso.<br /> In primo luogo, diciamo che l’odio per la “finestrella nera” è parente stretto dell’amore per le interfacce grafiche, e la domanda “come faccio a nascondere la finestrella” viene subito prima o subito dopo quella, altrettanto celebre, <a href="https://pythoninwindows.blogspot.com/2019/03/e-adesso-dove-clicco.html">“e adesso dove clicco”</a>. Quindi la risposta è identica: Python è un programma a riga di comando, in linea di principio. Non “si clicca” da nessuna parte, e la “finestrella nera” è appunto l’indispensabile interfaccia di Python. Detto questo, approfondiamo il discorso. <br /> <a name='more'></a><br /> <div class="has-line-data" data-line-end="3" data-line-start="2"> <h2> La console di Windows.</h2> La famigerata “finestrella nera” non è altro che il prompt dei comandi di Windows. Più precisamente, è la finestra del <a href="https://en.wikipedia.org/wiki/Windows_Console">Windows Console Host</a> (<code>conhost.exe</code>), dentro la quale viene tipicamente eseguito il prompt dei comandi (<code>cmd.exe</code>), ma non necessariamente: la console potrebbe ospitare una shell alternativa come PowerShell, e così via. Potete aprire una shell in molti modi: per esempio con la combinazione di tasti <code>win+R</code> e poi scrivendo <code>cmd&lt;invio&gt;</code>.<br /> In Windows, un “programma” (un <i>processo</i>) può essere “GUI” (<i>Win32 host</i>) o “a riga di comando” (<i>console host</i>): un processo che richiede la riga di comando verrà appunto eseguito attraverso la shell (<code>cmd.exe</code>) e quindi provocherà l’apertura della console (<code>conhost.exe</code>). Un processo a interfaccia grafica (GUI), viceversa, non ha bisogno della shell per funzionare. Questa distinzione è una caratteristica del processo stesso, non del modo in cui viene chiamato. Per esempio, la calcolatrice è un programma GUI: uno di quelli che potete avviare normalmente facendo doppio clic sulla sua icona, per capirci. Però provate invece ad aprire una shell e avviare la calcolatrice invocando direttamente il suo eseguibile:&nbsp; <br /> <pre><code>&gt; calc </code></pre> La calcolatrice si avvia e (attenzione!) la shell <i>non resta bloccata</i> (l’esecuzione è <i>asincrona</i>) perché il processo (GUI) della calcolatrice è diverso, separato, da quello della shell che abbiamo usato per lanciarla. Una volta che la calcolatrice è avviata, possiamo anche chiudere la finestra della shell: la calcolatrice resterà in vita normalmente. Questo è il modo in cui funzionano i processi GUI. Potete provare con altri programmi tipici di questo genere: <code>notepad.exe</code>, <code>charmap.exe</code>, <code>mspaint.exe</code> e così via.<br /> <br /> Viceversa, <code>netstat.exe</code> è un tipico programma a riga di comando. Se cercate il suo eseguibile (in <code>C:\windows\System32</code>) e ci fate doppio clic sopra, vedrete che si apre la finestra della console mentre il programma viene eseguito (dovrebbe metterci qualche secondo, ammesso che abbiate almeno una scheda di rete sulla vostra macchina!). Al termine dell’esecuzione, la “finestrella nera” scompare da sola… semplicemente perché il processo è terminato, e la shell era la sua interfaccia con l’utente. Se provate a invocare <code>netstat.exe</code> da una shell già aperta, con <br /> <pre><code>&gt; netstat </code></pre> vedrete che la shell <i>resta bloccata</i> (esecuzione <i>sincrona</i>) per il tempo di esecuzione del programma, appunto perché <code>netstat.exe</code> usa la shell da cui è chiamato. Se provate a passare attraverso il comando <code>START</code>, che avvia il processo richiesto “separandolo” dalla shell, <br /> <pre><code class="language-shell" data-line-end="22" data-line-start="20">&gt; START netstat </code></pre> vedrete che si apre una seconda shell per l’esecuzione di <code>netstat.exe</code> e la shell originaria non resta bloccata (esecuzione <i>asincrona</i>). Questo è, in generale, come funzionano i processi a riga di comando. <br /> <h2> C’è Python e Pythonw…</h2> Nella distribuzione per Windows, l’eseguibile dell’interprete Python è presente in due versioni: <code>python.exe</code> è un programma a riga di comando, <code>pythonw.exe</code>… non lo è. Nel senso: è un programma Win32 host, anche se poi naturalmente non ha nessuna GUI da mostrare all’utente.<br /> Eseguire uno script con <code>pythonw.exe</code> in genere è problematico, a meno che uno non sappia esattamente quello che sta facendo. Per provare la differenza tra i due interpreti, potete creare uno script (supponiamo di chiamarlo <code>C:\test\test.py</code>): <br /> <pre><code><span class="hljs-comment"># file: C:\test\test.py</span> <span class="hljs-keyword">import</span> winsound print(<span class="hljs-string">'qualcosa nello standard output'</span>) <span class="hljs-comment"># 1/0 # eventualmente, per testare un'eccezione non gestita</span> winsound.Beep(<span class="hljs-number">4000</span>, <span class="hljs-number">2000</span>) </code></pre> Questo script emette un suono di due secondi, per testimoniare che in effetti sta girando anche quando non siamo in grado di vederlo. Potete invocarlo dalla shell in diversi modi per vedere la differenza: <br /> <pre><code class="language-shell" data-line-end="42" data-line-start="36">C:\test&gt; python test.py qualcosa nello standard output C:\test&gt; pythonw test.py C:\test&gt; START python test.py C:\test&gt; START pythonw test.py </code></pre> L’interprete <code>python.exe</code> esegue lo script nella shell, <i>bloccandola</i> fino al termine dell’esecuzione. D’altra parte, <code>pythonw.exe</code> non blocca la shell ed esegue lo script “distaccato”.<br /> <br /> Nota bene numero uno: questa idea di fornire due interpreti in ambiente Windows non è venuta solo agli sviluppatori Python, beninteso. Esistono <code>wperl.exe</code>, <code>rubyw.exe</code>, <code>javaw.exe</code>…<br /> Nota bene numero due: non confondete queste due versioni dell’eseguibile Python con il fatto che il vostro script potrebbe (o no) mostrare una GUI all’utente (con Tk, Qt, wxPython etc.). A Windows (e all’eseguibile Python, peraltro) non importa nulla di ciò che fate nel vostro script. Se il vostro script ha la GUI, si comporta allo stesso modo di tutti gli altri script: se lo eseguite con <code>python.exe</code> vedrete la finestrella nera (accanto alle finestre della vostra GUI, quindi), se lo eseguite con <code>pythonw.exe</code>, no. E questa, credo, è la risposta da un milione di dollari che il principiante cerca in modo compulsivo. Ma proseguiamo il nostro discorso.<br /> <br /> Una differenza fondamentale tra i due interpreti è che <code>pythonw.exe</code> <i>sopprime gli standard stream</i> che quindi non sono più disponibili. Un <code>print</code> nello <i>standard output</i> non ha nessun effetto, per esempio. Ma il peggio è che anche lo <i>standard error</i> è fuori gioco: questo significa che un’eccezione non gestita terminerà <i>silenziosamente</i> il programma, perché non esiste un posto dove pubblicare lo stacktrace dell’eccezione.<br /> In Python 2 le cose andavano anche peggio: gli <i>standard stream</i> con <code>pythonw.exe</code> erano dirottati verso descrittori <i>non validi</i>: questo comportava che anche un semplice <code>print</code> dimenticato nel codice generava un’eccezione non gestita (che a sua volta terminava silenziosamente l’esecuzione). In Python 3 <code>pythonw.exe</code> usa invece descrittori <i>nulli</i>, e questo consente almeno a <code>print</code> di essere una no-op. Ma ovviamente scrivere direttamente nello <i>standard output</i> (per esempio, <code>sys.stdout.write()</code>) genera pur sempre un’eccezione.<br /> <br /> Se intendete usare <code>pythonw.exe</code>, come minimo bisognerebbe re-direzionare lo <i>standard error</i> verso un file esterno, in modo da poter ispezionare la causa di un crash. Questo può essere fatto “da dentro” Python, con qualcosa come: <br /> <pre><code class="language-python" data-line-end="56" data-line-start="52"><span class="hljs-keyword">if</span> <span class="hljs-string">"w"</span> <span class="hljs-keyword">in</span> sys.executable: sys.stdout = open(<span class="hljs-string">'standard_output.txt'</span>, <span class="hljs-string">'a'</span>) sys.stderr = open(<span class="hljs-string">'standard_error.txt'</span>, <span class="hljs-string">'a'</span>) </code></pre> In alternativa, potete intervenire “da fuori” al momento dell’invocazione con la shell: <br /> <pre><code class="language-shell" data-line-end="60" data-line-start="58">C:\test&gt; pythonw test.py 1&gt;standard_output.txt 2&gt;standard_error.txt </code></pre> Beninteso, usare <code>pythonw.exe</code> può portare comunque a problemi differenti: per esempio, se lo script resta “in stallo” senza terminare in modo appropriato (per esempio, intrappolato in un ciclo <code>while</code> senza uscita), il processo resterà in vita senza che voi possiate interromperlo (e perfino accorgervene, in effetti). <br /> <h2> Usare le associazioni dei file.</h2> Usare <code>pythonw.exe</code> è differente se lo invochiamo attraverso una chiamata a <code>ShellExecuteEx</code>, ovvero quando lasciamo che sia Windows a trovare il programma giusto da eseguire, in base alle associazioni dei file. Possiamo verificarlo rinominando il nostro script <code>test.pyw</code>, in modo che Windows sappia che vogliamo che sia eseguito da <code>pythonw.exe</code>. A questo punto, dalla shell: <br /> <pre><code>C:\test&gt; test.pyw </code></pre> lascerà a Windows il compito di trovare l’eseguibile adatto con cui far girare lo script, e <code>ShellExecuteEx</code> lo indirizzerà appunto verso <code>pythonw.exe</code>. Notate però che questa volta fare <br /> <pre><code>C:\test&gt; test.pyw 1&gt;standard_output.txt 2&gt;standard_error.txt </code></pre> per re-indirizzare gli <i>standard streams</i> non funziona perché <code>ShellExecuteEx</code> tiene in considerazione gli handle passati dalla shell <i>solo</i> quando avvia un programma a riga di comando, ma invece <i>non</i> li considera per i programmi non-GUI. Per esempio, <br /> <pre><code>C:\test&gt; test.py 1&gt;standard_output.txt 2&gt;standard_error.txt </code></pre> qui il re-indirizzamento funziona correttamente.<br /> In definitiva, quando lanciate direttamente lo script <code>*.pyw</code> con le associazioni dei file, l’unica possibilità è quindi re-indirizzare “da dentro” lo script Python, come abbiamo visto sopra. Oppure, naturalmente, <i>non</i> usare affatto le associazioni dei file e invocare esplicitamente l’eseguibile. Le associazioni dei file, naturalmente, sono usate anche quando fate doppio clic per avviare lo script: <br /> <ul> <li class="has-line-data" data-line-end="82" data-line-start="81">doppio-clic su <code>test.py</code> usa <code>python.exe</code> e quindi apre una shell che resta visibile fino al termine dell’esecuzione dello script;</li> <li class="has-line-data" data-line-end="84" data-line-start="82">rinominare il file <code>test.pyw</code> lo associa all’interprete <code>pythonw.exe</code>: a questo punto un doppio clic su <code>test.pyw</code> fa partire lo script senza nessuna “finestrella nera” visibile, ma anche senza nessuna possibilità di re-indirizzare “da fuori” gli <i>standard streams</i>.</li> </ul> <h2> Usare un collegamento.</h2> Uno <i>shortcut</i> (collegamento) permette di eseguire un programma con qualche personalizzazione aggiuntiva, ma <i>non</i> è la stessa cosa di avviarlo dalla shell. Potete inserire nella “destinazione” del collegamento tutti gli argomenti che l’eseguibile accetta, ma non potete metterci dei comandi della shell: per esempio, vale scriverci dentro <code>pythonw -m timeit "list(range(1000))"</code>, perché questi sono argomenti validi dell’eseguibile <code>python(w).exe</code>; ma d’altra parte non vale <code>pythonw -m timeit "list(range(1000))" &gt; res.txt</code> se volete leggere il risultato in un file. Quindi, in generale, niente re-direzione “da fuori” degli <i>standard stream</i>, ma solo eventualmente “da dentro” il codice Python.<br /> Un trucco potrebbe essere quello di usare il collegamento per invocare l’eseguibile <code>cmd.exe</code>, invece di <code>python(w).exe</code>, e usare quindi l’opzione <code>/c</code> per eseguire un comando della shell. Nel nostro esempio, sarebbe <code>cmd /c pythonw -m timeit "list(range(1000))" &gt; res.txt</code>. Tuttavia questo (indovinate!) apre la famigerata finestrella nera, perché adesso il collegamento punta a <code>cmd.exe</code> e non più a <code>pythonw.exe</code>.<br /> Infine, si intende che anche nei collegamenti potete usare le associazioni dei file come sopra: invece di mettere nella destinazione <code>pythonw test.pyw</code>, potete metterci anche solo <code>test.pyw</code> e lasciare che sia <code>ShellExecuteEx</code> a trovare l’eseguibile giusto in base all’estensione del file. <br /> <h2> Usare un file batch.</h2> Usare un file <code>*.bat</code> per lanciare uno script Python è abbastanza comune. In un file batch si possono scrivere più cose di quelle che stanno nella “destinazione” di un collegamento: può servire per preparare l’esecuzione dello script Python impostando variabili d’ambiente, creando risorse di vario tipo etc. Vale la pena di notare che quasi tutto si può fare anche dall’interno del codice Python: quindi ha senso usare un file batch solo per le configurazioni specifiche della singola macchina su cui intendiamo operare (o al massimo, della distribuzione Windows del nostro programma).<br /> <br /> Dal punto di vista che ci interessa qui, il problema dei batch è che ovviamente… aprono la finestrella nera. Potete verificarlo facendo doppio clic su un semplice file <code>test.bat</code> che lancia lo script Python che abbiamo già visto. Basta una riga: <br /> <pre><code>REM file C:\test\test.bat REM qui le operazioni di preparazione che servono... pythonw test.py </code></pre> Anche se lo script è invocato con l’interprete <code>pythonw.exe</code>, non possiamo evitare che la shell si apra per eseguire il file batch che lo invoca. Possiamo <i>mitigare</i> questo problema, passando attraverso <code>START</code>: <br /> <pre><code>REM file C:\test\test.bat REM qui le operazioni di preparazione che servono... START pythonw test.py </code></pre> Questo apre il processo “in una nuova finestra”… che non esiste, trattandosi di <code>pythonw.exe</code>: siccome l’esecuzione è <i>asincrona</i>, la console usata dal file batch termina immediatamente dopo. Su un computer moderatamente performante, e se le operazioni del file batch non durano a lungo prima di avviare Python, l’effetto della finestrella che si apre e subito si chiude non dovrebbe neppure notarsi. <br /> <h2> Usare uno script VB (o PowerShell).</h2> Dal momento che un file batch non può fare a meno di aprire una console, <i>se</i> davvero abbiamo bisogno di “preparare” l’esecuzione del nostro script Python, e <i>se</i> non ci accontentiamo del trucco di passare attraverso <code>START</code>, <i>allora</i> non resta che utilizzare un <i>altro</i> linguaggio di scripting per queste operazioni preliminari, che non sia quello di <code>cmd.exe</code> e dei file batch. Ovviamente dobbiamo cercare qualcosa che siamo sicuri di trovare sempre in ambiente Windows… e questo restringe il campo a due opzioni: VBScript e PowerShell (che però nel nostro caso non è utile, come vedremo).<br /> <br /> La classica combo VBScript+COM fa tanto… anni '90, ma non è il caso di formalizzarci. La cosa interessante per noi adesso è che il suo eseguibile <code>wscript.exe</code> (il Windows Script Host) è un processo non-GUI, quindi uno script VB <i>non</i> apre la famigerata finestrella nera. Possiamo quindi semplicemente creare un file <code>C:\test\test.vbs</code> e scriverci dentro qualcosa del genere: <br /> <pre><code>' file C:\test\test.vbs ' qui le operazioni di preparazione che servono... Dim Shell Set Shell = WScript.CreateObject("WScript.Shell") Shell.CurrentDirectory = "D:\test" 'eventualmente Shell.Run "pythonw test.pyw" </code></pre> Notate che il metodo <code>Run</code> dell’oggetto COM <code>WScript.Shell</code> non prevede un modo per passare la directory corrente voluta: quindi, se volete passare qualcosa di diverso da quella corrente dello script, occorre impostarla prima… ma se il vostro script non usa la directory corrente, o si accontenta di quella dello script VB, o la imposta per conto suo, potete anche ignorare la cosa.<br /> Questo script invoca <code>pythonw.exe</code>, che come sappiamo non apre la console. Ma a dire il vero il metodo <code>Run</code> prevede anche l’opzione per lanciare un processo non-GUI <i>nascondendo</i> la finestra della shell. Avremmo potuto scrivere anche <br /> <pre><code>Shell.Run "python test.py", 0 </code></pre> dove lo <code>0</code> impone di nascondere la shell, perfino se adesso stiamo invocando <code>python.exe</code>. Prima di lanciare l’eseguibile di Python con il nostro script, dovremmo eseguire le operazioni di preparazione che ci servono (altrimenti, a che scopo passare per uno script preliminare?).<br /> Se però non vogliamo lavorare troppo in VB e preferiamo preparare l’ambiente a colpi di comandi della shell, nessun problema: possiamo usare VB anche solo per lanciare un file batch, che a sua volta lancerà l’eseguibile Python. Forse è un po’ convoluto, ma a volte è davvero più comodo così. Con il batch file che abbiamo già preparato, possiamo quindi scrivere il nostro script VB in questo modo: <br /> <pre><code>Dim Shell Set Shell = WScript.CreateObject("WScript.Shell") Shell.Run "cmd /c test.bat", 0 </code></pre> Certo, il file batch di per sé vorrebbe aprire la console… ma lo script VB glielo impedisce!<br /> <br /> Se VB vi sembra un po’ troppo demodè, l’alternativa giovane e cool, naturalmente, è PowerShell. Il problema è che, a differenza di <code>wscript.exe</code>, PowerShell è anche lui un processo “a console”, e di conseguenza uno script PowerShell non può fare a meno di aprire la finestrella nera. La tecnica di mitigazione qui consiste nell’invocare l’opzione <code>-WindowStyle Hidden</code> dell’eseguibile: per esempio, potete creare un collegamento e scriverci dentro qualcosa come <code>powershell test.ps2 -WindowStyle Hidden</code>. Anche così però la console deve comunque aprirsi almeno un istante all’inizio, prima che l’eseguibile abbia tempo di nasconderla.<br /> Detto questo, se vi piace PowerShell e avete del lavoro “pesante” da fare prima di avviare Python, uno script PowerShell è senz’altro un’alternativa migliore ai vecchi batch file. <br /> <h2> Usare Subprocess.</h2> Può capitare di usare Subprocess per avviare, dall’interno dello script Python, un altro processo esterno. Talvolta potrebbe essere addirittura un secondo processo Python. Ora, l’API di Subprocess è molto ricca e permette di avere un controllo molto fine sulla modalità di esecuzione del processo, gli <i>standard streams</i>, l’exit code, etc.<br /> Per quanto riguarda l’aspetto che ci interessa qui, valgono le stesse regole viste finora: se usate Subprocess per avviare un processo “console”, si aprirà la finestra della console. Questo è vero, prevedibilmente, se usate l’opzione <code>shell=True</code> di <code>subprocess.Popen</code> (o della scorciatoia <code>subprocess.run</code>) per eseguire comandi della shell; oppure, in modo equivalente, se usate Subprocess per eseguire <code>cmd.exe</code> (per esempio, <code>subprocess.run("cmd /c ...."</code>).<br /> Attenzione, comunque: se avete avviato lo script con <code>pythonw.exe</code> e dal suo interno usate Subprocess per avviare un file batch o comunque un processo a console <i>nella stessa console</i>, per esempio <code>subprocess.run("netstat")</code>, ovviamente anche il processo secondario lavorerà nella console invisibile di <code>pythonw.exe</code>, e questo risolve il problema alla radice. A meno che, ovviamente, il processo secondario (file batch o altro) a sua volta non decida di aprire una console per conto suo…<br /> Se invece usate <code>START</code> per avviare il processo in modo “distaccato” e quindi asincrono, per esempio <code>subprocess.run("START netstat", shell=True)</code>, si aprirà una nuova console visibile <i>anche se</i> il processo principale è <code>pythonw.exe</code>. <br /> <h2> Riassumendo…</h2> Python, lo ripetiamo ancora una volta, è un processo a console ed è logico e naturale che operi all’interno della shell del sistema operativo, a sua volta ospitata nella finestra della console. La console deve restare visibile, perché è il luogo dove avviene tutto l’I/O del processo Python.<br /> <br /> Se (e solo se) sapete molto bene quello che state facendo, e siete pronti a gestire da soli l’I/O in modo alternativo a quello naturale proposto da Python, allora potete usare <code>pythonw.exe</code> per eseguire il vostro script Python: questo è un eseguibile Win32, senza console.<br /> La strada più comoda per usare <code>pythonw.exe</code> è lasciar fare il lavoro a <code>ShellExecuteEx</code> rinominando lo script con l’estensione associata <code>*.pyw</code>. A questo punto, il doppio clic sull’icona dello script (azione cara al principiante!) lo avvia “senza la finestrella nera”. Questo è tutto ciò che occorre sapere dal lato Python del problema.<br /> <br /> Se però avete bisogno <i>anche</i> di eseguire batch file o altri programmi di vario tipo (prima di avviare Python, o dal suo interno con Subprocess), ovviamente Python non è più responsabile e dovete regolarvi con il sistema operativo. Se il programma è Win32, non c’è la console (e ricordiamo in particolare che uno script VB è un programma Win32, al contrario di uno script batch o PowerShell). Viceversa, se il programma è a console, opera nella stessa console da cui viene chiamato, ovvero in una nuova console se passate da <code>START</code>. </div> ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-68751894831832341322019-11-08T13:16:00.002+01:002019-11-08T13:19:00.431+01:00Nuovi capitoli per "Capire wxPython"Ho appena pubblicato il primo aggiornamento del mio libro <a href="https://leanpub.com/capirewxpython">Capire wxPython</a>: quattro capitoli, oltre 40 pagine nuove. Adesso il libro è lungo 392 pagine (!) e potete acquistarlo sempre per un prezzo suggerito di 15 euro (!!). Chi l'ha già acquistato ha diritto a scaricare la versione aggiornata, beninteso. E nuovi aggiornamenti arriveranno... oh, se arriveranno.<br /> <br /> I nuovi capitoli sono quattro:<br /> <ul> <li>uno, più breve, sui cosiddetti <i>masked control</i>:&nbsp; sono caselle di testo che ammettono solo certi valori, in base a "maschere" sofisticate; qui dò qualche (prezioso!) consiglio su come usarli in coppia con i validatori. </li> <li>uno, più lungo, sulle date e le ore: qui faccio una lunga introduzione sugli strumenti e le API che wxPython mette a disposizione, e poi faccio una carrellata dei diversi widget che permettono di selezionare una data/ora.</li> <li>e infine due capitoli sul problema di come testare un'applicazione wxPython. E qui... mi scateno 😀</li> </ul> Se il capitolo sulle date mi ha portato via qualche sana ora di ricerca e di selezione del materiale, i due capitoli sul testing me li sono proprio dovuti inventare da zero. Volete sapere una cosa buffa? <b>Non esiste materiale che spiega come testare una GUI wxPython</b> (e ho l'impressione, neanche una GUI con le Qt... ma restiamo in tema). Al massimo potete trovare qualche polveroso snippet di codice, qualche indicazione generica, e su tutte la sempreverde: usa un framework di GUI automation (spoiler alert: la GUI automation non serve quasi mai davvero). E la ancora-più-sempreverde: non vale la pena di testare la GUI. E nient'altro.<br /> <br /> Invece fare gli "unit test" del codice resta fondamentale, e si deve fare anche per il codice delle GUI, ci mancherebbe. Solo che è difficile, se non si sa da dove partire. Bisogna aggirare il problema del main loop di wxPython, ma nello stesso tempo processare gli eventi che servono.<br /> <br /> Nella mia esperienza, ho testato abbastanza spesso le mie GUI, ma non avevo mai provato a mettere insieme quello che ho capito sull'argomento: è stato... abbastanza complicato ma credo di avercela fatta. Parto da alcuni suggerimenti generali, e poi affronto scenari di testing via via più sofisticati: da quelli che si possono fare "a motore spento" (con il main loop non attivo), fino alla GUI automation vera e propria.<br /> <br /> Il risultato è che questi due capitoli, a mia conoscenza, sono adesso <b>la più completa risorsa che è possibile trovare sull'argomento</b>, anche in inglese.<br /> <br /> E adesso? Beh, sto mettendo in cantiere altri aggiornamenti per il libro... gli argomenti non mancano di certo. Ci sono argomenti più noiosi (intendo per me da scrivere...) e altri più stimolanti ma difficili... mi rendo conto che dovrei dare la precedenza alle cose di cui si sente più la mancanza (le liste... le immagini e la grafica...) ma spesso si tratta di cose faticose da organizzare.<br /> In questo momento sono un po' indeciso, ma nel prossimo futuro vedo arrivare gli eventi del mouse e della tastiera (focus, etc), un capitolo faticosissimo sugli event loop, uno sui widget di help per l'utente... e così via. Le vacanze di Natale si avvicinano, e dovrei avere un po' di tempo per lavorarci... Con un pizzico di ottimismo, direi che il prossimo aggiornamento potrebbe uscire a Gennaio.<br /> <br />ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-8512227009436064692019-05-19T16:39:00.001+02:002019-11-08T13:18:37.282+01:00Capire wxPythonE così è andata a finire che ho scritto un libro. "<a href="https://leanpub.com/capirewxpython" rel="nofollow" target="_blank">Capire wxPython</a>: Strumenti e buone pratiche per progettare applicazioni GUI desktop complesse" è la nuova incarnazione dei miei <i>Appunti wxPython</i>, che nel frattempo erano diventati decisamente troppo vecchi per restare ancora in giro.<br /> In effetti la prima idea era semplicemente di aggiornare gli <i>Appunti</i>, trasportare il codice a Python 3 e a wxPython "Phoenix", ma poco o nulla di più. Poi però mi sono lasciato prendere la mano (mi capita) e ne è venuto fuori un "mostro" di qualche centinaio di pagine... per adesso.<br /> <br /> Leanpub è l'ideale per questo genere di pubblicazioni: ti consente di far uscire il libro anche se non è ancora completo, e poi aggiornarlo man mano. L'idea è vedere se qualcuno se ne accorge, e nel frattempo continuare a scrivere. Se comprate il libro adesso, tutti i futuri aggiornamenti saranno naturalmente gratis.<br /> <br /> Già adesso il libro è <i>molto </i>conveniente: sono più di 300 pagine A4 dense, senza neanche una figura (!), con centinaia di righe di codice. Penso che quando sarà finito, il libro avrà un migliaio di pagine. I miei <i>Appunti </i>erano già la risorsa più completa disponibile in Italiano su wxPython: questo libro supera gli <i>Appunti </i>sotto tutti gli aspetti.<br /> Dalla pagina di Leanpub potete scaricare un "campione omaggio", come si dice: contiene qualche capitolo un po' preso a caso, giusto per dare l'idea del tono e del livello di dettaglio.<br /> <br /> Questo non è un libro che vi lascia a piedi. A dirla tutta, ho sempre detestato questi libri magari pure costosi, che alla fine si limitano a mettere in bella copia le cose facili, quelle che comunque si possono trovare anche su internet. wxPython è un framework complesso, ricco di opzioni ma anche pieno di trappole fastidiose: il mio libro va a fondo delle cose, non nasconde la polvere sotto il tappeto, non gira elegantemente intorno alle difficoltà.<br /> <br /> Ce l'ho messa tutta per mantenere la promesssa del titolo: <i>Capire </i>wxPython. Fatemi sapere se ci sono riuscito!<br /> <br />ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-3515300675164696352019-05-13T20:30:00.000+02:002019-05-13T20:30:42.608+02:00Non Pensare da Informatico!<i>Pensare da Informatico (PdI) è un libro generoso e benintenzionato, e il suo autore merita un posto tra i santi evangelisti di Python. È anche purtroppo un libro tragicamente pieno di inesattezze, spiegazioni didatticamente male impostate, esempi poco ispirati. Il bonus/malus di essere gratis e tradotto in Italiano ne ha facilitato la diffusione e ha prodotto negli anni una quantità di storture nella testa di centinaia di studenti. Inauguro qui una serie di note in cui analizzo in modo non sistematico i problemi del libro. Non certo per "fare le pulci" con cattiveria, ma per mettere in guardia i suoi lettori: PdI è un libro facile e scorrevole, ma nasconde le sue insidie.</i><br /> <br /> Proprio oggi un post su Python.it ha attirato la mia attenzione sul breve paragrafo 11.6 (versione 2.2.23) dedicato alla memoizzazione nel classico caso di Fibonacci ricorsivo. E va detto prima di tutto che questo è un <i>buon</i> paragrafo: l'idea di parlare di memoizzazione nel capitolo sui dizionari è buona, usare Fibonacci per introdurre la memoizzazione va benone, la spiegazione è chiara, c'è una figura che aiuta a capire il problema. Davvero, per la media dei paragrafi di PdI, questo è un <i>buon</i> paragrafo.<br /> <br /> Solo che poi il codice è questo (ops):<br /> <br /> <pre><code class="lang-python"><span class="hljs-comment"># PdI, esempio dal paragrafo 11.6</span> memo = {<span class="hljs-number">0</span><span class="hljs-symbol">:</span><span class="hljs-number">0</span>, <span class="hljs-number">1</span><span class="hljs-symbol">:</span><span class="hljs-number">1</span>} <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fibonacci</span><span class="hljs-params">(n)</span></span>: <span class="hljs-keyword">if</span> n <span class="hljs-keyword">in</span> <span class="hljs-symbol">memo:</span> <span class="hljs-keyword">return</span> memo[n] res = fibonacci(n-<span class="hljs-number">1</span>) + fibonacci(n-<span class="hljs-number">2</span>) memo[n] = res <span class="hljs-keyword">return</span> res</code></pre> <pre><code class="lang-python">&nbsp;</code></pre> Notate nulla di strano? Io niente, a una prima scorsa. Ma appena l'occhio si posa per più di pochi secondi su questo esempio, suona un campanello d'allarme. <i>Dove si ferma</i> questa funzione? Che cosa le impedisce di annidarsi in ricorsioni infinite, man mano che le chiamate a <code>fibonacci(n-1)</code> e <code>n-2</code> diventano numeri negativi? In effetti, la prima volta che PdI fa l'esempio di Fibonacci ricorsivo, qualche capitolo prima, la funzione ha la classica forma a cui siamo abituati:<br /> <br /> <pre><code class="lang-python"><span class="hljs-comment"># PdI, esempio dal paragrafo 6.7</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fibonacci</span><span class="hljs-params">(n)</span>:</span> <span class="hljs-keyword">if</span> n == <span class="hljs-number">0</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">elif</span> n == <span class="hljs-number">1</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">1</span> <span class="hljs-keyword">else</span>: <span class="hljs-keyword">return</span> fibonacci(n<span class="hljs-number">-1</span>) + fibonacci(n<span class="hljs-number">-2</span>) </code></pre> <br /> Nella versione con memoizzazione, dove sono finiti i test di arresto per <code>n==0</code> e <code>n==1</code>? Semplice: l'idea dell'autore è che non sono più necessari perché una <i>attenta e conveniente</i> inizializzazione della cache <code>memo = {0:0, 1:1}</code> si occupa di gestire la cosa. Ma lo sentite il campanello di allarme? Adesso abbiamo una funzione che opera correttamente <i>solo</i> in presenza di uno stato esterno indipendente, correttamente configurato a parte. Notate che questo stato esterno dovrebbe essere "solo" una cache messa lì per ottimizzare la performance della funzione: in teoria potrebbe <i>non esserci</i>. Per sua natura una cache è volatile: altri processi potrebbero decidere di svuotarla, o il contenuto potrebbe andar perso in una serializzazione tra due sessioni. Insomma, già è un anti-pattern bello e buono che una funzione possa <i>non funzionare correttamente</i> in base alla configurazione di uno stato esterno; ma che poi questo stato esterno sia proprio <i>una cache</i>, uno degli oggetti per definizione più aleatori, è proprio un pugno nell'occhio.<br /> <br /> E sì, certo, lo so: <i>questo</i> codice funziona; nessuno si sogna di serializzare, svuotare o manipolare <i>questa</i> cache. E poi il focus del paragrafo è sulla memoizzazione, c'è davvero bisogno di essere così precisi sul resto?<br /> Ma vedete, questo è proprio un fastidioso <i>modus operandi</i> onnipresente in PdI: mentre spiega una cosa, e magari anche ben(ino), introduce sottili storture e cattive abitudini che magari non c'entrano nel merito ma intanto restano lì, nella testa di chi legge. Nel caso in oggetto, costava davvero tanta fatica scrivere invece questo?<br /> <br /> <pre><code class="lang-python"><span class="hljs-comment"># PdI, esempio dal paragrafo 11.6 - meglio, questa volta</span> memo = {} <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fibonacci</span><span class="hljs-params">(n)</span>:</span> <span class="hljs-keyword">if</span> n <span class="hljs-keyword">in</span> memo: <span class="hljs-keyword">return</span> memo[n] <span class="hljs-keyword">if</span> n &lt; <span class="hljs-number">2</span>: <span class="hljs-comment"># per brevità uniamo i due test</span> res = n <span class="hljs-keyword">else</span>: res = fibonacci(n<span class="hljs-number">-1</span>) + fibonacci(n<span class="hljs-number">-2</span>) memo[n] = res <span class="hljs-keyword">return</span> res </code></pre> <br /> Questa versione non è poi così diversa: la prima volta che <code>n==0</code> o <code>n==1</code>, il valore finisce comunque nella cache e da quel punto in poi è la cache a gestire anche i casi-limite (<code>if n in memo:...</code>). La differenza è puramente... pedagogica. Intanto la cache non ha più bisogno di essere <i>configurata proprio nel modo giusto</i>. E poi adesso la funzione "basta a se stessa": funziona già di suo. La memoizzazione è una <i>feature</i> in più, ma non pregiudica il corretto funzionamento della funzione. La cache può fare quello che vuole, ma la nostra funzione restituisce sempre il valore corretto, magari solo più lentamente: come appunto dovrebbe essere.<br /> <br /> Certo adesso non siamo in grado di apprezzare fino in fondo quanto sia migliore questo modo di impostare il problema. L'attività di memoizzazione è "incastrata" dentro la funzione e non possiamo farci molto. L'ideale sarebbe <i>spostare</i> la gestione della cache in una funzione esterna <i>indipendente</i> dalla nostra <code>fibonacci</code>, e poi <i>applicare</i> questa funzione di memoizzazione alla <code>fibonacci</code>, magari con un elegante decoratore. A questo punto sarebbe ovvia l'utilità di non far dipendere il funzionamento di <code>fibonacci</code> dalla sua cache: la memoizzazione è una <i>feature</i> esterna che può essere applicata oppure no, senza cambiare una riga del codice interno di <code>fibonacci</code>:<br /> <br /> <pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">memoizza</span><span class="hljs-params">(funzione)</span>:</span> <span class="hljs-comment"># siccome "memoizza" può servire in teoria per diverse funzioni, </span> <span class="hljs-comment"># non possiamo più usare una cache globale, beninteso!</span> memo = {} <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">controlla</span><span class="hljs-params">(x)</span>:</span> <span class="hljs-keyword">if</span> x <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> memo: memo[x] = funzione(x) <span class="hljs-keyword">return</span> memo[x] <span class="hljs-keyword">return</span> controlla <span class="hljs-meta">@memoizza</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fibonacci</span><span class="hljs-params">(n)</span>:</span> <span class="hljs-keyword">if</span> n &lt; <span class="hljs-number">2</span>: <span class="hljs-comment"># per brevità uniamo i due test</span> <span class="hljs-keyword">return</span> n <span class="hljs-keyword">else</span>: <span class="hljs-keyword">return</span> fibonacci(n<span class="hljs-number">-2</span>) + fibonacci(n<span class="hljs-number">-1</span>) </code></pre> <br /> D'accordo, nessuno dice che bisogna arrivare a questo: il lettore di PdI a quel punto non ha ancora queste competenze, e quindi va benissimo anche scrivere <code>fibonacci</code> con la memoizzazione incorporata. A patto di scriverla bene però!, in modo da conservare l'idea giusta di fondo: che la memoizzazione è uno strumento aggiunto per aumentare la performance, ma non deve cambiare in nessun modo il funzionamento interno di <code>fibonacci</code>.<br /> <br /> <br /> <i>Bonus</i>: forse avete anche storto il naso per il modo di usare la cache <code>memo</code> come variabile globale, a livello del modulo. Niente paura: il paragrafo successivo (11.7) discute proprio questo aspetto... O forse, ripensadoci: p-a-u-r-a! Dopo un paragrafo tutto sommato <i>buono</i>, l'11.7 è invece un paragrafo <i>terribile</i>. Intanto, che ci sta a fare un discorso sulle variabili globali nel capitolo sui dizionari? Ma insomma, qui ci vorrebbe un articolo a parte. Per il momento, un aspetto interessante che purtroppo PdI <i>non menziona</i> è questo: aver scelto di usare come cache un oggetto mutabile globale ha un effetto collaterale positivo, che la cache non viene <i>mai</i> invalidata. Quindi la cache non aiuta solo nelle chiamate ricorsive durante l'esecuzione della funzione, ma anche <i>nelle chiamate tra una funzione e l'altra</i>. Ovvero, se chiamate <code>fibonacci(100)</code> con cache, questo è più veloce della stessa chiamata senza cache. Ma non solo: la seconda volta che chiamate <code>fibonacci(100)</code>, il risultato sarà istantaneo perché la cache non si è svuotata nel frattempo. Ecco un caso in cui avere uno stato globale mutabile ha un lato positivo.<br /> ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-72844734591374349942019-03-28T17:08:00.000+01:002019-12-28T13:25:39.013+01:00Distribuire un programma - il modo facile.&nbsp;Aggiornamento: a partire dalle riflessioni di questo post, ho scritto WinPackIt. Vedi <a href="https://pythoninwindows.blogspot.com/2019/12/winpackit-il-modo-facile-per.html">qui</a>. <br /> <a name='more'></a><br /> <br /> Lo confesso, questa eterna domanda "come faccio a compilare e distribuire il mio programma" mi ha sempre un po' infastidito. Intanto perché, in generale, non è una cosa facile: Python non "compila", per cominciare. Poi perché ormai, in questa era del cloud, il modo canonico di distribuire il codice Python sta diventando quello di <i>offrirlo come servizio</i> invece di <i>come prodotto</i>: vale a dire, gestire l'installazione di Python e del proprio codice su un server, e lasciare che gli utenti vi si colleghino con un browser o magari interrogando una api web.<br /> Detto questo, l'esigenza tipica del principiante ("metti che adesso scrivo un programma, ma come faccio a darlo al mio amico Gigi che non ha Python installato sul suo computer?") è pur sempre legittima. Non è che manchino i tool per "congelare" in qualche modo uno script Python e prepararlo per la distribuzione: ci sono <a href="https://cx-freeze.readthedocs.io/en/latest/" rel="nofollow">cx_freeze</a>, <a href="http://www.py2exe.org/" rel="nofollow">py2exe</a>, <a href="https://www.pyinstaller.org/" rel="nofollow">Pyinstaller</a>, <a href="https://py2app.readthedocs.io/en/latest/" rel="nofollow">py2app </a>e via discorrendo. Ma ciascuno di questi prodotti ha i suoi pro e contro, le sue idiosincrasie, i suoi difettucci. E si tratta comunque di imparare uno strumento nuovo, capire a fondo quello che fa, non perdere la testa quando non fa quello che si vuole... insomma, è comunque una bella fatica.<br /> <br /> Intanto però Python, almeno su Windows, a partire dalla versione 3.5.2 ha introdotto una novità <i>molto interessante</i>, passata purtroppo in sordina: <b>una distribuzione sotto forma di pacchetto zip "embeddable"</b>. Potete trovarla accanto alle altre, nella consueta pagina dei download: per esempio per la versione 3.7.3 (la più recente al momento in cui scrivo) potete trovarla <a href="https://www.python.org/downloads/release/python-373/" rel="nofollow">qui</a>, denominata "Windows x86-64 embeddable zip file". Si tratta di un normale file zip che contiene una versione minima funzionante di Python: non c'è la documentazione, manca Idle e tutta una serie di accessori, a partire da Pip. C'è però l'interprete <code>python.exe</code> (e anche il più comodo <code>pythonw.exe</code> che esegue lo script senza mostrare la finestra della shell, come forse saprete) e c'è tutta la libreria standard compressa a sua volta in uno zip interno (che non occorre scompattare: ricordiamo che Python è in grado di importare i moduli anche all'interno di un archivio zip).<br /> L'idea di questa distribuzione è di rendere più semplice l'inclusione di Python all'interno di altri programmi (magari scritti in altri linguaggi). Se volete approfondire potete leggere <a href="https://devblogs.microsoft.com/python/cpython-embeddable-zip-file/" rel="nofollow">questo articolo</a> di Steve Dower. Si può però anche usare questo zip in modo proficuo per creare distribuzioni "portatili" dei nostri programmi. In effetti esiste un tool (<a href="https://pynsist.readthedocs.io/en/latest/" rel="nofollow">Pynsist</a>) che fa proprio questo, appoggiandosi anche al package builder <a href="https://nsis.sourceforge.io/Download" rel="nofollow">NSIS </a>- insomma, un'altra soluzione potenzialmente interessante ma anche un po' complicata.<br /> <br /> Io però voglio proporvi invece un piccolo espediente per usare la distribuzione "zippata" di Python per distribuire il nostro programma in modo semplice, senza nessun bisogno di strumenti esterni. Si tratta di un sistema meno elegante ma veloce e facile da capire. In pratica occorre:<br /> - il file zip della distribuzione "embeddable" di Python;<br /> - i file del nostro programma, eventualmente in un package o comunque in una directory;<br /> - che il nostro programma dipenda solo dai moduli della libreria standard oppure da pacchetti esterni installati con Pip dentro un <a href="https://pythoninwindows.blogspot.com/2018/08/installare-e-usare-python-su-windows3.html">virtual environment</a>;<br /> - infine, occorre tener presente che questo sistema funzionerà solo su Windows.<br /> <br /> Ed ecco quello che dovete fare:<br /> - estrarre il file "zip" della distribuzione di Python in una directory vuota;<br /> - al suo interno, creare inoltre una directory (chiamiamola "my_project" per esempio) dove copiate semplicemente tutti i file del vostro programma, così come sono;<br /> - sempre all'interno della directory-madre, creare un'altra directory (che per convenzione conviene chiamare "vendor", visto che quello che state per fare è appunto un <i>vendoring</i>) dove copiate tutti i package esterni usati dal vostro programma: li trovate nella directory <code>...\Lib\site-packages</code> del vostro virtual environment.<br /> - modificare il file "python37._pth" (che controlla la <code>sys.path</code> del vostro ambiente "embedded") in modo da aggiungere le due directory appena create; <br /> - infine, create un file batch "start.bat" dove impostate la <a href="https://pythoninwindows.blogspot.com/2019/03/e-adesso-dove-clicco.html">directory corrente</a> giusta e avviate il programma con l'interprete Python corretto.<br /> L'utente finale dovrà semplicemente copiare tutto questo sul suo computer e avviare il programma facendo doppio clic sul file batch. Potete anche, naturalmente, creare un collegamento al file batch e dotarlo di un'icona personalizzata, se volete aggiungere un po' di colore.<br /> <br /> Riassumendo, la directory della vostra "build" avrà questa struttura:<br /> <pre>my_build |- my_project | (qui i file del programma) | |- vendor | (qui i file dei package esterni) | |- start.bat |- python37._pth |- python.exe |- pythonw.exe ... |- ... e tutti i file estratti da python-3.7.3-embed-amd64.zip </pre> Se questa è la struttura della directory, allora il file "start.bat" dovrà semplicemente contenere qualcosa del genere:<br /> <code> cd my_project<br /> "..\pythonw.exe" my_script.py</code><br /> La prima riga imposta la directory di lavoro corretta, e la seconda avvia l'entry-point del vostro programma con l'interprete che si trova nella directory superiore.<br /> <br /> Siccome tutto questo procedimento non è altro che la copia e la modifica di un po' di file, la cosa più conveniente è farsi uno script che automatizza tutto questo.<br /> Supponete di avere un progetto Python (chiamiamolo "magazzino"... un tipico gestionale!) che, seguendo <a href="https://pythoninwindows.blogspot.com/2018/08/installare-e-usare-python-su-windows6.html">la struttura suggerita nella mia guida</a>,&nbsp; è organizzato in questo modo:<br /> <pre>magazzino |- magazzino | (qui i moduli del codice) | |- build (la directory che contiene le build) | | | |- python-3.7.3-embed-amd64.zip | |- build.py |- (vari file accessori, test, documentazione...) </pre> Conservate per comodità il file della distribuzione "embedded" di Python dentro la directory "build" (vale la pena di sottolinearlo: se <a href="https://pythoninwindows.blogspot.com/2018/08/installare-e-usare-python-su-windows_23.html">usate </a>un version control system come Git, allora la directory "build" dovrà essere aggiunta <code>a .gitignore</code> per escluderla dalla sorveglianza).<br /> <br /> Fatto questo, una bozza di script "build.py" per automatizzare il processo potrebbere essere:<br /> <pre># build.py import sys import os import os.path from shutil import copytree, ignore_patterns import datetime import zipfile # CONFIGURAZIONE # -------------- PROJECT_NAME = 'magazzino' # nome del progetto SOURCE_ROOT = './magazzino' # root del codice (relativa a questo file) ENTRY_POINT_SCRIPT = 'main.py' # script da eseguire BUILD_ROOT = './build' # root delle builds (relativa a questo file) VENDOR_DIRNAME = 'vendor' # nome della dir dei packages non-standard lib CODE_DIRNAME = 'project' # nome della dir del codice del progetto PYTHON_ZIP_FILE = 'python-3.7.3-embed-amd64.zip' # python zippato da copiare # pattern da ignorare quando si copiano i vendor packages e i file del progetto IGNORE_PACKAGES_PATTERNS = ('__pycache__',) IGNORE_CODE_PATTERNS = ('__pycache__',) # ----------------------------------------------------------------------- PYTHON_ZIP_PATH = os.path.join(BUILD_ROOT, PYTHON_ZIP_FILE) THIS_BUILD_PATH = os.path.join(BUILD_ROOT, datetime.datetime.now().strftime("%y%m%d_%H%M%S")) PACKAGES_PATH_ORIG = os.path.join(os.path.dirname(sys.executable), '../Lib/site-packages' ) PACKAGES_PATH_DEST = os.path.join(THIS_BUILD_PATH, VENDOR_DIRNAME) PROJECT_PATH_ORIG = SOURCE_ROOT PROJECT_PATH_DEST = os.path.join(THIS_BUILD_PATH, CODE_DIRNAME) def build(): os.mkdir(THIS_BUILD_PATH) print('scompatto la distribuzione Python...') with zipfile.ZipFile(PYTHON_ZIP_PATH, 'r') as python_zip: python_zip.extractall(THIS_BUILD_PATH) print('copio i package richiesti...') copytree(PACKAGES_PATH_ORIG, PACKAGES_PATH_DEST, ignore=ignore_patterns(*IGNORE_PACKAGES_PATTERNS)) print('sistemo la sys.path...') with open(os.path.join(THIS_BUILD_PATH, 'python37._pth'), 'a') as f: f.write(VENDOR_DIRNAME+'\n') f.write(CODE_DIRNAME+'\n') print('copio i moduli del programma...') copytree(PROJECT_PATH_ORIG, PROJECT_PATH_DEST, ignore=ignore_patterns(*IGNORE_CODE_PATTERNS)) print('creo un file batch come entry point...') namefile = 'start_%s.bat' % PROJECT_NAME cmd_txt = 'cd %s\n"..\\python.exe" %s\n' % (CODE_DIRNAME, ENTRY_POINT_SCRIPT) with open(os.path.join(THIS_BUILD_PATH, namefile), 'a') as f: f.write(cmd_txt) msg = '\Fatto!\nPuoi fare doppio clic su "%s" per avviare il programma.' % namefile print(msg) if __name__ == '__main__': build() </pre> Questo script crea una directory in "build" (con un nome variabile che dipende dalla data e ora attuali) che contiene tutto quanto serve per distribuire ed eseguire il vostro programma. <br /> Vale la pena di ricordarlo ancora: è <i>fortemente consigliabile</i> usare un virtual environment nel quale avete installato solo i pacchetti esterni che effettivamente vi servono per il programma. Viceversa, questo script funzionerà senza problemi, ma "pescherà" <i>tutti </i>i pacchetti installati nel Python di sistema, anche quelli che non servono, e riempirà la vostra directory di distribuzione di un sacco di robaccia inutile.<br /> <br /> E questo è tutto... Ripeto, non è una soluzione elegante e compatta come quella che offrono i tool "seri" in circolazione... ma è concettualmente molto semplice e facile da tenere sotto controllo, e ha il vantaggio di non ricorrere a nessuno strumento aggiuntivo. ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-12040916264649523182019-03-17T18:31:00.006+01:002020-10-15T22:48:40.075+02:00...E adesso dove clicco?Dopo aver scritto una lunga guida per principianti su come installare e usare Python su Windows... mi rendo conto che forse ancora non basta, o che forse non ho colpito il centro del bersaglio. Provo a rimediare. <i>(Nota successiva... nel frattempo la lunga guida è diventata un libro, e nel libro parlo in modo più completo anche dell'argomento di questo articolo...)</i><br /> Il punto, mi sembra, è che il principiante assoluto in genere riesce a installare Python con una certa facilità: dopo tutto, è solo un installer! si scarica! si fa doppio clic! funziona! Dopo di che, tutto diventa improvvisamente più difficile. Magari il principiante ha in mente <a href="https://products.office.com/it-it" rel="nofollow">Visual Basic</a>, o chissà forse <a href="https://www.embarcadero.com/products/delphi" rel="nofollow">Delphi</a>, e si aspetta in sostanza un RAD vecchio stile, o un <a href="https://www.quickbase.com/" rel="nofollow">ambiente "low code"</a> come si preferisce dire oggi: un bel programma grafico dall'aspetto amichevole dove progettare visualmente il layout di una finestra con pulsanti, caselle di testo... Ma Python non ha niente di tutto questo (non <i>è </i>niente di tutto questo, a dire il vero).<br /> <br /> Quindi il principiante resta bloccato e si chiede: <i>...e adesso dove clicco?</i><br /> <h2> Che cosa è Python (e che cosa non è).</h2> Se avete installato Python per conto vostro, senza fare attenzione al significato di tutte le opzioni e senza aver seguito passo-passo una guida ragionata, allora ci sono già discrete probabilità che abbiate sbagliato qualcosina che verrà poi a tormentarvi dopo. Ma poco male, tutto sommato.<br /> Adesso però si pone il problema di che cosa farci, con questo Python. Certo, Python è un linguaggio di programmazione: ma <i>l'esperienza pratica</i> di lavorare con Python è molto diversa da quella che offrono tool visuali come <a href="https://youtu.be/8JWLxiOdOzg?t=70" rel="nofollow">questo</a>, o <a href="https://youtu.be/H1drA6Q5QSQ?t=27" rel="nofollow">questo</a>, o <a href="https://youtu.be/MhJZwLgvFjc?t=46" rel="nofollow">questo</a>. Programmare in Python assomiglia probabilmente più a <a href="https://youtu.be/UlREhZ-orlk?t=36" rel="nofollow">questo</a>, o a... <a href="https://youtu.be/kqUR3KtWbTk?t=21" rel="nofollow">questo</a> forse. Non è che non sia possibile programmare finestre, pulsanti, menu e caselle di testo in Python: ma <i>Python stesso</i>, lui, non offre questo tipo di interfaccia più "amichevole".<br /> Perché? Beh, perché Python in primo luogo non è nato con quella mentalità e quella finalità in testa. Anche se è possibile programmare più o meno qualunque cosa in Python, comprese quindi le interfacce grafiche e i videogames, Python è nato e cresciuto soprattutto nella comunità degli hacker Unix/Linux, come concorrente di Perl per creare script e programmi server-side, o di Java per creare applicazioni business-oriented.<br /> <h2> Python è un programma a riga di comando.</h2> E ripeto per maggior chiarezza: Python è un <a href="https://it.wikipedia.org/wiki/Interfaccia_a_riga_di_comando" rel="nofollow">programma a riga di comando</a>. L'esperienza pratica di lavorare con Python è quella di aprire una shell del sistema operativo e avviare Python con le opzioni corrette. Se foste utenti Linux, questa cosa probabilmente non vi sconvolgerebbe più di tanto. Ma nel mondo Windows, per gli utenti "normali", è raro avere a che fare con la shell e i programmi a riga di comando. Quindi tanto vale cominciare a farsi un po' di pratica, no?<br /> La <a href="https://it.wikipedia.org/wiki/Cmd.exe" rel="nofollow">shell storica</a> di Windows è <code>cmd.exe</code>. A dire il vero si tratta di una shell molto limitata, e ormai da tempo <a href="https://it.wikipedia.org/wiki/Windows_PowerShell" rel="nofollow">PowerShell </a>è un'alternativa preferibile. Tuttavia per lavorare con Python <code>cmd.exe</code> è più che sufficiente. Per aprire la shell ci sono molti modi: potete usare la combinazione di tasti <code>win+x</code> per mostrare il menu di sistema, scegliere "Esegui", e quindi inserire <code>cmd</code> e dare "invio". Se non avete proprio mai lavorato con la shell, forse vi conviene cercare in rete una guida (come <a href="http://dosprompt.info/" rel="nofollow">questa</a>, per esempio).<br /> La cosa importante da tenere a mente è la nozione di "directory corrente", o "directory di lavoro": è la directory (la "cartella", come si dice tra <i>niubbi </i>Windows) a cui punta in quel momento il prompt della shell. Per esempio, è probabile che all'inizio la directory corrente sia qualcosa come <code>C:\Users\vostro_nome&gt;</code>.Potete cambiare la directory corrente con il comando <code>cd</code> e "navigare" in questo modo nell'albero del file system. La nozione di directory corrente è importante perché l'indicazione di questa directory è "passata" a Python quando viene avviato dalla shell. In pratica Python, mentre è in esecuzione, "sa" qual è la directory corrente della shell che lo ha invocato e occasionalmente ne fa utilizzo. Per esempio, quando Python deve accedere a una risorsa esterna (aprire un file di testo per leggerne il contenuto, per dire) allora risolve la path di quella risorsa <i>relativamente </i>alla directory corrente. <br /> <h2> Usare Pyhton in modo interattivo.</h2> Se siete riusciti ad aprire una shell, allora basta inserire <code>python</code> e dare "invio" per aprire Python in modalità interattiva. Non importa in quale directory di lavoro siete: Python è nella <a href="https://it.wikipedia.org/wiki/Variabile_d%27ambiente_(Windows)" rel="nofollow">PATH di sistema</a>, quindi Windows trova sicuramente il suo eseguibile e... lo esegue, naturalmente.<br /> La modalità interattiva è il modo più semplice e immediato per usare Python: in pratica Python vi offre un prompt dei comandi (molto riconoscibile e caratteristico: "<code>&gt;&gt;&gt;</code>"). Voi inserite un comando valido, date "invio" e Python valuta immediatamente il comando e fornisce la risposta (quando è il caso - oppure non fa nulla di visibile, dipende dal comando). Provate per esempio:<br /> <code>&gt;&gt;&gt; 2 + 2<br /> 4<br /> &gt;&gt;&gt; print('ciao')<br /> ciao<br /> &gt;&gt;&gt; a = 10<br /> &gt;&gt;&gt; a / 2<br /> 5</code><br /> Notate che se provate questo:<br /> <code>&gt;&gt;&gt; import os<br /> &gt;&gt;&gt; os.getcwd()</code><br /> Python vi dice qual è la directory corrente che ha ricevuto dalla shell al momento di essere invocato. Se adesso provate per esempio ad aprire un file di testo:<br /> <code>&gt;&gt;&gt; open('test.txt', 'r')</code><br /> Questo vi restituisce un errore se effettivamente non esiste un file <code>test.txt</code> nella stessa directory corrente.<br /> Per uscire dal prompt di Python, inserite <code>exit()</code> e "invio".<br /> <br /> Se adesso guardate nel Menu Avvio, una delle voci che l'installazione di Python ha aggiunto si chiama "Python 3.7 (64bit)" o qualcosa del genere: non è altro che una normale shell in cui è già stato avviato Python. Potete usare quella, se preferite cliccare sulle icone invece di aprire una shell per conto vostro e avviare manualmente Python. Lo svantaggio però è che non potete controllare la directory corrente, e che non potete fare uso dei fondamentali virtual environments.<br /> <h2> Usare Python per eseguire uno script.</h2> L'altra modalità in cui potete usare Python è per eseguire una script. Uno script è un normale file di testo "puro" che contiene una serie di istruzioni Python (possibilmente valide!) che verranno eseguite una dopo l'altra in una volta sola. Per <i>scrivere </i>uno script potete usare un <a href="https://pythoninwindows.blogspot.com/2018/08/installare-e-usare-python-su-windows_23.html">editor di testo</a> a vostra scelta (con l'esperienza imparerete a capire quale vi piace di più). Il nome del file deve terminare con l'estensione "<code>.py</code>".<br /> Per <i>eseguire </i>lo script, occorre invocare Python dalla shell passando come argomento lo script da eseguire. Per esempio,<br /> <code>&gt; python mioscript.py</code><br /> Naturalmente in questo modo "mioscript.py" deve essere nella directory corrente al momento di avviare Python. In alternativa potete specificare il percorso completo dello script da avviare:<br /> <code>&gt; python C:\percorso\completo\di\mioscript.py</code><br /> o ancora, una <i>path relativa</i> alla directory corrente:<br /> <code>&gt; python percorso\relativo\di\mioscript.py</code><br /> Quindi, per esempio, se avete uno script in <code>C:\Users\vostro_nome\Documents\scripts\mioscript.py</code>, potete aprire una shell e<br /> <code>&gt; cd C:\Users\vostro_nome\Documents\scripts<br /> C:\Users\vostro_nome\Documents\scripts&gt; python mioscript.py</code><br /> oppure qualcosa del genere:<br /> <code>&gt; cd C:\Users\vostro_nome\Documents<br /> C:\Users\vostro_nome\Documents&gt; python scripts\mioscript.py</code><br /> Se il vostro script produce un output (per esempio a seguito di un <code>print</code> nello script), voi vedrete l'output nella shell da cui avete avviato lo script.<br /> <h2> Ma... io volevo CLICCARE!?!</h2> Eh, purtroppo vi è andata male. Certamente ci sono modi di esecuzione più "cliccosi" ma paradossalmente sono anche più complicati (nel senso: in apparenza più semplici, ma nascondono delle trappole che dovete conoscere <i>alla perfezione</i>).<br /> Quello che però dovete capire fin dal principio è che in Python non esiste un "ambiente grafico di esecuzione" predefinito per gli script. Potete sicuramente configurare il vostro editor/IDE per eseguire script Python in modo più "amichevole", ma questo dipende da voi, dal vostro editor, dalle vostre necessità. Di per sé, Python non offre niente del genere all'inizio. L'ambiente di esecuzione predefinito di Python è la shell del sistema operativo. <br /> <br /> Se avete letto una delle migliaia di guide dozzinali che si trovano in giro, e che incoraggiano il principiante a eseguire uno script "facendo doppio clic", o "premendo F5 da IDLE" (ci arriviamo...), ecco: quelle guide vi stanno portando fuori strada. <i>Soprattutto </i>i principianti dovrebbero invece impratichirsi con il <i>vero </i>ambiente di esecuzione di Python: la shell.<br /> La shell vi permette di tenere sotto controllo quattro aspetti fondamentali:<br /> 1) la directory corrente da passare a Python;<br /> 2) il virtual environment dentro cui eseguire lo script;<br /> 3) lo standard input/output/error (che non hanno bisogno di essere re-indirizzati con strane manovre);<br /> 4) eventualmente le variabili d'ambiente che accompagnano l'esecuzione dello script, anche se questo all'inizio non dovrebbe impensierirvi molto.<br /> <i>Sicuramente </i>ci sono modi per controllare tutto questo anche in altri ambienti di esecuzione (editor etc.): ma si tratta di sovra-strutture che mascherano la "verità" della shell, e quindi dovete fare la fatica ulteriore di capire come il vostro ambiente di esecuzione si comporta rispetto a questi quattro aspetti.<br /> <br /> Tutti i problemi (che vedete in giro sui forum o che avete voi stessi) del tipo "quando avvio lo script in Anaconda funziona ma con Idle no, però poi se faccio doppio clic allora succede una cosa ma se invece sposto lo script succede un'altra cosa..." hanno una sola e semplice risposta:<br /> - se non stai usando la shell per lavorare con Python, allora impara prima a usare la shell. <br /> <h2> E Idle, allora?&nbsp;</h2> Ecco, Idle. Se posso permettermi un giudizio personale (sul mio blog!) - <i>non usate Idle</i>, punto. Idle è un discreto IDE, e ha il vantaggio (l'unico) di essere incluso nell'installazione di Python. Per il resto, è abissalmente indietro rispetto alle feature che anche un "normale" editor (<a href="https://www.sublimetext.com/" rel="nofollow">SublimeText</a>, <a href="https://code.visualstudio.com/" rel="nofollow">Visual Studio Code</a>...) può offrire oggi... per non parlare ovviamente dei super-editor (<a href="https://www.vim.org/" rel="nofollow">Vim</a>, <a href="https://www.gnu.org/software/emacs/" rel="nofollow">Emacs</a>...) e dei super-IDE (<a href="https://visualstudio.microsoft.com/" rel="nofollow">Visual Studio</a>, <a href="https://www.eclipse.org/" rel="nofollow">Eclipse</a>...). Vale la pena di investire un po' di tempo, fin da subito, a imparare a usare un buon editor, e saltare completamente la "fase-Idle".<br /> <br /> Detto questo, Idle esiste e se volete potete usarlo. Potete raggiungerlo comodamente dal Menu Avvio (è un'altra delle voci che vengono aggiunte dall'installazione) e presenta una shell Python (quella col prompt <code>&gt;&gt;&gt;</code>, ricordate) in una veste grafica un po' più confortevole di quella offerta dalla comune shell di sistema <code>cmd.exe</code>.<br /> Idle comprende anche un editor (accessibile dal menu "File -&gt; New File") per comporre i vostri script... nonché un ambiente di esecuzione che si ottiene semplicemente premendo "F5" (o scegliendo "Run" dal menu). Quando Idle esegue uno script, lo standard input/output/error viene re-indirizzato in una finestra della shell di Idle.<br /> <br /> Se avete seguito fin qui, avrete già capito che Idle non vi offre tutto il controllo che una normale esecuzione dalla shell vi garantisce. Per cominciare, non potete passare una directory corrente a Python: l'unica è cambiarla "da dentro" Python con <code>os.chdir</code>. Naturalmente non è possibile impostare le variabili d'ambiente né re-indirizzare l'output (anche qui, occorre lavorare "da dentro" con il modulo <code>os</code>).<br /> Non è possibile impostare un virtual environment: se aprite Idle con il Menu Avvio, otterrete sempre e solo l'ambiente Python "di sistema". La soluzione a questo problema è leggermente più complessa. Occorre ricordare che, dopo tutto, Idle è esso stesso un programma (un modulo) Python: quindi, invece di eseguirlo con il Python di sistema, niente vieta di avviarlo con il Python di un virtual environment. Per esempio, se avete un virtual environment in <code>C:\venvs\mio_venv</code>, allora potete aprire una normale shell cmd.exe e dare:<br /> <code>&gt; C:\venvs\mio_venv\scripts\python -m idlelib</code><br /> per avviare una istanza di Idle con il Python del virtual environment. Se preferite avere qualcosa da cliccare, allora create una shortcut (un collegamento) magari sul desktop con quella riga di comando come target.<br /> Il problema, naturalmente, è che questo andrebbe fatto per ogni singolo virtual environment che andrete a creare, perché Idle di per sé non ha un meccanismo per "applicarsi" a venv diversi. <br /> <h2> Ma quindi, tu con che cosa lavori?</h2> Mah, non è che importi poi molto: dovete capire come volete lavorare voi, non copiare cosa fa qualcun altro. In ogni caso, ho sempre usato SublimeText e più recentemente Visual Studio Code, ed entrambi comunque sempre con un certo giudizio. Per esempio, Visual Studio Code non ha nessun problema ad offrire al suo interno anche una shell Python, e anche un ambiente di esecuzione ("Esegui modulo...", quelle cose lì). Visual Studio Code è un editor "adulto": riconosce i virtual environment, gli ambienti "preparati" come <a href="https://www.anaconda.com/distribution/" rel="nofollow">Anaconda</a>, è in grado di presentarmi super-shell come <a href="https://jupyter.org/" rel="nofollow">Jupyter Notebook</a>... Ma se devo essere sincero, nove volte su dieci per eseguire i miei programmi e la shell Python preferisco ancora la vecchia <code>cmd.exe</code>. Uso <a href="https://cmder.net/" rel="nofollow">ConEmu </a>per mantenere organizzate le mie shell (è normale tenerne aperte più di una) e non ho problemi.<br /> <h2> Ma l'utente finale...?</h2> Ecco, questo è un discorso completamente diverso. Si capisce che l'utente finale del vostro programma, lui, dovrà avere una <i>entry-point</i> comoda... qualcosa su cui fare doppio clic, finalmente!<br /> Ma intanto c'è una differenza tra Python e un <i>programma scritto in </i>Python. Voi, in quanto programmatori, siete utenti di Python, non di un programma Python. E Python, abbiamo visto, si usa in un certo modo.<br /> Un programma Python, poi, ha tutto il diritto di avere un'installer personalizzato; un'interfaccia grafica; una bella icona; una bella icona su cui fare doppio clic!, eccetera. Questi però sono problemi di distribuzione e installazione dei programmi, e potete scommetterci che ci sono modi per affrontarli e risolverli anche nel mondo Python (se è questo tipo di programmi che vi interessa scrivere).<br /> <br /> Ma intanto dovreste prima imparare, voi, come si usa Python per scrivere un programma. Il resto verrà. <br /> <br />ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-81113372975703055282019-01-09T22:48:00.000+01:002019-01-09T22:48:10.435+01:00Esercizio: funzioni ricorsive.Un piccolo esercizio sulla ricorsione, proposto su <a href="https://forumpython.it/">forumpython.it</a>:<br /> <blockquote class="tr_bq"> Si consideri il seguente solitario: abbiamo una sequenza iniziale di N interi; una mossa consiste nel selezionare due numeri consecutivi la cui somma sia pari, e sostituirli con la loro media aritmetica. Data una configurazione iniziale noi siamo interessati a trovare la lista di tutte le possibili configurazioni finali (quelle per cui non c’è possibilità di continuare il gioco per mancanza di possibili mosse).<br /> Ad esempio dalla configurazione<br /> <code>"10 20 30 40 5 1"</code><br /> le possibili configurazioni finali sono 5:<br /> <code>["8", "14", "17", "15 20 1", "10 25 40 3"]</code></blockquote> L’esercizio prescrive poi di usare una funzione ricorsiva, che deve accettare in input una stringa di numeri separati da spazio e restituire una lista di stringhe, come nell’esempio.<br /> Azzardiamo una soluzione in cinque minuti. Per prima cosa separiamo la parte interessante da quella noiosa: la faccenda dell’input come stringa e output come lista di stringhe è del tutto secondaria e ce ne occuperemo più in là. Cominciamo invece a scrivere una funzione che accetta, com’è naturale, una lista di interi:<br /> <pre><code class="language-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">game_engine</span><span class="hljs-params">(lst)</span>:</span> founds = set() <span class="hljs-comment"># ... tutto il resto</span> <span class="hljs-keyword">return</span> founds </code></pre> Le nostre soluzioni saranno raccolte in <code>founds</code> che è un set perché, così a occhio, troveremo un sacco di doppioni e non vogliamo portarceli dietro. Per prima cosa, liberiamoci del caso degenere in cui la nostra lista sia più corta di due elementi: quando è così, questa è già sicuramente una configurazione “finale” che non può essere accorciata, e merita di essere aggiunta alle soluzioni:<br /> <pre><code class="language-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">game_engine</span><span class="hljs-params">(lst)</span>:</span> founds = set() <span class="hljs-keyword">if</span> len(lst) &lt; <span class="hljs-number">2</span>: founds.add(tuple(lst)) <span class="hljs-keyword">else</span>: <span class="hljs-comment"># ... tutto il resto</span> <span class="hljs-keyword">return</span> founds </code></pre> Al momento di aggiungere una soluzione a <code>founds</code> dobbiamo trasformarla in tupla, perché un set non può contenere liste. Questo è un po’ scomodo, ma l’alternativa è tenere le soluzioni in una lista e dover cercare ogni volta se la soluzione non è già presente: <code>if lst not in founds: founds.append(lst)</code>.<br /> Il nostro compito a questo punto sarebbe di scansionare la lista alla ricerca di coppie “eliminabili” (ossia la cui somma è pari). Qualunque sia il nostro metodo di scansione, va detto che se non troviamo nessuna coppia eliminabile vuol dire che la lista è “finale” e deve essere aggiunta alle soluzioni. Gestiamo prima di tutto questo caso, aggiungendo un semplice flag:<br /> <pre><code class="language-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">game_engine</span><span class="hljs-params">(lst)</span>:</span> founds = set() <span class="hljs-keyword">if</span> len(lst) &lt; <span class="hljs-number">2</span>: founds.add(tuple(lst)) <span class="hljs-keyword">else</span>: no_change = <span class="hljs-keyword">True</span> <span class="hljs-comment"># ... scansioniamo la lista</span> <span class="hljs-keyword">if</span> no_change: founds.add(tuple(lst)) <span class="hljs-keyword">return</span> founds </code></pre> Adesso affrontiamo il problema principale, ovvero scansionare la lista alla ricerca di coppie “eliminabili”:<br /> <pre><code class="language-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">game_engine</span><span class="hljs-params">(lst)</span>:</span> founds = set() <span class="hljs-keyword">if</span> len(lst) &lt; <span class="hljs-number">2</span>: founds.add(tuple(lst)) <span class="hljs-keyword">else</span>: no_change = <span class="hljs-keyword">True</span> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(lst)-<span class="hljs-number">1</span>): s = lst[i] + lst[i+<span class="hljs-number">1</span>] <span class="hljs-keyword">if</span> s % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>: no_change = <span class="hljs-keyword">False</span> lst_copy = lst[:] lst_copy[i] = s // <span class="hljs-number">2</span> lst_copy.pop(i+<span class="hljs-number">1</span>) founds.update(game_engine(lst_copy)) <span class="hljs-keyword">if</span> no_change: founds.add(tuple(lst)) <span class="hljs-keyword">return</span> founds </code></pre> Ecco quel che succede: prendendo un elemento alla volta, lo sommiamo con il suo successore. Se la somma è pari, allora dobbiamo prima di tutto segnalare che la lista sta per essere modificata (<code>no_change = False</code>) e quindi non deve essere considerata “finale”. Poi dobbiamo fare una <i>copia</i> della lista: questo perché dobbiamo comunque tenere la lista originale intatta per proseguire successivamente nella scansione (potrebbero esserci altre coppie “eliminabili” più in là). Dalla lista copiata eliminiamo la coppia che ci interessa sostituendo l’elemento con la media, e rimuovendo il successore.<br /> A questo punto entra in gioco la ricorsione: la nuova lista così ottenuta viene passata di nuovo alla nostra funzione, per cercare ulteriori soluzioni. Questa ricerca “annidata” produrrà un suo set <code>founds</code> di soluzioni, che aggiungeremo semplicemente a quello che abbiamo già (<code>founds.update(...)</code>). Alla fine, ogni funzione annidata non potrà che arrivare a una configurazione finale e restituire le sue soluzioni senza più ulteriori chiamate ricorsive.<br /> Se provate questa funzione con qualche semplice lista di numeri, vedrete che… funziona appunto (per comodità metto i test alla fine: inutile dire che invece dovreste scriverli man mano che procedete…). Abbiamo un problema, però: non appena la lista di partenza diventa un po’ lunga, dieci elementi o giù di lì, il tempo di esecuzione si fa noiosamente lungo. Per fortuna c’è un modo facile per ottimizzare: se provate a fare a mano qualche esempio, vi accorgerete ben presto che molti rami dell’albero delle soluzioni si ripetono identici. Basterà quindi tener conto di tutte le sotto-liste man mano che finiamo di scansionarle, per evitare di perdere tempo a ripeterle:<br /> <pre><code class="language-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">faster_game_engine</span><span class="hljs-params">(lst, already_done=None)</span>:</span> founds = set() <span class="hljs-keyword">if</span> already_done <span class="hljs-keyword">is</span> <span class="hljs-keyword">None</span>: already_done = set() lst_as_tuple = tuple(lst) <span class="hljs-keyword">if</span> len(lst) &lt; <span class="hljs-number">2</span>: already_done.add(lst_as_tuple) founds.add(lst_as_tuple) <span class="hljs-keyword">else</span>: <span class="hljs-keyword">if</span> lst_as_tuple <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> already_done: no_change = <span class="hljs-keyword">True</span> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(lst)-<span class="hljs-number">1</span>): s = lst[i] + lst[i+<span class="hljs-number">1</span>] <span class="hljs-keyword">if</span> s % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>: no_change = <span class="hljs-keyword">False</span> lst_copy = lst[:] lst_copy[i] = s // <span class="hljs-number">2</span> lst_copy.pop(i+<span class="hljs-number">1</span>) founds.update(faster_game_engine(lst_copy, already_done)) already_done.add(lst_as_tuple) <span class="hljs-keyword">if</span> no_change: founds.add(lst_as_tuple) <span class="hljs-keyword">return</span> founds </code></pre> <code>already_done</code> è un set dove raccogliamo tutte le sotto-liste man mano che diventano “finali”, cioè che abbiamo finito di scansionarle. Il consueto pattern <code>if already_done is None:...</code> serve a proteggerci dalla vecchia trappola degli <a href="https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments">argomenti di default mutabili</a> (ricordiamo che i set, come le liste, sono mutabili!). Prima di imbarcarci nella scansione vera e propria, controlliamo di non aver già incontrato in passato quella lista (<code>if lst_as_tuple not in already_done</code>). Infine passiamo a ogni chiamata ricorsiva successiva anche un riferimento a <code>already_done</code> (e quindi a tutte le sotto-liste scansionate fino a quel momento).<br /> Se provate questa versione della nostra funzione con una lista più lunga, scoprirete di aver guadagnato tutta la velocità che vi serve.<br /> Non resta che occuparci degli aspetti noiosi del problema: scriviamo una funzione separata che ha il compito di formattare gli input e gli output come vuole l’esercizio:<br /> <pre><code class="language-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">game</span><span class="hljs-params">(s, engine=faster_game_engine)</span>:</span> lst = list(map(int, s.split())) res = list(engine(lst)) res.sort() res.sort(key=len) res = [<span class="hljs-string">' '</span>.join(map(str, i)) <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> res] <span class="hljs-keyword">return</span> res </code></pre> La funzione <code>game</code> è il nostro entry-point per l’esercizio: accetta una stringa di numeri separati da spazi, la trasforma in una lista, chiama il “motore” del gioco vero e proprio, e poi ritrasforma l’output in una lista di stringhe. La lista è ordinata per lunghezza delle soluzioni e poi lessicograficamente, in modo da ottenere output più leggibili e soprattutto ripetibili.<br /> Scriviamo infine alcuni test per essere sicuri che tutto funzioni davvero:<br /> <pre><code class="language-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test</span><span class="hljs-params">()</span>:</span> tests = ( (<span class="hljs-string">''</span>, [<span class="hljs-string">''</span>]), (<span class="hljs-string">'1'</span>, [<span class="hljs-string">'1'</span>]), (<span class="hljs-string">'3 2'</span>, [<span class="hljs-string">'3 2'</span>]), (<span class="hljs-string">'2 4'</span>, [<span class="hljs-string">'3'</span>]), (<span class="hljs-string">'2 3 5'</span>, [<span class="hljs-string">'3'</span>]), (<span class="hljs-string">'2 4 6'</span>, [<span class="hljs-string">'2 5'</span>, <span class="hljs-string">'3 6'</span>]), (<span class="hljs-string">'2 5 5'</span>, [<span class="hljs-string">'2 5'</span>]), <span class="hljs-comment"># l'esempio dell'esercizio:</span> (<span class="hljs-string">'10 20 30 40 5 1'</span>, [<span class="hljs-string">'8'</span>, <span class="hljs-string">'14'</span>, <span class="hljs-string">'17'</span>, <span class="hljs-string">'15 20 1'</span>, <span class="hljs-string">'10 25 40 3'</span>]), ) <span class="hljs-keyword">for</span> test, res <span class="hljs-keyword">in</span> tests: g1 = game(test, engine=game_engine) g2 = game(test, engine=faster_game_engine) <span class="hljs-keyword">assert</span> g1 == g2 == res </code></pre> Ecco fatto. Non troppo difficile, dopo tutto. Probabilmente con un po' di lavoro in più si può ottimizzare ulteriormente: per esempio ci può venire in mente che, una volta che abbiamo imparato a tener conto delle sotto-liste già scansionate, non corriamo più il rischio di ottenere doppioni delle soluzioni. Di conseguenza possiamo usare una semplice lista per raccoglierle, invece di un set. Se avete voglia di indagare ulteriormente, fatemi sapere che cosa ne pensate... e buono studio delle funzioni ricorsive!<br /> <div class="SnapLinksContainer" style="display: none; margin-left: 0px; margin-top: 0px;"> <div class="SL_SelectionRect"> <div class="SL_SelectionLabel"> </div> </div> <svg class="SnapLinksHighlighter" xmlns="http://www.w3.org/2000/svg"> <rect height="0" width="0"></rect> <!-- Used for easily cloning the properly namespaced rect --> </svg></div> ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]0tag:blogger.com,1999:blog-8801312244642714488.post-79294438680477368822018-09-17T15:00:00.000+02:002020-05-11T12:13:02.017+02:00Progetti multilingua in Python (parte 9/9).<h2> </h2> <h3> Attenzione!</h3> <b>Queste guide non sono più aggiornate, e in alcuni punti sono proprio superate.</b><br /> <b>Il mio libro <a href="https://leanpub.com/pythoninwindows" rel="nofollow" target="_blank">Python in Windows</a> tratta questi temi, e molto altro, in modo molto più completo e aggiornato.&nbsp;</b><br /> <br /> <h2> Considerazioni varie (parte seconda).</h2> <h3> <a href="https://www.blogger.com/null" id="Pi_lingue_contemporaneamente_nella_stessa_applicazione_2"></a>Più lingue contemporaneamente nella stessa applicazione.</h3> Si può fare, certo. Non è uno scenario consueto e certamente <code>gettext</code> non è nato con questo <i>use case</i> in mente, ma si può fare. La chiave qui è ricordare che quando “installate” <code>gettext</code>, concretamente non fate altro che collegare il nome <code>_</code> al metodo <code>gettext</code> di un oggetto <code>GNUTranslations</code> (creato da funzioni come <code>gettext.translation</code> e <code>gettext.install</code>). Non è necessario installare <code>gettext</code> nei <code>__builtins__</code> però: Python ha la nozione di <i>namespace</i>, e ogni <i>namespace</i> può avere il suo <code>_</code> collegato a una diversa istanza di <code>GNUTranslations</code>. Potete “installare” lingue diverse in ciascun modulo, in ciascuna classe, in ciascuna funzione:<br /> <pre><code class="language-python"><span class="hljs-keyword">import</span> gettext <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span>:</span> _ = gettext.translation(<span class="hljs-string">'myapp'</span>, <span class="hljs-string">'locale'</span>, languages=[<span class="hljs-string">'it'</span>]).gettext <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">foo</span><span class="hljs-params">(self)</span>:</span> _ = self._ print(_(<span class="hljs-string">'una stringa'</span>)) <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bar</span><span class="hljs-params">()</span>:</span> _ = gettext.translation(<span class="hljs-string">'myapp'</span>, <span class="hljs-string">'locale'</span>, languages=[<span class="hljs-string">'en'</span>]).gettext print(_(<span class="hljs-string">'una stringa'</span>)) Foo().foo() <span class="hljs-comment"># stampa la versione italiana della stringa</span> bar() <span class="hljs-comment"># stampa la versione inglese</span> </code></pre> In questo modo, per esempio, potete costruire un’interfaccia grafica dove ogni finestra è tradotta in una lingua diversa.<br /> <h3> <a href="https://www.blogger.com/null" id="Motivi_occasionali_per_importare_i_moduli_tradotti_unit_test_documentazione_26"></a>Motivi occasionali per importare i moduli “tradotti” (<i>unit test</i>, documentazione…).</h3> Come abbiamo visto, nelle prime fasi dello sviluppo del codice, quando la vostra preoccupazione è di marcare le stringhe per la traduzione ma non avete ancora installato <code>gettext</code>, in genere risolvete il problema dei nomi <code>_</code> e <code>ngettext</code> mettendo in cima ai moduli qualcosa come:<br /> <pre><code class="language-python">_ = <span class="hljs-keyword">lambda</span> i: i ngettext = <span class="hljs-keyword">lambda</span> i, j, k: i </code></pre> In seguito, quando avviate il meccanismo di <code>gettext</code>, togliete queste definizioni per non “schermare” i nuovi <code>__builtins__</code> installati da <code>gettext</code>. Adesso la vostra applicazione funziona, se parte dal giusto <i>entry-point</i>.<br /> Ci sono però diverse altre occasioni in cui il vostro codice potrebbe non essere eseguito “nell’ordine giusto”: una suite di test come <code>unittest</code> potrebbe importare ed eseguire il codice di un modulo qualsiasi; la stessa cosa potrebbe fare un tool di documentazione come Sphinx per estrarre le <i>docstring</i>; e così via. Questi strumenti inciampano in una raffica di <code>NameError</code>, visto che non riescono a trovare <code>_</code> o <code>ngettext</code> definiti da nessuna parte.<br /> Non ci sono delle soluzioni veramente eleganti. Per esempio, potete iniettare voi stessi delle lambda <i>noop</i> nei <code>__builtins__</code> all’inizio di un modulo di test:<br /> <pre><code class="language-python"><span class="hljs-comment"># modulo test.py</span> <span class="hljs-keyword">import</span> unittest <span class="hljs-keyword">import</span> builtins builtins.__dict__[<span class="hljs-string">'_'</span>] = <span class="hljs-keyword">lambda</span> i:i builtins.__dict__[<span class="hljs-string">'ngettext'</span>] = <span class="hljs-keyword">lambda</span> i, j, k: i <span class="hljs-keyword">from</span> main <span class="hljs-keyword">import</span> * <span class="hljs-comment"># il modulo che volete testare</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestMain</span><span class="hljs-params">(unittest.TestCase)</span>:</span> <span class="hljs-comment"># etc etc</span> </code></pre> Oppure potete lasciare le lambda nei vari moduli “tradotti”, disattivandole in base a una costante globale:<br /> <pre><code class="language-python"><span class="hljs-keyword">from</span> settings <span class="hljs-keyword">import</span> GETTEXT_IS_ACTIVE <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> GETTEXT_IS_ACTIVE: _ = <span class="hljs-keyword">lambda</span> i: i ngettext = <span class="hljs-keyword">lambda</span> i, j, k: i <span class="hljs-comment"># etc etc</span> </code></pre> In questo modo potete impostare la costante globale a <code>True</code> solo prima di avviare “davvero” la vostra applicazione, lasciandola invece a <code>False</code> per le normali operazioni di sviluppo, test, etc.<br /> <h3> <a href="https://www.blogger.com/null" id="Testare_gettext_68"></a>Testare <code>gettext</code>.</h3> Non dovreste testare <code>gettext</code>, per lo stesso motivo per cui non dovreste testare <code>math.sqrt</code>: è il <i>vostro</i> codice che dovete testare, non quello della libreria standard di Python.<br /> Piuttosto, occasionalmente potrebbe servirvi “testare” le traduzioni nel senso di avere comunque qualcosa da vedere anche quando i cataloghi non sono ancora pronti: per esempio, per avere un riscontro visivo del cambiamento dello stato di una GUI quando cambiate lingua. Oppure, potreste voler disporre di un valore prevedibile della stringa “tradotta”, da poter inserire in un <i>unit test</i>.<br /> In casi del genere è facile sostituire gli oggetti di <code>gettext</code> con dei <i>mock</i>. Questo per esempio “traduce” una stringa anteponendole il codice della lingua. Se non c’è nessuna lingua impostata (perché non avete passato un parametro <code>languages</code> né avete impostato una variabile d’ambiente opportuna), allora antepone <code>--</code> alla stringa “tradotta”.<br /> <pre><code class="language-python"><span class="hljs-keyword">import</span> gettext <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DummyGNUTranslations</span><span class="hljs-params">(gettext.NullTranslations)</span>:</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, language)</span>:</span> self._language = language.upper() <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">gettext</span><span class="hljs-params">(self, message)</span>:</span> <span class="hljs-keyword">return</span> self._language + message <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dummy_translation</span><span class="hljs-params">(domain, localedir=None, languages=None, class_=None, fallback=False, codeset=None)</span>:</span> language = <span class="hljs-string">'--'</span> <span class="hljs-comment"># no language found!</span> <span class="hljs-keyword">if</span> languages <span class="hljs-keyword">is</span> <span class="hljs-keyword">None</span>: <span class="hljs-keyword">import</span> os <span class="hljs-keyword">for</span> envar <span class="hljs-keyword">in</span> (<span class="hljs-string">'LANGUAGE'</span>, <span class="hljs-string">'LC_ALL'</span>, <span class="hljs-string">'LC_MESSAGES'</span>, <span class="hljs-string">'LANG'</span>): val = os.environ.get(envar) <span class="hljs-keyword">if</span> val: language = val.split(<span class="hljs-string">':'</span>)[<span class="hljs-number">0</span>] <span class="hljs-keyword">break</span> <span class="hljs-keyword">else</span>: language = languages[<span class="hljs-number">0</span>] <span class="hljs-keyword">return</span> DummyGNUTranslations(language) gettext.translation = dummy_translation <span class="hljs-comment"># mock di translation</span> gettext.translation(<span class="hljs-string">'myapp'</span>, <span class="hljs-string">'locale'</span>, languages=[<span class="hljs-string">'it'</span>]).install() print(_(<span class="hljs-string">'una stringa'</span>)) <span class="hljs-comment"># stampa "ITuna stringa"</span> gettext.translation(<span class="hljs-string">'myapp'</span>, <span class="hljs-string">'locale'</span>, languages=[<span class="hljs-string">'en'</span>]).install() print(_(<span class="hljs-string">'una stringa'</span>)) <span class="hljs-comment"># stampa "ENuna stringa"</span> gettext.install(<span class="hljs-string">'myapp'</span>, <span class="hljs-string">'locale'</span>) <span class="hljs-comment"># stampa "--una stringa" se nessuna variabile d'ambiente e' impostata</span> print(_(<span class="hljs-string">'una stringa'</span>)) <span class="hljs-keyword">import</span> os os.environ[<span class="hljs-string">'LANG'</span>] = <span class="hljs-string">'it'</span> gettext.install(<span class="hljs-string">'myapp'</span>, <span class="hljs-string">'locale'</span>) print(_(<span class="hljs-string">'una stringa'</span>)) <span class="hljs-comment"># stampa "ITuna stringa"</span> </code></pre> <h3> <a href="https://www.blogger.com/null" id="Tradurre_i_campi_di_un_database_118"></a>Tradurre i campi di un database.</h3> Un’applicazione è talvolta molto più del semplice codice, e una stringa da tradurre può arrivare da posti diversi di un modulo Python. Uno scenario tipico è quando il testo da tradurre è conservato in un database: qui naturalmente <code>gettext</code> non può arrivare.<br /> Va detto che nella maggior parte dei casi, se siete in questo scenario, state anche usando un framework in grado di darvi una mano. Per esempio, considerate l’applicazione web di un giornale online: un <i>contributor</i> carica un articolo che finisce nel database, i traduttori ne producono altre versioni (che stanno sempre nel database), e infine il framework si incarica di mostrare all’utente la versione desiderata. Per compiti del genere, Django ha diverse soluzioni: tra queste, <a href="https://django-parler.readthedocs.io/en/latest/">Django Parler</a> e <a href="http://django-modeltranslation.readthedocs.io/en/latest/">Django Modeltranslation</a> che implementano le due strategie di basso livello di cui parleremo tra poco.<br /> Se non usate nessun framework, come minimo volete accedere al database attraverso un toolkit/ORM come <a href="https://www.sqlalchemy.org/">SqlAlchemy</a>. Anche in questo caso, controllate prima se non esistano soluzioni già pronte. Per esempio per SqlAlchemy ci sono <a href="https://sqlalchemy-i18n.readthedocs.io/en/latest/">SqlAlchemy-i18n</a> e una delle <a href="http://sqlalchemy-utils.readthedocs.io/en/latest/internationalization.html">SqlAlchemy-utils</a>, che adottano ciascuno uno dei due approcci normalmente usati in questi casi.<br /> Se non usate neppure un ORM… probabilmente vi conviene ripensarci. Implementare da zero a mano il supporto per la i18n del database non è proprio semplicissimo. Inoltre, se avete bisogno di fornire traduzioni, in genere significa che la vostra è già un’applicazione “importante”, e allora davvero volete affidarvi a soluzioni artigianali? Detto questo, tenente conto che ci sono due approcci tipici per risolvere il problema:<br /> <ul> <li>creare, per ogni tabella da tradurre, tante tabelle “tradotte” quante sono le lingue, con una foreing key verso la tabella “da tradurre”;</li> <li>creare, per ciascun campo da tradurre, tante colonne “tradotte” nella stessa tabella quante sono le lingue.</li> </ul> Nel primo caso avete quindi qualcosa come<br /> <pre><code>CREATE TABLE articles (id, author, title, text); CREATE TABLE articles_it (id, title, text, article_id); CREATE TABLE articles_fr (id, title, text, article_id); </code></pre> E nel secondo caso:<br /> <pre><code>CREATE TABLE articles (id, author, title, title_it, title_fr, text, text_it, text_fr); </code></pre> Il primo approccio ha il vantaggio di non modificare la tabella originale; è poi facile aggiungere una nuova lingua all’occorrenza (basta creare una nuova tabella). Lo svantaggio principale è che ogni query che richiede un campo tradotto ha bisogno di un join alla tabella secondaria, e i join notoriamente sono più lenti.<br /> Il secondo approccio ha il vantaggio di non aver bisogno di join per la richiesta dei campi tradotti. Tuttavia la tabella finisce per avere un gran numero di colonne, e per aggiungere una lingua occorre fare una <code>ALTER TABLE</code>.<br /> In genere, se avete bisogno (come probabile) di poche lingue che potete decidere all’inizio, allora il secondo approccio dovrebbe essere preferibile. Se invece prevedete un gran numero di lingue che si aggiungeranno nel tempo, potreste provare il primo approccio.<br /> <h3> <a href="https://www.blogger.com/null" id="Tradurre_stringhe_allinterno_di_file_di_testo_152"></a>Tradurre stringhe all’interno di file di testo.</h3> È veramente difficile immaginare uno scenario in cui sia utile inserire stringhe da tradurre in un <i>file di configurazione</i>. Dopo tutto, questi file sono fatti perché l’utente possa modificarli: e se l’utente modifica una stringa da tradurre, allora dovrebbe anche estrarre di nuovo il catalogo <code>.po</code>, ri-tradurselo e ri-compilarlo. Viceversa, se <i>non</i> volete che l’utente modifichi una stringa da tradurre, allora perché inserirla in un file di configurazione? Mettete la stringa in un <code>settings.py</code> invece che in un <code>settings.ini</code>, e traducetela con <code>gettext</code> come al solito.<br /> Un caso speciale sono le varie <i>resources</i> (tipicamente, file xml) che i gui framework possono usare per definire gli elementi dell’interfaccia: questi file contengono tra l’altro anche le varie etichette di pulsanti, menu, etc. Va detto che il framework di solito ha già i meccanismi necessari per tradurre questi file. Per esempio, in wxPython una <a href="https://wxpython.org/Phoenix/docs/html/wx.xrc.XmlResource.html">XmlResource</a> viene automaticamente passata attraverso <code>wx.GetTranslation</code> al momento del caricamento. In PyQt, <a href="http://doc.qt.io/qt-5/qtdesigner-manual.html">QtDesigner</a> ha la possibilità di produrre file <code>.ui</code> traducibili.<br /> Un altro caso verosimile è quando la stringa da tradurre si trova all’interno di un template. Di nuovo, qui è probabile che stiate usando un framework che già offre soluzioni al riguardo. Per esempio, sia Django che Flask non hanno problemi a tradurre stringhe dentro i template html, o ai file di codice javascript.<br /> Tuttavia una web-app non è l’unico scenario possibile per voler usare un template. In ogni caso, se state usando un <i>template engine</i>, allora è probabile che esista già una soluzione. Per esempio, Jinja2 ha una <a href="http://jinja.pocoo.org/docs/2.10/extensions/#i18n-extension">estensione i18n</a> che fa il lavoro. Se non state usando un <i>template engine</i> per produrre i vostri template… probabilmente vi conviene ripensarci. È un altro di quegli scenari in cui reinventare la ruota non serve a niente.<br /> Detto questo, probabilmente vi conviene cercare di limitare il più possibile l’uso di stringhe “statiche” (da tradurre o meno) all’interno dei template. Dopo tutto, sono appunto dei template: potete mettere queste stringhe in un modulo Python, tradurle con <code>gettext</code> e iniettare poi il risultato nel template.<br /> Infine, se i file di testo non sono dei template, oppure è scomodo gestirli con un <i>template engine</i>, oppure se proprio preferite evitare il <i>template engine</i> e fare tutto a mano, allora probabilmente l’ultima risorsa che fa al caso vostro è <a href="http://babel.pocoo.org/en/latest/">Babel</a>. Questo toolkit estrae stringhe da tradurre da normali moduli Python, ma oltre a questo ha la possibilità di estrarre da altri file: tipicamente si tratta di template html, ma potete scrivere delle “regole di estrazione” personalizzate per qualsiasi tipo di file.<br /> <h3> <a href="https://www.blogger.com/null" id="Uso_di_framework_166"></a>Uso di framework.</h3> Se la vostra applicazione utilizza un framework per l’interfaccia con l’utente, controllate fin da subito se questo dispone già di strumenti propri per il supporto i18n. Per esempio:<br /> <ul> <li><code>django.utils.translation</code> in <a href="https://docs.djangoproject.com/en/2.0/topics/i18n/translation/">Django</a>,</li> <li>Flask Babel per <a href="http://pythonhosted.org/Flask-Babel/">Flask</a>,</li> <li><code>pyramid.i18n</code> in <a href="https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/i18n.html">Pyramid</a>,</li> <li><code>wx.GetTranslation</code> in <a href="https://wxpython.org/Phoenix/docs/html/internationalization.html">wxPython</a>,</li> <li><code>QTranslator</code> in <a href="https://doc.qt.io/qt-5/qtranslator.html">PyQt</a>,</li> <li><code>sphinx-intl</code> per <a href="http://www.sphinx-doc.org/en/master/intl.html">Sphinx</a>,</li> <li>eccetera.</li> </ul> Studiate che cosa vi offre il framework, prima di ricorrere a <code>gettext</code>: spesso si tratta di soluzioni più integrate e su misura per le esigenze del framework, e vale la pena di usare quelle.<br /> <h3> <a href="https://www.blogger.com/null" id="Librerie_e_programmi_esterni_180"></a>Librerie e programmi esterni.</h3> Oltre agli strumenti di GNU Gettext che abbiamo già visto, ci sono alcune librerie Python che possono aiutarvi con la manipolazione dei cataloghi <code>.po</code>. Una è <a href="https://bitbucket.org/izi/polib/wiki/Home">Polib</a> che presenta un’interfaccia Python per esplorare, modificare e creare i cataloghi. Un’altra è <a href="http://babel.pocoo.org/en/latest/index.html">Babel</a>, che è un toolkit più completo per il supporto i18n e l10n dei progetti Python.<br /> Un problema separato sono gli strumenti specifici per i traduttori, che raramente sono in grado di lavorare direttamente sui file <code>.po</code> senza rischiare di danneggiarli. L’interfaccia più usata è <a href="https://poedit.net/">Poedit</a>, un editor grafico di cataloghi pensato soprattutto per i traduttori.<br /> Se poi la traduzione di applicazioni diventa un business davvero complesso, potete dare un’occhiata a qualche <i>translation manager</i> più raffinato, in grado di gestire tutti i passaggi del lavoro in modo integrato: per esempio <a href="https://www.transifex.com/">Transifex</a> o <a href="https://phraseapp.com/">Phraseapp</a>, che però sono a pagamento.ricpolhttp://www.blogger.com/profile/14517908201220138917[email protected]1