Disclaimer: this is probably not a very good idea, as it isn't very readable, but I'm just adding it here for completeness.
Exploit how and works
Here is an expression that works if you want to apply an arbitrary function transform to a nullable variable var: Optional[T], returning the result of that computation if the variable is not None, or else None (in the spirit of Java's Optional):
var and transform(var)
This works for any function, not just in a boolean context (e.g. the condition to an if clause). The reason is, the and operator is defined as follows (from the docs):
The expression x and y first evaluates x; if x is false, its value is returned; otherwise, y is evaluated and the resulting value is returned.
Thus, if var is None, var and transform(var) will evaluate to None (without evaluating transform(var), since boolean operators in Python are short-circuiting), and if it is not, it will evaluate to the result of transform(var) and return that.
Not quite as concise as a proper null pattern, but still less verbose than
transform(var) if var is not None else None
Example
import datetime
def null_pattern_demo(x: datetime.datetime):
return x and x.timestamp()
>>> null_pattern_demo(None)
>>> null_pattern_demo(datetime.datetime(2020, 6, 29))
1593381600.0
Chaining
In Python 3.8+, with the help of assignment expressions, you can even chain this pattern, i.e. you can compose a sequence of functions and return None if any of the computations returned None, or return the final result otherwise:
var and (r := f(var)) and (r := g(r)) and (r := h(r))
This will evaluate to None as soon as any of the intermediate steps (or var itself) returns None. As a side effect, the final result will also be stored in r.
Example
def f(x, y):
return 2*x if x == y else None
def g(x, y):
return x + y if y != 42 else None
def h(x):
return x**2
If we try apply f, g and h composed in one go, we will get an error if one of the intermediate steps returns None:
>>> h(g(f(2, 2), 42))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in h
TypeError: unsupported operand type(s) for ** or pow(): 'NoneType' and 'int'
But, instead, we can use the above trick:
>>> (x := None) and (x := f(x, 3)) and (x := g(x, 1)) and (x := h(x))
>>> (x := 2) and (x := f(x, 3)) and (x := g(x, 1)) and (x := h(x))
>>> (x := 2) and (x := f(x, 2)) and (x := g(x, 42)) and (x := h(x))
>>> (x := 2) and (x := f(x, 2)) and (x := g(x, 1)) and (x := h(x))
25