Documentation
The PEP 8 style guide recommends adding docstrings for functions. You can convert the function comment:
def getExpression(level, operator):
# Generate operands based on the level
into a docstring:
def getExpression(level, operator):
""" Generate operands based on the level """
It would also be good to add details about the input types and return types.
Naming
PEP 8 recommends snake_case for function names.
getExpression would be get_expression
Validation
Unless the code that calls your function makes sure that the
inputs are valid, consider adding some simple checking to the function.
For example, you should think about how to handle unexpected values
passed to the level parameter. From the code, you expect it to be
an integer between 1 and 5. If the function is passed an integer
outside that range (like 6), the code exits with an error. The same
is true if passed a floating-point value (2.1) or a string ("hello").
DRY
There is a lot of repeated code. For example, for the addition
operator, you can take advantage of the relationship between
the level and the upper and lower limits of the values passed
to randint. For levels 2 and above, you can replace the hard-coded
values (10, 99, 100, 999, etc.) with function calls, where you declare
new functions:
def lower_limit(level):
""" Return lower limit as a power of 10 """
return 10**(level - 1)
def upper_limit(level):
""" Return lower limit as a power of 10, minus 1 """
return (10**level) - 1
This reduces the addition code to:
if operator == "+":
# Addition
if level == 1:
num1 = random.randint(1, 9) # Single digit operand
num2 = random.randint(1, 9)
else:
num1 = random.randint(lower_limit(level), upper_limit(level))
num2 = random.randint(lower_limit(level), upper_limit(level))
# Return the expression and result
result = num1 + num2
expression = f"{num1} + {num2} = "
This eliminates much of the repetition, and it allows the code to scale
to more values of level. These functions can also be used with the other operators.