the python future statement from __future__ import feature
provides nice way ease transition new language features. is possible implement similar feature python libraries: from myproject.__future__ import feature
?
it's straightforward set module wide constants on import statement. isn't obvious me how ensure these constants don't propagate code executed in imported modules -- should require future import enable new feature.
this came in discussion of possible indexing changes in numpy. don't expect used in numpy, can see being useful other projects.
as concrete example, suppose want change how indexing works in future version of numpy. backwards incompatible change, decide use future statement ease transition. script using new feature looks this:
import numpy np numpy.__future__ import orthogonal_indexing x = np.random.randn(5, 5) print(x[[0, 1], [0, 1]]) # should use "orthogonal indexing" feature # prints 2x2 array of random numbers # want use legacy project uses indexing, # hasn't been updated use "orthogonal indexing" feature legacy_project import do_something do_something(x) # should *not* use "orthogonal indexing"
if isn't possible, what's closest can enabling local options? example, possible write like:
from numpy import future future.enable_orthogonal_indexing()
using context manager fine, problem don't want propagate options nested scopes:
with numpy.future.enable_orthogonal_indexing(): print(x[[0, 1], [0, 1]]) # should use "orthogonal indexing" feature do_something(x) # should *not* use "orthogonal indexing" inside do_something
the way python pretty simple:
in importer, when try import .py
file, code first scans module future statements.
note things allowed before future statement strings, comments, blank lines, , other future statements, means doesn't need parse code this. that's important, because future statements can change way code parsed (in fact, that's whole point of having them…); strings, comments, , blank lines can handled lexer step, , future statements can parsed simple special-purpose parser.
then, if future statements found, python sets corresponding flag bit, re-seeks top of file , calls compile
flags. example, from __future__ import unicode_literals
, flags |= __future__.unicode_literals.compiler_flag
, changes flags
0
0x20000
.
in "real compile" step, future statements treated normal imports, , end __future__._feature
value in variable unicode_literals
in module's globals.
now, can't quite same thing, because you're not going reimplement or wrap compiler. can use future-like statements signal ast transform step. this:
flags = [] line in f: flag = parse_future(line) if flag none: break flags.append(flag) f.seek(0) contents = f.read() tree = ast.parse(contents, f.name) flag in flags: tree = transformers[flag](tree) code = compile(tree, f.name)
of course have write parse_future
function return 0 blank line, comment, or string, flag recognized future import (which can dynamically if want), or none
else. , have write ast transformers each flag. can pretty simple—e.g., can transform subscript
nodes different subscript
nodes, or call
nodes call different functions based on form of index.
to hook import system, see pep 302. note gets simpler in python 3.3, , simpler again in python 3.4, if can require 1 of versions, instead read import system docs minimum version.
for great example of import hooks , ast transformers being used in real life, see macropy. (note it's using old 2.3-style import hook mechanism; again, own code can simpler if can use 3.3+ or 3.4+. , of course code isn't generating transforms dynamically, complicated part of macropy…)