3

Suppose I had two functions within another function like so:

def FooBar(isTheWorldRound = True):

    def Foo():
        print("Hi, I'm foo.")

    def Bar():
        print("Hi, I'm bar.")

    theFunction = None
    if (isTheWorldRound):
        return Bar
    else:
        return [Bar, Foo]

So, I can do this:

myFunction = FooBar(False)
myFunction()
>>> Hi, I'm Bar
>>> Hi, I'm Foo

Concerning this example I have two questions:

  1. What is the proper way to perform the commented line?
  2. Is there a way I can do this without explicitly defining Foo?
Woody1193
  • 7,252
  • 5
  • 40
  • 90
  • 3
    Why do you think that won't work? You can certainly put functions in a list. And you aren't *"defining these inside another function"*, just referring to them. What are you actually trying to achieve (for example, what is the expected output)?! – jonrsharpe Jun 25 '15 at 10:44
  • @jonrsharpe Sorry I wasn't being clear. How about now? – Woody1193 Jun 25 '15 at 10:49
  • It's still not clear what you mean by *"without defining `Foo`"* - Python isn't (quite) magic, at some point you'll need to define the callables, either with `def` or a `lambda`. – jonrsharpe Jun 25 '15 at 11:05
  • Sorry, when I say "without defining" I mean "without explicitly defining". Would a lamba be acceptable here, then? – Woody1193 Jun 26 '15 at 02:27
  • 1. How is a `lambda` **not** *"explicitly defining"*? 2. Acceptable to whom? In such a trivial case a `lambda` would be fine (or, as `print` is a function, use `functools.partial`) but I assume this is a toy example. – jonrsharpe Jun 26 '15 at 06:38
  • Yes it is, but I think we both understand exactly what I meant by "explicitly defining" – Woody1193 Jun 28 '15 at 01:51
  • I can only assume you mean using `def` and are considering `lambda` to not be an explicit definition (though it is) – jonrsharpe Jun 28 '15 at 05:33

4 Answers4

2

Putting two functions into a list gives you just that; a list of functions. It does not make a new function that calls both of the previous functions. For that, you need to define a new wrapper function, e.g.:

def call_all(*funcs):
    """Create a new wrapper to call each function in turn."""
    def wrapper(*args, **kwargs):
        """Call the functions and return a list of their outputs."""
        return [func(*args, **kwargs) for func in funcs]
    return wrapper

(if the * syntax is unfamiliar, see What does ** (double star) and * (star) do for parameters?), which you can now use like:

theFunction = call_all(Bar, Foo)

Note also that:

theFunction = None
if (isTheWorldRound):
    return Bar
else:
    return [Bar, Foo]

is a bit awkward, I would write it as:

if isTheWorldRound:
    return Bar
return [Bar, Foo]

You should also rename the functions/variables per the style guide.

Community
  • 1
  • 1
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • Is there some way of using partial and apply? – Peter Wood Jun 25 '15 at 10:55
  • Thanks, I really appreciate it. So, if I understand your code correctly, the `wrapper(*args, **kwargs)` function will call each function in succession with the appropriate arguments. Is that correct? – Woody1193 Jun 25 '15 at 10:56
  • Also, in this case you would have to explicitly name your arguments, as positional arguments would be taken in the applied order for the first function and then the second, or am I off base? – Woody1193 Jun 25 '15 at 10:57
  • 1
    @Woody1193 it calls each function with whatever positional/keyword arguments you pass in, then returns a list containing the return values from each function (in this case, just `[None, None]`, but it's useful to have access in the more general case). You don't *need* to use only keyword arguments, the same positional arguments will also be passed to each inner function. – jonrsharpe Jun 25 '15 at 10:58
  • 1
    @PeterWood possibly, but note that [`apply` has been deprecated since 2.3](https://docs.python.org/2/library/functions.html#apply). – jonrsharpe Jun 25 '15 at 10:59
1

You can certainly compose a new function in a way FooBar will return a single function that evaluates one or both of them.

Consider this:

def F():
    funcs = [Foo, Bar]
    ret = None
    for f in funcs:
        ret = f()
    return ret

You can make a closure in your FooBar to return a single composition:

def FooBar(isTheWorldRound = True):
    if (isTheWorldRound):
        funcs = [Bar]
    else:
        funcs = [Bar, Foo]

    def theFunction():
        ret = None
        for f in funcs:
            ret = f()
        return ret
    return theFunction

Luckily this is easy in Python where functions are first-class objects.

Edit: seems you want to execute the functions directly during the execution of FooBar. Then you can ditch the closure but still call all the functions in a loop.

mike3996
  • 17,047
  • 9
  • 64
  • 80
1

Replace the commented line with:

return lambda: [None, Foo(), Bar()][0]

It will work as expected:

>>> myFunction = FooBar(False)
>>> myFunction()
Hi, I'm foo.
Hi, I'm bar.

What it does is creating an anonymous function, calling both Foo() and Bar() when invoked, and returning None.

fferri
  • 18,285
  • 5
  • 46
  • 95
0

Actually, that should work. Without the parentheses, it's a function pointer (or whatever Python's equivalent is).

To call it, you'd do something like:

theFunction[0]()

To get the 0th function in the list, then apply the parentheses (or any arguments if needed).

Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
  • I didn't downvote. Your answer requires using the return type differently depending upon the boolean passed as a parameter. This isn't a consistent interface. – Peter Wood Jun 25 '15 at 11:04
  • He's changed the question since I answered it. Before, the question seemed to just be "can a store a function in a list". And everyone got down voted; not just me. – Carcigenicate Jun 25 '15 at 11:06
  • You might consider that a warning not to answer unclear questions without waiting for clarification! – jonrsharpe Jun 25 '15 at 11:31