Archivi Categorie: Python

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).

Formattare le stringhe in Python

Python lo uso da sempre (cioè no, ho solo dimenticato quando l’ho scoperto (o meglio il numero della versione che mi sembra di ricordar è troppo basso per essere credibile) e capita che ogni tanto scopro di non essere aggiornato, sapete l’evilussione 🧐 Per fortuna c’è Twitter che mi suggerisce, via prof Corno ecco: Python 3’s f-Strings: An Improved String Formatting Syntax (Guide).

La documentazione ufficiale corrente di Python (3.6 o mi sto perdendo qualcosa anche qui? sì. c’è la 3.7 nuova di pakka, non ancora installata) è aggiornata e dice tutto, anche se in modo un po’ troppo sintetico per quelli come me.

A formatted string literal or f-string is a string literal that is prefixed with ‘f‘ or ‘F‘.

Conviene quindi seguire la guida di RealPython, anzi RealPython è da followare su Twitter, fatto.

Anzi, la guida è talmente OK che non continuo a scrivere, c’è tutto là. A me resterebbe di aggiornare gazillioni di script che usano non immaginate cosa (l’evilussione) ma poi mi viene da pensare che (cit.)

anche perché capita troppo spesso che parto con l’idea di aggiornare e mi scopro a riscrivere ⭕

Perché il ciclo for di Python è speciale

Il titolo avrebbe dovuto essere “perché range(start, end) non comprende end?” ma si sarebbe perso il riferimento a for.

Forse lo sanno tutti, forse qualcuno l’ha scoperto da poco, altri ancora forse no. E perché?  Sto dicendo del ciclo for in Python.

Nella riscrittura di in Python di un programmino Basic c’è stata un po’ di confusione sugli indici di arrays e contatori di cicli.

Utilizzo MY-BASIC di Wang Renxin per visualizzare il ciclo for come lo intende il Basic, in tutte le sue varianti e incarnazioni, in particolare nel mio caso si tratta di VB, Visual Basic.

run e bye sono estensioni di MY-BASIC, il comando mb invece è un alias mio ma l’esempio –minimo– illustra come il Basic gestisce il ciclo, in particolare il ciclo comprende il limite finale, 5 in questo caso. Lo stesso comportamento lo troviamo nel linguaggio da cui il Basic deriva, il Fortran (dove il ciclo si chiama do). L’istruzione può prevedere un passo per l’indice, così:

OK? Ma questa è un’eccezione, dal C in poi il ciclo for ha una sintassi diversa, per esempio con nodejs (un sapore di JavaScript) posso scrivere:

Ovviamente si può scrivere <= al posto di < comprendendo così il limite superiore. Con C e C++ la sintassi è la stessa, migliora solo l’istruzione di scrittura, printf() e/o cout <<.

Il for per Python è diverso, funziona con le liste:

Potrei usare una lista di numeri:

Per creare una lista di numeri, come quelle che si trovano di solito nei cicli for esiste la funzione range().

The range type represents an immutable sequence of numbers and is commonly used for looping a specific number of times in for loops.

Come si vede il limite finale non è mai compreso. Per cui il ciclo iniziale (quello dei numeri da 1 a 5) sarà:

Visto 6 e non 5.

C’è un potivo per tutto ciò, il BDFL (emerito da due giorni) Guido la sa lunga:

in questo modo la lunghezza (len()) della lista è uguale al limite superiore del range(). Altre cosa da ricordare (già usata implicitamente ma è bene ricordarla per quelli come me che vengono dal Fortran) è che gli indici partono da 0, zero.

Questo è spiegato più in dettaglio qui.

E per chi vuole saperne di più passo la parola a EDW, qui: Why numbering should start at zero.

Sostituire i TABs e i caratteri non-ASCII

A volte il testo preso dal Web o da altri posti ancora non è come lo vorremmo. Capita che contenga caratteri strani, per esempio TABs, caratteri Unicode (a volte sono OK, a volte no, per esempio nel codice apici e virgolette devono essere quelli classici anche se meno belli), λ (OK ma non nella REPL di Racket (sì, questo solo per me)). Ecco il terminale è uno di quelli che certe cose proprio non le accetta.

Vero che in (quasi) qualsiasi editor si può fare ma se del caso…

Comincio con TAB, serve come base poi generalizzabile (file stab):

#!/bin/bash

if [ -z $1 ]; then
  echo 'uso:' $0 '[sost-char] file'
  exit 2
elif [ -z $2 ]; then
  SOST=' '
  PROC=$1
else
  SOST=$1
  PROC=$2
fi

sed "s/\t/$SOST/g" $PROC

Lanciato senza opzioni esce con un messaggio:

È richiesto il nome del file preceduto opzionalmente con il carattere (o i caratteri) che sostituiranno il TAB.

OK? C’è (ancora, pare che adesso Windows 10 sia sulla buona strada) chi preferisce Python. Si può fare; anzi lo script risulta simile (stab.py):

#!/usr/bin/python3

import sys, re

if len(sys.argv) == 1:
  print ('uso:', sys.argv[0], '[sost-char] file')
  exit(2)
elif len(sys.argv) == 2:
  sost = ' '
  proc = sys.argv[1]
else:
  sost = sys.argv[1]
  proc = sys.argv[2]

with open(proc, 'r') as f:
  txt = f.readlines()

for st in txt:
  print(re.sub('\t', sost, st), end='')

È possibile evitare l’uso delle espressioni regolari (modulo re che ho preferito per uniformità con lo script seguente): basta sostiture l’ultimo ciclo for con

for st in txt:
  print(st.replace('\t', sost), end='')

Generalizzando arrivo ad andare alla ricerca di tutti i caratteri fuori dalla sequenza ASCII. Ho copiato in un file un paio di tweets (dimenticando di salvare il nome degli autori) contenenti citazioni quotate. Nel primo si usano le virgolette che si trovano nella stampa tedesca, nel secondo no, roba corrente ontehtoobz. Ho aggiunto un paio di emoji, ormai hanno invaso il Web. Poi passo a evidenziali:

Questo è lo script fuori.py:

#!/usr/bin/python3

import sys, re

if len(sys.argv) == 1:
  print ('uso:', sys.argv[0], '[sost-char] file')
  exit(2)
elif len(sys.argv) == 2:
  sost = '_'
  proc = sys.argv[1]
else:
  sost = sys.argv[1]
  proc = sys.argv[2]

with open(proc, 'r') as f:
  txt = f.readlines()

for st in txt:
  print(re.sub(r'[^\x00-\x7f]', sost, st), end='')

Procedo come per il TAB ma con sed ci sono problemi, dovuti alla tabella caratteri caricata se abbiamo capito bene. Ancora da indagare (e risolvere), materia per un prossimo post.

OK, 😁⭕

Globale o locale?

Jake 💥 rockz e oggi ci racconta di un caso che neanche Dirk Gentlyquesto.

Lo riscrivo con una piccola modifica (l’istruzione print per vedere il risultato), file e0.py:

def g(x):
  def f():
    x += 1
  f()

print(g(0))

Err! 😡, e come dice Jake: That was a fun one to explain 😀. Dico subito che da solo non ci sono arrivato; poi mi sono detto che dovevo pensarci ma…

Provo a fare come avrei dovuto. Invece di operare sulla variabile (globale) x nella f() ritorno il valore di x modificato, così (file e1.py):

def g(x):
  def f():
    return x + 1
  f()

print(g(0))

OOPS! per Python è OK ma non per me, correggo (e2.py):

def g(x):
  def f():
    return x + 1
  return f()

print(g(0))

Chiaro? Torno al tweet, nel thread c’è la dritta, il link a –indovina– la documentazione ufficiale di Python, qui: Why am I getting an UnboundLocalError when the variable has a value?

La spiegazione è diversa (più esaustiva) della mia, in sintesi:

This is because when you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope. Since the last statement in foo assigns a new value to x, the compiler recognizes it as a local variable. Consequently when the earlier print(x) attempts to print the uninitialized local variable and an error results.

Logico. E con certi linguaggi –nuovi, di moda– le variabili, quando ci sono, sono quelle, restano tali, niente modifiche. Per esempio Haskell –lasciato come esercizio. È da tanto che volevo dire questa frase 😊.

Invece in un’altra famiglia di linguaggi –sexy, i miei preferiti– non serve scrivere “return“, la funzione è un valore (file e3.rkt):

(define (g x)
  (define (f)
    (+ 1 x))
    (f))

(displayln (g 0))

Ma nessun lisper userebbe la funzione f(), viene chiamata una volta sola, per questo c’è lambda (e4.rkt):

(define (g x)
  ((lambda (t) (+ 1 t)) x))

(displayln (g 0))

Non mi piace, inutilmente contorta, basta (e5.rkt):

(define (g x)
  (+ 1 x))

(displayln (g 0))

Dove ho semplificato (troppo? f e g in un programma reale conterranno altre istruzioni) il codice. Ma era ridondante, lambda si usa quando serve.

Poi ci ho ripensato ancora (tutta la notte) e sono giunto a una conclusione che forse vale solo per me ma penso che il codice del tweet sia sbagliato come progettazione. Se x dev’essere globale si deve dichiarare, così (e6.py):

def g(x):
  def f():
    global x
    x += 1
  f()

# main
x = 0
g(0)
print(x)

Ovviamente il parametro in g() è inutile e dannoso come si scoprirà nel debug della prima revisione (e7.py):

def g(x):
  def f():
    global x
    x += 1
  f()

# main
x = 0
g(42) # <- ====
print(x)

L’istruzione global in Python

Aaron Meurer twitta un quiz, questo. Sembra facile, anzi troppo e essendo un quiz c’è chi –correttamente– risponde senza eseguire il codice. La maggioranza sbaglia (55%).

Adesso non vi dico come ho votato ma devo confessare che avevo già affrontato un caso simile (c’era di mezzo il Basic, non sono riuscito a ritrovarlo, peccato). Ma mi va di ravanare l’argomento. Intanto la versione originale:

# aam.py

a = 1

def maybe_inc_a(inc):
  if inc:
    global a
  a += 1

maybe_inc_a(False)
print(a

OK, cioè no, inc è False quindi quindi il blocco if non viene eseguito, provo a commentarlo ed ecco:

# aam-c.py

a = 1

def maybe_inc_a(inc):
  # if inc:
  #   global a
  a += 1

maybe_inc_a(False)
print(a)

Errore, la soluzione la da Aaron (dopo un paio di giorni):

The global statement applies to the entire function regardless where it is.

E da anche un suggerimento:

The best practice is to put global declarations at the top of the function or right before the variable is first used.

Io che vengo dal Fortran sono per il top, chi è avvezzo a C/C++ preferisce molto probabilmente l’altra posizione (for int i...).

Tratta poi altri casi ma mi basta questo. Intanto, ripensandoci c’è un comportamento un po’ particolare, cosa succede con questo codice?

# aam-g.py

st = 'ciao'
a = 1

def maybe_inc_a(inc):
  print(st)
  print(a)

maybe_inc_a(False)

Ovviamente non è possibile modificare le variabili.

In questi casi diventa importante la documentazione. Cerco per global ed ecco:

The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals. It would be impossible to assign to a global variable without global, although free variables may refer to globals without being declared global.

Names listed in a global statement must not be used in the same code block textually preceding that global statement.

Names listed in a global statement must not be defined as formal parameters or in a for loop control target, class definition, function definition, import statement, or variable annotation.

E subito sotto segue nonlocal, mai usato ma mai dire mai (uh! l’ho detto 3 volte in questa frase 😊).

In conclusione non ho detto niente più di Aaron ma metti che i miei amici pythonisti –colpevolmente– non lo seguano su Twitter…

Python, le liste e i cicli for (e non riesco mai a ricordarmi)

Yep! proprio così. Ci sono delle cose che anche se ti sono già capitate (a me più volte) e finanche se te l’hanno già detto (a me più volte) non riesci a ricordartele. O capita solo a me? Oggi voglio scrivere di una di queste così me ne ricorderò. O se me lo faranno notare quando mi capiterà potrò dire “lo so; l’ho anche scritto, qui!”.

Non so voi ma alle volte la bontà di un’elaborazione (un run) viene valutata da un valore finale o la forma di una linea (o un punto) su un grafico. O sono solo io? A volte il valore finale non cambia al cambiare dei dati di input o il grafico è subito esatto o sempre sbagliato… Ecco parlo di uno di quei casi.

È una cosa semplice, la sanno tutti (anch’io) e nell’esempio qui sotto si vede subito dov’è il bug ma in un programma vero, scritto come al solito non è immediatamente evidente e bisogna andarlo a stanare. E (o capita solo a me?) i propri errori sono quelli che sono i più nascosti. Ma basta con l’intro giustificativa inizio.

Uno script se non limitato a poche righe sarà organizzato in funzioni, che possono poi essere contenute in classi. Mi occuperò solo delle funzioni, ecco quad() una funzione elementare per fare il quadrato di un numero:

def quad(n):
  return n * n

Funziona proprio come ci si aspetta (uso la REPL):

$ python3 -q
>>> def quad(n):
...   return n * n
...
>>> i = 5
>>> q = quad(i)
>>> q
25
>>> i
5

quad(), essendo Python un tipper ducko fa più di quel che si pensa:

>>> type(i)
<class 'int'>
>>> type(q)
<class 'int'>
>>> f = 4.2
>>> r = quad(f)
>>> r
17.64
>>> type(f)
<class 'float'>
>>> type(r)
<class 'float'>

In ogni caso i parametri (i e f) vengono passati per valore. Ecco una versione ridondante, con print di debug; tipo di istruzioni che possono capitare se la funzione è complessa e si è pasticcioni:

def quadp(n):
  n *= n
  print("dentro", n)
  return n

Vediamola in azione:

>>> n = 8
>>> s = quadp(n)
dentro 64
>>> s
64
>>> n
8

OK! una cosa simile capita con le stringhe:

>>> def up(st):
...   return st.upper()
...
>>> st = 'ciao!'
>>> us = up(st)
>>> st
'ciao!'
>>> us
'CIAO!'

ho barato: ho applicato a st il metodo upper() che non la modifica ma ne crea una copia. Se modifico st nella funzione ottengo:

>>> def upm(st):
...   st = st.upper()
...   print("dentro", st)
...   return st
...
>>> st
'ciao!'
>>> us = upm(st)
dentro CIAO!
>>> us
'CIAO'
>>> st
'ciao!'

Tutto come previsto.

E le liste? In Python le liste vengono usate (anche?) al posto degli arrays; e funzionano bene.

Ecco lsquad(), restituisce la lista dei quadrati degli elementi della lista passata.

>>> def lsquad(ls):
...   ls = [ls[n] * ls[n] for n in range (len(ls))]
...   return ls
...
>>> lorig = [1, 2, 3, 4]
>>> lq = lsquad(lorig)
>>> lq
[1, 4, 9, 16]
>>> lorig
[1, 2, 3, 4]

OK, ma io che sono vecchio, più vecchio della list comprehension a  volte  spesso scrivo così:

>>> def lsfq(ls):
...   for n in range(len(ls)):
...     ls[n] = ls[n] * ls[n]
...   return ls
...
>>> lorig
[1, 2, 3, 4]
>>> lq = lsfq(lorig)
>>> lq
[1, 4, 9, 16]
>>> lorig
[1, 4, 9, 16]

OOPS! con il ciclo for la stringa passata è stata aggiornata!

E chiamate successive producono questo effetto:

>>> lq = lsfq(lorig)
>>> lorig
[1, 16, 81, 256]
>>> lq = lsfq(lorig)
>>> lorig
[1, 256, 6561, 65536]

Mi suggeriscono (tra le altre cose) questa variante, con costruzione di una nuova lista (rl):

def lstq(ls):
  rl = []
  for n in ls:
    rl.append(n * n)
  return rl

È OK ma sono sempre per la list comprehension. Forse solo questione di gusti.
CMQ, panico? No, c’è tutto scritto nella documentazione. E adesso anche qui.

E poi, se vi ricordate anche il C… (quella cosa del puntatore all’elemento iniziale dell’array, o ricordo male?) 😉

Oh! post è già stato aggiornato (due volte) in bozza prima ancora di essere pubblicato nel blog. E chissà se adesso è OK.

👽

Un paio di cose OK di Python

Attualmente sono alle prese con un programmino scritto in Python sviluppato con un giovane nerd in remoto, 15 km. Funziona. E stanno capitando altre due cose che forse dovrei aver dato per scontato.

L’analisi e lo schema iniziale sono stati molto ampliati strada facendo; poi sono stati rivisti e aggiustati più volte. Ma mai stravolti e questo lo considero abbastanza normale.

Non so voi ma io vivo circondato da windowsiani duri e puri. Sempre in modo “chissene 🦎”. Tra le specifiche c’era che il programma era solo per Linux 😁 Poi… adesso vi conto.

In realtà lo sapevo, credo di averlo detto anche in questo blog (ma non mi va di cercarlo). Sì, è saltato fuori che “non si sa mai; e se poi…” insomma Windows 😐

Non l’ho fatto io ma lo riporto, è una caratteristica positiva di Python:

>>> import platform
>>> platform.platform()
'Linux-4.13.0-36-generic-x86_64-with-Ubuntu-17.10-artful'

OK, il più è fatto, ecco:

>>> pl=platform.platform()
>>> pl.startswith('Linux')
True

Le istruzioni legate all’OS sono pochissime. Ancora meno si hai cura di evitare pathnames letterali (ma non sempre è possibile). Ci sono poi alcune funzioni specifiche di un OS particolare; da evitare e ampiamente evitabili.

Post troppo corto? Allora aggiungo un’altra roba, questa scoperta solo ora (a me nessuno dice mai niente). È un problema dei dizionari, i dicts.
Il dict funziona perfettamente tranne che in un caso, improbabile ma c’è Murphy: i dati non sono sortati e se vuoi stamparlo su carta e lo vuoi ordinato come fai? Semplice:

>>> from operator import itemgetter
>>> anim = {'gatto' : 'cat', 'cane' : 'dog', 'maiale' : 'pig', 'mucca' : 'cow'}
>>> type(anim)
<class 'dict'>
>>> s_anim = sorted(anim.items(), key=itemgetter(0))
>>> type(s_anim)
<class 'list'>
>>> k_anim = dict(s_anim)
>>> type(k_anim)
<class 'dict'>
>>> k_anim
{'cane': 'dog', 'gatto': 'cat', 'maiale': 'pig', 'mucca': 'cow'}
>>> l_anim = sorted(anim.items(), key=itemgetter(1))
>>> l_anim
[('gatto', 'cat'), ('mucca', 'cow'), ('cane', 'dog'), ('maiale', 'pig')]

Semplice vero? si trasforma il dict in lista di tuple e questa è ordinabile con sorted(). Via itmgetter() posso selezionare il sort sia per key che value. Infine con dict ricavo il dizionario dalla lista.

Sì lo so che questo lo sanno tutti. Tranne me. A me nessuno dice mai niente, nada, zilch 😡 (auto-cit.).

👽