Don't Make It Callable

Wed 13 February 2019 by Moshe Zadka

There is a lot of code that overloads the __call__ method. This is the method that "calling" an object activates: something(x, y, z) will call something.__call__(x, y, z) if something is a member of a Python-defined class.

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?

The first use-case is easily done better with a named method, and more readably: accepting callbacks. Let's say that the function interesting_files will call the passed-in callback with names of interesting files.

We can, of course, use __call__:

class PrefixMatcher:

    def __init__(self, prefix):
        self.prefix = prefix
        self.matches = []

    def __call__(self, name):
        if name.startswith(self.prefix):
            self.matches.append(name)

    def random_match(self):
        return random.choice(self.matches)

matcher = PrefixMatcher("prefix")
interesting_files(matcher)
print(matcher.random_match())

But it is more readable, and obvious, if we...don't:

class PrefixMatcher:

    def __init__(self, prefix):
        self.prefix = prefix
        self.matches = []

    def get_name(self, name):
        if name.startswith(self.prefix):
            self.matches.append(name)

    def random_match(self):
        return random.choice(self.matches)

matcher = PrefixMatcher("prefix")
interesting_files(matcher.get_name)
print(matcher.random_match())

We can pass the matcher.get_name method, which is already callable directly to interesting_files: there is no need to make PrefixMatcher callable by overloading __call__.

If something really is nothing more than a function call with some extra arguments, then either a closure or a partial would be appropriate.

In the example above, the random_match method was added to make sure that the class PrefixMatcher is justified. If this was not there, either of these implementations would be more appropriate:

def prefix_matcher(prefix):
    matches = []
    def callback(name):
        if name.startswith(prefix):
            matches.append(name)
    return callback, matches

matcher, matches = prefix_matcher("prefix")
interesting_files(matcher)
print(random.choice(matches))

This uses the function closure to capture some variables and return a function.

def prefix_matcher(prefix, matches, name):
    if name.startswith(prefix):
        matches.append(name)

matches = []
matcher = functools.partial(prefix_matcher, "prefix", matches)
interesting_files(matcher)
print(random.choice(matches))

This uses the funcotools.partial functions to construct a function that has some of the arguments "prepared".

There is one important use case for __call__, but it is specialized: it is a powerful tool when constructing a Python-based DSL. Indeed, this is exactly the time when we want to trade away "doing exactly when the operator always does" in favor of "succint syntax dedicated to the task at hand."

A good example of such a DSL is stan, where the __call__ function is used to construct XML tags with attributes: div(style="color: blue").

In almost every other case, avoid the temptation to make your objects callable. They are not functions, and should not be pretending.


Staying Safe with Open Source

Thu 24 January 2019 by Moshe Zadka

A couple of months ago, a successful attack against the Node ecosystem resulted in stealing an undisclosed amount of bitcoins from CoPay wallets.

The technical flow of the attack is well-summarized by the NPM blog post. Quick summary:

  1. nodemon, a popular way to run Node applications, depends on event-stream.
  2. The …
read more

Checking in JSON

Tue 08 January 2019 by Moshe Zadka

JSON is a useful format. It might not be ideal for hand-editing, but it does have the benefit that it can be hand-edited, and it is easy enough to manipulate programmatically.

For this reason, it is likely that at some point or another, checking in a JSON file into your …

read more

Office Hours

Sat 08 December 2018 by Moshe Zadka

If you want to speak to me, 1-on-1, about anything, I want to be able to help. I am a busy person. I have commitments. But I will make the time to talk to you.

Why?

  • I want to help.
  • I think I'll enjoy it. I like talking to people …
read more

Common Mistakes about Generational Garbage Collection

Wed 28 November 2018 by Moshe Zadka

(Thanks to Nelson Elhage and Saivickna Raveendran for their feedback on earlier drafts. All mistakes that remain are mine.)

When talking about garbage collection, the notion of "generational collection" comes up. The usual motivation given for generational garbage collection is that "most objects die young". Therefore, we put the objects …

read more

The Conference That Was Almost Called "Pythaluma"

Wed 07 November 2018 by Moshe Zadka

As my friend Thursday said in her excellent talk (sadly, not up as of this time) naming things is important. Avoiding in-jokes is, in general, a good idea.

It is with mixed feelings, therefore, that my pun-loving heart reacted to Chris's disclosure that the most common suggestion was to call …

read more

Why No Dry Run?

Sat 06 October 2018 by Moshe Zadka

(Thanks to Brian for his feedback. All mistakes and omissions that remain are mine.)

Some commands have a --dry-run option, which simulates running the command but without taking effect. Sometimes the option exists for speed reasons: just pretending to do something is faster than doing it. However, more often this …

read more

Managing Dependencies

Sun 02 September 2018 by Moshe Zadka

(Thanks to Mark Rice for his helpful suggestions. Any mistakes or omissions that remain are my responsibility.)

Some Python projects are designed to be libraries, consumed by other projects. These are most of the things people consider "Python projects": for example, Twisted, Flask, and most other open source tools. However …

read more

Tests Should Fail

Thu 02 August 2018 by Moshe Zadka

(Thanks to Avy Faingezicht and Donald Stufft for giving me encouragement and feedback. All mistakes that remain are mine.)

"eyes have they, but they see not" -- Psalms, 135:16

Eyes are expensive to maintain. They require protection from the elements, constant lubrication, behavioral adaptations to protect them and more. However …

read more

Thank you, Guido

Thu 02 August 2018 by Moshe Zadka

When I was in my early 20s, I was OK at programming, but I definitely didn't like it. Then, one evening, I read the Python tutorial. That evening changed my mind. I woke up the next morning, like Neo in the matrix, and knew Python.

I was doing statistics at …

read more