There's a beautiful feature that I first came across with in Groovy many years ago and that over time has made it into C# first and TypeScript/JavaScript recently. It goes under different names: Safe Navigation, Save Dereferencing, Optional Chaining. Well, I'm refering to the "?" operator when accessing an attribute, that will return null (undefined in JavaScript) if the attribute is missing, and will not continue with ensuing attribute look ups if we were in a chain. I mean:
country?.capital?.location?.xCoordinate;
Notice that optional chaining also works for method invokation, using in JavaScript this odd syntax: ob.method?.()
It's one of those few features that I miss in Python. There is proposal PEP 505 to add it to the language, but for the moment it's in deferred state. So, same as I did 10 years ago when JavaScript was missing the feature and I sort of implemented a workaround, I've written a simple python function to sort of simulate it. We pass the chain as a List of strings (representing the attributes) or tuples (representing parameters for a method invokation):
UPDATE: The below implementation is quite a crap, for a better one check this follow-up post.
def safe_access(ob, chain):
"""
sort of "safe accessor" (javascript's x?.attr?.attr2...)
this version includes method invocation, so it's quite different from my getattr_chain.py
"""
cur_value = ob
for item in chain:
if isinstance(item, str): #accessing attribute
cur_value = getattr(cur_value, item, None)
else: #invoking
cur_value = cur_value(*item)
if cur_value is None:
return None
return cur_value
class Person:
def __init__(self, name, age, profession):
self.name = name
self.age = age
self.profession = profession
class Profession:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def format_profession(self, st1, st2):
return f"{st1}-{self.name}-{st2}"
p1 = Person("Francois", 4, Profession("programmer", 50000))
print(safe_access(p1, ["name"]))
print(safe_access(p1, ["city"]))
print(safe_access(p1, ["city", "name"]))
print(safe_access(p1, ["profession", "salary"]))
print(safe_access(p1, ["profession"
, "format_profession", ("¡¡", "!!")
, "upper", ()
]))
In JavaScript, if we try to invoke a method, but it exists as a data attribute, not as a function, the optional chaining will throw an error: TypeError: xxx is not a function
> ob = {name: "aa"};
> ob.name?.();
Uncaught TypeError: ob.name is not a function
I'm also doing that in my python code (you'll get a TypeError, xxx is not callable). I think it could make sense to return None rather than throwing an exception, for that we would update the invokation part in the above function with a callable check, like this:
# cur_value = cur_value(*item)
# if not callable, return None also
if callable(cur_value):
cur_value = cur_value(*item)
else:
return None
Safe dereferencing/Optional Chaining is related to other 2 features, also present in C# and recently in JavaScript (when I wrote this post it had not made it into JavaScript yet), the null coalescing operator and the null coalescing assignment. I think the aforementioned deferred PEP should also address these 2 things. For the moment, depending on the data that you are expecting, you could check with (as we used to do in JavaScript until recently) or. I mean a = b or c (rather than an hypothetycal: a = b ?? c). You can take a look at this discussion to see the possible issues with this approach.
No comments:
Post a Comment