In C# there's a null-coalescing operator (written as ??
) that allows for easy (short) null checking during assignment:
string s = null;
var other = s ?? "some default value";
Is there a python equivalent?
I know that I can do:
s = None
other = s if s else "some default value"
But is there an even shorter way (where I don't need to repeat s
)?
??
operator is proposed as PEP 505.
None
-coalescing operator. The ternary alternative is way more verbose and the or
solution is simply not the same (as it handles all "falsy" values, not just None
- that's not always what you'd want and can be more error-prone).
other = s or "some default value"
Ok, it must be clarified how the or
operator works. It is a boolean operator, so it works in a boolean context. If the values are not boolean, they are converted to boolean for the purposes of the operator.
Note that the or
operator does not return only True
or False
. Instead, it returns the first operand if the first operand evaluates to true, and it returns the second operand if the first operand evaluates to false.
In this case, the expression x or y
returns x
if it is True
or evaluates to true when converted to boolean. Otherwise, it returns y
. For most cases, this will serve for the very same purpose of C♯'s null-coalescing operator, but keep in mind:
42 or "something" # returns 42
0 or "something" # returns "something"
None or "something" # returns "something"
False or "something" # returns "something"
"" or "something" # returns "something"
If you use your variable s
to hold something that is either a reference to the instance of a class or None
(as long as your class does not define members __nonzero__()
and __len__()
), it is secure to use the same semantics as the null-coalescing operator.
In fact, it may even be useful to have this side-effect of Python. Since you know what values evaluates to false, you can use this to trigger the default value without using None
specifically (an error object, for example).
In some languages this behavior is referred to as the Elvis operator.
Strictly,
other = s if s is not None else "default value"
Otherwise, s = False
will become "default value"
, which may not be what was intended.
If you want to make this shorter, try:
def notNone(s,d):
if s is None:
return d
else:
return s
other = notNone(s, "default value")
other = s.name if s is not None else "default value"
, is there also a shorter way like notNone
?
Here's a function that will return the first argument that isn't None
:
def coalesce(*arg):
return reduce(lambda x, y: x if x is not None else y, arg)
# Prints "banana"
print coalesce(None, "banana", "phone", None)
reduce()
might needlessly iterate over all the arguments even if the first argument is not None
, so you can also use this version:
def coalesce(*arg):
for el in arg:
if el is not None:
return el
return None
def coalesce(*arg): return next((a for a in arg if a is not None), None)
does the same as your last example in one line.
In case you need to chain more than one null-conditional operation such as:
model?.data()?.first()
This is not a problem easily solved with or
. It also cannot be solved with .get()
which requires a dictionary type or similar (and cannot be nested anyway) or getattr()
which will throw an exception when NoneType doesn't have the attribute.
The relevant PEP considering adding null coalescing to the language is PEP 505 and the discussion relevant to the document is in the python-ideas thread.
model?.data()?.first()
. This answer isn't relevant to the question.
I realize this is answered, but there is another option when you're dealing with dict-like objects.
If you have an object that might be:
{
name: {
first: "John",
last: "Doe"
}
}
You can use:
obj.get(property_name, value_if_null)
Like:
obj.get("name", {}).get("first", "Name is missing")
By adding {}
as the default value, if "name" is missing, an empty object is returned and passed through to the next get. This is similar to null-safe-navigation in C#, which would be like obj?.name?.first
.
.get
, this only works for dict-like objects
getattr()
also.
get
on dict does not use the default parameter if the value is None but uses the default parameter if the value does not exist because the key is not in the dict. {'a': None}.get('a', 'I do not want None')
will still give you None
as a result.
Addionally to @Bothwells answer (which I prefer) for single values, in order to null-checking assingment of function return values, you can use new walrus-operator (since python3.8):
def test():
return
a = 2 if (x:= test()) is None else x
Thus, test
function does not need to be evaluated two times (as in a = 2 if test() is None else test()
)
In addition to Juliano's answer about behavior of "or": it's "fast"
>>> 1 or 5/0
1
So sometimes it's might be a useful shortcut for things like
object = getCachedVersion() or getFromDB()
Regarding answers by @Hugh Bothwell, @mortehu and @glglgl.
Setup Dataset for testing
import random
dataset = [random.randint(0,15) if random.random() > .6 else None for i in range(1000)]
Define implementations
def not_none(x, y=None):
if x is None:
return y
return x
def coalesce1(*arg):
return reduce(lambda x, y: x if x is not None else y, arg)
def coalesce2(*args):
return next((i for i in args if i is not None), None)
Make test function
def test_func(dataset, func):
default = 1
for i in dataset:
func(i, default)
Results on mac i7 @2.7Ghz using python 2.7
>>> %timeit test_func(dataset, not_none)
1000 loops, best of 3: 224 µs per loop
>>> %timeit test_func(dataset, coalesce1)
1000 loops, best of 3: 471 µs per loop
>>> %timeit test_func(dataset, coalesce2)
1000 loops, best of 3: 782 µs per loop
Clearly the not_none
function answers the OP's question correctly and handles the "falsy" problem. It is also the fastest and easiest to read. If applying the logic in many places, it is clearly the best way to go.
If you have a problem where you want to find the 1st non-null value in a iterable, then @mortehu's response is the way to go. But it is a solution to a different problem than OP, although it can partially handle that case. It cannot take an iterable AND a default value. The last argument would be the default value returned, but then you wouldn't be passing in an iterable in that case as well as it isn't explicit that the last argument is a default to value.
You could then do below, but I'd still use not_null
for the single value use case.
def coalesce(*args, **kwargs):
default = kwargs.get('default')
return next((a for a in arg if a is not None), default)
For those like me that stumbled here looking for a viable solution to this issue, when the variable might be undefined, the closest i got is:
if 'variablename' in globals() and ((variablename or False) == True):
print('variable exists and it\'s true')
else:
print('variable doesn\'t exist, or it\'s false')
Note that a string is needed when checking in globals, but afterwards the actual variable is used when checking for value.
More on variable existence: How do I check if a variable exists?
(variablename or False) == True
is the same as variablename == True
Python has a get function that its very useful to return a value of an existent key, if the key exist;
if not it will return a default value.
def main():
names = ['Jack','Maria','Betsy','James','Jack']
names_repeated = dict()
default_value = 0
for find_name in names:
names_repeated[find_name] = names_repeated.get(find_name, default_value) + 1
if you cannot find the name inside the dictionary, it will return the default_value, if the name exist then it will add any existing value with 1.
hope this can help
The two functions below I have found to be very useful when dealing with many variable testing cases.
def nz(value, none_value, strict=True):
''' This function is named after an old VBA function. It returns a default
value if the passed in value is None. If strict is False it will
treat an empty string as None as well.
example:
x = None
nz(x,"hello")
--> "hello"
nz(x,"")
--> ""
y = ""
nz(y,"hello")
--> ""
nz(y,"hello", False)
--> "hello" '''
if value is None and strict:
return_val = none_value
elif strict and value is not None:
return_val = value
elif not strict and not is_not_null(value):
return_val = none_value
else:
return_val = value
return return_val
def is_not_null(value):
''' test for None and empty string '''
return value is not None and len(str(value)) > 0
Success story sharing
None
, and empty containers (including strings) are considered false, in addition to the constantFalse
. Most everything else is considered true. I would say that the main danger here would be that you would get a true but non-string value, but that won't be an issue in some programs.datetime.time(0)
was also falsy!other = "some default value" if s is None else s
. See also: stackoverflow.com/questions/13710631/…