Archivi Categorie: Python

Gestione di una coda in Python – 1

art-ma-2

Un post facile, didascalico, introduttivo alla valutazione per uno script che si comporta proprio come la fila allo sportello. Altamente sperimentale, nèh!

Si può fare con una lista. Ecco come crearla e inserire elementi:

>>> ls = []
>>> len(ls)
0
>>> ls.append(1)
>>> ls.append(2)
>>> ls.append('tre')
>>> ls
[1, 2, 'tre']
>>> len(ls)
3

Richiamare elementi:

>>> ls[2]
'tre'

Verificare se un elemento è presente:

>>> 2 in ls
True
>>> 8 in ls
False

Estrarre elementi:

>>> ls = [1, 2, 3, 4, 5, 6]
>>> ls.pop()
6
>>> ls
[1, 2, 3, 4, 5]

non come serve, funziona come una pila (stack). È però immediato rimediare, così:

>>> ls.pop(0)
1
>>> ls
[2, 3, 4, 5]
>>> ls.pop(0)
2
>>> ls
[3, 4, 5]

In previsione di un uso in qualche misura intensivo conviene costruire funzioni per semplificarne la gestione. Le funzioni che si costruiscono sono semplici e per quanto possibile conviene usare la sintassi lambda. Inoltre i nomi inizieranno tutti con q (come  coda  queue).

L’assegnazione è semplice, resta LS = [].

Inserimento

>>> qapp = lambda ls, dat : ls.append(dat)

la provo:

>>> LS = []
>>> qapp(LS, 'a')
>>> qapp(LS, 'b')
>>> qapp(LS, 'c')
>>> LS
['a', 'b', 'c']

Estrazione del primo dato, quello in testa il car del Lisp:

>>> qext = lambda ls : ls.pop(0)  # versione iniziale, migliorabile
>>> d = qext(LS)
>>> d
'a'
>>> e = qext(LS)
>>> e
'b'
>>> f = qext(LS)
>>> f
'c'
>>> g = qext(LS)
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1, in 
IndexError: pop from empty list

OOPS! funziona se la lista non è vuota; da correggere testanto la lista con un if che in lambda ha una sintassi sua:

>>> qext = lambda ls : ls.pop(0) if len(ls) > 0 else None
>>> LS = ['1', 'b', 'ultimo']
>>> qex(LS)
'1'
>>> qext(LS)
'b'
>>> d = qext(LS)
>>> d
'ultimo'
>>> e = qext(LS)
>>> print(e)
None

OK; per estrarre un dato che non è il primo

>>> qexnth = lambda ls, n : ls.pop(n) if len(ls) >= n else None
>>> LS = [1, 2, 3, 4]
>>> qexnth(LS, 2)
3
>>> LS
[1, 2, 4]
>>> d = qexnth(LS, 5)
>>> LS
[1, 2, 4]
>>> print(d)
None

Ma è più utile estrarre un dato in base an suo valore, non per l’indice

>>> qexd = lambda ls, dat : ls.pop(dat in ls) if dat in ls else None
>>> LS = [1, 10, 100]
>>> qexd(LS, 10)
10
>>> LS
[1, 100]
>>> qexd(LS, 1000)
>>> LS
[1, 100]

OK 😁 Continua, forse 😁

Radici di espressioni e interattività

sl104

Continuo (in qualche misura) con quanto detto qui, integro, rispondo a osservazioni, varie e tutte sensate.

Per prima cosa si è visto che il calcolo simbolico non è (nel nostro caso) necessario (o quasi mai). E, ma lo sapevamo già, Maxima anche; anche se a me piace e continuerò a parlarne 🧐

In questi giorni Redmonk ha pubblicato la lista aggiornata dei linguaggi più usati. Cose interessanti, non nuove ma una sbirciatina è d’obbligo.

Sì, JavaScript (con altri, Java, C++) è in testa 🤩 Principalmente, credo, per il Web, dentro le pagine HTML, ma si può –e viene– usato anche fuori, vedi Node.

Io sono tifoso di Python, anche per via di NumPy, SciPy, Matplotlib &co. In alternativa c’è Octave (la versione FOSS di MATLAB). Ci sarebbe anche Julia ma è ancora poco usato, troppo nuovo e con modalità diverse da quella corrente (l’opposto di Node); per adesso vale quanto detto per Maxima, ma forse in futuro…

Per elaborazioni semplici e da aggiustare spesso sono da escludere i linguaggi compilati, C++, Java e altri ancora (Rust).

Sì, resta JavaScript, Node in questo caso, alternativo a Python. Per chi lo usa abitualmente va bene, per me dovrei impratichirmi con i nomi delle funzioni e dove trovarle nella galassia di packages (sbaglio o è labirintica?). Ma funziona, è OK anche Node.

Riporto due casi dal post citato, usando Python:

$ python3 -q
>>> expr = "(a + b)**2 - b**2 + a*b - c"
>>> a = 1; b = 2; c = 3
>>> eval(expr)
4
>>> from sympy import real_roots, Symbol, real_roots
>>> x = Symbol('x')
>>> expr = 8*x**3 + 4*x**2 + 2*x - 258
>>> real_roots(expr)
[3]
>>> len(real_roots(expr))
1
>>> real_roots(expr)[0]
3

In tanti ci dicono di usare eval() solo quando necessario per tanti motivi, p.es. è pesante per la macchina, ma nel nostro caso è quel che serve, evita di dover costruire una funzione ad hoc o usare lambda.

Naturalmente lo stesso vale per Node:

$ node
> expr = "(a + b)**2 - b**2 + a*b - c"
'(a + b)**2 - b**2 + a*b - c'
> a = 1; b = 2; c = 3
3
> eval(expr)
4

Per le radici delle equazioni occorre installare qualche package, ce ne sono diversi, a una rapida googlata ho trovato nerdamer, Algebrite, algebra.js, math.js, Zolmeister, … Da valutare e scegliere. Da vedere con npm search.

Gli esempi (Python e Node) visualizzano versioni elementari di REPL (read-eval-print-loop) cioè l’uso interattivo di linguaggi di programmazione. Ai vecchi come me viene da dire “ah, come il BASIC!“; altri (sì, anche me) invece direbbero “ah, il LISP!“. Per calcoli al volo è l’ideale. Quelle visualizzate sono le REPL minime, ne esistono di molto più evolute e potenti, p.es. l’ambiente di Octave. Secondo me REPL è la via.

Radici e espressioni simboliche

elea3

Un paio di idee, senza arrivare agli script, anche perché –adesso vi conto.

Non dico che sono in ritardo su quanto ho promesso; faccio quello che gli americani chiamano appellarsi al quarto emendamento, ecco.

Ci sono tanti linguaggi di programmazione, alcuni rendono più semplice fare determinate operazioni, altri sono più simpatici (non a tutti, ovvio), altri ancora non sono così conosciuti (almeno da me).

Ci sono argomenti che ho già trattato in passato ma che adesso salta fuori che sarebbe opportuno riprendere:

  • trovare le radici di un’espressione;
  • semplificare un’espressione simbolica.

Si può fare? ci provo.

Trovare le radici di un’espressione

0

Considero l’espressione in figura, equazione di terzo grado, il primo che è moderatamente ostico (o comunque non immediato). _c è uno script derivato da bc, ne semplifica in qualche misura l’uso (forse lo racconterò, non oggi che sono in ritardo).

La prima idea, quella giusta secondo me, è di usare Maxima:

(%i1) expr : 8*x^3 + 4*x^2 + 2*x - 258 = 0;
                             3      2
(%o1)                     8 x  + 4 x  + 2 x - 258 = 0
(%i2) solve (expr);
                    sqrt(123) %i + 7      sqrt(123) %i - 7
(%o2)        [x = - ----------------, x = ----------------, x = 3]
                           4                     4

OK, ma delle tre soluzioni due sono complesse; se voglio solo quelle reali opero così:

(%i3) realroots (expr);
(%o3)                               [x = 3]

OK, ma Maxima è vecchio, implementato come si usava una volta, poco conosciuto, …

Qualcosa di più mainstream, tipo JavaScript? Ahemmm… quello no, ma c’è Python.
Per le espressioni simboliche si può usare il package SymPy.

>>> from sympy import Symbol
>>> x = Symbol('x')
>>> expr = 8*x**3 + 4*x**2 + 2*x - 258
>>> from sympy.solvers import solve
>>> solve(expr)
[3, -7/4 - sqrt(123)*I/4, -7/4 + sqrt(123)*I/4]

OK, da tutte le soluzioni, anche quelle complesse; notare che SymPy usa anche simboli aggiuntivi rispetto a Python, qui I equivale a j di Python. Naturalemte se voglio solo le soluzioni reali posso:

>>> from sympy import real_roots
>>> real_roots(expr)
[3]

OK. JavaScript, in realtà Node resta da vedere, e personalmente m’interessa poco, anzi niente.

Semplificazione espressioni simboliche

Alle volte capitano; roba che si fa in prima media, e forse me la sono dimenticata.
Con Maxima ho:

(%i1) expr : (a + b)^2 - b^2 + a*b - c;
                                         2    2
(%o1)                     (- c) + (b + a)  - b  + a b
(%i2) ratsimp (expr);
                                               2
(%o2)                         (- c) + 3 a b + a
(%i3) a : 1; b : 2; c : 3;
(%o3)                                  1
(%o4)                                  2
(%o5)                                  3
(%i6) ev (expr);
(%o6)                                  4

Mentre com Python:

>>> from sympy import Symbol, simplify
>>> a = Symbol('a')
>>> b = Symbol('b')
>>> c = Symbol('c')
>>> expr = (a + b)**2 - b**2 + a*b - c
>>> simplify(expr)
a**2 + 3*a*b - c

Calcolare il valore numerico è un po’ più quixotico, serve lambda:

>>> expr
a**2 + 3*a*b - c
>>> f = lambda a, b, c : a**2 + 3*a*b - c
>>> f(1, 2, 3)
4

ma si può fare di meglio:

>>> a = 1; b = 2; c = 3
>>> expr = a**2 + 3*a*b - c
>>> eval(str(expr))
4

Ci sarebbe ancora un’alternativa (sexy) ma non adesso; prossimamente; forse.

Numeri troppo grandi o troppo piccoli – 2

sl100

Continuo (e concludo) su come tratto numeri troppo grandi o troppo piccoli, proseguendo da qui e con un chiarimento (solo per me) raccontato qui.

Il post precedente si concludeva con l’osservazione che a volte è meglio Excel (o equivalenti open source). E poi un conto è la programmazione ma alla fine nella relazione i numeri devo scriverli in italiano.

Conviene usare Python (o, in alternativa Node), qualcosa come questo:

$ python3 -q
>>> N = 123456789
>>> t = '{:,}'.format(N)
>>> t
'123,456,789'
>>> t = t.replace(',', '.')
>>> t
'123.456.789'

OK, facilmente leggibile; volendo si potrebbe anche rendere come piace a qualcuno, i mate per esempio, usando uno spazio al posto del punto, così:

>>> t.replace('.', ' ')
'123 456 789'

Questo va bene per gli interi ma deve funzionare anche per i numeri reali, quelli con la virgola (cioè  il punto  no la virgola).

>>> N = 123456789.369
>>> t = '{:,}'.format(N)
>>> t
'123,456,789.369'
>>> t = t.replace('.', '_')
>>> t
'123,456,789_369'
>>> t = t.replace(',', '.')
>>> t
'123.456.789_369'
>>> t = t.replace('_', ',')
>>> t
'123.456.789,369'

OK, un ultimo passo: la precisione cioè il numero di cifre dopo la virgola:

>>> round(N, 2)
123456789.37

OK, notare che round() funziona con i numeri; si può usare anche con le stringhe ma queste devono essere  ‘mericane  da programmatori:

>>> N = '123456789.125'
>>> round(float(N), 2)
123456789.12

altrimenti ottengo un errore

>>> N = '123456789,125'
>>> round(float(N), 2)
Traceback (most recent call last):
  File "", line 1, in 
ValueError: could not convert string to float: '123456789,125'

OK, l’arrotondamento va fatto prima dell’italianizzazione del numero. Raccogliere tutto quanto in unn script è immediato (fnum.py):

from sys import argv
if len(argv) == 1:
    prec = 2
else:
    prec = int(argv[1])

st = input('')
# '{:,}'.format()
st = st.replace(',', '.')
isfloat = '.' in st
try:
    if isfloat:
        n = round(float(st), prec)
        if n == int(n):
            n = int(n)
    else:
        n = int(st)

except:
    print('errore!')
    exit()

#italianizzo
t = '{:,}'.format(n)
t = t.replace('.', '_')
t = t.replace(',', '.')
t = t.replace('_', ',')

print(t)
$ echo 123456789.369 | py3 fnum.py 2
123.456.789,37
$ echo 123456789 | py3 fnum.py 2
123.456.789
$ echo 123456789.0002 | py3 fnum.py 2
123.456.789

OK, proprio come Excel (o meglio equivalenti open). E possiamo anche inserirlo automaticamente nella clipboard, come raccontato nel secondo dei post citati:

$ echo 123456789.0002 | py3 fnum.py 2 | _k
123.456.789
$ echo `_p`
123.456.789

Ovviamente potevo scrivere (meglio) anche:

$ echo $(_p)
123.456.789

Ma in fondo posso inglobare nello script anche _k, troppo semplice , lasciato come esercizio. Inoltre in quest’ultimo caso lo script fnum.py non risulta più Windows compatibile (dovesse servire).

Altro esercizio, allenarsi su quanto suggerito da Katie, qui.

Messaggio colorato nel terminale – 1

kk2

Se un messaggio è importante deve attrarre l’attenzione. Argomento già trattato in passato ma questa è la versione 2018.

Ci sono due modi facili: un suono e un colore diverso dal solito.

Produrre un beep è semplice, così:

#!/bin/bash
echo -en "\a"

o così:

#!/usr/bin/python3
print("\a", end="")

Visualizzare il testo colorato è risultato immediato con Python, più impegnativo con Bash. I codici delle sequenze di escape ANSI si trovano (come farei senza la Wiki) qui: ANSI escape code – Colors.

La versione Python:

#!/usr/bin/python3

import sys

RED='\033[01;31m'
GREEN='\033[01;32m'
YELLOW='\033[01;33m'
BLUE='\033[01;34m'
ENDC='\033[00m'
BEEP='\a'

dis = str.join(' ', sys.argv[1:])

dis = dis.replace('%R', RED)
dis = dis.replace('%G', GREEN)
dis = dis.replace('%Y', YELLOW)
dis = dis.replace('%B', BLUE)
dis = dis.replace('%Z', ENDC)
dis = dis.replace('%_', BEEP)

print(dis)

 

I codici sono nella forma %L dove %L è una lettera (o anche più di una, o _ per il beep) che genera una sequenza improbabile: di solito % finisce la parola, terminando un numero.

Più impegnativo produrre l’equivalente Bash: abbiamo, PLG e io, provato con sed ma con pessimi risultati. Poi la dritta, da Stack Overflow ed ecco:

#!/bin/bash

st=$*
st=$(printf '%s' "$st" | sed -e 's/:R/\\033[01;31m/g')
st=$(printf '%s' "$st" | sed -e 's/:G/\\033[01;32m/g')
st=$(printf '%s' "$st" | sed -e 's/:Y/\\033[01;33m/g')
st=$(printf '%s' "$st" | sed -e 's/:B/\\033[01;34m/g')
st=$(printf '%s' "$st" | sed -e 's/:Z/\\033[00m/g')

echo -e $st

sh
Rispetto alla versione Python cambia il codice, uso : al posto di %. Probabilmente da uniformare. Prossimamente, forse, chissà 😐

Convertire codice Fortran in Python – 2

Continuo da qui.

Questione preliminare: perché non usare le liste?
Risposta: le liste sono per elementi eterogenei e sono lente e avide di memoria. Se i dati sono omogenei (p.es. tutti int o tutti float o casi simili) ci sono gli array mono- e pluri-dimensionali (matrici).

Python ha un modulo per la gestione degli arrays, ma c’è di meglio: NumPy.

Ogni trattazione di questo argomento inizia con un avvertimento: attenzione alla gestione degli indici, e continua con l’esempio Fortran “per colonna” invece del più comune “per riga”. OK, da verificare. C’è poi un altro aspetto cui prestare attenzione, anzi due. In Fortran il primo indice è 1; in Pthon 0; lo sanno tutti, è banale ma è facile fare confusione, esempio:

$ py3
>>> import numpy as np
>>> ar = np.arange(10)
>>> ar
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> ar.shape
(10,)
>>> ar.size
10
>>> ar[0]
0
>>> ar[5]
5
>>> ar[ar.size]
Traceback (most recent call last):
  File "", line 1, in 
IndexError: index 10 is out of bounds for axis 0 with size 10
>>> ar[ar.size - 1]
9

OK? Quando si ha una matrice attenzione ala gestione degli indici, dicono tutti. Da verificare in Fortran (ebbene sì! ma è semplice e c’è una piacevole sorpresa).

Gestione indici per per riga, file row_col.f

      program row_col

      integer row, col
      real m(1000,1000)

      do row = 1, 1000
          do col = 1, 1000
              m(row, col) = col + 10 * row
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      do row = 1, 1000
          do col = 1, 1000
              m(row, col) = m(row, col) / 2
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      end

Compilo ed eseguo:

$ gfortran -o row-col row-col.f
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,016s
user	0m0,012s
sys	0m0,004s
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,018s
user	0m0,014s
sys	0m0,004s
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,019s
user	0m0,012s
sys	0m0,008s

Uh! per un milione di dati float, real*4 in Fortran 77, il tempo di esecuzione è troppo piccolo per essere significativo. E questa è la versione lenta, ecco quella corretta, col-row.f

      program col_row

      integer row, col
      real m(1000,1000)

      do row = 1, 1000
          do col = 1, 1000
              m(col, row) = col + 10 * row
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      do row = 1, 1000
          do col = 1, 1000
              m(col, row) = m(col, row) / 2
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      end

Compilo ed eseguo:

$ gfortran -o col-row col-row.f
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,018s
user	0m0,018s
sys	0m0,000s
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,017s
user	0m0,005s
sys	0m0,013s
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,017s
user	0m0,013s
sys	0m0,004s

sì, migliore! di quanto? pochissimo!

Non siamo più ai tempi del PDP-11, nemmeno a quelli del VAX suo successore e la RAM oggi è chip e abbondante. Ecco perché il Web è pieno di foto di gatti, mica come ao tempi di Lenna!

OK, questo per il Fortran, entri Python, con Numpy.

Prima versione col-row.py

#!/usr/bin/python3

import numpy as np

M = np.arange(1000*1000, dtype=float).reshape(1000,1000)
for row in range(1000):
    for col in range(1000):
        M[col, row] = col + 1 + 10 * (row + 1)

print(M[0, 0], M[1, 199], M[999, 0])

for row in range(1000):
    for col in range(1000):
        M[col, row] = M[col, row] / 2

print(M[0, 0], M[1, 199], M[999, 0])

Eseguo:

$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,811s
user	0m0,798s
sys	0m0,012s
$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,807s
user	0m0,781s
sys	0m0,020s
$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,798s
user	0m0,776s
sys	0m0,016s

Seconda versione row-col.py

#!/usr/bin/python3

import numpy as np

M = np.arange(1000*1000, dtype=float).reshape(1000,1000)
for row in range(1000):
    for col in range(1000):
        M[row, col] = col + 1 + 10 * (row + 1)

print(M[0, 0], M[1, 199], M[999, 0])

for row in range(1000):
    for col in range(1000):
        M[row, col] = M[row, col] / 2

print(M[0, 0], M[1, 199], M[999, 0])
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,771s
user	0m0,755s
sys	0m0,016s
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,823s
user	0m0,800s
sys	0m0,024s
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,794s
user	0m0,774s
sys	0m0,020s

sì, Python è lento ma ampiamente accettabile.

OK, ho tutto quello che serve per la conversione; con Python sarà semplice aggiornare e aggiungere nuove funzionalità, i grafici, ma non solo. Per esempio capita (ahemm, sempre) di dover fare la matrice trasposta, quella con righe e colonne scambiate. A me che sono vecchio vedere come fa NumPy vengono le vertigini:

$ py3
>>> import numpy as np
>>> M = np.arange(12).reshape(3,4)
>>> M
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> M.T
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])

Il manuale di NumPy è qui.

OK? vero che Python è (quasi) l’unico linguaggio in città. O almeno quello più sexy 🤩

Convertire codice Fortran in Python – 1

Si può fare ma c’è qualche aspetto da considerare.

Intanto le abitudini di una volta, complice una possibilità del Fortran non presente in Python. Nel Fortran (parlo delle versioni vecchie 77 e precedenti; il 77 è tuttora usato per quel che ne so) ci sono due tipi di sottoprogrammi: le funzioni e le subroutines. Queste ultime, affini alle funzioni C di tipo void erano di gran lunga le preferite. C’è una giustificazione: le variabili sono passate per indirizzo (reference, non so se i due termini sono rigorosamente equivalenti, questi erano quelli correnti). Esempio:

N = 8
CALL DOPPIO (N)
PRINT* N

SUBROUTINE DOPPIO (N)
N = 2 * N
END

Il risultato che ottengo è 16, cioè il valore di N modificato nella subroutine viene conservato nel main, è cambiato il valore memorizzato all’indirizzo della variabile e questo è quello che ho passato al sottoprogramma. Con Python (per quel che ne so) questo non è possibile, ecco qua:

$ py3
>>> def doppio (n):
...   n = 2 * n
...   print('in doppio:', n)
...
>>> n = 8
>>> doppio (n)
in doppio: 16
>>> print (n)
8

alla funzione doppio ho passato il valore 8 e al di fuori di questa il valore di n resta 8 indipendentemente da quanto avvenuto nel sottoprogramma. Ovvia la soluzione, la stesa da sempre, in Fortran:

N = 8
N = IDOPPIO (N)
PRINT* N

FUNTION IDOPPIO (N)
N = 2 * N
RETURN (N)
END

Notare che ho cambiato il nome alla funzione, variabili e funzioni sono di default REAL*4 tranne quelle il cui nome inizia con le lettere dell’intervallo [I..N] che sono INTEGER*2 o INTEGER*4 a seconda della versione e delle opzioni di compilazione. Caratteristico del Fortran è che la precisione viene espressa in byte e non in bit.

In Python  la funzione può ritornare una lista, torna comoda per le subroutines:

$ py3
>>> def pot(a, b, p):
...   a = a ** p
...   b = b ** p
...   return a, b
...
>>> r = pot(3, 4, 2)
>>> print(r)
(9, 16)
>>> # ovviamente
... a, b = pot(3, 4, 2)
>>> print(a)
9
>>> print(b)
16

Quindi una prima regola pratica: scrivere funzioni che ritornano i valori delle variabili che ci servono. Ma ci sono dei casi in cui questa pratica non basta, il caso di arrays e matrici di dimensioni rilevanti.

In Fortran c’è l’istruzione COMMON:

COMMON /DATI/ A, B(10), C, D
COMMON /MAT/ P(20, 20), R(10, 10, 10)

Ogni funzione o subroutine che usi A, B, C o D dovrà dichiarare il COMMON /DATI/, idem per il caso di /MAT/.

In Python si ottiene un risultato simile (meno granulare, meno raffinato ma forse meno soggetto a bug) con le variabili globali:

$ py3
>>> #a = 0
>>> b = []
>>>
>>> def popola_dati():
...   global a
...   a = 42
...   for c in range(10):
...     b.append(c ** 2)
...
>>>
>>> popola_dati()
>>> print('a =', a)
a = 42
>>> print('b =', b)
b = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

OK, notare che a devo dichiararla global nella funzione a differenza di b che dev’essere inizializzato nel main e diventa automaticamente globale. b è una lista, non esattamente l’equivalente degli arrays e matrici del Fortran. Le liste sono versatili ma onerose, da modificare, in un post successivo, pausa 😎

Il caso di case

Ogni tanto capita di dover aggiornare un programma vecchio, adeguarlo, ampliarlo. Capita anche che deve funzionare anche con Linux e allora il dilemma se modificarlo o rifarlo ex-novo non si pone.

Usiamo cosa? Python, ovvio (ahemmm… quasi ma questa volta sì). E salta fuori la solita storia che adesso provo ancora una volta a dire che secondo me non ha ragione d’essere.

Il programma vecchio, in Visual Basic, quindi solo Windows contiene (semplifico, riduco all’essenziale) questo codice:

Select Case N
  case 0 To 1
    Print "piccolo"
  Case 2
    Print "giusto"
  Case 3, 4
    Print "grosso"
  Case Else
    Print "valori tra 0 e 4"
End Select

Select/Case è la versione Basic di switch/case del C e linguaggi da esso derivati. Per esempio JavaScript:

n = parseInt(process.argv[2]);
switch(n) {
  case 0 : case 1:
    console.log("troppo piccolo");
    break;
  case 2:
    console.log("giusto!");
    break;
  case 3: case 4:
    console.log("troppo grande");
    break;
  default:
    console.log("valori validi da 0 a 4");
}
* case $ node case.js 0
troppo piccolo
* case $ node case.js 1
troppo piccolo
* case $ node case.js 2
giusto!
* case $ node case.js 4
troppo grande
* case $ node case.js 100
valori validi da 0 a 4
* case $ node case.js -100
valori validi da 0 a 4

Con Bash è anche più sinttetico, ma non gira su Windows (cioè non su tutti i ‘puters, non su quello del Boss).

N=$1
echo "caso" $N
case $N in
  0 | 1) echo "piccolo" ;;
  2) echo "giusto" ;;
  3 | 4) echo "grande" ;;
  *) echo "NO! valori [0 .. 4]" ;;
esac

Python non ha un’istruzione simile, perché non serve:

from sys import argv
n = int(argv[1])

if n in [0, 1]   : print("troppo piccolo")
elif n == 2      : print("giusto!")
elif n in [3, 4] : print("troppo grande")
else             : print("valori validi da 0 a 4")

if/elif/else. Secondo me è anche molto leggibile (OK, dipende dai gusti). Una soluzione simile è immediata con JavaScript:

n = parseInt(process.argv[2]);

if ([0, 1].indexOf(n) >= 0)      { console.log("troppo piccolo"); }
else if (n == 2)                 { console.log("giusto!"); }
else if ([3, 4].indexOf(n) >= 0) { console.log("troppo grande"); }
else                             { console.log("valori validi da 0 a 4"); }

Vero che è più bello della versione case?

Le graffe …, direbbe un C-ista o un programmatore Java e/o JS. Ma anche un pythonista potrebbe andare a capo dopo il :. E il codice vero sarebbe più complesso, su più righe. Certo; questo è solo un esempio per dire che **case non serve quasi mai.

Ma sai che il Lisp… OK, ma il Lisp è diverso.

Un po’ di quirks con Python

Recentemente un tweet intercettato via un retweet di qualche tweep mi ha coinvolto in discussioni senza troppo senso ma –credo– da riportare.

KDnuggets visualizza una serie di tricks dichiarando che Most of the tricks and tips are taken from PyTricks and a few blogs.

Ecco ‏PyTricks di Max, brennerm. L’unico Max Brenner trovato su Twitter è una catena di bar, non credo sia chi sto cercando.

Ma mai disperare, una rapida googlata ed eccolo, qui. C’è –c’era– anche su Twitter ma sembra sia stata solo una prova, 42 tweets, l’ultimo vecchio di un anno.

OK, torno ai suggerimenti Python. L’inizio è stato l’inversione della stringa:

>>> st = '0123456789'
>>> rst = st[::-1]
>>> st
'0123456789'
>>> rst
'9876543210'

Con due giovani promesse della CS (ragazzi dovreste bloggare, imho) abbiamo cercato di mettere i valori appropriati nei camp i(start e stop) lasciati ai valori di default senza riuscirci. Perché pare che davvero non si può, vedi qui.

In Python c’è il metodo reverse() per le sequenze mutabili (ma le stringhe sono immutabili).

>>> ls = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sl = ls.reverse()
>>> sl
>>> ls
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

uh! non quello che mi aspettavo (RTFM!): The reverse() method modifies the sequence in place for economy of space when reversing a large sequence. To remind users that it operates by side effect, it does not return the reversed sequence.

Altro trick intrigante è la trasposizione di una matrice. Max riporta:

>>> original = [['a', 'b'], ['c', 'd'], ['e', 'f']]
>>> transposed = zip(*original)
>>> print(list(transposed))
[('a', 'c', 'e'), ('b', 'd', 'f')]
>>> transposed
<zip object at 0x7f150fbd4448>

che, ovviamente, funziona. Il guaio è che che il codice non dice tutto quel che vorrei.

>>> orig = [['a', 'b'], ['c', 'd'], ['e', 'f']]
>>> trans = [*zip(*orig)]
>>> trans
[('a', 'c', 'e'), ('b', 'd', 'f')]

Ecco mancava un *: An asterisk * denotes iterable unpacking. Its operand must be an iterable. The iterable is expanded into a sequence of items, which are included in the new tuple, list, or set, at the site of the unpacking. [qui]. Conviene anche ripassare qui.

Mi sa che è da vedere anche zip(). Qui, qui, qui e qui.
Ovviamente dopo la documentazione ufficiale (già detto RTFM?), qui.

Ci sono altri suggerimenti, interessanti quelli sui dizionari; ma anche altri. Tutti.

Un quiz & considerazioni personali relative

Cosa non si trova ontehtoobz! (auto-cit.) 😯 Ieri questo: I was just shown this. Guess what this does in Python? powers = [lambda x: x**i for i in range(10)] powers[3](2).

Sukant è conosciuto, in diversi socials, rockz. E, confesso, ho provato a risolvere il suo quiz, nel senso di correggere la funzione, senza riuscirci. Poi ho letto i commenti, già David A Roberts 💥 doveva mettermi sulla retta via:

* quiz $ py3
>>> def f(i): return lambda x: x**i
...
>>> powers = [f(i) for i in range(10)]
>>> powers[3](2)
8

ma ci sono altri come me che non sono così pronti: How is it Alex Jones got banned, and yet Sukant still has a twitter account after posting this?? dice Warner e Leftmost Grief Node: Haha the Pythonistas responding to this in my org are like, “Well that code is not Pythonic so…”.

das portal ci spiega il perché, Dan P ci dice che Scala…

Ma la risposta migliore è quella di jeeger che si potrebbe tradurre con *RTFM!*, in questo caso The Hitchhiker’s Guide to Python, in particolare –chiaro come il sole– Late Binding Closures.

Non copio è là.
Da leggere attentamente, anzi studiare che poi interrogo, nèh! 🧐

Ci ho pensato su. Anzi ci ho dormito su. E ho maturato una mia opinione che potrò cambiare se e solo se fatti o circostanze me lo consiglieranno.

In questo caso –e tanti altri casi simili– il problema non esiste:

>>> pow(2, 3)
8
>>> pow(2, 0.5)
1.4142135623730951
>>> pow(-2, 0.5)
(8.659560562354934e-17+1.4142135623730951j)

Non ce l’ho con lambda e le funzioni e quant’altro ma con il loro (ab)uso. E usare solo quello che si conosce davvero. Detto in altri termini non ho ancora digerito il fatto che non ho trovato il bug 👿

Comunque…

Siate semplici (cit.). Anzi, qui. È talmente autoevidente che non si sa di chi è; è Patrimonio dell’Umanità (se non lo è dovrebbe esserlo).