Skip to content

Commit 7f2a9d0

Browse files
committed
Improve __init__.py: no namespace pollution
Also refactor and improve tests
1 parent 4229699 commit 7f2a9d0

8 files changed

Lines changed: 182 additions & 111 deletions

File tree

future/__init__.py

Lines changed: 108 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@
1515
from __future__ import (division, absolute_import, print_function,
1616
unicode_literals)
1717
from future import *
18-
18+
1919
followed by clean Python 3 code (with a few restrictions) that can run
20-
unchanged on Python 2.7 or Python 3.3. For example::
20+
unchanged on Python 2.7.
21+
22+
On Python 3, the ``from future import *`` line has no effect (i.e. no
23+
namespace pollution.) On Python 2 it shadows builtins to provide the
24+
Python 3 semantics. (See below for the explicit import form.)
2125
22-
# New range object with slicing support
26+
For example::
27+
28+
# New iterable range object with slicing support
2329
for i in range(10**11)[:10]:
2430
pass
2531
2632
# Other common iterators: map, reduce, zip
27-
iter = zip(range(3), ['a', 'b', 'c'])
28-
assert iter != list(iter)
33+
my_iter = zip(range(3), ['a', 'b', 'c'])
34+
assert my_iter != list(my_iter)
2935
3036
# New simpler super() function:
3137
class VerboseList(list):
@@ -39,25 +45,31 @@ def append(self, item):
3945
# This identity is restored. This is normally valid on Py3 and Py2, but
4046
# 'from __future__ import unicode_literals' breaks it on Py2:
4147
assert isinstance('happy', str)
42-
43-
See below for more explicit forms of the import line.
44-
45-
Another feature offered is support for the standard library
46-
reorganization (PEP 3108)::
47-
48+
49+
# The round() function behaves as it does in Python 3, using "Banker's
50+
# Rounding" to the nearest even last digit:
51+
assert round(0.1250, 2) == 0.12
52+
53+
# input() is now safe (no eval()):
54+
name = input('What is your name?\n')
55+
print('Hello ' + name)
56+
57+
``future`` also supports the standard library reorganization (PEP 3108)
58+
via import hooks, allowing standard library modules to be accessed under
59+
their Python 3 names and locations::
60+
4861
from future import standard_library_renames
49-
62+
5063
import socketserver
5164
import queue
5265
import configparser
53-
# etc.
66+
# and other moved modules
5467
55-
and other modules renamed for Python 3.
56-
57-
The above * import is equivalent to::
68+
If you prefer explicit imports, the explicit equivalent of the ``from
69+
future import *`` line above is::
5870
5971
from future.common_iterators import zip, map, filter
60-
from future.features import range, super
72+
from future.modified_builtins import (range, super, round, input)
6173
from future.disable_obsolete_builtins import (apply, cmp, coerce,
6274
execfile, file, long, raw_input, reduce, reload, unicode,
6375
xrange, StandardError)
@@ -68,7 +80,7 @@ def append(self, item):
6880
6981
- future.standard_library_renames
7082
- future.common_iterators
71-
- future.features
83+
- future.modified_builtins
7284
- future.disable_obsolete_builtins
7385
- future.str_as_unicode
7486
@@ -126,9 +138,17 @@ def append(self, item):
126138
127139
:Q: Why is there a need for this?
128140
129-
:A: To reduce cruft in single-source codebases that support both Python 2
130-
and 3.
141+
:A: "Python 2 is the next COBOL." - Alex Gaynor, at PyCon AU 2013
142+
143+
Python 3.3 is a better language and better set of standard libraries
144+
than Python 2.x in almost every way.
131145
146+
``future`` helps you to take advantage of the cleaner syntax and
147+
semantics of Python 3 code today while still supporting Python 2.
148+
149+
The goal is to encourage writing future-proof code while still
150+
supporting the platform of today.
151+
132152
133153
Other compatibility tools
134154
-------------------------
@@ -174,25 +194,6 @@ def greet(name):
174194
http://www.youtube.com/watch?v=xNZ4OVO2Z_E.)
175195
176196
177-
:Q: What is the relationship between this project and ``python-modernize``?
178-
179-
:A: ``python-modernize`` is great, and this project is designed to
180-
complement it. For a project wishing to migrate to Python 3,
181-
python-modernize is useful for starting the process of cleaning up
182-
legacy code idioms which would cause SyntaxErrors on Python 3. The
183-
output of ``python-modernize`` should hopefully be a valid common
184-
subset of Python 3 and Python 2 that should run under either
185-
platform.
186-
187-
However, the output of ``python-modernize`` is not clean Python 3
188-
code; it requires that code contain various backward-compatibility
189-
warts and a runtime dependency on the six module.
190-
191-
``future`` goes further in allowing either the output of
192-
``python-modernize`` or hand-written Python 3 code to run with less
193-
work and and less backward-compatible cruft on Python 2.
194-
195-
196197
:Q: What is the relationship between this project and ``six``?
197198
198199
:A: ``future`` is a higher-level interface that incorporates the ``six``
@@ -201,26 +202,43 @@ def greet(name):
201202
the interface they offer, the Python versions they target, and the
202203
extent of the support they offer for new Python 3 features.
203204
204-
Codebases that use ``six`` are sometimes standard Python 3 code,
205-
sometimes Python 2 code, and sometimes neither (``six``-specific
206-
wrapper interfaces).
205+
Although ``six`` is a remarkable achievement -- making it possible to
206+
write a single-source codebase that runs on both Python 2 and Python
207+
3 -- codebases that use ``six`` directly tend to be mixtures of
208+
Python 2 code, Python 3 code, and ``six``-specific wrapper
209+
interfaces. In practice it often looks like this::
207210
208-
Here is a simple example of code compatible with both Python 2 and
209-
Python 3 using ``six``::
211+
from sklearn.externals.six.moves import (cStringIO as StringIO,
212+
xrange)
213+
214+
for i, (k, v) in enumerate(sorted(six.iteritems(params))):
215+
# ...
216+
217+
if six.PY3:
218+
exec(open('setup.py').read(), {'__name__'='__main__'})
219+
else:
220+
execfile('setup.py', {'__name__'='__main__'})
210221
211-
from six.moves import xrange
212-
for i in xrange(10**10): # non-standard Python 3 code
222+
for i in xrange(10**10): # non-standard Python 3
213223
pass
214224
215-
Here is the corresponding example using the ``future`` module::
225+
226+
This is crufty and non-standard Python 3 code that puts a maintenance
227+
burden on the code to support Python 2 indefinitely.
228+
229+
Here is the equivalent code using the ``future`` module::
230+
231+
from future import standard_library_renames, range
232+
233+
for i, (k, v) in enumerate(sorted(params.items())):
234+
# ...
235+
236+
exec(open('setup.py').read(), {'__name__'='__main__'})
216237
217-
from future.features import range
218238
for i in range(10**10): # standard Python 3
219239
pass
220240
221-
Note that the former introduces the obsolete xrange() back into the
222-
codebase in order to offer backward compatibility for Python 2. The
223-
latter example is standard Python 3 code, with an import line that
241+
This is standard Python 3 code, with an import line that
224242
has no effect on Python 3.
225243
226244
Another difference is version support: ``future`` supports only
@@ -230,10 +248,25 @@ def greet(name):
230248
functions) are superseded by features introduced in Python 2.6 or
231249
2.7.
232250
233-
The final difference is that ``future`` offers some backported features
251+
The final difference is in scope: ``future`` offers more backported features
234252
from Python 3, including the improved no-argument super() function,
235-
and the new range object (with slicing support). More backported
236-
features will be added in the future.
253+
the new range object (with slicing support), rounding behaviour, etc.
254+
More backported features will be added in the future. This should
255+
reduce the burden on every project to roll its own py3k compatibility
256+
wrapper module.
257+
258+
:Q: What is the relationship between this project and ``python-modernize``?
259+
260+
:A: For a project wishing to migrate to Python 3, python-modernize is
261+
very useful for starting the process of cleaning up legacy code
262+
idioms which would cause SyntaxErrors on Python 3. The output of
263+
``python-modernize`` should hopefully be a valid common subset of
264+
Python 3 and Python 2 that should run under either platform.
265+
266+
Currently, python-modernize produces code with a run-time dependency
267+
on ``six`` (see above). We will aim to provide an alternative set of
268+
fixes for ``python-modernize`` to produce cleaner Python 3 code using
269+
``future`` as an alternative depencency to ``six``.
237270
238271
239272
:Q: How did the original need for this arise?
@@ -254,7 +287,7 @@ def greet(name):
254287
for at least the next 5 years, one of the promised benefits of Python
255288
3 -- cleaner code with fewer of Python 2's warts -- was difficult to
256289
realise before in practice in a single codebase that supported both
257-
versions.
290+
platforms.
258291
259292
260293
:Q: Do you support Pypy?
@@ -271,23 +304,35 @@ def greet(name):
271304
272305
:A: Yes, we welcome bug reports, tests, and pull requests.
273306
307+
274308
"""
275309

276310
from __future__ import (division, absolute_import, print_function)
277311

278-
from future.common_iterators import *
279-
from future.features import *
280-
from future.disable_obsolete_builtins import *
281-
from future.str_is_unicode import *
312+
from future import six
313+
314+
if not six.PY3:
315+
from future.common_iterators import (filter, map, zip)
316+
from future.disable_obsolete_builtins import (apply, cmp, coerce,
317+
execfile, file, long, raw_input, reduce, reload, unicode,
318+
xrange, StandardError)
319+
from future.modified_builtins import (round, input, range, super)
320+
from future.str_is_unicode import str # not python_2_unicode_compatible
321+
322+
# Only shadow builtins on Py2; no new names
323+
__all__ = ['filter', 'map', 'zip', 'apply', 'cmp', 'coerce', 'execfile',
324+
'file', 'long', 'raw_input', 'reduce', 'reload', 'unicode',
325+
'xrange', 'StandardError', 'round', 'input', 'range', 'super',
326+
'str']
282327

328+
else:
329+
# No namespace pollution on Py3
330+
__all__ = []
283331

284332
__ver_major__ = 0
285-
__ver_minor__ = 1
333+
__ver_minor__ = 2
286334
__ver_patch__ = 0
287335
__ver_sub__ = ''
288336
__version__ = "%d.%d.%d%s" % (__ver_major__,__ver_minor__,__ver_patch__,__ver_sub__)
289-
VERSION = __version__
290337

291-
# __all__ = ['disable_obsolete_builtins', 'common_iterators', 'str_is_unicode',
292-
# 'standard_library_renames', 'features']
293338

future/common_iterators.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,36 @@
11
"""
2-
This module is designed to be used as follows:
2+
This module is designed to be used as follows::
33
44
from __future__ import division, absolute_import, print_function
55
from future.common_iterators import *
66
7-
And then, for example:
7+
And then, for example::
88
99
for i in range(10**10):
1010
pass
11-
11+
1212
for (a, b) in zip(range(10**10), range(-10**10, 0)):
1313
pass
1414
1515
Note that this is standard Python 3 code, plus some imports that do
1616
nothing on Python 3.
1717
18-
The iterators this brings in are:
18+
The iterators this brings in are::
19+
1920
- range
2021
- filter
2122
- map
2223
- zip
2324
2425
range is equivalent to xrange on Python 2 by default. As an alternative,
2526
there is a pure Python backport of Python 3's range iterator available
26-
with slicing support. To use it, add:
27+
with slicing support. To use it, add::
2728
28-
from future.features import range
29+
from future.modified_builtins import range
2930
3031
The other iterators (filter, map, zip) are from the itertools module on
31-
Python 2.
32+
Python 2. Note that these are also available in the ``future_builtins``
33+
module on Python 2 (but not Python 3).
3234
"""
3335

3436
from __future__ import division, absolute_import, print_function

future/disable_obsolete_builtins.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,36 @@
1313
1414
The following functions are disabled:
1515
16-
apply, cmp, coerce, execfile, file, long,
16+
apply, cmp, coerce, execfile, file, input, long,
1717
raw_input, reduce, reload, unicode, xrange
1818
19-
and this exception class:
19+
Note that both input() and raw_input() are disabled. Although input()
20+
exists as a builtin in Python 3, the Python 2 input() builtin is unsafe
21+
to use because it can lead to shell injection. Therefore we shadow it by
22+
default, in case someone forgets to import our replacement input()
23+
somehow and expects Python 3 semantics.
2024
21-
StandardError
25+
Fortunately, input() seems to be seldom used in the wild in Python 2, so
26+
we could probably even delete it from __builtin__... TODO: test this.
27+
28+
(Note that callable() is not among the functions disabled; this was
29+
reintroduced into Python 3.2.)
2230
23-
(Note that callable() is not among them; this was reintroduced into
24-
Python 3.2.)
31+
This exception class is also disabled:
32+
33+
StandardError
2534
26-
Also to do:
27-
- Fix round()
28-
- Fix input()
29-
- Fix int()
3035
"""
3136

3237
from __future__ import division, absolute_import, print_function
3338

3439
import inspect
3540

36-
from . import six
41+
from future import six
3742

38-
OBSOLETE_BUILTINS = ['apply', 'cmp', 'coerce', 'execfile', 'file', 'long',
39-
'raw_input', 'reduce', 'reload', 'unicode', 'xrange',
40-
'StandardError']
43+
OBSOLETE_BUILTINS = ['apply', 'cmp', 'coerce', 'execfile', 'file',
44+
'input', 'long', 'raw_input', 'reduce', 'reload',
45+
'unicode', 'xrange', 'StandardError']
4146

4247

4348
def disabled_function(name):
@@ -54,8 +59,9 @@ def disabled(*args, **kwargs):
5459

5560

5661
if not six.PY3:
57-
caller = inspect.currentframe().f_back
62+
# caller = inspect.currentframe().f_back
5863

5964
for fname in OBSOLETE_BUILTINS:
60-
caller.f_globals.__setitem__(fname, disabled_function(fname))
65+
# caller.f_globals.__setitem__(fname, disabled_function(fname))
66+
locals()[fname] = disabled_function(fname)
6167

future/modified_builtins/newsuper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
behaviour of super() in Python 3. It is designed to be used as follows:
44
55
from __future__ import division, absolute_import, print_function
6-
from future.features import super
6+
from future.modified_builtins import super
77
88
And then, for example:
99

0 commit comments

Comments
 (0)