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

3

6

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.

Hoseahoseia answered 16/8, 2019 at 0:59 Comment(2)
None-coalescing, -aware, -safe operators is proposed on PEP 505 which has deferred status.Cowpox
@Cowpox nope, that's the bitwise OR operator.Drainage
P
9

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)
Partisan answered 16/8, 2019 at 1:5 Comment(3)
null_coalesce([0,0,3]) == [0, 0, 3] What's going on there?Hufuf
@Hufuf it's star-args, not a single arg. You should be passing 0, 0, 3 to the function, not [0, 0, 3] if you want to just return 3.Cowpox
@Cowpox ohhh, oopsHufuf
D
6

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)
Drainage answered 16/8, 2019 at 1:14 Comment(4)
Alternatively, operator.or_Cowpox
@Cowpox operator.__or__ and operator.or_ are for bitwise OR. That's another thing. I quickly edited my answer after noticing.Drainage
The ability to have a predicate in this is really nice +1Hoseahoseia
Oops! Good call!Cowpox
C
1

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.)

Crackerjack answered 16/8, 2019 at 4:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.