Standard library differences from Python 3

The renamed and reorganised modules you will meet in a 2.7 codebase, and the import line each one becomes in Python 3.

Modules that moved

Python 2Python 3Note
urllib2urllib.requestSplit into request, parse, error, robotparser
urlparseurllib.parseMerged into the urllib package
ConfigParserconfigparserRenamed, and interpolation semantics tightened
cPicklepickleThe fast C implementation is now the only one
cStringIO / StringIOio.StringIO / io.BytesIOText and bytes streams are separate types
QueuequeueRenamed
HTMLParserhtml.parserMoved into the html package
httplibhttp.clientRenamed
SocketServersocketserverRenamed
thread_threadThe low-level module; prefer threading
# a portable import block, as used by libraries that support both
try:
    import configparser
except ImportError:              # Python 2
    import ConfigParser as configparser

try:
    from urllib.request import urlopen, Request
except ImportError:
    from urllib2 import urlopen, Request

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

Libraries such as six and python-future exist precisely to hide this block. Writing it by hand is fine for a small script and becomes noise in a large codebase.

Same name, different behaviour

# 1. StringIO: bytes oriented in 2, and no unicode support from cStringIO
from StringIO import StringIO
buf = StringIO()
buf.write("hello")
print buf.getvalue()

# 2. ConfigParser: percent signs are interpolation markers
import ConfigParser
cfg = ConfigParser.ConfigParser()
cfg.read("app.ini")
value = cfg.get("main", "template")     # a literal % must be written %%

# 3. subprocess.check_output exists, but check_ouput text handling differs
import subprocess
out = subprocess.check_output(["git", "rev-parse", "HEAD"])   # returns bytes
  • In Python 3 configparser defaults to no interpolation via ConfigParser(interpolation=None), which is often what you want for values that legitimately contain a percent sign.
  • Python 3 separates text and binary streams strictly: io.StringIO for text, io.BytesIO for bytes. Python 2's StringIO was happily both.
  • Many Python 3 functions gained a text=True option to return decoded strings; in Python 2 you decode the bytes yourself.

Bridging with six

import six

# type checks that work under both versions
if isinstance(value, six.string_types):        # str in 3, (str, unicode) in 2
    pass
if isinstance(value, six.text_type):           # unicode in 2
    pass
if isinstance(value, six.binary_type):
    pass

# module aliases without a try/except
from six.moves import configparser, queue, urllib_request

# metaprogramming helpers
@six.add_metaclass(RegistryMeta)
class Plugin(object):
    pass
⚠️
six makes a codebase run under both interpreters, but it does not make it idiomatic Python 3. Treat it as scaffolding for the migration, not as the destination, and delete the dependency once the last Python 2 target is gone.

FAQ

Can I just use the Python 3 import names in a 2to3 run?
Yes, that is exactly what 2to3 does, and it is dependable for these renames. Review the output anyway: the tool rewrites imports but cannot know that your ConfigParser values contain percent signs.
Is cPickle faster than pickle in Python 3?
In Python 3 cPickle no longer exists because pickle already uses the C implementation automatically. Removing the import is the whole change.

Migrating to Python 3 Lists, dicts and the Python 2-only APIs

Last refreshed 2026-09-18.