What log_calls Can Decorate

In this document, the phrase “decorated callable” appears frequently. Generally we use callable as a generic term that includes global functions as well as methods and properties of classes. We use it to emphasize that what is said applies equally to global functions, methods and properties, and indeed to anything that log_calls can decorate.

We use more the specific terms decorated function, decorated method, and so on, as appropriate for examples, and when what is said applies to the narrower class of callables named but perhaps not to all callables.

Functions defined with def, methods and properties don’t exhaust the callables that log_calls can decorate. Lambda expressions are functions, and can be decorated by using log_calls() as a functional, without the @ syntactic sugar:

>>> f = log_calls()(lambda x: 2 * x)
>>> f(3)
<lambda> <== called by <module>
    arguments: x=3
<lambda> ==> returning to <module>
6

The question arises: what, exactly, can log_calls decorate? (and thus, what can’t it decorate?) We won’t attempt to give necessary and sufficient conditions for that set of callables. But the following is true:

Anything that log_calls can decorate is a callable,
but not every callable can be decorated by log_calls.

Whatever log_calls cannot decorate, it simply returns unchanged.

What is a “callable”?

Loosely, a “callable” is anything that can be called. In Python, the term has a precise meaning, encompassing not only functions and methods but also classes, as well as instances of classes that implement a __call__ method. A correct though unsatisfying definition is: an object is callable iff the builtin callable function returns True on that object. The Python documentation for callable is good as far as it goes, but a bit breezy; greater detail can be found in the stackoverflow Q&A What is a “callable” in Python? and in the articles cited there.

A few negative examples

log_calls can’t decorate callable builtins, such as len — it just returns the builtin unchanged:

>>> len is log_calls()(len)    # No "wrapper" around len -- not deco'd
True
>>> dict.update is log_calls()(dict.update)
True

Similarly, log_calls doesn’t decorate builtin or extension type classes, returning the class unchanged:

>>> _ = log_calls()(dict)
>>> dict(x=1)               # dict.__init__ not decorated, no output

It also doesn’t decorate various objects which are callables by virtue of having a __call__ method, such as functools.partial objects:

>>> from functools import partial
>>> def h(x, y): return x + y
>>> h2 = partial(h, 2)        # so h2(3) == 5
>>> h2lc = log_calls()(h2)
>>> h2lc is h2                # not deco'd
True

However, log_calls can decorate classes whose instances are callables by virtue of implementing a __call__ method:

>>> @log_calls()
... class Rev():
...     def __call__(self, s):  return s[::-1]
>>> rev = Rev()
>>> callable(rev)
True
>>> rev('ABC')                        
Rev.__call__ <== called by <module>
    arguments: self=<Rev object at 0x...>, s='ABC'
Rev.__call__ ==> returning to <module>
'CBA'