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.
import sys
python_version_major = sys.version_info [ 0 ]
if python_version_major > 2:
long = int
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.
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()
def __nonzero__ ( self ):
…
__bool__ = __nonzero__
raise SerializationError, “decoding error”
ma deve essere:
raise SerializationError ( “decoding error” )
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
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.
from __future__ import print_function
def print(*args, sep=' ', end='\n', file=None)
try:
…
except TypeError as exc:
…
from __future__ import unicode_literals
Questo probabilmente non significa che le stringhe lette, per esempio, dalla riga di comando siano unicode.
"bytes" in Python2 fornisce una stringa immutabile. Per avere una lista di bytes modificabile si usa "bytearray".
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à "//".
isinstance(x, collections.Callable)
func(*args, **kwargs)
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).
Per fare un set vuoto: set()
{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'])
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 funzionifunc(*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'])
Commenti
Posta un commento