Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi-argument null coalesce and built-in "or" function in Python

Python has a great syntax for null coalescing:

c = a or b

This sets c to a if a is not False, None, empty, or 0, otherwise c is set to b.

(Yes, technically this is not null coalescing, it's more like bool coalescing, but it's close enough for the purpose of this question.)

There is not an obvious way to do this for a collection of objects, so I wrote a function to do this:

from functools import reduce

def or_func(x, y):
    return x or y

def null_coalesce(*a):
    return reduce(or_func, a)

This works, but writing my own or_func seems suboptimal - surely there is a built-in like __or__? I've attempted to use object.__or__ and operator.__or__, but the first gives an AttributeError and the second refers to the bitwise | (or) operator.

As a result I have two questions:

  1. Is there a built-in function which acts like a or b?
  2. Is there a built-in implementation of such a null coalesce function?

The answer to both seems to be no, but that would be somewhat surprising to me.

like image 317
shayaan Avatar asked Aug 16 '19 00:08

shayaan


Video Answer


3 Answers

It's not exactly a single built-in, but what you want to achieve can be easily done with:

def null_coalesce(*a):
    return next(x for x in a if x)

It's lazy, so it does short-circuit like a or b or c, but unlike reduce.

You can also make it null-specific with:

def null_coalesce(*a):
    return next(x for x in a if x is not None)
like image 125
viraptor Avatar answered Oct 23 '22 21:10

viraptor


Is there a built-in function which I can use which acts like a or b?

No. Quoting from this answer on why:

The or and and operators can't be expressed as functions because of their short-circuiting behavior:

False and some_function()
True or some_function()

in these cases, some_function() is never called.

A hypothetical or_(True, some_function()), on the other hand, would have to call some_function(), because function arguments are always evaluated before the function is called.


Is there a built-in implementation of such a null coalesce function?

No, there isn't. However, the Python documentation page for itertools suggests the following:

def first_true(iterable, default=False, pred=None):
    """Returns the first true value in the iterable.

    If no true value is found, returns *default*

    If *pred* is not None, returns the first item
    for which pred(item) is true.

    """
    # first_true([a,b,c], x) --> a or b or c or x
    # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
    return next(filter(pred, iterable), default)
like image 5
Marco Bonelli Avatar answered Oct 23 '22 20:10

Marco Bonelli


Marco has it right, there's no built-in, and itertools has a recipe. You can also pip install boltons to use the boltons.iterutils.first() utility, which is perfect if you want short-circuiting.

from boltons.iterutils import first

c = first([a, b])

There are a few other related and handy reduction tools in iterutils, too, like one().

I've done enough of the above that I actually ended up wanting a higher-level tool that could capture the entire interaction (including the a and b references) in a Python data structure, yielding glom and its Coalesce functionality.

from glom import glom, Coalesce

target = {'b': 1}
spec = Coalesce('a', 'b')

c = glom(target, spec)
# c = 1

(Full disclosure, as hinted above, I maintain glom and boltons, which is good news, because you can bug me if you find bugs.)

like image 1
Mahmoud Hashemi Avatar answered Oct 23 '22 22:10

Mahmoud Hashemi