Confusion about call expressions

The grammar for a call expression says that the callee can itself be a call expression.

I can’t think of an example of when this would be the case. Can anyone provide a short example of this?

FYI: This isn’t a school assignment.

Thanks,
Melissa

Hi Melissa,

I’m sorry, I don’t understand your question. The page you linked to does
not have the word “callee” in it, and I can’t work out what you mean by
“the callee can itself be a call expression”.

Can you copy and paste the exact quote you are referring to?

The grammar for a call
expression

says that the callee can itself be a call expression.

I can’t think of an example of when this would be the case. Can anyone
provide a short example of this?

Well, it means you can go f()() provided that f() returns a callable.

Functions are first class objects like anything else. So you can return
a function from a function. Or from any other expression.

Example:

def do_this(): print("this")

def do_that(): print("that")

def do_the_other(): print("the_other")

ACTIONS = {
    'a': do_this,
    'b': do_that,
    'c': do_the_other,
}

def pick_action(label):
    return ACTIONS[label]

action = pick_action('b')
action()

or more concisely:

pick_action('b')()

In a sense, when you call a method on an object:

>>> 'abc'.strip('c')
'ab'

the method named “strip” is looked up on the object ‘abc’. So the
expression:

'abc'.strip

returns a callable. It gives a “bound method”, meaning a reference to
the “strip” method associated with the particular str instance ‘abc’ -
“bound to ‘abc’”. And you then call that bound method with ‘c’,
stripping that off the end.

Another common situation returning a callable is a decorator. When you
decorate a function like this:

from contextlib import contextmanager

@contextmanager
def markout(blah):
    print("mark1", blah)
    yield
    print("mark2", blah)

In the above, “contextmanager” is a decorator: Glossary — Python 3.12.1 documentation

It is a function which accepts a function and returns a function,
usually a new one. In the example above, it turns the generator function
“markout” into a context manager. It is shorthand for:

def markout(blah):
    print("mark1", blah)
    yield
    print("mark2", blah)

markout = contextmanager(markout)

where we define the function “markout”. That bind the name “markout” to
a function definition. The we reassign that name to the result of
contextmanager(markout). The “@contextmanager” syntax is just sugar to
make decorators easy to use.

Usually a decorator makes a new function to supplant the old one, and
the new function calls the old one, basiclyembellishing what it did.

Look at this:

def trace(func):
    def traced(*a, **kw):
        print("calling", func, "with args", repr(a), "and kwargs", repr(kw))
        result = func(*a, **kw)
        print("return from", func, "==>", repr(result))
        return result
    return traced

This trace() function defines a new function traced(), which prints out
stuff about the call of func(), calls func(), prints the result, then
returns the result.

Then you could use this when debugging. Any function you want to watch
you coud just decorate:

@trace
def buggy_func(a, b):
    return a + b + 1  # off-by-one error

and get informative prints when you run the programme.

Note: when you decorate buggy_func() above, the trace() function is
actually called at that point to get a function to slot in instead of
the bare buggy_func(). So traced() is a shiny new function every time
you decorate something. trace() itself gets passed buggy_func as its
“func” parameter, and traced() uses that variable.

Because traced() was defined during the run of trace(), it has access
to the variables in play when trace() was running, specificly “func”.
This access is called a “closure”, and is what makes this so easy.

If you decorate another function as well, trace() runs again, and the
new traced() function it returns has access to that second function as
“func”, so it will call the second function as you would want.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

It’s the grammar. A call expression starts:

call := primary "(" ............

and a primary itself may be a call:
https://docs.python.org/3/reference/expressions.html#grammar-token-primary

Hence the description.

Anyway, the thing that is being called, the callee, may be any primary
expression. The OP was seeking an example where that expression was
itself a call.

Cheers,
Cameron Simpson cs@cskk.id.au