Category Archives: Python

Python – cose dimenticate (da me)

Sapete la memoria –la mia. E anche se Python (versione 3.x) è uno dei miei linguaggi di programmazione preferiti capita che perdo ore per non ricordare cose che sapevo, che ho usato tante volte e –adesso vi conto.

Copiare liste
Non è come mi è venuto da fare, ecco la versione che viene in mente al volo, senza stare lì a pensarci su. Identica per le versioni 2 e 3, cambia solo print che da parola chiave è diventato funzione.

copy2.py

L = [1, 2, 3]
M = L
print L
print M

print "\nmodifico L"
L[1] = 10
print L
print M

copy3.py

L = [1, 2, 3]
M = L
print(L)
print(M)

print("\nmodifico L")
L[1] = 10
print(L)
print(M)

Uh! correggo subito:

copy3in.py

L = [1, 2, 3]
M = L
print(L)
print(M)

print("\nmodifico L")
L.append(4)
print(L)
print(M)

print("\ncambio L")
L = [10, 20, 30, 40]
print(L)
print(M)

print("\ncome copiare")
L = [1, 2, 3]
M = list(L)
print(L)
print(M)

print("\ned ecco")
L[1] = 0
print(L)
print(M)

Intanto anche append() e funzioni similari funzionano come la versione niubba. Se invece cambio la lista l’equivalenza con la vecchia versione viene persa.

Ma il sistema vero per copiare è usare la funzione list.

Tutte cose che sapevo (OK, liberi di non credermi ma davvero, avevo già affrontato questo in passato).

Chiamata a funzione
Un’altra cosa dimenticata mi è capitata nel debug (che è ancora in corso). Mentre per le liste rimando alla documentazione quest’ultima è logica e risaputa da tutti. La so anch’io ma sapete com’è –la memoria.

func.py

def f(x):
  return x * x

# main
print(f(3))

g = f

print(g(5))

Notare i nomi economici, sto vedendo Haskell e sono stato contagiato. Notare come si può copiare la funzione f chiamandola g. Anzi le due sono la stessa funzione; cambia solo il nome. Quasi come in Haskell, dove però si esagera.

Ma tornando al dunque ho scoperto che c’è exit anche senza dover importarla da sys. Ecco la prima versione:

exit.py

print("prima di exit")
exit
print("dopo exit")

non funziona, ovviamente perché non è vista come una chiamata alla funzione con quel nome, mancano le parentesi, roba che fin dai tempi del C 😐

exit-f.py

print("prima di exit")
exit()
print("dopo exit")

Sì lo so, colpa mia, devo stare più attento. E consultare il manuale (online). E, dopo aver progettato con carta matita e gomma, controllare ogni singola parte (classe, funzione, sezione di codice) 😐

Bon, torno al debug, ancora non funziona 😡

🤩

Annunci

Python o Bash per gli scripts?

Se devi ripetere tre (numero che può variare ma comunque non molto più grande di 3) volte la stessa sequenza di comandi conviene fare uno script. Nel mondo Linux (e Unix prima ancora) esistono diversi linguaggi di scripting, si può scegliere volta per volta il più adatto. Un caso simile è capitato proprio ieri, adesso racconto.

Mi serve un contatore per memorizzare successivamente log di elaborazioni. Ecco lo script Bash che ho prodotto:

x-num

#/bin/bash

FileName="$HOME/dep/elab-num"

if [ -e "$FileName" ]; then
  NUM=`cat $FileName`
else
  NUM=0
fi

NUM=$((NUM + 1))
echo $NUM > $FileName

echo $NUM # qui vanno i comandi veri

Il numero dell’ultima chiamata a x-num è memorizzato nel file ~/dep/elab-num. Lo script legge questo numero, lo incrementa di uno, aggiorna il file scrivendo il numero aggiornato e, in questo esempio, scrive sul terminale questo stesso numero. Nella versione vera qui vengono i comandi di elaborazione dei dati. Qualora il file di log non esista il numero dell’elaborazione viene assunto 0; quindi funziona anche in questo caso con indice 1.

Provo a testarlo:

OK, funziona.
Qui devo raccontare una cosa personale, mi è già capitata più volte, continua a capitare, anche ieri… Nello script non si può usare il carattwere ~ per indicare la home, bisogna usare $HOME, cosa che mi dimentico invariabilmente. Adesso che l’ho scritta chissà…

Ieri ero con un amico che è uscito con la solita domanda: “E per Windows come si fa?”

Bella domanda, io uso Python. E questo è l’equivalente Python del precedente

py-num.py

#!/usr/bin/python3

import os

home = os.getenv('HOME')

FileName = os.path.join(home, 'dep/elab-num')

if os.path.exists(FileName):
  with open (FileName, 'r') as f:
    num = int(f.readline())
else:
    num = 0

num += 1
with open (FileName, 'w') as f:
  print(num, file=f)

print(num)

Cancello il file del contatore per essere nelle stesse condizioni di prima ed ecco:

OK. I due scripts fanno esattamente la stessa cosa, possono essere usati in modo intecambiabile (con Linux).

Sono entrambi molto piccoli:

Quale preferire? Se è solo per me io sceglierei la versione Bash, ma sono gusti, l’abitudine (fin dagli anni ’80). La versione Python è però, previa modifica del nome del file, funzionante anche su Windows.

Probabilmente la variabile d’ambiente corrispodente a HOME credo sia HOMEPATH ma chi conosco opera differentemente impostando FileName = "c:/dep/elab-num.txt". Sì meglio specificare l’estensione, anche se di solito non si vede.

“Sai, queste cose si possono fare anche in Basic” (cit.). Certo 😋

🤩

È vero guarda qua: anche loro!

Non so se conoscete Sandro 💥, quello di Quantizzando 💥? Dovreste.
Non perché lui racconti di roba di ‘puters ma rockz! 💥 e poi oggi ci dice qualcosa dei linguaggi di programmazione che è proprio quello che volevo sentir dire.

Si può partire da questo cinguettio: Come utilizzare le pulsar per osservare le onde gravitazionali generate subito dopo il Big Bang che vi indica il post sul blog di cui in oggetto.

Il post non parla di ‘puters e linguaggi per ‘puters ma ci dice (in fondo, ma leggetelo tutto il post):

Ultima cosa davvero molto importante: i dati e i codici usati dal team di NANOGrav sono pubblici.

Ah! qui volevo arrivare:
Indovinate il linguaggio? Sì! 💥
Indovinate che moduli usano? Sì! 💥

Proprio come dice sempre Jake (è astro 💥 anche lui) e, si parva licet, me too 😋

🤩

Leggere e scrivere dizionari (e JSON)

Forse ne ho già parlato in passato (ma la memoria…) e non è nemmeno una cosa tanto interessante ma capita e allora ecco 😊

Il dictionary è un tipo che può tornare comodo, spesso. Ci sono diversi modi per creane; copio dalla documentazione:

a = dict(one=1, two=2, three=3)
b = {'one': 1, 'two': 2, 'three': 3}
c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
d = dict([('two', 2), ('one', 1), ('three', 3)])
e = dict({'three': 3, 'one': 1, 'two': 2})
a == b == c == d == e
True

Spesso nella pratica succede che la costruzione avviene più gradualmente:

d0.py

dic = {}

dic.update({1:'foo'})
k = 3
v = 'baz'
dic.update({k:v})
k = 'due'
v = 'bar'
dic.update({k:v})

print('dic =', dic)      #1

dic.update({5:'cinque'})
print('dic =', dic)      #2

dic.pop(5)               #3
print('dic =', dic)

Autoevidente ma comunque lo creo vuoto, lo popolo e visaulizzo (#1). Aggiungo un altro elemento (#2), che poi cancello (#3).

Questo è semplice, sarebbe possibile crearlo ogni volta, probabilmente con uno o più cicli leggendo i dati da file.

Ma se il dizionario, costruito in tempi successivi, supera una certa dimensione conviene scriverlo su file e poi rileggerlo quando serve.

Anticamente (non so se c’è anche su questo blog) usavo istruzioni molto semplici.
Per memorizzarlo su file:

d1.py

dic = {1: 'foo', 3: 'baz', 'due': 'bar'} #1
st = str(dic)                            #2
with open('dic', 'w') as f:
    f.write(st)

il dizionario dic in #1 è quello precedentemente costruito. Per scriverlo su file devo conventirlo in stringa (#2).

Successivamente posso leggerlo:

d2.py

with open('dic', 'r') as f:
    st = f.read()            #1

dic = eval(st)               #2
print(dic)
print(type(dic))             #3

lo leggo come stringa (#1) che valuto con eval() (#2) ottenendo quanto voluto (#3).

OK, funziona. Ma si può fare in modo più canonico (Python è un linguaggio ancora in evoluzione, nel senso che si aggiungono moduli a un ritmo sconvolgente).

d3.py

import json                  #1

with open('jdic', 'r') as f: #2
    try:
        dic = json.load(f)
    except ValueError:
        dic = {}

print(dic)

Sì, esiste il modulo json (#1) che fa quello che ci serve. Con un paio di avvertenze:

  • sia le chiavi che i valori devono essere stringhe;
  • occorre usare " come delimitatore di stringa; ' da errore.

Questo è il contenuto del file jdic

La scrittura su file del dizionario via json peraltro non è soggetta alle condizioni necessarie per la lettura; la conversione a stringa di chiavi e valori che si rendessero necessarie è trasparente:

d4.py

import json

dic = {1: 'foo', 3: 'baz', 'due': 'bar'} #1

with open('jdicw', 'w') as f: #2
    json.dump(dic, f)

Il formato JSON è molto popolare, Python si adegua, esiste json.tool che deve essere usato nel terminale:

🤩

Non sempre i linguaggi sono uguali

Altro post su cose viste aggiornando script. Non so se interessa ma per me che sono niubbo… 😉

Questa è la versione minima di un codice che non fa quello che dovrebbe fare:

manca-0.py

def pari(n):
    print(n, "è pari")

def dispari(n):
    print(n, "è dispari")

def valeDieci():
    print('Dieci')

def p_d(n):
    ok = False
    if n % 2 == 0:
        ok = True
        pari(n)
    elif n % 2 == 1:
        dispari(n)
    elif ok and (n == 10):
        vale_dieci()

# main

p_d(5)
p_d(8)
p_d(10)

Come si vede la condizione ok and (n == 10) non viene eseguita anche quando il valore del parametro n è quello giusto. Ovvio ok è False, come settato all’inizio della funzione.

Probabilmente nello scrivere il codice si è fatta confusione tra leistruzioni if e switch/case. Quest’ultima è presente in tanti linguaggi (qui, qui e qui). A volte con nome diverso, p.es. case nel Basic, Delphi, Lisp (cond). Ma in questi linguaggi viene eseguito il primo caso vero, e si esce, è una variante di if/else if/.../else.

In Python manca, anche se c’è un modo semplice per costruirla, nella versione non-C.

La differenza fondamentale è che l’if non esamina tutti i casi (elif e else in Python) ma esce appena ne trova un caso vero. Quindi nel nostro caso la condizione eseguita è n % 2 == 0 cioè n è pari.

La correzione è immediata: suddividere l’if in due istruzioni:

manca-1.py

def pari(n):
    print(n, "è pari")

def dispari(n):
    print(n, "è dispari")

def valeDieci():
    print('Dieci')

def p_d(n):
    ok = False
    if n % 2 == 0:
        ok = True
        pari(n)
    elif n % 2 == 1:
        dispari(n)

    if ok and (n == 10):
        vale_dieci()

# main

p_d(5)
p_d(8)
p_d(10)

Uh! errore 💥 Manca la funzione vale_dieci().

Errore mio: l’ho definita come valeDieci(), correggo ed ecco:

manca-2.py

def pari(n):
    print(n, "è pari")

def dispari(n):
    print(n, "è dispari")

def vale_dieci():
    print('Dieci')

def p_d(n):
    ok = False
    if n % 2 == 0:
        ok = True
        pari(n)
    elif n % 2 == 1:
        dispari(n)

    if ok and (n == 10):
        vale_dieci()

# main

p_d(5)
p_d(8)
p_d(10)

OK! 😁

Personalmente non sapevo che l’interprete Python ignora le parti di codice che non deve eseguire immediatamente. Nei casi precedenti la chiamata alla funzione vale_dieci() non era richiesta e la sua mancanza non veniva rilevata. Ho provato con interpreti di altri linguaggi e il comportamento è analogo. Credo che questo non capiti con i linguaggi compilati (ma almeno anticamente il Fortran…), sarebbe da verificare.

Una curiosità: il nome di una funzione in Python è solo una variabile come tutte le altre. Esempio:

rinomina.py

def nome_vero(msg):
    print(msg, "\n")

#main
myfun = nome_vero

myfun("ciao!")
myfun("😊 😊 😊")

Va da sé che per me –sono vecchio– questo, se non per motivi particolari, va evitato.

coreano.py

def 안녕(msg):
    print(msg, "\n")

#main
myfun = 안녕

myfun("ciao! 안녕")
myfun("😊 😊 😊")

Sì in questo caso è OK; ma chi ha scritto quella funzione? E se dico “annyong” vado bene? E com’è che vale per loro ma non per tutti (cioè non per me)?

🤩

Essere semplici

Python è uno dei miei linguaggi preferiti. Recentemente mi è capitato di aggiornare script vecchi, scritti da me e da altri, di parecchi non si conosce l’autore. Ho materiale per un paio di post, oggi comincio con uno elementare ma per me che sono niubbo di una certa importanza.

Mi è successo di trovare, più volte, delle espressioni come queste:

Il risultato è chiaro:

e se ripeto l’istruzione torno al valore precedente

Non so se è corretto ma da noi questo è conosciuto come flip-flop (lo so che è anche un’altra cosa 😋).

Ma –tranne casi particolari– secondo me non si deve usare. Scrivere invece ok = True o ok = False. Il perché è semplice: nel debug (o aggiornamento) non devo ricordarmi il valore corrente per negarlo (ovviamente deve saperlo il programmatore iniziale).

Inoltre c’è il duck typing e Python ne è dentro fino al collo. Una variabile non solo varia (OK, come fanno le variabili ma adesso sono indaffarato con la programmazione funzionale) ma varia anche il tipo. E capitano cose come queste:

Siamo passati in modo trasparente da int a bool.

In certi linguaggi si definisce false come 0 e true come 1, o, –spesso– come non 0. È il nostro caso?

No, non esattamente: c’è identità tra 0 e False e tra 1 e True ma per gli altri valori non si sa.

Non ci limitiamo agli interi

Tra le versioni 2.x e 3.x è cambiata la sintassi per la valutazione dell’ugualianza tra valori. Mentre prima si usava <> per dire diverso adesso si deve scrivere !=

Ma capitano cose impreviste

cioè in questo caso è diverso da entrambi i possibili valori booleani. In questi casi –che non si dovrebbero usare– fare sempre il cast:

Dovrebbe essere possibile anche questo

anche se potrebbero insorgere problemi di arrotondamento.

E si può trafficare anche con le stringhe (ht _s 😁):

Non so se questo post ha una qualche valenza. Ma a me è capitato 😡

🤩

Un mystero pythonico – readline

Ieri Doug Hellmann racconta del suo modulo Python readline, qui: readline — The GNU readline Library.

Non l’ho seguito alla lettera, anzi, non sono così diligente. Però OK, funziona. Adesso vi conto.

Ecco uno script minimo, std.py:

while True:
    line = input('Prompt ("stop" to quit): ')
    if line == 'stop':
        break
    print('ENTERED: {!r}'.format(line))

Provo a eseguirlo…

Ahemmm… come già sapevo i tasti freccia, Home, Fine e simili non funzionano 😡

Ma, dice Doug… modifico lo script, adesso rl.py:

import readline

while True:
    line = input('Prompt ("stop" to quit): ')
    if line == 'stop':
        break
    print('ENTERED: {!r}'.format(line))

ed ecco:

OK! funzionano 😊

Notare che l’unica differenza è l’import di readline. readline non viene usata esplicitamente nel codice. Ma c’è e lavora. Guido, me lo spieghi? Anzi no, ancora più semplice: c’è nel manuale? e se no lo aggiungi? Grazie, nèh.

🤩

Leggere dati in formato CSV con impostazioni locali

Argomento già trattato anche da me in passato ma ogni tanto torna d’attualità.
Necessita di qualche istruzione collegata alle impostazioni locali e altre legate a preferenze personali. Per esempio non usare SciPy, anche se si usa Python, perché poi finisce su Windows. In compenso vale (dovrebbe, spero) sia per M$ Excel che per i fogli di calcolo FOSS; io uno Libre Office.

Ecco un file di dati in formato CSV (dati.csv), salvato con queste impostazioni

1;2;3;4;5,6;"sette";"più parole"
"04/10/17";"questa è la data";"e con separatore migliaia";1.234.567,89;;;
"fine";;;;;;

Nella shell IPython (ma sono OK tutte le altre) ecco, apro il file [1], leggo la prima riga [2] ed elaboro:

Si rende necessario togliere l’a-capo [4]; notare che le stringhe in Python sono immutabili quindi salvo l’elaborazione in sts, st resta invariata; questo anche successivamente passando da sts a stn.

Devo poi importare il modulo re [6], quello delle espressioni regolari che uso [7] per sostiture il separatore decimale virgola con il punto.

Dalla stringa ottengo gli elementi componenti, numeri interi e float e testo con split [9]

I componenti della lista **lst sono stringhe [13, 17] e devono essere convertiti con int [14] per gli interi e float [18] per o floating-point.

Passo ora alla seconda riga.

La data è semplicemente una stringa, sta al programmatore riconoscerla come tale.

Il numero in formato “valuta” non ci consente più di usare la regexp di prima per via della presenza del punto (separatore di migliaia) che si confonderebbe con il separatore decimale che vogliano ottenere. Dobbiamo pertanto eliminarlo [24] (non siamo contabili!) prima di regolarizzare il separatore decimale [25].

Attenzione: il punto per regexp significa “ogni carattere” per cui si è reso necessario “escaparlo [30] così: “\.“.

le liste derivanti da un file CSV hanno tutte la stessa lunghezza, con elementi vuoti corrispondenti alle celle non definite [35].

Facile, vero? 😊

🤢

SymPy – 26 – conclusioni

Continuo da qui.

L’esame di SymPy finisce qui. La documentazione continua, anzi entra nel vivo dopo il tutorial che ho esaminato. Ma (il bello di essere ormai fuori dal mondo del lavoro) ci sono altre cose –über-sexy– da seguire, prossimamente.

SymPy è OK. Essere integrato con Python, anzi essere Python, è un punto a suo favore (anzi di più!). Essere gestibile all’interno della REPL è un’altro punto. Per quel che ho visto conviene usare la REPL di Python o quella di IPython (come faccio io) e non quella integrata nel Web disponibile sul sito di SymPy: non è aggiornata e soggetta a rallentamenti non dovuti alla connessione.

Uno dei motivi che mi hanno portato alla scoperta di SymPy è la possibilità di visualizzare i risultati in formato LaTeX; quasi, c’è bisogno di qualche edizione dell’output.

Occorre usare l’output di print().

Si può inserire in WordPress in questa forma:

f{\left (x \right )} - 2 \frac{d}{d x} f{\left (x \right )} + 
\frac{d^{2}}{d x^{2}} f{\left (x \right )} = \sin{\left (x \right )}

tra i tag $latex#formula#$ e appare così:

f{\left (x \right )} - 2 \frac{d}{d x} f{\left (x \right )} + \frac{d^{2}}{d x^{2}} f{\left (x \right )} = \sin{\left (x \right )}

In alternativa si può utilizzare LaTeX Equation Editor.

Si può quindi scaricare l’immagine, cosa che faccio abitualmente (non sono tanto bravo con LaTeX), dopo aver modificato le opzioni come indicato in figura.

Un’alternativa (über-sexy, anzi di più) è Maxima (e wxMaxima, o GMaxima, o …); ne ho accennato in passato, sarebbe un’altra cosa da approfondire. Avendo tempo.

🤢

SymPy – 25 – manipolazione avanzata di espressioni – 3

Continuo da qui, copio qui.

Percorrere l’albero (tree)
With this knowledge [post precedente], let’s look at how we can recurse through an expression tree. The nested nature of args is a perfect fit for recursive functions. The base case will be empty args. Let’s write a simple function that goes through an expression and prints all the args at each level.

See how nice it is that () signals leaves in the expression tree. We don’t even have to write a base case for our recursion; it is handled automatically by the for loop.

Let’s test our function.

Can you guess why we called our function pre? We just wrote a pre-order traversal function for our expression tree. See if you can write a post-order traversal function.

Such traversals are so common in SymPy that the generator functions preorder_traversal and postorder_traversal are provided to make such traversals easy. We could have also written our algorithm as

Prevenire la valutazione dell’espressione
There are generally two ways to prevent the evaluation, either pass an evaluate=False parameter while constructing the expression, or create an evaluation stopper by wrapping the expression with UnevaluatedExpr.

If you don’t remember the class corresponding to the expression you want to build (operator overloading usually assumes evaluate=True), just use sympify and pass a string:

Note that evaluate=False won’t prevent future evaluation in later usages of the expression:

That’s why the class UnevaluatedExpr comes handy. UnevaluatedExpr is a method provided by SymPy which lets the user keep an expression unevaluated. By unevaluated it is meant that the value inside of it will not interact with the expressions outside of it to give simplified outputs. For example:

The x remaining alone is the x wrapped by UnevaluatedExpr. To release it:

Other examples:

A point to be noted is that UnevaluatedExpr cannot prevent the evaluation of an expression which is given as argument. For example:

Remember that expr2 will be evaluated if included into another expression. Combine both of the methods to prevent both inside and outside evaluations:

UnevalutedExpr is supported by SymPy printers and can be used to print the result in different output forms. For example

In order to release the expression and get the evaluated LaTeX form, just use doit():

Uhmmmm… un po’ pasticciato ma OK 😊

:mrgreen: