-2

I have a lot of functions like the following, which recursively call themselves to get one or many returns depending on the type of the argument:

def get_data_sensor(self, sensorname):
    if isinstance(sensorname, list):
        return [get_data_sensor(self, sensor) for sensor in sensorname]
    return get_data(os.path.join(self.get_info("path"), "{0}.npy".format(sensorname)))

I would like to call my function recursively without having to know my current function name, I don't want to have my function name twice in the code to limit copy-paste error.

Determine function name from within that function (without using traceback) shows how to get the actual function name, but I need the function itself to call it.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Vincent Bénet
  • 1,212
  • 6
  • 20
  • 1
    How regularly is this really a problem that needs to be solved…? – deceze Jun 21 '21 at 09:42
  • How did you _use_ `sys._getframe`? Also given that the recursive call is by definition inside the function itself, when is this an issue? – jonrsharpe Jun 21 '21 at 09:43
  • I have something like 800 functions that have this: if str return a value elif list return the list of the result function – Vincent Bénet Jun 21 '21 at 09:44
  • Did you read e.g. https://stackoverflow.com/q/5067604/3001761? – jonrsharpe Jun 21 '21 at 09:44
  • It sounds like you could use a _decorator_ to abstract that, but a clearer [mre] would help determine that. – jonrsharpe Jun 21 '21 at 09:45
  • 1
    If you're saying you have that much copy-and-paste code, you should reconsider what exactly you're doing and perhaps write one generic higher-order function instead. – deceze Jun 21 '21 at 09:45
  • Sounds like bad programming practice. – Joshua Jun 21 '21 at 09:45
  • *"Which one?"* - I'm saying you could possibly _write_ one, but without more information about your actual context it's hard to say for sure. *"I need the function itself to call her"* - so did you research how to get the function given its name? Can you show specifically how you've tried to apply that? – jonrsharpe Jun 21 '21 at 10:01
  • @jonrsharpe Here an example in the edit of the post (in few seconds) – Vincent Bénet Jun 21 '21 at 10:10
  • 2
    Yes, you can trivially abstract that with a decorator; the function itself would only need to contain the code to deal with the scalar case (string) and the decorator would handle both cases by calling the wrapped function either once with the string (scalar) or once for each item in the list (vector). If you're unfamiliar with decorators, see https://stackoverflow.com/a/1594484/3001761. – jonrsharpe Jun 21 '21 at 10:17
  • 1
    I'd also suggest reading https://meta.stackexchange.com/q/66377/248731 - in this case, the example you provided to illustrate the solution you were struggling with had very little to do with the actual problem you were trying to solve. – jonrsharpe Jun 21 '21 at 10:21

1 Answers1

2

You can abstract that logic outside the function entirely by using a decorator (see this thorough answer if you're unfamiliar with decorators):

from functools import wraps

def autolist(func):
    @wraps(func)
    def wrapper(args):
        if isinstance(args, list):
            return [func(arg) for arg in args]
        return func(args)
    return wrapper

This decorator can be applied to any function requiring the pattern, which now only needs to implement the scalar case:

>>> @autolist
... def square(x):
...     return x ** 2
...
>>> square(1)
1
>>> square([1, 2, 3])
[1, 4, 9]

If you're applying it to a method, as self implies, you'll also need to take that argument into account in the wrapper. For example, if the relevant argument is always the last one you could do:

def autolist(func):
    @wraps(func)
    def wrapper(*args):
        *args, last_arg = args
        if isinstance(last_arg, list):
            return [func(*args, arg) for arg in last_arg]
        return func(*args, last_arg)
    return wrapper

This would work on methods, too:

>>> class Squarer:
...     @autolist
...     def square(self, x):
...             return x ** 2
...
>>> Squarer().square(1)
1
>>> Squarer().square([1, 2, 3])
[1, 4, 9]
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437