Monday, 14 November 2022

Python Optional Chaining Revisited

I've been thinking again about that Python missing feature (Optional Chaining) I wrote about in this post. It seems like there are no plans to approve the PEP-505 (None aware operators), as there are quite a few opponents to the syntax. I have to admit that it's true that coming across something like:
obj?.client?.getPosts?.("url")?[0]?["title]
is not particularly beautiful and goes rather against the "almost pseudocode" syntax ideal of Python, though anyway I think it's much better than a long chain of if's. Having just a safe_access function added to the standard library would be a good alternative. Thinking of that, I've realised that the implementation of such a function that I wrote for that post is rather stupid :-), does not work with indexing and has the terrible inconvenience of using strings for the attributes names.

The different access errors that we can get when trying to access "things" (attributes, indexing, invoking) on None, or on an object lacking such attributes or indexing or not being callable, etc are: TypeError, KeyError, IndexError, AttributeError, so why not to write our code like this:


try:
	first_title = obj.client.getPosts("url")[0]["title"]
except TypeError, KeyError, IndexError, AttributeError:
	first_title = None
	

Well, that's OK, but it's 4 lines vs 1 line if we had an optional chaining operator. So, let's put the try-catch in a function, and use a lambda to express the access chain, like this:


def safe_access(fn):
    try:
        return fn()
    except (TypeError, KeyError, IndexError, AttributeError):
        return None
        

first_title = safe_access(lambda: obj.client.getPosts("url")[0]["title"])


Honestly I think it looks quite good. Notice that I'm not passing the object being accessed as a parameter to the lambda, I'm directly trapping it as a closure variable. Having a function like this in functools would be even more convenient that implementing the "?" thing, as it would not involve any addition to the language syntax.

Thanks to looking into this topic again I've come across 2 very interesting ideas. One is the Maybe pattern, and its implementation in Python. The other one quite blew me away. An implementation of PEP-505 as a polyfill. So, how can you do that? Well, you can hook into the modules import process and do crazy stuff with them like transforming their source code before python compiles them to bytecodes. This way, you can add extra features to the language as far as you can then compile them to standard python. This is a very powerful feature but that seems rather unknown. The modules import process is documented here

No comments:

Post a Comment