Have you ever wanted to escape ** the task of writing None
checks and conditionals ** forever? I have.
None
in Python seems to correspond to null
and nil
in other languages, but Python does not have operators or methods that make it easier to handle ** Null, which is common in other languages ** ..
Since there is no help for it, I decided to substitute ** standard 3 species ** by writing ** functions **.
nullutil.py
# Null Coalesce
# This means:
# lhs ?? rhs
def qq(lhs, rhs):
return lhs if lhs is not None else rhs
# Safty Access
# This means:
# instance?.member
# instance?.member(*params)
def q_(instance, member, params=None):
if instance is None:
return None
else:
m = getattr(instance, member)
if params is None:
return m
elif isinstance(params, dict):
return m(**params)
elif isinstance(params, list) or isinstance(params, tuple):
return m(*params)
else:
return m(params)
# This means:
# instance?[index]
def qL7(collection, index):
return collection[index] if collection is not None else None
# Safety Evalate (do Syntax)
# This means:
# params?.let{expression}
# do
# p0 <- params[0]
# p1 <- params[1]
# ...
# return expression(p0, p1, ...)
def q_let(params, expression):
if isinstance(params, dict):
for param in params.values():
if param is None:
return None
return expression(**params)
elif isinstance(params, list) or isinstance(params, tuple):
for param in params:
if param is None:
return None
return expression(*params)
else:
return expression(params) if params is not None else None
I couldn't write well and relied on ʻAny` in many places, but I also prepared a stub.
nullutil.pyi
from typing import TypeVar, Hashable, Mapping, MutableMapping, Sequence, MutableSequence, Any, Union, Optional, Callable, AnyStr
from typing import overload
T = TypeVar('T')
U = TypeVar('U')
H = TypeVar('H', Hashable)
SeqT = Union[Sequence[T], MutableSequence[T]]
MapT = Union[Mapping[H, T], MutableMapping[H, T]]
C = Union[list, tuple, dict]
# Null Coalesce
# This means:
# lhs ?? rhs
def qq(lhs: Optional[T], rhs: T) -> T: ...
# Safty Access
# This means:
# instance?.member
# instance?.member(*params)
def q_(instance: Optional[Any], member:AnyStr, params: Optional[Any]) -> Optional[Any]: ...
# This means:
# instance?[index]
@overload
def qL7(collection: Optional[SeqT], index: int) -> Optional[T]: ...
@overload
def qL7(collection: Optional[MapT], index: H) -> Optional[T]: ...
# Safety Evalate (do Syntax)
# This means:
# params?.let{expression}
# do
# p0 <- params[0]
# p1 <- params[1]
# ...
# return expression(p0, p1, ...)
@overload
def q_let(params: Optional[T], expression: Callable[[T], U]) -> Optional[U]: ...
@overload
def q_let(params: Optional[C], expression: Callable[..., T]) -> Optional[T]: ...
"** I want to retrieve the value of a variable, but if it is Null, I want to give a default value **"
The ** Null coalescing operator ** answers such a request.
In languages where the Null coalescing operator can be used, it can be written as follows.
For Swift
foo = bar ?? default_value
For Kotlin
foo = bar ?: default_value
As an alternative to this, I created a ** qq
function **.
guide = 'Mirai Hirano'
researcher = 'Kako Nanami'
curator = None
# print(guide ?? 'John Doe')
print(qq(guide, 'John Doe'))
# print(researcher ?? 'John Doe')
print(qq(researcher, 'John Doe'))
# print(curator ?? 'John Doe')
print(qq(curator, 'John Doe'))
Mirai Hirano
Kako Nanami
John Doe
If you try to call a member that may be Null (Nullable) directly, you will get an exception if it is really Null.
import numpy as np
import pandas as pd
np.random.seed(365)
score = np.clip(np.rint(np.random.normal(80., 15., 500)).astype(int), 0, 100)
mean = np.mean(score)
std = np.std(score)
mean_difference = score - mean
standard_score = mean_difference * (10. / std) + 50.
column_dict = {'Grades': score, 'Difference from average': mean_difference, 'Deviation value': standard_score,}
column_list = ['Grades', 'Difference from average', 'Deviation value',]
score_df = pd.DataFrame(column_dict)[column_list]
none_df = None
display(score_df.sort_values('Grades'))
display(none_df.sort_values('Grades'))
Grades | Difference from average | Deviation value | |
---|---|---|---|
249 | 34 | -45.632 | 16.784097 |
82 | 36 | -43.632 | 18.239913 |
89 | 36 | -43.632 | 18.239913 |
372 | 41 | -38.632 | 21.879453 |
112 | 42 | -37.632 | 22.607361 |
... | ... | ... | ... |
197 | 100 | 20.368 | 64.826033 |
43 | 100 | 20.368 | 64.826033 |
337 | 100 | 20.368 | 64.826033 |
334 | 100 | 20.368 | 64.826033 |
280 | 100 | 20.368 | 64.826033 |
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-50-badfe23fbcf4> in <module>
1 display(score_df.sort_values('Grades'))
----> 2 display(none_df.sort_values('Grades'))
AttributeError: 'NoneType' object has no attribute 'sort_values'
"I want to call a member of a ** Nullable instance. If it is Null, the return value can be Null **"
The ** safe call operator ** answers such a request.
In languages where safe call operators are available, you can write:
For Swift
foo?.bar()
For Kotlin
foo?.bar()
As an alternative to this, I created a ** q_
function **.
# display(score_df?.sortvalues('Grades'))
display(q_(score_df,'sort_values','Grades'))
# display(none_df?.sortvalues('Grades'))
display(q_(none_df,'sort_values','Grades'))
Grades | Difference from average | Deviation value | |
---|---|---|---|
249 | 34 | -45.632 | 16.784097 |
82 | 36 | -43.632 | 18.239913 |
89 | 36 | -43.632 | 18.239913 |
372 | 41 | -38.632 | 21.879453 |
112 | 42 | -37.632 | 22.607361 |
... | ... | ... | ... |
197 | 100 | 20.368 | 64.826033 |
43 | 100 | 20.368 | 64.826033 |
337 | 100 | 20.368 | 64.826033 |
334 | 100 | 20.368 | 64.826033 |
280 | 100 | 20.368 | 64.826033 |
None
** When specifying multiple arguments **, give them in ** list, tuple or dictionary **.
# score_df?.sort_values(by='Deviation value', ascending=False)
q_(score_df, 'sort_values', {'by': 'Deviation value', 'ascending': False})
When calling ** field ** instead of method, ** omit ** the third argument.
# score_df?.index
q_(score_df, 'index')
If the third argument is omitted for the method, a callable function object is simply returned, so when calling a method with no arguments **, give an empty list, tuple, or dictionary **.
# standard_score?.min()
q_(standard_score, 'min', ())
#Since None is not callable, the following notation may cause exceptions.
# q_(standard_score, 'min')()
Some languages also have a ? []
Notation. I also created a qL7
function to access elements by subscripts for lists, dictionaries, and Numpy tensors. ~~ I have made the function name similar to the general notation, but it makes me feel that it is about to reach its limit. ~~
# standard_score?[5]
qL7(standard_score, 5)
Expressions that take a Nullable value as an argument may fly an exception if it is really Null.
import numpy as np
sequence = np.arange(0, 10)
none_array = None
print(sequence * 2)
print(none_array * 2)
[ 0 2 4 6 8 10 12 14 16 18]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-82-44094c5f4f90> in <module>
1 print(sequence * 2)
----> 2 print(none_array * 2)
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
"I want to evaluate an expression that takes a ** Nullable value as an argument. This expression expects Non-Null, so if it is Null, the return value can be Null **."
The system that evaluates formulas safely ** answers such demands.
In the case of Swift, a Nullable instance has a ** map
method **, so you can safely evaluate it by giving it a closure.
For Swift
foo.map { $0 * 2 }
In the case of Kotlin, it is realized by safely calling the ** let
method ** of the Non-Null instance.
For Kotlin
foo?.let { it * 2 }
As an alternative to these, I created a ** q_let
function **.
# print(sequence?.let { it * 2 } )
print(q_let(sequence, lambda it: it * 2))
# print(none_array?.let { it * 2 } )
print(q_let(none_array, lambda it: it * 2))
[ 0 2 4 6 8 10 12 14 16 18]
None
Of course, the lambda expression part can be replaced with a ** defined function **.
np.random.seed(365)
n01 = np.random.randn(10)
# n01?.let { np.mean(it) }
q_let(n01, np.mean)
As you may have noticed, the q_let
function is an alternative to what the q_
function can do.
# score_df?.sort_values('Deviation value', ascending=False)
# <=> score_df?.let { it.sort_values('Deviation value', ascending=False) }
q_let(score_df, lambda it: it.sort_values('Deviation value', ascending=False))
If the positional argument and the name argument cannot be given in one list or dictionary in a single list / difficult, the q_let
function can be used instead. However, in this case the chain is very difficult to write, so in that case it is easier to use the q_
function.
If there are multiple Nullable variables, map
and let
will be nested and it will be difficult. Haskell seems to be able to write this easily by using the do notation. Since the q_let
function is a function after all, I made it possible to take a collection as an argument ** from the beginning.
import math
r = 5
pi = math.pi
# r?.let { x -> pi?.let { y -> x**2 * y } }
q_let([r, pi,], lambda x, y: x**2 * y)
First of all, because it is forcibly implemented by a function, ** the number of characters will inevitably increase **. The ??
operator is 2 characters, but qq (,)
is 5 characters. ?.
Etc. are even more miserable with quotations that are not originally needed.
Another thing is that unlike the ** operator, it can't be inlaid **, which makes the chain look terrible **.
Below is an example of a chain in Swift.
foo?.bar()?.baz?.qux() ?? default_value
It's very refreshing, but when I try to write the same thing with the function I created this time, it looks like this.
qq(q_(q_(q_(foo,'bar',()),'baz'),'qux',()), default_value)
It's no longer a chain but nested **, and I have no idea what surrounds it. Too terrible. When you come here
if foo is None:
ret = default_value
else:
temp = foo.bar()
if temp is None:
ret = default_value
else:
temp = temp.baz
if temp is None:
ret = default_value
else:
temp = temp.qux()
ret = temp if temp is not None else default_value
Still looks better.
qq(
q_(
q_(
q_(
foo, 'bar', ()
), 'baz'
), 'qux', ()
), default_value
)
If you write it like this, it's a little easier to see **.
Recommended Posts