Passa ai contenuti principali

Migrazione da Python 2 a 3

Il tool 2to3 fornito nella distribuzione di Python 3 si concentra sulla trasformazione dei vostri sorgenti da Python 2 a Python 3 rendendoli spesso inutilizzabili in Python 2. Però una versione del linguaggio non esclude l'altra: utilizzando Python 2.6 o 2.7 ci si può preparare alla migrazione verso Python 3 scrivendo per quanto possibile del codice compatibile con entrambe le versioni. Per far questo si devono attivare i backport presenti in 2.6 e 2.7 e utilizzare le nuove sintassi compatibili anche con le versioni 2.


L'elenco che segue non è esaustivo: è il frutto dell'esperienza che deriva dall'aver modificato un po' di moduli per renderli compatibili con entrambe le versioni del linguaggio.

Modifiche generiche

I “long” spariranno

In Python 3 non ci sarà più distinzione tra int e long e quest’ultimo verrà eliminato, così come la sintassi “0L”. Quindi, per evitare problemi di sintassi, al posto di “0L” si può utilizzare “long(0)” in modo tale che in Python 3 si può ridefinire long=int e le cose continuano a funzionare. All’inizio del file occorre inserire il test sulla versione di Python per effettuare la sostituzione:

import sys
python_version_major = sys.version_info [ 0 ]

if python_version_major > 2:
long = int


Funzione xrange sostituita da range

Anche in questo caso, come nel precedente, basta un test sulla versione di Python per rinominare xrange in range quando serve.

Il tipo “unicode” sparirà

In Python 3 tutte le stringhe saranno unicode, quindi non esisterà più il tipo “unicode” ma solo il tipo “str”. Come nei casi precedenti un test sulla versione di Python sarà sufficiente per porre unicode = str quando serve.

Funzioni che restituiranno un iteratore al posto di una lista

Tali funzioni sono: zip, range, map.
Queste funzioni a partire da Python 3 restituiranno un iteratore, mentre in Python 2 restituiscono una lista. Per compatibilità occorre chiudere la funzione incriminata nella funzione “list” che obbliga il risultato a essere una lista anche in Python 3.


Metodo next sostituito da __next__

Nella definizione di un iteratore il metodo “next” è stato sostituito da “__next__”. Di conseguenza, per compatibilità, basta definirli uguali:

def next ( self ):
 ...
__next__ = next

Questo implica anche che per ottenere l’elemento successivo di un iteratore non si può più chiamare il metodo “next” dell’iteratore, ma è necessario utilizzare la funzione “next” introdotta da Python 2.6:

next(foo) al posto di foo.next()

Metodo __nonzero__ sostituito da __bool__

Per implementare il test di verità su un oggetto in Python 2 si usa il metodo __nonzero__ mentre in Python 3 questo viene sostituito da __bool__. Di conseguenza, per compatibilità, basta definirli uguali:

def __nonzero__ ( self ):
 …
__bool__ = __nonzero__

Le eccezioni non possono essere stringhe

In altre parole non è più valida la sintassi:

raise SerializationError, “decoding error”

ma deve essere:

raise SerializationError ( “decoding error” )

Modifiche ai moduli

Modulo “exceptions” rimosso

Le eccezioni non appartengono più al modulo “exceptions” ma sono state spostate direttamente in __builtins__, quindi il modulo “exceptions” è stato rimosso. In Python 2 questo modulo veniva caricato automaticamente all’avvio dell’interprete, quindi raramente un programma aveva bisogno di importarlo esplicitamente.

Moduli “md5”, “sha”, ecc. raggruppati in “hashlib”

I moduli relativi alla generazione di hash sono stati raggruppati sotto hashlib, quindi le import devono essere sostituite. Per esempio la seguente riga:

import md5

deve diventare:

from hashlib import md5

Attenzione: il modulo “sha” è stato suddiviso in varie versioni: “sha1”, “sha224” ecc., quindi la import nuova deve preoccuparsi di cambiarne il nome se si vuole mantenere la compatibilità con il vecchio codice:

from hashlib import sha1 as sha

Modifiche presenti da Python 2.5

Import assoluti

Gli import in Python 3 sono assoluti, ovvero per importare un file dallo stesso package occorre utilizzare il punto “.” prima del nome del file per renderlo relativo.
Per abilitare la funzione da Python 2.5 in su:

from __future__ import absolute_import

dopodiché gli import assumono il seguente aspetto:

from .foo import Foo
from . import foo

ecc., mentre la sintassi tradizionale:

import foo

cercherà sempre a partire dalla root dei pacchetti.

Questo però comporta un problema: gli import relativi si basano sul contenuto dell’attributo __name__ di un modulo. Se si esegue il modulo come __main__, per esempio per fare dei test, l’interprete non sa da dove partire per cercare i moduli importati con path relativo e solleva un’eccezione. Questo implica che non si possono più eseguire i moduli come programmi, ma occorre sempre installarli e importarli, dopodiché i path relativi funzioneranno. In alternativa si potrebbero usare solo path assoluti, ma questo a sua volta implica ancora che il modulo deve essere installato prima di essere testato, oltre al fatto che se lo si sposta in un’altra directory occorre anche modificare il sorgente. In altre parole: il primo modulo deve essere importato con path assoluto, dopodiché tutti gli import relativi funzionano.
Tutto ciò è piuttosto fastidioso perché impedisce di testare i moduli senza installarli e non si può più usare il trucco del __name__ == ‘__main__’.

Una soluzione piuttosto laboriosa è aggiungere il seguente codice all’inizio di un modulo che si vuole anche rendere eseguibile:

if __name__ == '__main__':
 import os, sys
 # path completo di questo file
 __this_path = os.path.abspath ( sys.argv [ 0 ] )
 # nome del modulo (nome di questo file senza estensione)
 __this_name = os.path.splitext ( os.path.basename ( __this_path ) ) [ 0 ]
 # path completo della directory che contiene questo file
 __parent_path = os.path.dirname ( __this_path )
 # nome della directory che contiene questo file
 __parent_name = os.path.basename ( __parent_path )
 # aggiungo al path la directory sopra due livelli (ovvero non quella che
 # contiene lo script, ma quella immediatamente sopra)
 sys.path.insert ( 0, os.path.dirname ( __parent_path ) )
 # importo la directory che contiene lo script come un modulo
 __import__ ( __parent_name )
 # cambio il nome di questo script
 __name__ = '%s.%s' % ( __parent_name, __this_name )
 # da questo punto gli import relativi funzionano


Queste righe trasformano la directory in cui è contenuto il modulo in un package aggiungendolo a sys.path e modificano il nome interno del modulo (quello visto dall’interprete) in ‘<package>.<modulo>’ in modo tale che gli import relativi seguenti funzionino. Lavorando con i path non ci si lega al nome della directory che può essere cambiato.

NB: se si usa sys.path.insert si rischia che nel package che si sta testando ci siano delle directory il cui nome va in conflitto con quelle di sistema e che quindi avrebbero precedenza su queste ultime; se si usa sys.path.append si rischia che venga letta una versione precedentemente installata del package invece di quella di test. Non credo si possano salvare capra e cavoli.

Modifiche presenti da Python 2.6

Print function


from __future__ import print_function
def print(*args, sep=' ', end='\n', file=None)

Nuova sintassi per catturare il valore di un'eccezione

In Python 3 la keyword “as” sarà obbligatoria:

try:
   …
except TypeError as exc:
   …

Formato unicode per tutte le stringhe costanti


from __future__ import unicode_literals

Questo probabilmente non significa che le stringhe lette, per esempio, dalla riga di comando siano unicode.

Distinzione tra unicode e 8 bit

Tutto ciò che è 8 bit dovrebbe essere costruito con "bytes" oppure b'': in Python2 "bytes" è sinonimo di "str", quindi il risultato non coincide con quello di Python3, però "bytes" deve essere utilizzato nei test tipo ``isinstance(x, bytes)`` per far capire a 2to3 se si parla di unicode o di 8 bit.

"bytes" in Python2 fornisce una stringa immutabile. Per avere una lista di bytes modificabile si usa "bytearray".

Notazione per numeri ottali e binari

La nuova notazione per i numeri ottali è "0o" invece del solo "0" iniziale. Nel contempo è stata introdotta la notazione "0b" per i numeri binari, oltre al costruttore "bin" che converte un numero in binario.

Divisione intera

La divisione "classica" è una divisione intera. Per passare alla "true division" occorre:

from __future__ import division

True division for ints and longs will convert the arguments to float and then apply a float division.  That is, even 2/1 will return a float (2.0), not an int.  For floats and complex, it will be the same as classic division.

La divisione intera diventerà "//".

"callable" rimosso

"callable" non è più supportato; al suo posto occorre usare:

isinstance(x, collections.Callable)

"has_key" rimosso

Il metodo "has_key" del dizionario è stato rimosso: occorre usare l'operatore "in".

"apply" rimosso

"apply" non è più supportato: occorre usare la chiamata estesa delle funzioni

func(*args, **kwargs)

Modifiche presenti da Python 2.7

DeprecationWarning

Questi warning dal 2.7 sono disabilitati di default. Durante la fase di sviluppo sarebbe opportuno riabilitarli con -Wdefault da riga di comando o equivalenti:
  • impostare la variabile di ambiente PYTHONWARNINGS a "default"
  • da codice chiamare: warnings.simplefilter('default')

Dizionario: keys, values e items


In 3.0 questi metodi restituiscono una "view" che si aggiorna automaticamente se si modifica il dizionario. In 2.7 questo comportamento è presente nei metodi viewkeys, viewvalues e viewitems che vengono correttamente convertiti da 2to3.

In alternativa, per continuare a usare keys, values e items nel vecchio modo (ovvero dando per scontato che restituiscano una lista anche in Python 3) si possono chiudere all’interno della funzione  “list()” che in Python 3 trasforma la view in una lista mentre in Python 2 trasforma la lista in se stessa (non facendo nulla ai fini del programma).

Sintassi per set letterali

Parentesi graffe: {1, 2, 3, 4}
Per fare un set vuoto: set()

Dictionary e set comprehensions

>>> {x: x*x for x in range(6)}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
>>> {('a'*x) for x in range(6)}
set(['', 'a', 'aa', 'aaa', 'aaaa', 'aaaaa'])

Metodo __cmp__ rimosso

Il metodo __cmp__ verrà rimosso in Python 3.0: occorre definire tutti i metodi di paragone (__lt__, ecc.). C'è una utility in functools che aiuta nel compito.

importlib

Python 3.1 include la libreria importlib che è una reimplementazione del meccanismo di import. La versione in Python 2.7 contiene solo la funzione import_module.

Commenti

Post popolari in questo blog

Nuovo sito "Creare programmando" con una sezione per la didattica

Ho organizzato i miei tutorial, fatti con Scratch o altri linguaggi, in un nuovo sito che si chiama "Creare programmando" e che dovrebbe rendere più agevole lo scarico degli stessi. Questo sito contiene anche una sezione dedicata alla didattica tramite il computer in cui voglio raccogliere idee e tutorial che possono trovare applicazione nelle scuole oltre che nei CoderDojo, magari semplicemente perché forniscono degli spunti agli insegnanti sugli argomenti che si possono sviluppare (nel vero senso della parola) tramite il computer. Ho inaugurato la sezione didattica con un nuovo tutorial in Scratch dedicato alle regioni italiane. Cliccate qui per andarlo a vedere.

Un tutorial software per allenarsi all'uso dei sensori hardware

Durante l'ultima sessione di CoderDojo MXP abbiamo proposto ai ragazzi un tutorial che ha lo scopo di impratichirli nei ragionamenti che si rendono necessari quanto ci si trova a dover impostare la traiettoria di una macchinina utilizzando solo un sensore in grado di rilevare il colore della traccia visibile sul pavimento (in scala di grigi). Non avendo (ancora) a disposizione macchinine e sensori "reali" abbiamo iniziato a simulare la situazione disegnando una pista con Scratch utilizzando quattro diversi colori. Il compito dei ragazzi era di riuscire a fare in modo che lo sprite dell'automobilina seguisse la pista senza uscire di strada: le correzioni alla traiettoria avvenivano utilizzando il blocco "sta toccando il colore..." come se ci fosse un sensore in grado di vedere il colore della traccia sotto all'automobile. E se ne sono viste di tutti i colori perché il compito non era facile. Alla fine, per stimolare un po' di competizione tr

Sed: caratteri speciali che si possono usare nel lato destro delle sostituzioni

Il titolo è lungo, ma serve semplicemente per dire che nelle sostituzioni tramite la sed , ovvero quelle realizzate con il comando s/// , è possibile utilizzare alcune sequenze di escape oltre ai soliti backreference dei gruppi "catturati" con le parentesi nell'espressione regolare di sinistra. Nella parte destra dell'espressione (cioè quella dopo la seconda barra /) si possono usare: - \L per trasformare in minuscolo tutto  quello che segue (la L sta per "lowercase"); - \l (è una elle minuscola) per trasformare in minuscolo solo il carattere seguente; - \U per trasformare in maiuscolo tutto  quello che segue (la U sta per "uppercase"); - \u per traformare in maiuscolo solo il carattere seguente; - \E per marcare la fine della trasformazione dei caratteri. Gli "operatori" \L e \U agiscono fino alla fine dell'espressione (delimitata dalla barra / finale) oppure fino alla \E successiva. Quindi, per esempio, per trasformare sol