8

Is there an equivalent to R's do.call in python?

do.call(what = 'sum', args = list(1:10)) #[1] 55
do.call(what = 'mean', args = list(1:10)) #[1] 5.5

?do.call
# Description
# do.call constructs and executes a function call from a name or a function and a list of arguments to be passed to it.
Deena
  • 5,925
  • 6
  • 34
  • 40

3 Answers3

5

There is no built-in for this, but it is easy enough to construct an equivalent.

You can look up any object from the built-ins namespace using the __builtin__ (Python 2) or builtins (Python 3) modules then apply arbitrary arguments to that with *args and **kwargs syntax:

try:
    # Python 2
    import __builtin__ as builtins
except ImportError:
    # Python 3
    import builtins

def do_call(what, *args, **kwargs):
    return getattr(builtins, what)(*args, **kwargs)

do_call('sum', range(1, 11))

Generally speaking, we don't do this in Python. If you must translate strings into function objects, it is generally preferred to build a custom dictionary:

functions = {
    'sum': sum,
    'mean': lambda v: sum(v) / len(v),
}

then look up functions from that dictionary instead:

functions['sum'](range(1, 11))

This lets you strictly control what names are available to dynamic code, preventing a user from making a nuisance of themselves by calling built-ins for their destructive or disruptive effects.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
5

do.call is pretty much the equivalent of the splat operator in Python:

def mysum(a, b, c):
    return sum([a, b, c])

# normal call:
mysum(1, 2, 3)

# with a list of arguments:
mysum(*[1, 2, 3])

Note that I’ve had to define my own sum function since Python’s sum already expects a list as an argument, so your original code would just be

sum(range(1, 11))

R has another peculiarity: do.call internally performs a function lookup of its first argument. This means that it finds the function even if it’s a character string rather than an actual function. The Python equivalent above doesn’t do this — see Martijn’s answer for a solution to this.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • Thanks @KonradRudolph for your help. In principle, I am parsing a text file, in which the aggregation function is specified there. I would like to avoid using a switch/dictionary that maps such string to its function. Any ideas on that as well? – Deena Aug 02 '16 at 14:50
  • @Dee Use the dictionary (actually, no: use a `context` object that provides a dictionary-like interface, but isolates side-effects of the called functions from the rest of the program). It’s the appropriate solution for this case, both in Python and in R. Letting the user execute arbitrary functions is never a good idea anyway. – Konrad Rudolph Aug 02 '16 at 15:08
  • Simply saying something is "too much magic" is not helpful. The ability to do what was not originally expected is part of the experience of programming. – Michael Tuchman Oct 02 '20 at 03:38
  • 1
    @MichaelTuchman But I’m not “simply saying” it, I’m *explaining why*. And writing code that does unexpected things is very much *not* “part of the experience” — it’s plain *bad code*. Reliance on “magic” is widely regarded as the antithesis of good code. R’s implicit `match.fun` in particular are completely unnecessary, and a bizarre subversion of the type system, which is pretty widely agreed to be a bad thing. — That being said, in the particular case of `do.call` there’s actually a good reason to always use (hard-coded!) string names in hindsight. – Konrad Rudolph Oct 02 '20 at 07:49
  • I appreciate your elaboration of your point of view. Let me rephrase. If you try to rigorously define "magic" in this context, you will see where the issue actually lies. There is no such thing as "magic", only different kinds of code. So long as a specification exists for how it works, there is no such thing as "subversion" or "unintended effects", just different use cases. – Michael Tuchman Oct 02 '20 at 16:27
  • @MichaelTuchman For what it’s worth I believe you’re fundamentally wrong. With your reasoning you can justify *any* weak typing, even when empirical evidence shows it to lead to unnecessary bugs and even security holes (as is the case e.g. in C, where such evidence exists). The fact that R allows you to write, verbatim, `"a string" = "a value"` has no valid justification, it’s purely moronic. The fact that R sometimes (but inconsistently, and not always!) implicitly converts strings to functions is less bad, but still unnecessary and, on balance, harmful. – Konrad Rudolph Oct 02 '20 at 18:17
3

Goes similar to previous answer, but why so complicated?

def do_call(what, args=[], kwargs = {}):
    return what(*args, **kwargs)

(Which is more elegant than my previously posted definition:)

def do_call(which, args=None, kwargs = None):
    if args is None and kwargs is not None:
        return which(**kwargs)
    elif args is not None and kwargs is None:
        return which(*args)
    else:
        return which(*args, **kwargs)

Python's sum is different than R's sum (1 argument a list expected vs. arbitraily many arguments expected in R). So we define our own sum (mysum) which behaves similarly to R's sum. In a similar way we define mymean.

def mysum(*args):
    return sum(args)

def mymean(*args):
    return sum(args)/len(args)

Now we can recreate your example in Python - as a reasonable 1:1 translation of the R function call.

do_call(what = mymean, args=[1, 2, 3])
## 2.0
do_call(what = mysum, args=[1, 2, 3])
## 6

For functions with argument names, we use a dict for kwargs, where the parameter names are keys of the dictionary (as strings) and their values the values.

def myfunc(a, b, c):
    return a + b + c

do_call(what = myfunc, kwargs={"a": 1, "b": 2, "c": 3})
## 6

# we can even mix named and unnamed parts
do_call(what = myfunc, args = [1, 2], kwargs={"c": 3})
## 6
Gwang-Jin Kim
  • 9,303
  • 17
  • 30