For a non-recursive solution, you can break down the problem in simpler components:
- tokenizing expressions into a partial RPN stack for specific operators
 
- building the RPN notation by successive refinement (using the tokenizer) until only values and operators remain
 
- executing the RPN stack to get the final result
 
The tokenize() function breaks down an expression into first level tokens for a given set of equal-precedence operators.  This returns a mini-RPN stack (only for those operators) with operands being any string between these operators.  Tokens that are enclosed in parentheses will be processed on a next refinement pass (i.e. by removing the outermost parentheses)
def tokenize(exp,opers):
    result = []
    token,operator,enclosed,pLevel  = "","",True,0
    for c in exp+opers[0]: # extra operator to force out last operation
        if c in opers and token and pLevel == 0:
            if enclosed: token = token[1:-1] # remove enclosing parentheses
            result.append(token)
            if operator: result.append(operator)
            operator,token,enclosed = c,"",True
        else:
            token  += c
            pLevel += (c=="(")-(c==")")
            if c != ")" and pLevel==0: enclosed = False
    return result
The 'RPN()' function breaks down the whole expression by iteratively refining the tokens in a global RPN stack.
def RPN(S):
    result = [S.replace(" ","").replace("**","^")] # unrefined RPN
    done  = False
    while not done: # iterative refinement loop
        previous = result.copy()
        for opers in (["+","-"],["*","/"],["^"]): # reversed precedence
            result = [token for exp in result for token in tokenize(exp,opers)]
            done = result == previous
            if not done: break # refine more, restart operators
    return result
The 'calc()' function takes the components of the RPN stack and executes each operation in order to obtain the resulting value
def calc(S):
    values = []
    for op in RPN(S): # execute RPN
        if   op == "+": values[-2:] = [values[-2] +  values[-1]]
        elif op == "-": values[-2:] = [values[-2] -  values[-1]]
        elif op == "*": values[-2:] = [values[-2] *  values[-1]]
        elif op == "/": values[-2:] = [values[-2] /  values[-1]]
        elif op == "^": values[-2:] = [values[-2] ** values[-1]]
        else: values.append(float(op))
    return values[0]
output:
print(RPN("44 / (7 + 4) * 5 - 17"))
# ['44', '7', '4', '+', '/', '5', '*', '17', '-']
print(calc("44 / (7 + 4) * 5 - 17"))
# 3.0
note that this simple approach does not support monadic negations such as 2**-3 which you would have to write as 2**(-3)