Questa è una vecchia versione del documento!


Porting da Python 2 a Python 3

*WIP*: Quello seguente è un articolo in divenire

Differenze con impatto maggiore

Python nell'evoluzione dalla versione 2 alla versione 3 ha radicalmente cambiato molte parti del linguaggio.
Fra le modifiche con un impatto piú rilevante troviamo:

  • la scrittura su console print
  • Sollevare e Gestire eccezioni
  • la divisione intera
  • Unicode
  • xrange

Nelle seguenti sezioni vedremo come scrivere codice che sia compatibile su entrambe le versioni.

print

Print è ora una funzione e per tanto vuole le parentesi tonde.

# python 2.x
print "Ciao"
 
# python 3.x
print("Ciao")

Per una semplice print, potete mettere le parentesi anche su Python 2 ed il risultato sarà identico.
Varia invece se usate una print con parti concatenate da virgola

# python 2.x
print "Ciao", " Come stati?"
>> Ciao Come stai?
 
# python 3.x
print ("Ciao", " Come stati?")
>> Ciao Come stai?

In questo caso, aggingendo le parentesi se python 2 otteniamo un risultato che è quello desiderato

# python 2.x
print ("Ciao", " Come stati?")
>> ("Ciao", "Come stai?")

Per ovviare a questo spiacevole risultato, ci viene in aiuto il modulo __future__

# python 2.x
from __future__ import print_function
 
print ("Ciao", " Come stati?")
>> Ciao Come stai?

Divisione Intera

Questa modifica è particolarmente pericolosa poiché può spesso passare inosservata (non genera un SyntaxError). Quindi si consiglia l'importazione del modulo __future__ in tutti gli script Python 2 ove sia prevista una divisione).

Python 2

print 'Python', python_version()
print '3 / 2 =', 3 / 2
print '3 // 2 =', 3 // 2
print '3 / 2.0 =', 3 / 2.0
print '3 // 2.0 =', 3 // 2.0
>> Python 2.7.6
3 / 2 = 1
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0

Python 3

print('Python', python_version())
print('3 / 2 =', 3 / 2)
print('3 // 2 =', 3 // 2)
print('3 / 2.0 =', 3 / 2.0)
print('3 // 2.0 =', 3 // 2.0)
>> Python 3.4.1
3 / 2 = 1.5
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0

Python 2 e 3

# Python 2 and 3
from __future__ import division
 
assert 3 / 2 == 1.5

Long integers

In Python 3, il tipo int corrisponde come implementazione al vecchio tipo long, quindi int e long diventano equivalenti.

Python 2

bigint = 1L
 
if isinstance(bigint, (int, long)):
    ...    

Python 3

bigint = 1
 
if isinstance(bigint, int):
    ...    

Python 2 e 3

ATTENZIONE: A Python 3 da fastidio la 'L' quindi in Python 2 sarà necessario modificare il codice come mostrato di seguito.

from builtins import int
bigint = int(1)
 
if isinstance(bigint, int):             # matches both int and long on Py2
    ...

Octal constants

Backtick repr

Unicode (text) string literals e Byte-string literals

In Python 2 esistono due diversi tipi stringa:

  • ASCII str()
  • UNICODE unicode()

Ma non esiste la stringa di byte.
Ora, in Python 3, tutte le stringhe sono Unicode (utf-8), in aggiunta abbiamo due nuove classi: byte e bytearrays.

Python 2

print 'Python', python_version()
 
>> Python 2.7.6
 
print type(unicode('Questa è come una stringa di Python3'))
 
>> <type 'unicode'>
 
print type(b'il tipo byte non esiste')
 
>> <type 'str'>
 
print 'Queste due stringe sono' + b' dello stesso tipo'
 
>> Queste due stringe sono dello stesso tipo
 
print type(bytearray(b'Stranamente però esiste il bytearray'))
 
>> <type 'bytearray'>

Python 3

print('Python', python_version())
print('ora le stringhe sono utf-8 \u03BCnico\u0394é!')
 
>> Python 3.7.3
   strings are now utf-8 μnicoΔé!
 
print('Python', python_version(), end="")
print(' ha', type(b' il tipo bytes'))
 
>> Python 3.7.3 ha <class 'bytes'>
 
print('e Python', python_version(), end="")
print(' ha anche ', type(bytearray(b'bytearrays')))
 
>> e Python 3.7.3 ha anche <class 'bytearray'>
 
'Ora cercare di sommare una stringa' + b' ed un bytes provoca errore.'
 
>> Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    'Ora cercare di sommare una stringa' + b' ed un bytes provoca errore.'
TypeError: can only concatenate str (not "bytes") to str

Python 2 e 3

# Byte-String
# Python 2 and 3: byte-strings as strings.
from builtins import str
a = u'abc'
b = b'def'
c = b.decode()
assert isinstance(a, str) and isinstance(c, str)
# ...
 
# Unicode: Alternative 1
# Python 2 and 3
s1 = u'The Zen of Python'
s2 = u'きたないのよりきれいな方がいい\n'
 
# Unicode: Alternative 2
# Python 2 and 3
from __future__ import unicode_literals    # rende tutte le stringhe unicode
 
s1 = 'The Zen of Python'
s2 = 'きたないのよりきれいな方がいい\n'

xrange

Poichè xrange() è generalmente più veloce di range(), in Python 3 range() è implementata come xrange() mentre quest'ultima è stata rimossa.
Questo lo rende efficientissimo su iterazioni 1-time, mentre su iterazioni multiple perde un po' di efficienza perchè, ogni volta, la generazione viene rifatta da zero.
In Python 3, la chiamata alla funzione xrange() genera un NameError.

Sollevare Eccezioni

Mentre Python 2 accetta sia la sintassi “vecchia” che la “nuova”, Python 3 genera un SyntaxError se non l'argomento non viene messo tra parentesi.

Python 2

raise IOError, "file error"
 
>> Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    raise IOError, "file Error"
IOError: file Error
 
raise IOError("file error")
 
>> Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    raise IOError("file Error")
IOError: file Error

Python 3

raise IOError, "file error"
 
>> SyntaxError: invalid syntax
 
raise IOError("file error")
 
>> Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    raise IOError("file error")
OSError: file error

Python 2 e 3

Poichè Python 2 supporta la sintassi con le parentesi è buona cosa scrivere le raise con tale sintassi anche in Python 2.

Sollevare Eccezioni (TraceBack)

Python 2

traceback = sys.exc_info()[2]
raise ValueError, "dodgy value", traceback

Python 3

raise ValueError("dodgy value").with_traceback()

Python 2 e 3

# Python 2 e 3: Alternativa 1
from future.utils import raise_
 
traceback = sys.exc_info()[2]
raise_(ValueError, "dodgy value", traceback)
 
#...
#...
 
# Python 2 e 3: Alternativa 2
from future.utils import raise_with_traceback
 
raise_with_traceback(ValueError("dodgy value"))

Gestire Eccezioni

In Python 3 ora è obbligatoria la keyword as.

Python 2

try:
    ...
except ValueError, e:
    ...

Python 3

try:
    ...
except ValueError as e:
    ...

Python 2 e 3

Python 2 supporta la sintassi di Python 3.

try:
    ...
except ValueError as e:
    ...

La funzione next() ed il metodo .next()

Mentre in Python 2.7 per ottenere l'elemento successivo di un generatore è possibile usare sia la funzione next() che il metodo .next(), il Python 3 è presente solo la funzione ed un eventuale chiamata al metodo genera un'eccezione di tipo AttributeError.

Python 2

my_generator = (letter for letter in 'abcdefg')
 
next(my_generator)
>> 'a'
 
my_generator.next()
>> 'b'

Python 3

my_generator = (letter for letter in 'abcdefg')
 
next(my_generator)
>> 'a'
 
my_generator.next()
>> Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    my_generator.next()
AttributeError: 'generator' object has no attribute 'next'

Python 2 e 3

La soluzione è utilizzare sempre la funzione next().

Variabili For-loop e perdita del namespace globale

In Python 3 non avviene più la perdita del namespace globale.

Python 2

i = 1
print 'before: i =', i
 
>> before: i = 1
 
print 'comprehension: ', [i for i in range(5)]
 
>> comprehension:  [0, 1, 2, 3, 4]
 
print 'after: i =', i
 
>> after: i = 4

Python 3

i = 1
print('before: i =', i)
 
>> before: i = 1
 
print('comprehension:', [i for i in range(5)])
 
>> comprehension:  [0, 1, 2, 3, 4]
 
print('after: i =', i)
 
>> after: i = 1

Comparazione fra tipi unorderable

InPython 3 il tentativo di comparare due entità non ordinabili restituisce un TypeError.

Python 2

print "[1, 2] > 'foo' = ", [1, 2] > 'foo'
 
>> [1, 2] > 'foo' =  False
 
print "(1, 2) > 'foo' = ", (1, 2) > 'foo'
 
>> (1, 2) > 'foo' =  True
 
print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2)
 
>> [1, 2] > (1, 2) =  False

Python 3

print("[1, 2] > 'foo' = ", [1, 2] > 'foo')
 
>> Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    print("[1, 2] > 'foo' = ", [1, 2] > 'foo')
TypeError: '>' not supported between instances of 'list' and 'str'
 
print("(1, 2) > 'foo' = ", (1, 2) > 'foo')
 
>> Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    print("(1, 2) > 'foo' = ", (1, 2) > 'foo')
TypeError: '>' not supported between instances of 'tuple' and 'str'
 
print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))
 
>> Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))
TypeError: '>' not supported between instances of 'list' and 'tuple'

Analisi degli input dell'utente tramite input()

In Python 2 la funzione input() cercava di determinare il tipo dell'input inserito (a volte causando problemi perchè gestiva la stringa di input come se fosse codice Python).
In Python 3 questo comportamento è stato corretto ed ora input() restituisce sempre una stringa.
Per lo stesso comportamento in Python 2 è necessario chiamare la funzione raw_input().

Python 2

my_input = input('enter a number: ')
 
>> enter a number: 123
 
type(my_input)
 
>> <type 'int'>
 
my_input = raw_input('enter a number: ')
 
>> enter a number: 123
 
type(my_input)
>> <type 'str'>

Python 3

my_input = input('enter a number: ')
 
>> enter a number: 123
 
type(my_input)
>> <class 'str'>

Restituire Oggetti Iterabili al posto si Liste

Come abbiamo già visto nella sezione xrange, alcune funzioni e metodi restituiscono oggetti iterabili in Python 3, anziché liste come in Python 2.
Poichè solitamente vengono ciclati una sola volta, questo cambiamento consente di salvare memoria.
Tuttavia, con una perdita di efficienza, è anche possibile, a differenza dei generatori, scorrere più volte se necessario.
E per quei casi in cui abbiamo davvero bisogno degli oggetti list, possiamo semplicemente convertire l'oggetto iterabile in una lista tramite la funzione list().

Python 2

print range(3)
 
>> [0, 1, 2]
 
print type(range(3))
 
>> <type 'list'>

Python 3

print(range(3))
 
>> range(0, 3)
 
print(type(range(3)))
 
>> <class 'range'>
 
print(list(range(3)))
 
>> [0, 1, 2]

Funzioni e metodi comunemente usati che non restituiscono più elenchi in Python 3:

  • zip()
  • map()
  • filter()
  • Metodo .keys() dei dizionari
  • Metodo .values() dei dizionari
  • Metodo .items() dei dizionari

Variazione del comportamento della funzione Round

Python 3 ha adottato il modo ormai standard di arrotondare i decimali quando si ottiene un pareggio (.5) alle ultime cifre significative.
Ora, in Python 3, i decimali vengono arrotondati al numero pari più vicino.
Purtroppo questo comportamento non è workaround-abile … il che significa che bisogna tenerne conto.

Python 2

round(15.5)
 
>> 16.0
 
round(16.5)
 
>> 17.0

Python 3

round(15.5)
 
>> 16
 
round(16.5)
 
>> 16

Custom class behaviour

Dictionaries

Imports relativi

Altre variazioni ai builtins

Standard library

Modernize

PyCharm e Cross-compatibilità

Fonti

python/porting_python_2_to_pyton_3.1579705358.txt.gz · Ultima modifica: 2020/01/22 15:02 da apressato
Torna su
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0