Upgrade from Python 2 to 3

Python 3 is growing in acceptance and usage. People in all sorts of fields are using Python, and many of them are switching to Python 3. This article will list down repeating problem (task) that I encounter during migration of my Pyramid project from Python 2 to Python 3. Previously posted as part 1 and part 2, I merged the two parts in this article. Here the problems, and of course the solution:

class advice impossible in python3. use the @implementer class decorator instead...

More or less similar with this issue:

zope.interface has had a feature for 10 years under Python2 which can't be made to work portably under Python3. It is spelled like:

from zope.interface import Implements

class Foo(object):
    implements(IFoo)
                    

There is a newer, Python3-compatible spelling, which the pyramid trunk now uses (as of today):

from zope.interface import implementer

@implementer(IFoo)
class Foo(object):
    pass
                    

That spelling only works under Python >= 2.6

ImportError: No module named 'urlparse'

simply refer to this page:
urlparse is part of the standard Python 2 library. It's shipped as part of Python; it isn't packaged separately on PyPI et al. urlparse.urlparse (the function) was renamed in Python 3 to urllib.parse.

If you need to write code which is Python2 and Python3 compatible you can use the following import:

try:
    from urllib.parse import urlparse
except ImportError:
    from urlparse import urlparse
                    

Or if you work only with Python 3, following import is enough:

from urllib.parse import urlparse
                    

ImportError: No module named 'ConfigParser'

In Python 3, ConfigParser has been renamed to configparser for PEP 8 compliance. So, to minimize changes, what I normally do:

import configparser as ConfigParser
                    

ImportError: No module named 'StringIO'

The StringIO and cStringIO modules are gone. Instead, import the io module and use io.StringIO or io.BytesIO for text and data respectively.

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO
                    

ImportError: No module named 'htmlparser'

Module html.parser defines a class HTMLParser which serves as the basis for parsing text files formatted in HTML (HyperText Mark-up Language) and XHTML.

Solution:

from html.parser import HTMLParser
                    

Sample:

from html.parser import HTMLParser

class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print("Encountered a start tag:", tag)
    def handle_endtag(self, tag):
        print("Encountered an end tag :", tag)
    def handle_data(self, data):
        print("Encountered some data  :", data)

parser = MyHTMLParser()
parser.feed('<html><head><title>Test</title></head>'
            '<body><h1>Parse me!</h1></body></html>')
                    

ImportError: No module named httplib

In Python 3, the module has been renamed to http.client

import http.client
httpRequest = ""
conn = http.client.HTTPConnection("localhost",8080)
conn.request("GET","/file.html",httpRequest)
response = conn.getresponse()
print(response.status,response.reason)
conn.close();
                    

ImportError: No module named urllib2

In python 3 urllib2 was merged into urllib. To make Python 2 code work in Python 3:

try:
    import urllib.request as urllib2
except ImportError:
    import urllib2
                    

But if not, here a sample for urllib.request

import urllib.request
wp = urllib.request.urlopen("http://goggle.com")
pw = wp.read()
print(pw)
                    

ImportError: No module named 'BaseHTTPServer'

BaseHTTPServer, SimpleHTTPServer modules in Python 2 have been merged into http.server module in Python 3. Sample:

import http.server
import ssl

httpd = http.server.HTTPServer(('localhost', 4443), http.server.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket, certfile='server.pem', server_side=True)
httpd.serve_forever()
                    

TypeError: the JSON object must be str, not 'bytes'

solve it using decode():

result = json.loads(response.readall().decode('utf-8'))
                    

TypeError:Unicode-objects must be encoded before hashing

Refer to this similar issue, It is because looking for a character encoding:

line.encode('utf-8')
                    

or another way around:

str(line)
                    

Signs 2 and 3

Upgrade Python 2 to 3

NameError: global name 'unicode' is not defined

Python 3 renamed the unicode type to str, the old str type has been replaced by bytes.

if isinstance(unicode_or_str, str):
    text = unicode_or_str
    decoded = False
else:
    text = unicode_or_str.decode(encoding)
    decoded = True
                    

In short; use str(*)

iteritems in Python

Based on this stackoverflow question: Why was iteritems() removed from Python 3? Seems like a terrific and useful method. What's the reasoning behind it?

In Python 2.x - .items() returned a list of (key, value) pairs.
In Python 3.x, .items() is now an itemview object, which behaves different - so it has to be iterated over, or materialised... So, list(dict.items()) is required for what was dict.items() in Python 2.x.

In Python 2.7:

common_keys = list(dict_a.viewkeys() & dict_b.viewkeys())
                    

Will give you a list of the common keys, but in Python 3.x - just use .keys() instead.

common_keys = list(dict_a.keys() & dict_b.keys())
                    

Python 3.x has generally been made to be more "lazy" - i.e. map is now effectively itertools.imap, zip is itertools.izip, etc.

'module' object has no attribute 'urlencode'

It's about urllib. In Python 3.x Do

import urllib.parse
                    

instead. urlencode is part of urllib.parse

ImportError: cannot import name 'quote'

Again about urllib. Similar like problem above you can do

from urllib.parse import quote
                    

For direct import

dictionary changed size during iteration

It's a problem in Python 3, in short; use list...

As explained, in Python 2.x calling keys makes a copy of the key that you can iterate over while modifying the dict:

for i in d.keys():
                    

Note that this doesn't work in Python 3.x because keys returns an iterator instead of a list. Another way is to use list to force a copy of the keys to be made. This one also works in Python 3.x:

for i in list(d):