Porting Python 2 Code to Python 3

Python 3 was released in Dec 2008 – almost 10 years and we still found a lot of python 2.7 developers. Python 3 is Not backward compatible with Python 2. Most language features are the same,  some detail has changed and many deprecated features have been tided up and removed

The main reason today to work with python 2.7 is to keep maintain a big project written in python 2. If you start a new project you definitely choose python 3.

It is not hard to port python 2 code to python 3 – you can do it manually or use the utility 2to3.py.

The Print Function

The first thing everyone notices about Python 3 is that the print statement is no longer a, um, statement: it is a built-in function.  The effect is that we now have to put parentheses around the thing we wish to print.

Most commonly used for displaying a comma separated list (Objects are stringified)

ls=[1,2,3]
print('hello',10,ls)

# prints:
# hello 10 [1, 2, 3]

Has several named arguments

  • end= characters to be appended, default is ‘\n’ (newline)
  • file= file object to be written to, default is sys.stdout
  • sep= separator used between list items, default is a single space
ls=[1,2,3]
print('hello',10,ls, sep=' $ ', end=':')
print('bye')

# prints:
# hello $ 10 $ [1, 2, 3]:bye

Also format changed but you can use the old format (using %)

Simple keyboard (stdin) input

raw_input is replaced by input. The input statement reads from stdin, which is usually the keyboard but may have been redirected

num = input("enter number")
print( "res", int(num) + 100)

# enter number:33
# res: 133

Misc Changes

  • The tests <> and cmp are no longer supported – both statements throws errors – use ==, !=
if x <> y:
   print("ok")

if cmp(x,y):
   print("ok")

 

  • Non Ascii Names are allowed – use magic comment for that
# coding=iso-8859-8

משתנה = 'hello'

print(משתנה)

 

  • Backticks are removed – use repr instead
d=10
r='2'

print (d+r)
#TypeError: unsupported operand type(s) for +: 'int' and 'str'

print( `d` + r)
# SyntaxError: invalid syntax (was ok in python 2)

print( repr(d) + r)
# 102
  • New format for octal numbers:
num = 0o127
  • Trailing L is no longer used – ints and long are the same
  • True, False, None are now keywords (In Python 2 they could be redefined)

 

Strings in python 3

  • Multi-byte characters
  • \unnnn –  two-byte Unicode character
  • \Unnnnnnnn –  four-byte Unicode character
  • \N{name}  –  a named Unicode character
  • The old Python 2 u”…” prefix is no longer supported
euro="\u20ac"
print( euro)
euro="\N{euro sign}"
print (euro)

# €
# €

bytes() and bytearray()

used for low level interfacing, convert to string using decode:

cb = b"simple single-byte string"
print(cb.decode())

Opening a file in binary mode returns a bytes object. Convert from a string using string.encode()

String formatting

Like printf in C, using the format method:

st = "hello {0:15d} hi {1:5.2f} bye {2}".format(10,3.2233,"have fun")
print(st)

# hello              10 hi  3.22 bye have fun

 

Collections

  • The format when printing a set changed in python 3
s1 = set('hello')
print (s1)

# {'l', 'e', 'o', 'h'}
  • Dictionary methods  iterkeys(), itervalues(), iteritems() removed
  • Return values from keys(), values(), and items() are view objects
  • map and filter return iterators (instead of list in python 2)

in python 2:

>>> ls = [1,2,3,4]
>>> ls2=map(lambda x:x*10,ls)
>>> ls2
[10, 20, 30, 40]

in python 3:

>>> ls = [1,2,3,4]
>>> ls2=map(lambda x:x*10,ls)
>>> ls2
<map object at 0x104d21208>
>>> next(ls2)
10

 

Forcing a user to supply named arguments

You can use * to force a user to supply named arguments:

def fn(*, a = 100,b = 200):
    return a+b*10


print(fn()) # 2100
print(fn(b=30,a= 10)) # 310
print(fn(10,20)) # TypeError fn() takes 0 positional arguments but 2 were given

Function Annotations 

You can add documentation to the parameters and the return value:

def add(a:"first number" = 0,
        b:"second number" = 0) -> "sum of a and b":
    return a+b

for item in add.__annotations__.items():
    print(item)

# ('a', 'first number')
# ('b', 'second number')
# ('return', 'sum of a and b')

variables in nested functions:

If we define a variables with the same name in a different scopes it may confuses the interpreter for example:

b = 3
def f1():
    b = 10

    def f2():
        if b < 100: # error here
            b += 1

    f2()
    print(b, "from f1")

f1()
print(b, "from main")

This code will fail with the error: UnboundLocalError: local variable ‘b’ referenced before assignment

To fix this you need to declare b as global or nonlocal

nonlocal

b = 3
def f1():
    b = 10

    def f2():
        nonlocal b
        if b < 100:
            b += 1

    f2()
    print(b, "from f2") # 11 from f2


f1()
print(b, "from main") # 3 from main

global

b = 3
def f1():
    b = 10

    def f2():
        global b
        if b < 100:
            b += 1

    f2()
    print(b, "from f2") # 10 from f2


f1()
print(b, "from main") # 4 from main

 

 Exceptions

Each exception has an arguments attribute stored in a tuple. The number of elements, and their meaning varies

The raise and except syntax changed and we throw only Exception objects (in python 2 you can throw string)

class CustomException(Exception):
    pass

def fn(*arguments):
    if not all(arguments):
        raise CustomException("False argument in fn")


try:
    fn('dev','',42)
except CustomException as err:
    print("Oops:",err)

 

Properties and decorators

The property() built-in function defines get, set, delete, and docstring methods for a specific attribute

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        print 'get'
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        print 'set'
        self._x = value

    @x.deleter
    def x(self):
        del self._x

some of the changes are also supported in python 2 and using the back port option you can make the porting process easier

 

Tagged