BeB00
BeB00

Reputation: 243

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?

Upvotes: 11

Views: 28140

Answers (3)

zwer
zwer

Reputation: 25829

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.

Upvotes: 29

Xingzhou Liu
Xingzhou Liu

Reputation: 1559

you can do:

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

a, b = function(a,b)
c, d = function(c,d)

python functions are closed -> when function(a,b) s called, a and b get reassigned to a local (to the function) references/pointers in1 and in2, which are not accessible outside of the function. provide references to those new values w/o using globals, you will need to pass that back through return.

When you pass an array or non primitive object into a function, you can modify the object's attributes and have those modifications be visible to other references for that object outside, because the object itself contain the pointers to those values, making the visible to anything else holding a pointer to that object.

Upvotes: 3

Professor_Joykill
Professor_Joykill

Reputation: 989

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.

Upvotes: 9

Related Questions