[Python-ideas] PEP 505: None-aware operators
Clément Pit-Claudel
cpitclaudel at gmail.com
Sun Jul 22 15:58:34 EDT 2018
On 2018-07-22 08:10, Steven D'Aprano wrote:
> Indeed. And I think we ought to think carefully about the benefits and
> costs of all of those variants separately.
>
> To me, the ?? operator seems like a clear and obvious win. The other
> variants are more complex and the benefit is not as obvious to me, so I
> haven't decided where I stand on them.
I like the ?? operator too; I like the short circuiting behavior a lot, and the semantics are simple.
I guess I'd use the other operators fairly often, too, mostly in quick-n-dirty scripts. The one place where I miss them is when browsing through dictionaries that I get by querying a remote server and deserializing the resulting JSON. I foten have situation where the value I'm interested in is e.g. either in response[0]["addresses"]["workplace"]["email"], or in response["records"][0]["contactInfo"]["emails"][0], and any of these subrecords may be missing.
Rewriting these using the ?[…] and ?. operators, I guess I would write something like this:
tmp = response?.get("records")
try:
tmp = tmp?[0]
except IndexError:
tmp = None
tmp = tmp?.get("contactInfo")?.get("emails")
try:
tmp = tmp?[0]
except IndexError:
tmp = None
Is there a shorter way to write these with the "?[…]" and "?." operators? I guess the difficulty is that I need to swallow index and key errors, not just the type errors that come from indexing into None.
For cases like the one above, I usually use something like nget(response, ["records"], [0], ["contactInfo"], ["emails"], [0]), where nget is defined as shown below (in this use case, the lack of short-circuiting isn't an issue):
def nget(obj, *fields, default=None):
for field in fields:
if obj is None:
return default
if isinstance(field, str):
obj = getattr(obj, field, None)
elif isinstance(field, list):
try:
obj = obj.__getitem__(field[0])
except (TypeError, KeyError, IndexError):
obj = None
return obj
class Test():
def __init__(self):
self.x = [{"y": 42, "z": ["aBc", "def"]}, [1]]
a = Test()
print(nget(a, "x", [0], ["z"], [0], [1])) # B
print(nget(a, "x", [0], ["y"])) # 42
print(nget(a, "z", [0], ["y"], default="not found")) # not found
print(nget(a, "z", [57], ["y"], default="not found")) # not found
It would probably not be hard to wrap this into a special object, to be able to write something like wrap(response)["records"][0]["contactInfo"]["emails"][0].unwrap(). "wrap" would change its argument into a proxy returning a special indexable variant of None on key errors, and that dictionary would also call "wrap" on the results of __getitem__. Something like this:
class wrap():
SENTINEL = object()
def __init__(self, obj):
self.obj = obj
def unwrap(self):
return self.obj
def __getitem__(self, key):
try:
return wrap(self.obj.__getitem__(key))
except (TypeError, AttributeError, KeyError):
return wrap(None)
a = [{"y": 42, "z": ["aBc", "def"]}, [1]]
print(wrap(a)[0]["z"][0][1].unwrap())
I think that's more or less what pymaybe does, in fact.
Cheers,
Clément.
More information about the Python-ideas
mailing list