Wandering Thoughts archives

2019-03-16

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.

python/CallableClassVsClosure written at 22:59:13; Add Comment

Staying away from Google Chrome after six months or so

Just short of six months ago, I wrote Walking away from Google Chrome, about how I had decided to stop using Chrome and only use Firefox. Although I didn't mention it in the entry, I implicitly included Chromium in this, which was really easy because I don't even have it installed on my Linux machines.

(A version of Chromium is available in Fedora, but it seems to be slightly outdated and I was always using Chrome in large part because of Google's bundled Flash, which is not in the open source Chromium build.)

Overall, I remain convinced that this is something that's worth doing, however small the impact of it may be. Subsequent developments in the Chrome world have reinforced both the alarming nature of Chrome's dominance and that Chrome's developers are either shockingly naive or deliberately working to cripple popular adblocking and content filtering extensions (see here, here, and here). Using Firefox is a little gesture against the former, however tiny, and provides me with some insulation from the latter, which it seems rather likely that Google will ram through sooner or later.

(It is not complete insulation, since many of the crucial extensions I use are developed for both Firefox and Chrome. One way or another, their development and use on Firefox would probably be affected by any Chrome changes here, if only because their authors might wind up with fewer users and less motivation to work on their addons.)

On a practical level I've mostly not had any problems sticking to this. My habits and reflexes proved more amenable to change than I was afraid of, and I haven't really had any problems with websites that made me want to just hit them with my incognito Chrome hammer. I've deliberately run Chrome a few times to test how some things behaved in it as compared to Firefox, but that's about it for my Chrome usage over the past six months (although I did have to do some initial work to hunt down various scripts that were using Chrome as their browser for various reasons).

My only significant use of Chrome was as my 'accept everything, make things work' browser. As I mentioned in my initial entry, in several ways Firefox works clearly better for this, and I've come to be more and more appreciative of them over the past six months. Cut and paste just works, Firefox requires no song and dance to remember my passwords, and so on. At this point I would find it reasonably annoying to switch much of my use back to Chrome.

That's the good cheery bit. The not as good, not as cheery bit is that after some experiences with Firefox on Javascript-heavy sites (especially some of our Grafana dashboards) and some experimentation, I've become convinced that on Fedora, Google's official Chrome is still faster than Fedora's Firefox on such sites (as it was a couple of years ago). There's a lot of magic that goes into compiling the actual binary of a modern browser (as I found out recently), so there are many potential causes; it could be Fedora's Firefox build, it could be that in general Linux Firefox builds are less focused on and less well optimized than Windows builds, or Firefox could genuinely be slower here for various reasons (including that people have tuned their Javascript's performance for Chrome, not Firefox).

For me the good news is that Fedora's Firefox on my office and home machines qualifies as fast enough for me. Perhaps I could get Chrome to update heavyweight Grafana dashboards somewhat faster, but it's not massively faster at that, and on more sane sites either Firefox works fine or Javascript performance is not the limiting factor; instead, it's things like whether or not either browser uses hardware acceleration for video decoding, which is somewhat variable and not always in Chrome's favour.

web/ChromeWalkingAwayII written at 01:01:22; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.