user15964
user15964

Reputation: 2639

How to map function on all argument values, as a list? but have explicit argument names in the function definition

I want to define a function using explicit argument names ff(a,b,c) in the function definition, but I also want to map a function over all arguments to get a list:

ff(a,b,c):
    return list(map(myfunc,[a,b,c]))

However, I don't want to explicitly write parameter names inside function as a,b,c. I want to do it like

ff(a,b,c):
    return list(map(myfunc,getArgValueList()))

getArgValueList() will retrieve the argument values in order and form a list. How to do this? Is there a built-in function like getArgValueList()?

Upvotes: 0

Views: 661

Answers (2)

jferard
jferard

Reputation: 8190

The *args construction will give you the arguments as a list:

>>> def f(*args): return list(map(lambda x:x+1, args))
>>> f(1,2,3)
[2, 3, 4]

If you are bound with the signature of f, you'll have to use the inspect module:

import inspect

def f(a, b,c):
    f_locals = locals()
    values = [f_locals[name] for name in inspect.signature(f).parameters]
    return list(map(lambda x:x+1, values))

inspect.signature(f).parameters gives you the list of arguments in the correct order. The values are in locals().

Upvotes: 1

abarnert
abarnert

Reputation: 366123

What you're trying to do is impossible without ugly hacks. You either take *args and get a sequence of parameter values that you can use as args:

def ff(*args):
    return list(map(myfunc, args))

… or you take three explicit parameters and use them by name:

def ff(a, b, c):
    return list(map(myfunc, (a, b, c)))

… but it's one or the other, not both.

Of course you can put those values in a sequence yourself if you want:

def ff(a, b, c):
    args = a, b, c
    return list(map(myfunc, args))

… but I'm not sure what that buys you.


If you really want to know how to write a getArgValueList function anyway, I'll explain how to do it. However, if you're looking to make your code more readable, more efficient, more idiomatic, easier to understand, more concise, or almost anything else, it will have the exact opposite effect. The only reason I could imagine doing something like this is if you had to generate functions dynamically or something—and even then, I can't think of a reason you couldn't just use *args. But, if you insist:

def getArgValueList():
    frame = inspect.currentframe().f_back
    code = frame.f_code
    vars = code.co_varnames[:code.co_argcount]
    return [frame.f_locals[var] for var in vars]

If you want to know how it works, most of it's in the inspect module docs:

  • currentframe() gets the current frame—the frame of getArgValueList.
  • f_back gets the parent frame—the frame of whoever called getArgValueList.
  • f_code gets the code object compiled from the function body of whoever called getArgValueList.
  • co_varnames is a list of all local variables in that body, starting with the parameters.
  • co_argcount is a count of explicit positional-or-keyword parameters.
  • f_locals is a dict with a copy of the locals() environment of the frame.

This of course only works for a function that takes no *args, keyword-only args, or **kwargs, but you can extend it to work for them as well with a bit of work. (See co_kwonlyargcount, co_flags, CO_VARARGS, and CO_VARKEYWORDS for details.)

Also, this only works for CPython, not most other interpreters. and it could break in some future version, because it's pretty blatantly relying on implementation details of the interpreter.

Upvotes: 2

Related Questions