Callable class instances versus closures in Python
Recently I read Don't Make It Callable (via), which advocates
avoiding having your class instances be callable (by __call__
on your classes). Let me quote its fundamental thesis on using
__call__
:
At first, like every operator overload, this seems like a nifty idea. And then, like most operator overload cases, we need to ask: why? Why is this better than a named method?
I wholeheartedly agree with this, and in the beginning I agreed
with the whole article. But then I began thinking about my usage
of __call__
and something that the article advocated as a
replacement, and found that I partially disagree with it. To quote
it again:
If something really is nothing more than a function call with some extra arguments, then either a closure or a partial would be appropriate.
(By 'partial', the article means the use of functools.partial
to construct a partially applied function.)
My view is that if you have to provide something that's callable,
a callable class is better than a closure because it's more
amenable to inspection. A class instance is a clear thing; you
can easily see what it is, what it's doing, and inspect the state
of instances (especially if you remember to give your class a
useful __str__
or __repr__
). You can
even easily give them (and their methods) docstrings, so that
help()
provides helpful information about them.
None of this is true of closures (unless you go well out of your way) and only a bit of it is true of partially applied functions. Even if you go out of your way to provide a docstring for your closure function, the whole assemblage is basically an opaque blob. A partially applied function is somewhat better because the resulting object exposes some information, but it's still not as open and transparent as an object.
This becomes especially important if your callable thing is going to be called repeatedly and hold internal state. It's far easier to make this internal state visible, potentially modifiable, and above all debuggable if you're using an object than if you try to wrap all of this up inside a function (or a closure) that manipulates its internal variables. Python objects are designed to be transparent (at least by default), as peculiar as this sounds in general.
(After all, one of the usual stated purposes of objects is to encapsulate things away from the outside world.)
Callable classes are unquestionably more verbose than closures, partially applied functions, or even lambdas, and sometimes this is annoying. But I think you should use them for anything that is not trivial by itself, and maybe even for small things depending on how long the resulting callable entities are going to live and how far away they are going to propagate in your program. The result is likely to be more maintainable and more debuggable.
PS: This somewhat biases me toward providing things with the entire
instance and using __call__
over providing a method on the
instance. If you're trying to debug something, it's harder to go
from a method to inspecting the instance it comes from. Providing
just a method is probably okay if the use is 'close' to the class
definition (eg, in the same file or the same module), because then
you can look back and forth easily. Providing the full instance is
what I'd do if I was passing the callable thing around to another
module or returning it as part of my public API.
Comments on this page:
|
|