[Python] None I made nullutil.py because it was cluttered by checking and branching.

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

Contents

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.

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]: ...

List

Null coalescing operator

"** 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

Safe call operator

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)

Safe formula evaluation

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)

Weaknesses

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

[Python] None I made nullutil.py because it was cluttered by checking and branching.
I tried using Google Translate from Python and it was just too easy
[Python] I introduced Word2Vec and played with it.
Python's lru_cache was slow (I investigated it because I misunderstood it)
I made a LINE BOT with Python and Heroku
I wondered if Python 3.4 was faster, but it was slower
I was able to repeat it in Python: lambda
[Python] I made a function that decrypts AES encryption just by throwing it with pycrypto.
I made a server with Python socket and ssl and tried to access it from a browser
[Python] I installed the game from pip and played it
[I made it with Python] XML data batch output tool
I made my own OSS because I wanted to contribute to it
I made a Chatbot using LINE Messaging API and Python
If I thought I didn't see the pyc file recently, it was quarantined in pycache by python3.
I made it because I want JSON data that can be used freely in demos and prototypes