Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Abusing Type Annotations

Rust-like "macros" in Python via egregious abuse of type annotations.


This project started out as an exploration into whether Rust-like macros could be brought to Python. Spoiler: They can be (but it's not pretty). Rust has more than one kind of macro, but "procedural" macros operate on the abstract syntax tree of your code (an abstract syntax tree is a data structure that represents your parsed code). This allows you to generate code or derive functionality provided by a library with very little effort on your part. It's pretty great.

The code in this repository is just a proof of concept, so you probably shouldn't use it for anything mission critical. If you want to read about what's going on here, how it works, etc, you can read the blog post:

Example 1 - Generating Instance Properties

I fully committed to the Rust way of doing things for this example, and worked on ASTs the whole way through. This example provides a decorator, @inrange, that generates properties based on the annotations of class variables.

The @inrange macro would take this class definition

class MyClass:
    var: "0 < var < 1"

and turn it into

class MyClass:
    var: "0 < var < 1"

    def __init__(self):
        self._var = None

    def var(self):
        return self._var

    def var(self, new_value):
        if (0 < new_value) and (new_value < 1):
            self._var = new_value

In this example the getter, setter, and __init__ are all constructed as ASTs, then compiled to functions, then bound to the class. I do not recommend this.

Example 2 - Notify on Write

The second example prints a notification to the terminal when writing to a variable that you've marked with a certain annotation. For example:

class MyClass:
    def __init__(self, x):
        self.x: "this one" = x
        self.y = True

This would print a message to the terminal whenever you try to assign a new value to foo.x (where foo is an instance of MyClass).

This example is much less AST wrangling than the first example, but the AST is still used to determine which fields are marked with the "this one" annotation. To intercept writes to the the variables, the class's __setattr__ method is overridden with one that will print messages before setting the new value.


Licensed under either of

at your option.


Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.