2

I'm new to Python and have a problem-specific question about how to access non-returned variables defined within a function.

I am using an analysis package in Python that requires a user-defined function as input. It expects a function defined in a particular way that returns a single output. However, for diagnostic purposes, I would like to obtain multiple outputs from this function after it is called (e.g. to produce diagnostic plots at a particular stage of the analysis). However, I can't modify the function to return multiple outputs (unless I modify every instance of it being called by the analysis code--not practical); this would result in an error.

For instance, in the following example,the user defined function returns f1+f2, but for diagnostic purposes, say I would like to know what f1 and f2 are individually:

def my2dfunction(x,y,theta):
'''x and y are 1-d arrays of len(x)==len(y)
   theta is an array of 5 model parameters '''
    f1=theta[0]+theta[1]*x+theta[2]*x**2
    f2=theta[3]*y+theta[4]*y**2
    return f1+f2

Researching on this site I've come up with 2 possible solutions:

  1. Create a global variable containing the values for f1 and f2 from the latest function call, which I can access at any time:

    live_diagnostic_info={'f1':0, 'f2':0}
    
    def my2dfunction(x,y,theta):
        f1=theta[0]+theta[1]*x+theta[2]*x**2
        f2=theta[3]*y+theta[4]*y**2
        global live_diagnostic_info={'f1':f1, 'f2':f2}
        return f1+f2
    
  2. Define a second function identical to the first with multiple return values to call in only the instances where I need diagnostic information.

    def my2dfunction_extra(x,y,theta):
        f1=theta[0]+theta[1]*x+theta[2]*x**2
        f2=theta[3]*y+theta[4]*y**2
        return f1+f2,f1,f2
    

I think both would work for my purposes, but I'm wondering if there is another way to pass non-returned variables from a function in Python. (e.g. I do a lot of coding in IDL, where extra information can be passed through keywords, without modifying the return statement, and wonder what the Python equivalent would be if it exists).

trillian
  • 81
  • 1
  • 8
  • For diagnostic purpose we mostly use `print f1, f2` inside function :) – furas Jul 26 '14 at 21:23
  • possible duplicate of [Can we access inner function outside its scope of outer function in python using outer function?](http://stackoverflow.com/questions/11625923/can-we-access-inner-function-outside-its-scope-of-outer-function-in-python-using) – Paulo Scardine Jul 26 '14 at 21:24
  • Write `my2dfunction_extra`, then modify your current function to call that and return only the first element. DRY. – roippi Jul 26 '14 at 21:26
  • furas--trying to diagnose my science analysis, not the actual code :-) – trillian Jul 27 '14 at 03:10
  • paulo--could be my unfamiliarity with Python, but I don't see a solution to my question in the suggested thread. Perhaps others will benefit more than me. – trillian Jul 27 '14 at 03:15

2 Answers2

0

Add an optional argument to the function

def my2dfunction(x,y,theta, local_debug=None):
    '''x and y are 1-d arrays of len(x)==len(y)
    theta is an array of 5 model parameters '''

    f1=theta[0]+theta[1]*x+theta[2]*x**2
    f2=theta[3]*y+theta[4]*y**2
    if local_debug is not None:
        local_debug["f1"] = f1
        local_debug["f2"] = f2
    return f1+f2

then call it with a dictionary

local_data = {}
my2dfunction(x,y,theta, local_data)

on return you have the information in the dict. Old client code will not be affected either in the return or in the supplied input values.

If instead you want to have them saved no matter who calls the routine, you can do the following. Create the routine as follows

def my2dfunction(x,y,theta, local_debug={}):
    '''x and y are 1-d arrays of len(x)==len(y)
    theta is an array of 5 model parameters '''

    f1=theta[0]+theta[1]*x+theta[2]*x**2
    f2=theta[3]*y+theta[4]*y**2
    local_debug["f1"] = f1
    local_debug["f2"] = f2
    return f1+f2

then you can access the local_debug dictionary from outside

python3: my2dfunction.__defaults__[0]
python2: my2dfunction.func_defaults[0]

Keep in mind that having mutables (e.g. lists, dictionaries etc.) as defaults is a python faux pas, but in this case has a motivation.

Community
  • 1
  • 1
Stefano Borini
  • 138,652
  • 96
  • 297
  • 431
  • The first solution is exactly what I was looking for. I was unaware that function arguments could be modified by the function outside its scope. From some simple tests it appears to me that this only works via referencing (I apologize if this isn't the proper terminology) the input variable rather than reassigning (again, terminology?) it. For instance, if inside a function with argument 'x' I write x=0., then x is unaffected outside the local scope, however if I write x[:]*=2. then I have modified x outside the function scope. Correct? (If I had the reputation points I would up-vote!) – trillian Jul 27 '14 at 02:59
  • @trillian: in python you are always passing references, but the best way to describe it is the following: you have the "space of the objects", where all your objects reside and the "space of the names", where you have the names like f1, f2. When you say f1 = someobject, you are writing an arrow that from f1 in the space of names goes to someobject in the space of objects. If you now say f2 = f1, you have another arrow that goes from f2 to someobject. This has implications when you distinguish mutable objects (e.g. a list) from immutable (e.g. an integer). If you now say f2 = 3... – Stefano Borini Jul 27 '14 at 06:44
  • @trillian: now you draw an arrow that from f2 goes to the number "3" in the space of objects, but you can't change the number 3. The number 3 is 3, it's an immutable object. If however you had f1=[] then f2=f1, and you do f1.append("hello") and print f2, you will see that f2 also contains "hello" because both f1 and f2 have arrows pointing to the same object in the object space, namely that list, which is a mutable object. – Stefano Borini Jul 27 '14 at 06:47
  • @trillian: this is why it's dangerous to specify a mutable (e.g. def foo(a=[])) as a default argument. The evaluation of that default happens at function definition, _not_ at function evaluation (i.e. when you run it), meaning that there's only one object associated to that default, and if you modify it, it will stay modified. – Stefano Borini Jul 27 '14 at 06:48
0

You can write a decorator, so you don't need to copy-paste anything for your second solution:

def return_first(f):
    return lambda *args, **kwargs: f(*args, **kwargs)[0]

return_first takes a function, and returns the same function, but with only first returned value.

def my2dfunction_extra(x,y,theta):
    f1=theta[0]+theta[1]*x+theta[2]*x**2
    f2=theta[3]*y+theta[4]*y**2
    return f1+f2,f1,f2

my2dfunction = return_first(my2dfunction_extra)
DLunin
  • 1,050
  • 10
  • 20
  • Thank you for this solution. I'm green enough that I don't quite follow the syntax of your function, but it looks handy! – trillian Jul 27 '14 at 03:07