Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying function arguments

Sorry if this is a dumb question, but I've looked for a while and not really found the answer.

If I'm writing a python function, for example:

def function(in1, in2):
    in1=in1+1
    in2=in2+1

How do I make these changes stick?

I know why they dont, this has been addressed in many answers, but I couldn't find an answer to the question of how to actually make them do so. Without returning values or making some sort of class, is there really no way for a function to operate on its arguments in a global sense?

I also want these variables to not be global themselves, as in I want to be able to do this:

a=1
b=2
c=3
d=4
function(a,b)
function(c,d)

Is this just wishful thinking?

like image 866
BeB00 Avatar asked Jul 14 '17 20:07

BeB00


2 Answers

It can be done but I'm warning you - it won't be pretty! What you can do is to capture the caller frame in your function, then pick up the call line, parse it and extract the arguments passed, then compare them with your function signature and create an argument map, then call your function and once your function finishes compare the changes in the local stack and update the caller frame with the mapped changes. If you want to see how silly it can get, here's a demonstration:

# HERE BE DRAGONS
# No, really, here be dragons, this is strictly for demonstration purposes!!!
# Whenever you use this in code a sweet little pixie is brutally killed!

import ast
import inspect
import sys

def here_be_dragons(funct):  # create a decorator so we can, hm, enhance 'any' function
    def wrapper(*args, **kwargs):
        caller = inspect.getouterframes(inspect.currentframe())[1]  # pick up the caller
        parsed = ast.parse(caller[4][0], mode="single")  # parse the calling line
        arg_map = {}  # a map for our tracked args to establish global <=> local link
        for node in ast.walk(parsed):  # traverse the parsed code...
            # and look for a call to our wrapped function
            if isinstance(node, ast.Call) and node.func.id == funct.__name__:
                # loop through all positional arguments of the wrapped function
                for pos, var in enumerate(funct.func_code.co_varnames):
                    try:  # and try to find them in the captured call
                        if isinstance(node.args[pos], ast.Name):  # named argument!
                            arg_map[var] = node.args[pos].id  # add to our map
                    except IndexError:
                        break  # no more passed arguments
                break  # no need for further walking through the ast tree
        def trace(frame, evt, arg):  # a function to capture the wrapped locals
            if evt == "return":  # we're only interested in our function return
                for arg in arg_map:  # time to update our caller frame
                    caller[0].f_locals[arg_map[arg]] = frame.f_locals.get(arg, None)
        profile = sys.getprofile()  # in case something else is doing profiling
        sys.setprofile(trace)  # turn on profiling of the wrapped function
        try:
            return funct(*args, **kwargs)
        finally:
            sys.setprofile(profile)  # reset our profiling
    return wrapper

And now you can easily decorate your function to enable it to perform this ungodly travesty:

# Zap, there goes a pixie... Poor, poor, pixie. It will be missed.
@here_be_dragons
def your_function(in1, in2):
    in1 = in1 + 1
    in2 = in2 + 1

And now, demonstration:

a = 1
b = 2
c = 3
d = 4
# Now is the time to play and sing along: Queen - A Kind Of Magic...
your_function(a, b)  # bam, two pixies down... don't you have mercy?
your_function(c, d)  # now you're turning into a serial pixie killer...

print(a, b, c, d)  # Woooo! You made it! At the expense of only three pixie lives. Savage!
# prints: (2, 3, 4, 5)

This, obviously, works only for non-nested functions with positional arguments, and only if you pass simple local arguments, feel free to go down the rabbit hole of handling keyword arguments, different stacks, returned/wrapped/chained calls, and other shenanigans if that's what you fancy.

Or, you know, you can use structures invented for this, like globals, classes, or even enclosed mutable objects. And stop murdering pixies.

like image 65
zwer Avatar answered Oct 15 '22 12:10

zwer


If you are looking to modify the value of the variables you could have your code be

def func(a,b):
    int1 = a + 2
    int2 = b + 3
    return int1,int2

a = 2
b = 3
a,b = func(a,b)

This allows you to actually change the values of the a and b variables with the function.

like image 35
Professor_Joykill Avatar answered Oct 15 '22 11:10

Professor_Joykill