Small Python class for Lindenmayer Systems
This is a neat implementation of Lindenmayer systems. I have some suggestions for simplifying and organizing the code.


The docstring for a method or a function comes after the def line (not before, as in the code here). So you need something like:

def evaluate(self, depth):
    """Evaluate system by recursively applying the rules on the axiom."""
    for symbol in self.axiom:
        self.evaluate_symbol(symbol, depth)


and then you can use the help function from the interactive interpreter:

&gt;&gt;&gt; help(LSystem.evaluate)
Help on function evaluate in module LSystem:

evaluate(self, depth)
    Evaluate system by recursively applying the rules on the axiom.

The Symbol class is redundant — it only has one attribute, and doesn't have any methods other than the constructor. Instead of constructing Symbol objects, you could just use functions:

def A():
    print('A', end='')

def B():
    print('B', end='')


and instead of calling symbol.leaf_function(), you could just call symbol().

In the Koch example, you already have functions so you can just omit the construction of the Symbol objects and write:

koch_curve_system = LSystem(
    axiom = [draw_forward, turn_right, turn_right, draw_forward, turn_right,
             turn_right, draw_forward],
    rules = {
        draw_forward: [draw_forward, turn_left, draw_forward,
                       turn_right, turn_right, draw_forward, turn_left,
                       draw_forward],
    }
)


Alternatively, you could rename the functions and leave the definition of the system unchanged.
The code in evaluate is very similar to the code in evaluate_symbol. This suggests that it would result in simpler code if you described the Lindemayer system in a different way, giving an initial symbol instead of an initial list of symbols. (And possibly giving an extra rule mapping the initial symbol to a list.)

If you try this, then you'll find that the LSystem class is redundant too: the only thing you can do with it is to call its evaluate method, so you might as well just write it as a function:

def evaluate_lsystem(symbol, rules, depth):
    """Evaluate a Lindenmayer system.

    symbol: initial symbol.
    rules: rules for evolution of the system, in the form of a
        dictionary mapping a symbol to a list of symbols. Symbols
        should be represented as functions taking no arguments.
    depth: depth at which to call the symbols.

    """
    if depth &lt;= 0 or symbol not in rules:
        symbol()
    else:
        for produced_symbol in rules[symbol]:
            evaluate_lsystem(produced_symbol, rules, depth - 1)


Then the algae example becomes:

evaluate_lsystem(A, {A: [A, B], B: [A]}, 4)

In the snowflake example, there is persistent shared state (the position and heading of the turtle). When you have persistent shared state it makes sense to define a class, something like this:

class Turtle:
    """A drawing context with a position and a heading."""
    angle = 0
    x = 0
    y = WINDOW_SIZE[1]*3/4

    def forward(self, distance):
        """Move forward by distance."""
        start = [self.x, self.y]
        self.x += distance * cos(self.angle)
        self.y += distance * sin(self.angle)
        end = [self.x, self.y ]
        pygame.draw.line(window, LINE_COLOR, start, end, LINE_WIDTH)

    def turn(self, angle):
        """Turn left by angle."""
        self.angle += angle


and then:

turtle = Turtle()
forward = lambda: turtle.forward(1)
left = lambda: turtle.turn(pi/3)
right = lambda: turtle.turn(-pi/3)
initial = lambda: None
rules = {
    initial: [forward, right, right, forward, right, right, forward],
    forward: [forward, left, forward, right, right, forward, left, forward],
}
evaluate_lsystem(initial, rules, 5)