Skip to main content

Toolbox for working with the Python AST

Project description

Build Status Coverage Status

Toolbox for working with the Python AST

pip install ast_tools

Useful References

Passes

ast_tools provides a number of passes for rewriting function and classes (could also work at the module level however no such pass exists). Passes can be applied in one of two ways:

@apply_ast_passes([pass1(), pass2()])
def foo(...): ...

@end_rewrite()
@pass2()
@pass1()
@begin_rewrite()
def foo(...): ...

Each pass takes as arguments an AST, an environment, and metadata and returns (possibly) modified versions of each. apply_ast_pass and begin_rewrite begin a chain of rewrites by first looking up the ast of the decorated object and gather attempts to gather locals and globals from the call site to build the environment.

After all rewrites have run apply_ast_pass / end_rewrite serialize and execute the rewritten ast.

Know Issues

Collecting the AST

apply_ast_pass and begin_rewrite rely on inspect.getsource to get the source of the decorated definition (which is then parsed to get the initial ast). However, inspect.getsource has many limitations.

Collecting the Environment

apply_ast_pass and begin_rewrite do there best to infer the environment however there is no way to do this in a fully correct way. Users are encouraged to pass environment explicitly:

@apply_ast_passes(..., env=SymbolTable(locals(), globals()))
def foo(...): ...

@begin_rewrite(env=SymbolTable(locals(), globals()))
def foo(...): ...

Wrapping terminal passes

Terminal passes begin_rewrite / end_rewrite / apply_ast_passes must not be wrapped.

As decorators are a part of the AST of the object they are applied to they must be removed from the rewritten AST before it is executed. If they are not removed rewrites will recurse infinitely as

@apply_ast_passes([...])
def foo(...): ...

would become

exec('''\
@apply_ast_passes([...])
def rewritten_foo(...): ...
''')

Note: this would invoke apply_ast_passes([...]) on rewritten_foo

To avoid this the terminating pass filters itself (and other decorators in the rewrite group in the begin / end style) from the decorator list. If however the terminals are wrapped this filter will fail.

Inner decorators are called multiple times

Decorators that are applied before a rewrite group will be called multiple times. See https://github.com/leonardt/ast_tools/issues/46 for detailed explanation. To avoid this users are encouraged to make rewrites the inner most decorators when possible.

Macros

Loop Unrolling

Unroll loops using the pattern

for <var> in ast_tools.macros.unroll(<iter>):
    ...

<iter> should be an iterable object that produces integers (e.g. range(8)) that can be evaluated at definition time (can refer to variables in the scope of the function definition)

For example,

from ast_tools.passes import begin_rewrite, loop_unroll, end_rewrite

@end_rewrite()
@loop_unroll()
@begin_rewrite()
def foo():
    for i in ast_tools.macros.unroll(range(8)):
        print(i)

is rewritten into

def foo():
    print(0)
    print(1)
    print(2)
    print(3)
    print(4)
    print(5)
    print(6)
    print(7)

You can also use a list of ints, here's an example that also uses a reference to a variable defined in the outer scope:

from ast_tools.passes import apply_ast_passes, loop_unroll

j = [1, 2, 3]
@apply_ast_passes([loop_unroll()])
def foo():
    for i in ast_tools.macros.unroll(j):
        print(i)

becomes

def foo():
    print(1)
    print(2)
    print(3)

Inlining If Statements

This macro allows you to evaluate if statements at function definition time, so the resulting rewritten function will have the if statements marked "inlined" removed from the final code and replaced with the chosen branch based on evaluating the condition in the definition's enclosing scope. if statements are marked by using the form if inline(...): where inline is imported from the ast_tools.macros package. if statements not matching this pattern will be ignored by the rewrite logic.

Here's an example

from ast_tools.macros import inline
from ast_tools.passes import apply_ast_passes, if_inline

y = True

@apply_ast_passes([if_inline()])
def foo(x):
    if inline(y):
        return x + 1
    else:
        return x - 1


import inspect
assert inspect.getsource(foo) == f"""\
def foo(x):
    return x + 1
"""

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

ast_tools-0.1.2-py38-none-any.whl (38.2 kB view details)

Uploaded Python 3.8

ast_tools-0.1.2-py37-none-any.whl (38.0 kB view details)

Uploaded Python 3.7

File details

Details for the file ast_tools-0.1.2-py38-none-any.whl.

File metadata

  • Download URL: ast_tools-0.1.2-py38-none-any.whl
  • Upload date:
  • Size: 38.2 kB
  • Tags: Python 3.8
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/53.0.0 requests-toolbelt/0.9.1 tqdm/4.56.0 CPython/3.8.7

File hashes

Hashes for ast_tools-0.1.2-py38-none-any.whl
Algorithm Hash digest
SHA256 0f9b77b11ecb4c2e0c1eed9a4d9385d38f55bacdb1240bbd4972658dec5b9733
MD5 77e835b1694e3b9665ce80d81dca2664
BLAKE2b-256 9e5fea7765eace89e716a0a2384e21371c7f4325d87889a75956ba2ae4334326

See more details on using hashes here.

File details

Details for the file ast_tools-0.1.2-py37-none-any.whl.

File metadata

  • Download URL: ast_tools-0.1.2-py37-none-any.whl
  • Upload date:
  • Size: 38.0 kB
  • Tags: Python 3.7
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/53.0.0 requests-toolbelt/0.9.1 tqdm/4.56.0 CPython/3.7.1

File hashes

Hashes for ast_tools-0.1.2-py37-none-any.whl
Algorithm Hash digest
SHA256 0c3201f5c1d8752b808f7acc2a481b73d1111af6399788cda11ef71c84b3691d
MD5 3ceb99bc9bb7d93cbb5ce7ae948c05fa
BLAKE2b-256 b4aae6bcc849e83efa68b3fbb3bfbb135d0f002b2dabd26b9c8f36eeb0c357c6

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page