Q:

Why Isn't This L-System Code Returning a String?

I'm trying to write a program that takes a string and replaces certain characters with other strings. (A->AB) and (B->A) in this case. When I run it I expect the final string to be returned but instead nothing is returned.
def createSystem(seed,depth):

    startString = seed
    endString = ""

    for i in range(depth): 
        endString = processString(startString)
        startString = endString
    return endString   

def processString(oldstr):
    newstr=""
    for char in oldstr:
        newstr=newstr+applyrules(oldstr)

    return(newstr)

def applyrules(oldstr):
    output=""

    for char in oldstr:
        if char=="A":
            output.join("AB")
        elif char=="B":
            output.join("A")

    return(output)

print(createSystem("AB",1))

In this example I would expect the seed "AB" to produce the string "ABA", however nothing is returned to the console. Why is this? Thanks in Advance! - Eli
EDIT: The program compiles and doesn't produce any errors.

A:

It appears that you expect, for example,
output = ""
output.join("AB")

to mean: "output is an empty string; now cause output to become the result of adding 'AB' to the end of the existing output".
It does not.
It means: "output is an empty string; now make a new string by putting the existing output in between each character of 'AB', and throw that new string away (don't give it a name)."
In Python, strings are immutable - nothing you do with them can change their contents in-place, like with a list. You must use an operation that creates a new string, and reassign the result. Also, the join method is used to take an entire sequence of strings, and join them together, for example:
' '.join(['a', 'b', 'c']) # produces 'a b c'

The code doesn't raise an error because a Python string is also a valid sequence of strings (each one consisting of a single character). This is a special behaviour of strings, not shared by other sequences.
To use that method here, you would need to produce the sequence of 'AB' and 'A' fragments, and just call ''.join (i.e., we put an empty string in between each fragment) with that to get the result directly. We can do this with a generator expression. It looks like this:
def process_string(oldstr):
    return ''.join(
        'AB' if char == 'A' else 'A'
        for char in oldstr
    )

(Notice the naming convention for the function - see PEP 8 for standard style conventions in Python)
That's really all you need. Or you could apply the += logic that you had in mind in the original code, to build up the string a piece at a time (this is less efficient):
def process_string(oldstr):
    newstr = ''
    for char in oldstr:
        newstr += 'AB' if char == 'A' else 'A'
    return newstr # parentheses are meaningless here.

Or you can use the built-in support for this kind of string translation already provided by the string class (but this is more awkward than it ought to be):
def process_string(oldstr):
    return oldstr.translate(str.maketrans({'A': 'AB', 'B': 'A'}))

Here, str.maketrans calls a class method that belongs to the string class (named str and available automatically as a global from startup). You can read about these methods in the language documentation: str.translate; str.maketrans.
It appears that you got confused and tried to do both things. Kudos for trying to put the logic for the transformation rule into a separate function (applyrules), but that function would need to return just the fragment corresponding to a single input character. (After all, you've already set up to iterate over characters, and designed applyrules to accept a single character at a time.) The work that it does is simple enough that - at least for now - a separate function isn't really necessary (unless it helps you understand the code).
In the above examples, I have used a ternary conditional operator to represent the logic of choosing the replacement fragment for each input character. This is necessary for the generator expression approach, because you are writing a single expression and have nowhere to put an if:/else: block.

