2to3
This page is about porting your existing scripts and plugins to Python 3, while keeping Python 2 compatibility. The strategies described here have also been used to port most of the scripts in Pymol-script-repo.
Recipe overview:
- Make a backup of your scripts (of course)
- 2to3 -w ...
- fix imports
- fix str/bytes mixing
- ...
2to3
2to3 is an automated conversion tool and part of every Python installation. It will for example replace print "Hello"
with print("Hello")
. We'll exclude all import
fixes here, because we want to use a Python 2 compatible fix instead.
Run in a terminal:
2to3 -w --nofix=imports --nofix=urllib --nofix=future myscript.py
Imports
Place (the relevant subset of) this block at the beginning of each of your script files:
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
import sys
if sys.version_info[0] < 3:
import urllib2
from urllib2 import URLError, HTTPError
from urllib import quote
from idlelib.TreeWidget import TreeItem, TreeNode
from Tkinter import *
import Tkinter
import tkSimpleDialog
import tkFileDialog
import tkMessageBox
import tkColorChooser
import Queue
import ttk
from StringIO import StringIO
else:
import urllib.request as urllib2
from urllib.error import URLError, HTTPError
from urllib.parse import quote
from idlelib.tree import TreeItem, TreeNode
from tkinter import *
import tkinter as Tkinter
from tkinter import simpledialog as tkSimpleDialog
from tkinter import filedialog as tkFileDialog
import tkinter.messagebox as tkMessageBox
import tkinter.colorchooser as tkColorChooser
import queue as Queue
import tkinter.ttk as ttk
from io import StringIO
Some important notes:
from __future__ import division
will change the behavior of integer division,3/2 == 1
becomes3/2 == 1.5
. Use//
if you need floor division (3//2 == 1
in both Python 2 and 3).io.StringIO
is not an exact replacement forStringIO.StringIO
, it doesn't accept bytes input. You might needio.BytesIO
instead.
str vs. bytes
This can be the most tricky part. In Python 3, all strings are unicode, and unicode
was renamed to str
and str
to bytes
. Also, Python 2 allowed mixing of unicode
and bytes
and automatically encoded/decoded between them when needed. Python 3 doesn't do that, you have to encode and decode yourself, and pay attention to binary vs. text when writing files.
Python 2 | Python 3 | Literals (2 and 3 compatible) | |
---|---|---|---|
Unicode type | unicode | str | u"..." |
Binary type | str (alias bytes) | bytes | b"..." |
Code examples that works in Python 2 and 3:
text = u"Greek letter alpha: \u03B1"
binary = u"Greek letter alpha: \u03B1".encode("utf-8")
binary = b"Greek letter alpha: \xce\xb1"
text = b"Greek letter alpha: \xce\xb1".decode("utf-8")
Use binary mode when reading or writing bytes from/to files:
# text mode
with open("file.txt", "w") as f:
f.write(u"Hello World\n")
f.write(b"Hello World\n".decode("utf-8"))
# binary mode
with open("file.bin", "wb") as f:
f.write(b"Hello World\n")
f.write(u"Hello World\n".encode("utf-8"))
The subprocess modules opens streams in binary mode, unless you pass universal_newlines=True
:
# text mode
p = subprocess.Popen(['echo', 'Hello World'],
universal_newlines=True,
stdout=subprocess.PIPE)
text = p.stdout.read()
# binary mode
p = subprocess.Popen(['echo', 'Hello World'],
stdout=subprocess.PIPE)
binary = p.stdout.read()
string module
Most string
module functions are gone, use str
methods instead.
Examples:
Python 2 only | Python 2 and 3 |
---|---|
string.strip(" Hello ")
|
" Hello ".strip()
|
string.split("A B C")
|
"A B C".split()
|
string.join(["A", "B", "C"], " ")
|
" ".join(["A", "B", "C"])
|
Note: PyMOL has an interim solution for this, which will be removed in a future version:
if sys.version_info[0] == 3:
import string
for attr in ['capitalize', 'count', 'find', 'index', 'lower',
'replace', 'rstrip', 'split', 'strip', 'upper', 'zfill']:
setattr(string, attr, getattr(str, attr))
string.letters = string.ascii_letters
string.lowercase = string.ascii_lowercase
string.join = lambda words, sep=' ': sep.join(words)
string.atoi = int
Hex formatting of floats
This works in Python 2, but is an error in Python 3:
>>> '%x' % 12.34
TypeError: %x format: an integer is required, not float
Solution: Convert floats to int
:
>>> '%x' % int(12.34)
str.translate
The deletechars argument of str.translate is gone.
Python 2 only | Python 2 and 3 |
---|---|
"Hello".translate(None, "aeiou")
|
"".join(c for c in "Hello" if c not in "aeiou")
|