Skip to content

Commit 705fae3

Browse files
committed
Merge pull request #2226 from mdboom/six
Stop relying on 2to3 and use `six.py` for compatibility instead
2 parents d0a0100 + dddc262 commit 705fae3

File tree

229 files changed

+2668
-1670
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

229 files changed

+2668
-1670
lines changed

boilerplate.py

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
# For some later history, see
1717
# http://thread.gmane.org/gmane.comp.python.matplotlib.devel/7068
1818

19+
from __future__ import absolute_import, division, print_function, unicode_literals
20+
21+
import six
22+
1923
import os
2024
import inspect
2125
import random

doc/conf.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
'sphinxext.github',
3535
'numpydoc']
3636

37-
3837
try:
3938
import numpydoc
4039
except ImportError:
@@ -53,6 +52,9 @@
5352
# The suffix of source filenames.
5453
source_suffix = '.rst'
5554

55+
# This is the default encoding, but it doesn't hurt to be explicit
56+
source_encoding = "utf-8"
57+
5658
# The master toctree document.
5759
master_doc = 'contents'
5860

doc/devel/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
:maxdepth: 2
1414

1515
coding_guide.rst
16+
portable_code.rst
1617
license.rst
1718
gitwash/index.rst
1819
testing.rst

doc/devel/portable_code.rst

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
Writing code for Python 2 and 3
2+
-------------------------------
3+
4+
As of matplotlib 1.4, the `six <http://pythonhosted.org/six/>`_
5+
library is used to support Python 2 and 3 from a single code base.
6+
The `2to3` tool is no longer used.
7+
8+
This document describes some of the issues with that approach and some
9+
recommended solutions. It is not a complete guide to Python 2 and 3
10+
compatibility.
11+
12+
Welcome to the ``__future__``
13+
-----------------------------
14+
15+
The top of every `.py` file should include the following::
16+
17+
from __future__ import absolute_import, division, print_function, unicode_literals
18+
19+
This will make the Python 2 interpreter behave as close to Python 3 as
20+
possible.
21+
22+
All matplotlib files should also import `six`, whether they are using
23+
it or not, just to make moving code between modules easier, as `six`
24+
gets used *a lot*::
25+
26+
import six
27+
28+
Finding places to use six
29+
-------------------------
30+
31+
The only way to make sure code works on both Python 2 and 3 is to make sure it
32+
is covered by unit tests.
33+
34+
However, the `2to3` commandline tool can also be used to locate places
35+
that require special handling with `six`.
36+
37+
(The `modernize <https://pypi.python.org/pypi/modernize>`_ tool may
38+
also be handy, though I've never used it personally).
39+
40+
The `six <http://pythonhosted.org/six/>`_ documentation serves as a
41+
good reference for the sorts of things that need to be updated.
42+
43+
The dreaded ``\u`` escapes
44+
--------------------------
45+
46+
When `from __future__ import unicode_literals` is used, all string
47+
literals (not preceded with a `b`) will become unicode literals.
48+
49+
Normally, one would use "raw" string literals to encode strings that
50+
contain a lot of slashes that we don't want Python to interpret as
51+
special characters. A common example in matplotlib is when it deals
52+
with TeX and has to represent things like ``r"\usepackage{foo}"``.
53+
Unfortunately, on Python 2there is no way to represent `\u` in a raw
54+
unicode string literal, since it will always be interpreted as the
55+
start of a unicode character escape, such as `\u20af`. The only
56+
solution is to use a regular (non-raw) string literal and repeat all
57+
slashes, e.g. ``"\\usepackage{foo}"``.
58+
59+
The following shows the problem on Python 2::
60+
61+
>>> ur'\u'
62+
File "<stdin>", line 1
63+
SyntaxError: (unicode error) 'rawunicodeescape' codec can't decode bytes in
64+
position 0-1: truncated \uXXXX
65+
>>> ur'\\u'
66+
u'\\\\u'
67+
>>> u'\u'
68+
File "<stdin>", line 1
69+
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in
70+
position 0-1: truncated \uXXXX escape
71+
>>> u'\\u'
72+
u'\\u'
73+
74+
This bug has been fixed in Python 3, however, we can't take advantage
75+
of that and still support Python 2::
76+
77+
>>> r'\u'
78+
'\\u'
79+
>>> r'\\u'
80+
'\\\\u'
81+
>>> '\u'
82+
File "<stdin>", line 1
83+
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in
84+
position 0-1: truncated \uXXXX escape
85+
>>> '\\u'
86+
'\\u'
87+
88+
Iteration
89+
---------
90+
91+
The behavior of the methods for iterating over the items, values and
92+
keys of a dictionary has changed in Python 3. Additionally, other
93+
built-in functions such as `zip`, `range` and `map` have changed to
94+
return iterators rather than temporary lists.
95+
96+
In many cases, the performance implications of iterating vs. creating
97+
a temporary list won't matter, so it's tempting to use the form that
98+
is simplest to read. However, that results in code that behaves
99+
differently on Python 2 and 3, leading to subtle bugs that may not be
100+
detected by the regression tests. Therefore, unless the loop in
101+
question is provably simple and doesn't call into other code, the
102+
`six` versions that ensure the same behavior on both Python 2 and 3
103+
should be used. The following table shows the mapping of equivalent
104+
semantics between Python 2, 3 and six for `dict.items()`:
105+
106+
============================== ============================== ==============================
107+
Python 2 Python 3 six
108+
============================== ============================== ==============================
109+
``d.items()`` ``list(d.items())`` ``list(six.iteritems(d))``
110+
``d.iteritems()`` ``d.items()`` ``six.iteritems(d)``
111+
============================== ============================== ==============================
112+
113+
Numpy-specific things
114+
---------------------
115+
116+
When specifying dtypes, all strings must be byte strings on Python 2
117+
and unicode strings on Python 3. The best way to handle this is to
118+
force cast them using `str()`. The same is true of structure
119+
specifiers in the `struct` built-in module.

doc/users/plotting/examples/pgf_preamble.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, division, print_function, unicode_literals
3+
4+
import six
25

36
import matplotlib as mpl
47
mpl.use("pgf")
@@ -7,9 +10,9 @@
710
"text.usetex": True, # use inline math for ticks
811
"pgf.rcfonts": False, # don't setup fonts from rc parameters
912
"pgf.preamble": [
10-
r"\usepackage{units}", # load additional packages
11-
r"\usepackage{metalogo}",
12-
r"\usepackage{unicode-math}", # unicode math setup
13+
"\\usepackage{units}", # load additional packages
14+
"\\usepackage{metalogo}",
15+
"\\usepackage{unicode-math}", # unicode math setup
1316
r"\setmathfont{xits-math.otf}",
1417
r"\setmainfont{DejaVu Serif}", # serif font via preamble
1518
]
@@ -19,9 +22,9 @@
1922
import matplotlib.pyplot as plt
2023
plt.figure(figsize=(4.5,2.5))
2124
plt.plot(range(5))
22-
plt.xlabel(u"unicode text: я, ψ, €, ü, \\unitfrac[10]{°}{µm}")
23-
plt.ylabel(u"\\XeLaTeX")
24-
plt.legend([u"unicode math: $λ=∑_i^∞ μ_i^2$"])
25+
plt.xlabel("unicode text: я, ψ, €, ü, \\unitfrac[10]{°}{µm}")
26+
plt.ylabel("\\XeLaTeX")
27+
plt.legend(["unicode math: $λ=∑_i^∞ μ_i^2$"])
2528
plt.tight_layout(.5)
2629

2730
plt.savefig("pgf_preamble.pdf")

lib/matplotlib/__init__.py

+18-28
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@
9797
to MATLAB&reg;, a registered trademark of The MathWorks, Inc.
9898
9999
"""
100-
from __future__ import print_function, absolute_import
100+
from __future__ import absolute_import, division, print_function, unicode_literals
101101

102+
import six
102103
import sys
103104
import distutils.version
104105

@@ -166,17 +167,6 @@ def _forward_ilshift(self, other):
166167

167168
import sys, os, tempfile
168169

169-
if sys.version_info[0] >= 3:
170-
def ascii(s): return bytes(s, 'ascii')
171-
172-
def byte2str(b): return b.decode('ascii')
173-
174-
else:
175-
ascii = str
176-
177-
def byte2str(b): return b
178-
179-
180170
from matplotlib.rcsetup import (defaultParams,
181171
validate_backend,
182172
validate_toolbar)
@@ -224,7 +214,7 @@ def _is_writable_dir(p):
224214
try:
225215
t = tempfile.TemporaryFile(dir=p)
226216
try:
227-
t.write(ascii('1'))
217+
t.write(b'1')
228218
finally:
229219
t.close()
230220
except OSError:
@@ -304,7 +294,7 @@ def wrap(self, fmt, func, level='helpful', always=True):
304294
if always is True, the report will occur on every function
305295
call; otherwise only on the first time the function is called
306296
"""
307-
assert callable(func)
297+
assert six.callable(func)
308298
def wrapper(*args, **kwargs):
309299
ret = func(*args, **kwargs)
310300

@@ -330,7 +320,7 @@ def checkdep_dvipng():
330320
s = subprocess.Popen(['dvipng','-version'], stdout=subprocess.PIPE,
331321
stderr=subprocess.PIPE)
332322
line = s.stdout.readlines()[1]
333-
v = byte2str(line.split()[-1])
323+
v = line.split()[-1].decode('ascii')
334324
return v
335325
except (IndexError, ValueError, OSError):
336326
return None
@@ -347,7 +337,7 @@ def checkdep_ghostscript():
347337
stderr=subprocess.PIPE)
348338
stdout, stderr = s.communicate()
349339
if s.returncode == 0:
350-
v = byte2str(stdout[:-1])
340+
v = stdout[:-1]
351341
return gs_exec, v
352342

353343
return None, None
@@ -358,7 +348,7 @@ def checkdep_tex():
358348
try:
359349
s = subprocess.Popen(['tex','-version'], stdout=subprocess.PIPE,
360350
stderr=subprocess.PIPE)
361-
line = byte2str(s.stdout.readlines()[0])
351+
line = s.stdout.readlines()[0].decode('ascii')
362352
pattern = '3\.1\d+'
363353
match = re.search(pattern, line)
364354
v = match.group(0)
@@ -372,7 +362,7 @@ def checkdep_pdftops():
372362
stderr=subprocess.PIPE)
373363
for line in s.stderr:
374364
if b'version' in line:
375-
v = byte2str(line.split()[-1])
365+
v = line.split()[-1].decode('ascii')
376366
return v
377367
except (IndexError, ValueError, UnboundLocalError, OSError):
378368
return None
@@ -383,7 +373,7 @@ def checkdep_inkscape():
383373
stderr=subprocess.PIPE)
384374
for line in s.stdout:
385375
if b'Inkscape' in line:
386-
v = byte2str(line.split()[1])
376+
v = line.split()[1].decode('ascii')
387377
break
388378
return v
389379
except (IndexError, ValueError, UnboundLocalError, OSError):
@@ -395,7 +385,7 @@ def checkdep_xmllint():
395385
stderr=subprocess.PIPE)
396386
for line in s.stderr:
397387
if b'version' in line:
398-
v = byte2str(line.split()[-1])
388+
v = line.split()[-1].decode('ascii')
399389
break
400390
return v
401391
except (IndexError, ValueError, UnboundLocalError, OSError):
@@ -771,7 +761,7 @@ class RcParams(dict):
771761
"""
772762

773763
validate = dict((key, converter) for key, (default, converter) in
774-
defaultParams.iteritems())
764+
six.iteritems(defaultParams))
775765
msg_depr = "%s is deprecated and replaced with %s; please use the latter."
776766
msg_depr_ignore = "%s is deprecated and ignored. Use %s"
777767

@@ -856,7 +846,7 @@ def rc_params(fail_on_error=False):
856846
# this should never happen, default in mpl-data should always be found
857847
message = 'could not find rc file; returning defaults'
858848
ret = RcParams([(key, default) for key, (default, _) in \
859-
defaultParams.iteritems() ])
849+
six.iteritems(defaultParams)])
860850
warnings.warn(message)
861851
return ret
862852

@@ -888,7 +878,7 @@ def rc_params_from_file(fname, fail_on_error=False):
888878
rc_temp[key] = (val, line, cnt)
889879

890880
ret = RcParams([(key, default) for key, (default, _) in \
891-
defaultParams.iteritems()])
881+
six.iteritems(defaultParams)])
892882

893883
for key in ('verbose.level', 'verbose.fileo'):
894884
if key in rc_temp:
@@ -904,7 +894,7 @@ def rc_params_from_file(fname, fail_on_error=False):
904894
verbose.set_level(ret['verbose.level'])
905895
verbose.set_fileo(ret['verbose.fileo'])
906896

907-
for key, (val, line, cnt) in rc_temp.iteritems():
897+
for key, (val, line, cnt) in six.iteritems(rc_temp):
908898
if key in defaultParams:
909899
if fail_on_error:
910900
ret[key] = val # try to convert to proper type or raise
@@ -960,8 +950,8 @@ def rc_params_from_file(fname, fail_on_error=False):
960950

961951
rcParamsOrig = rcParams.copy()
962952

963-
rcParamsDefault = RcParams([ (key, default) for key, (default, converter) in \
964-
defaultParams.iteritems() ])
953+
rcParamsDefault = RcParams([(key, default) for key, (default, converter) in \
954+
six.iteritems(defaultParams)])
965955

966956
rcParams['ps.usedistiller'] = checkdep_ps_distiller(rcParams['ps.usedistiller'])
967957
rcParams['text.usetex'] = checkdep_usetex(rcParams['text.usetex'])
@@ -1033,7 +1023,7 @@ def rc(group, **kwargs):
10331023
if is_string_like(group):
10341024
group = (group,)
10351025
for g in group:
1036-
for k,v in kwargs.iteritems():
1026+
for k, v in six.iteritems(kwargs):
10371027
name = aliases.get(k) or k
10381028
key = '%s.%s' % (g, name)
10391029
try:
@@ -1289,4 +1279,4 @@ def test(verbosity=1):
12891279
verbose.report('verbose.level %s'%verbose.level)
12901280
verbose.report('interactive is %s'%rcParams['interactive'])
12911281
verbose.report('platform is %s'%sys.platform)
1292-
verbose.report('loaded modules: %s'%sys.modules.iterkeys(), 'debug')
1282+
verbose.report('loaded modules: %s'%six.iterkeys(sys.modules), 'debug')

lib/matplotlib/_cm.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Documentation for each is in pyplot.colormaps()
66
"""
77

8-
from __future__ import print_function, division
8+
from __future__ import absolute_import, division, print_function, unicode_literals
99
import numpy as np
1010

1111
_binary_data = {

0 commit comments

Comments
 (0)