Search code examples
pythonstringvariablesexecsetattr

Using setattr() to convert strings to variables


I am creating a sigsum() function which takes the sum using an input equation and an input variable. Here's what I have so far:

def sigsum(eqn, index, lower=0, upper=None, step=1):

    if type(step) is not int:
        raise TypeError('step must be an integer')
    elif step < 1:
        raise ValueError('step must be greater than or equal to 1')

    if upper is None:
        upper = 1280000

    if lower is None:
        lower = -1280000

    if (upper - lower) % step:
        upper -= (upper - lower) % step

    index = lower
    total = 0
    while True:
        total += eqn
        if index == upper:
            break
        index += step

    return total

Usage of function:

print(sigsum('1/(i+5)','i'))
>>> 12.5563

My current problem is converting 'eqn' and 'index' to variables that exist inside the function local namespace. I heard around that using exec is not a good idea and that maybe setattr() might work. Can anyone help me out? Thanks.


Solution

  • For eqn I suggest using a lambda function:

    eqn = lambda i: 1 / (i + 5)
    

    then index is not needed, because it is just "the variable passed to the function" (does not need a name).

    Then your function becomes

    def integrate(fn, start = 0, end = 128000, step = 1):
        """
        Return a stepwise approximation of
          the integral of fn from start to end
        """
        num_steps = (end - start) // step
        if num_steps < 0:
            raise ValueError("bad step value")
        else:
            return sum(fn(start + k*step) for k in range(num_steps))
    

    and you can run it like

    res = step_integrate(eqn)   # => 10.253703030104417
    

    Note that there are many steps to this, and many of them involve very small numbers; rounding errors can become a major problem. If accuracy is important you may want to manually derive an integral,

    from math import log
    
    eqn          = lambda i: 1 / (i + 5)
    eqn.integral = lambda i: log(i + 5)
    
    def integrate(fn, start = 0, end = 128000, step = 1):
        """
        Return the integral of fn from start to end
    
        If fn.integral is defined, used it;
        otherwise do a stepwise approximation
        """
        if hasattr(fn, "integral"):
            return fn.integral(end) - fn.integral(start)
        else:
            num_steps = (end - start) // step
            if num_steps < 0:
                raise ValueError("bad step value")
            else:
                return sum(fn(start + k*step) for k in range(num_steps))
    

    which again runs like

    res = step_integrate(eqn)   # => 10.150386692204735
    

    (note that the stepwise approximation was about 1% too high.)