Infix to Postfix in Python
A complete infix to postfix converter in Python — the conversion itself, a step-by-step trace for showing your working, and an evaluator that computes the result. Every listing here was run before it was published.
The complete Python program
Save this as infix_to_postfix.py. It needs Python 3.9 or newer
(for the list[str] annotations) and no third-party packages. It
handles multi-character operands, decimals, all six operators and nested
brackets.
"""Convert an infix expression to postfix (Reverse Polish Notation) using a stack."""
PRECEDENCE = {"^": 3, "*": 2, "/": 2, "%": 2, "+": 1, "-": 1}
RIGHT_ASSOCIATIVE = {"^"}
def tokenize(expression: str) -> list[str]:
"""Split an infix string into operands, operators and brackets.
Multi-character operands are kept whole, so "rate * 12" gives
["rate", "*", "12"] rather than one token per character.
"""
tokens: list[str] = []
i = 0
while i < len(expression):
char = expression[i]
if char.isspace():
i += 1
elif char.isalnum() or char in "_.":
start = i
while i < len(expression) and (expression[i].isalnum() or expression[i] in "_."):
i += 1
tokens.append(expression[start:i])
elif char in PRECEDENCE or char in "()":
tokens.append(char)
i += 1
else:
raise ValueError(f"invalid character: {char!r}")
return tokens
def infix_to_postfix(expression: str) -> str:
"""Return the postfix form of an infix expression."""
output: list[str] = []
stack: list[str] = []
for token in tokenize(expression):
# Order matters: test brackets and operators first, so anything
# left over is an operand.
if token == "(":
stack.append(token)
elif token == ")":
while stack and stack[-1] != "(":
output.append(stack.pop())
if not stack:
raise ValueError("unbalanced brackets: an extra ')'")
stack.pop() # discard the matching "("
elif token in PRECEDENCE:
while (
stack
and stack[-1] != "("
and (
PRECEDENCE[stack[-1]] > PRECEDENCE[token]
or (
PRECEDENCE[stack[-1]] == PRECEDENCE[token]
and token not in RIGHT_ASSOCIATIVE
)
)
):
output.append(stack.pop())
stack.append(token)
else:
output.append(token)
while stack:
top = stack.pop()
if top == "(":
raise ValueError("unbalanced brackets: a missing ')'")
output.append(top)
return " ".join(output)
if __name__ == "__main__":
expression = input("Enter an infix expression: ")
try:
print("Postfix expression:", infix_to_postfix(expression))
except ValueError as error:
print("Error:", error)
Run it
python3 infix_to_postfix.py
On Windows the command is usually python infix_to_postfix.py. You can
also import it rather than running it interactively:
>>> from infix_to_postfix import infix_to_postfix
>>> infix_to_postfix("A + B * C")
'A B C * +'
Sample output
Enter an infix expression: A+B*C
Postfix expression: A B C * +
Enter an infix expression: (A+B)*C-D
Postfix expression: A B + C * D -
Enter an infix expression: rate * 12 + 250
Postfix expression: rate 12 * 250 +
Enter an infix expression: (A+B
Error: unbalanced brackets: a missing ')'
Those are the real outputs of the listing above. Run the same expressions through the converter on the homepage to compare against the full stack trace.
How it works
The program is two functions. tokenize() turns the input string into a
list of operands, operators and brackets. infix_to_postfix() runs
Dijkstra's shunting-yard algorithm over that list using an ordinary Python
list as the stack.
| Part | Purpose |
|---|---|
| PRECEDENCE | Maps each operator to a number — and in PRECEDENCE doubles as the "is this an operator?" test |
| RIGHT_ASSOCIATIVE | A set holding only ^ |
| tokenize() | Groups runs of letters, digits, _ and . into whole operands |
| stack.append() | Push |
| stack.pop() | Pop |
| stack[-1] | Peek — reads the top without removing it |
| if not stack | The empty test; an empty list is falsy |
The loop tests "(", then ")", then
token in PRECEDENCE, and treats everything left over as an operand.
Testing for the operand first is tempting but fragile — a check like
token not in "()" uses substring matching, so it behaves
unexpectedly for multi-character tokens. Leaving the operand as the
else branch avoids the question entirely.
The two rules that decide correctness
Everything about whether this program is right or wrong lives in one
while condition.
while (
stack
and stack[-1] != "("
and (
PRECEDENCE[stack[-1]] > PRECEDENCE[token]
or (
PRECEDENCE[stack[-1]] == PRECEDENCE[token]
and token not in RIGHT_ASSOCIATIVE
)
)
):
output.append(stack.pop())
stack.append(token)
-
The
==branch. Equal precedence must also pop, for left-associative operators. Delete it andA - B - CbecomesA B C - -, which meansA - (B - C). -
The
not in RIGHT_ASSOCIATIVEguard. Without it,a ^ b ^ cbecomesa b ^ c ^instead ofa b c ^ ^. -
The
stack[-1] != "("guard. An opening bracket is a floor. Popping must never run past it.
This is the same condition as in the C version and in the pseudocode.
Printing a step-by-step trace
If your assignment asks you to show the stack at each step, this version prints the table for you. It imports the pieces from the program above rather than repeating them.
"""Print the stack and the output after every token, the way an exam answer wants it."""
from infix_to_postfix import PRECEDENCE, RIGHT_ASSOCIATIVE, tokenize
def infix_to_postfix_traced(expression: str) -> list[str]:
"""Convert to postfix, printing one table row per token."""
output: list[str] = []
stack: list[str] = []
print(f"{'TOKEN':<8}{'ACTION':<34}{'STACK':<16}OUTPUT")
print("-" * 78)
def row(token: str, action: str) -> None:
print(f"{token:<8}{action:<34}{' '.join(stack) or '-':<16}{' '.join(output) or '-'}")
for token in tokenize(expression):
if token == "(":
stack.append(token)
row(token, "push as a marker")
elif token == ")":
popped = []
while stack and stack[-1] != "(":
popped.append(stack.pop())
output.extend(popped)
stack.pop()
row(token, f"pop {', '.join(popped)}, drop the (" if popped else "drop the (")
elif token in PRECEDENCE:
popped = []
while (
stack
and stack[-1] != "("
and (
PRECEDENCE[stack[-1]] > PRECEDENCE[token]
or (
PRECEDENCE[stack[-1]] == PRECEDENCE[token]
and token not in RIGHT_ASSOCIATIVE
)
)
):
popped.append(stack.pop())
output.extend(popped)
stack.append(token)
row(token, f"pop {', '.join(popped)}, push {token}" if popped else f"push {token}")
else:
output.append(token)
row(token, "operand, straight to output")
tail = stack[::-1]
output.extend(tail)
stack.clear()
row("end", f"pop {', '.join(tail)}" if tail else "nothing left")
return output
if __name__ == "__main__":
result = infix_to_postfix_traced("A + B * C - (D / E)")
print("\nPostfix:", " ".join(result))
Running it prints:
TOKEN ACTION STACK OUTPUT
------------------------------------------------------------------------------
A operand, straight to output - A
+ push + + A
B operand, straight to output + A B
* push * + * A B
C operand, straight to output + * A B C
- pop *, +, push - - A B C * +
( push as a marker - ( A B C * +
D operand, straight to output - ( A B C * + D
/ push / - ( / A B C * + D
E operand, straight to output - ( / A B C * + D E
) pop /, drop the ( - A B C * + D E /
end pop - - A B C * + D E / -
Postfix: A B C * + D E / -
Evaluating the postfix result
Once an expression is in postfix, evaluating it takes a second stack and one pass: push operands, and on each operator pop two values, apply it, push the result.
"""Evaluate a postfix expression with a second stack."""
import operator
from infix_to_postfix import infix_to_postfix
OPERATIONS = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.truediv,
"%": operator.mod,
"^": operator.pow,
}
def evaluate_postfix(postfix: str) -> float:
"""Push operands; on each operator pop two values and push the result."""
stack: list[float] = []
for token in postfix.split():
if token in OPERATIONS:
if len(stack) < 2:
raise ValueError(f"not enough operands for '{token}'")
right = stack.pop()
left = stack.pop()
stack.append(OPERATIONS[token](left, right))
else:
stack.append(float(token))
if len(stack) != 1:
raise ValueError("malformed postfix expression")
return stack[0]
if __name__ == "__main__":
for infix in ["2 + 3 * 4", "(2 + 3) * 4", "3 + 4 * 2 / (1 - 5) ^ 2", "2 ^ 3 ^ 2", "100 / 5 / 2"]:
postfix = infix_to_postfix(infix)
print(f"{infix:<26} -> {postfix:<24} = {evaluate_postfix(postfix):g}")
2 + 3 * 4 -> 2 3 4 * + = 14
(2 + 3) * 4 -> 2 3 + 4 * = 20
3 + 4 * 2 / (1 - 5) ^ 2 -> 3 4 2 * 1 5 - 2 ^ / + = 3.5
2 ^ 3 ^ 2 -> 2 3 2 ^ ^ = 512
100 / 5 / 2 -> 100 5 / 2 / = 10
right = stack.pop() comes before
left = stack.pop(). The second value popped is the left operand,
because it was pushed first. Swap those two lines and addition still works but
subtraction, division and exponentiation all silently invert —
2 3 2 ^ ^ would give 81 instead of 512.
Notes for Python specifically
-
A list is already a stack.
append()andpop()are both O(1) at the end. There is no reason to write a Stack class, andcollections.dequebuys you nothing here. -
Do not use
pop(0). That pops the front, which is a queue, not a stack, and it is O(n). -
stack[-1]is peek. It raisesIndexErroron an empty list, which is why every use is guarded bystack and …; Python'sandshort-circuits, so the index is never evaluated when the stack is empty. -
Do not use
eval(). It is the usual shortcut for "evaluate this expression" and it executes arbitrary code. The postfix evaluator above is a dozen lines and cannot run anything. -
str.isalnum()is Unicode-aware. It returns True for letters beyond ASCII, socaféis accepted as one operand. That is usually what you want; if you need ASCII only, test againststring.ascii_lettersinstead.
Errors you are likely to hit
| Symptom | Cause | Fix |
|---|---|---|
| IndexError: pop from empty list | Peeking or popping without checking the stack first | Guard with while stack and … |
| KeyError: '(' | Looking up a bracket in PRECEDENCE | Keep the stack[-1] != "(" test before the lookup |
A-B-C gives A B C - - | Equal precedence pushes instead of popping | Add the == branch |
a^b^c gives a b ^ c ^ | ^ treated as left associative | Add the RIGHT_ASSOCIATIVE guard |
12+3 gives 1 2 3 + | Iterating over characters instead of tokens | Use tokenize(), which groups digit runs |
| Result is a list, not a string | output is a list of tokens | " ".join(output) |
2 ^ 3 ^ 2 evaluates to 81 | Operands popped in the wrong order | Pop the right operand first |
Frequently asked questions
How do you convert infix to postfix in Python?
Use a list as a stack. Scan the tokens left to right: append operands to the
output, push (, and on ) pop to the output until the
matching (. For an operator, pop while the top of the stack has
higher precedence — or equal precedence and the incoming operator is left
associative — then push it. Finally pop whatever remains and
" ".join() the output.
Which data structure should I use for the stack in Python?
A plain list. append() and pop() operate
on the end in O(1), and stack[-1] is the peek. A custom Stack class
adds nothing, and collections.deque is only worth it when you also
need fast operations at the front, which this algorithm never does.
Does this handle multi-digit numbers and variable names?
Yes. tokenize() consumes a whole run of alphanumeric characters,
underscores and dots as a single operand, so rate * 12 + 250 and
3.5 * 2 both work. Programs that loop over the string one character
at a time break on these.
Can I use eval() instead?
Not safely. eval() executes whatever it is given, so any expression
coming from a user is a code-execution hole. It also cannot show you the postfix
form or the stack, which is the entire point of the exercise.
How do I convert infix to prefix in Python instead?
Reverse the token list and swap every bracket, run the same routine with the associativity test flipped so left-associative operators pop only on strictly higher precedence, then reverse the result.
What Python version does this need?
Python 3.9 or newer, only because of the list[str] annotations.
To run it on 3.7 or 3.8, either delete the annotations or add
from __future__ import annotations at the top of the file.
Related
std::stack, plus a postfix evaluator using fmod and pow.
The same converter in JavaArrayDeque as the stack, plus a postfix evaluator.
Check your outputRun the same expressions through the converter and compare the stack trace.
The same converter in CAn array stack and a linked-list variant, both compile-tested.
The algorithm behind the codePseudocode, the precedence table and the complexity analysis.