Pattern matching
match tries each case from top to bottom and runs the first one that fits. A pattern can test shape and type and pull out parts in the same step.
def describe(cmd):
match cmd:
case ["go", direction]:
return f"going {direction}"
case ["take", *items] if items:
return f"taking {len(items)}"
case {"say": str(text)}:
return f"saying {text}"
case int() | float() as n:
return f"number {n}"
case _:
return "unknown"
print(describe(["go", "north"]))
print(describe(["take", "a", "b"]))
print(describe({"say": "hi", "to": "x"}))
print(describe(3.5), describe("go"))- A sequence pattern checks the length, and
*restcollects the remainder. Strings never match sequence patterns. - A mapping pattern checks only the keys it names, so extra keys are fine.
str(text)checks the type and binds the value.Point(x=0, y=y)matches the attributes of an object.|tries alternatives,ifadds a guard, andasnames the whole match.- A bare name always matches and binds. To compare with a constant, use a literal or a dotted name such as
Color.RED. case _:catches everything else.
Your task
Write evaluate(expr, env) for small expression trees: an int or float is itself, ("neg", x) is minus x, ("add", a, b) and ("mul", a, b) combine their evaluated parts, and {"var": name} is env[name]. Anything else raises ValueError.
The explanation opens here once every check passes.