[Python] Aucun J'ai créé nullutil.py car il était encombré de vérifications et de branchements.

Avez-vous déjà voulu échapper ** à la tâche d'écrire des vérifications et des conditions "None" pour toujours **?

None en Python semble correspondre à null et nil dans d'autres langages, mais Python n'a pas d'opérateurs ou de méthodes qui facilitent la gestion ** Null, ce qui est courant dans d'autres langages ** ..

Puisqu'il n'y a aucune aide pour cela, j'ai décidé de remplacer les ** types standard 3 ** en écrivant ** fonctions **.

<détails>

Contenu </ summary>

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

Je ne pouvais pas bien écrire et je me suis appuyé sur «N'importe» dans de nombreux endroits, mais j'ai aussi préparé un talon.

<détails>

Stub </ summary>

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

liste

Opérateur de coalescence nul

"** Je veux récupérer la valeur d'une variable, mais si elle est Null, je veux donner une valeur par défaut **"

L'opérateur ** Null coalescing ** répond à une telle demande.

Dans les langages où l'opérateur de fusion Null peut être utilisé, il peut être écrit comme suit.

Pour Swift


foo = bar ?? default_value

Pour Kotlin


foo = bar ?: default_value

Comme alternative à cela, j'ai créé une fonction ** qq **.

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

Opérateur d'appel sécurisé

Si vous essayez d'appeler directement un membre qui peut être Null (Nullable), vous obtiendrez une exception s'il est vraiment 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, 'Différence par rapport à la moyenne': mean_difference, 'Valeur d'écart': standard_score,}
column_list = ['Grades', 'Différence par rapport à la moyenne', 'Valeur d'écart',]
score_df = pd.DataFrame(column_dict)[column_list]
none_df = None

display(score_df.sort_values('Grades'))
display(none_df.sort_values('Grades'))
Grades Différence par rapport à la moyenne Valeur d'écart
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'

"Je souhaite appeler un membre d'une instance ** Nullable. S'il est Null, la valeur de retour peut être Null **"

Le ** opérateur d'appel sécurisé ** répond à une telle demande.

Dans les langues où des opérateurs d'appels sécurisés sont disponibles, vous pouvez écrire:

Pour Swift


foo?.bar()

Pour Kotlin


foo?.bar()

Comme alternative à cela, j'ai créé une fonction ** q_ **.

# 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 Différence par rapport à la moyenne Valeur d'écart
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

** Si vous spécifiez plusieurs arguments **, donnez-les dans une liste, une touche ou un dictionnaire **.

# score_df?.sort_values(by='Valeur d'écart', ascending=False)
q_(score_df, 'sort_values', {'by': 'Valeur d'écart', 'ascending': False})

Lorsque vous appelez ** field ** au lieu de method, ** omettez ** le troisième argument.

# score_df?.index
q_(score_df, 'index')

Si le troisième argument est omis pour une méthode, un objet fonction appelable est simplement renvoyé, donc lorsque vous appelez une méthode sans arguments, donnez une liste vide, un tapple ou un dictionnaire **.

# standard_score?.min()
q_(standard_score, 'min', ())

#Puisque None n'est pas appelable, la notation suivante peut provoquer des exceptions.
# q_(standard_score, 'min')()

Selon la langue, la notation ? [] Existe également. J'ai également créé une fonction qL7 pour accéder aux éléments par des indices pour les listes, les dictionnaires et les tenseurs Numpy. ~~ J'ai rendu le nom de la fonction similaire à la notation générale, mais cela me donne l'impression qu'il est sur le point d'atteindre sa limite. ~~

# standard_score?[5]
qL7(standard_score, 5)

Évaluation sûre des formules

Les expressions qui prennent une valeur Nullable comme argument peuvent voler une exception si elle est vraiment 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'

"Je veux évaluer une expression qui prend une valeur ** Nullable comme argument. Cette expression attend Non-Null, donc si elle est Null, la valeur de retour peut être Null **."

Le ** système d'évaluation des expressions sûres ** répond à ces demandes.

Dans le cas de Swift, les instances Nullable ont une méthode ** map **, qui peut être évaluée en toute sécurité en leur donnant une fermeture.

Pour Swift


foo.map { $0 * 2 }

Dans le cas de Kotlin, il est réalisé en appelant en toute sécurité la méthode ** let ** de l'instance Non-Null.

Pour Kotlin


foo?.let { it * 2 }

Comme alternative à ceux-ci, j'ai créé une fonction ** q_let **.

# 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

Bien entendu, la partie expression lambda peut être remplacée par une ** fonction définie **.

np.random.seed(365)

n01 = np.random.randn(10)

# n01?.let { np.mean(it) }
q_let(n01, np.mean)

Comme vous l'avez peut-être remarqué, la fonction q_let est une alternative à ce que la fonction q_ peut faire.

# score_df?.sort_values('Valeur d'écart', ascending=False)
# <=> score_df?.let { it.sort_values('Valeur d'écart', ascending=False) }
q_let(score_df, lambda it: it.sort_values('Valeur d'écart', ascending=False))

Si l'argument positionnel et l'argument nom ne peuvent pas être donnés ensemble dans une liste ou un dictionnaire / si c'est difficile, la fonction q_let peut être utilisée à la place. Cependant, dans ce cas, la chaîne est très difficile à écrire, donc dans ce cas, il est plus facile d'utiliser la fonction q_.

S'il y a plusieurs variables Nullable, map et let seront imbriquées et ce sera difficile. Haskell semble être capable d'écrire cela facilement en utilisant la notation do. Puisque la fonction q_let est une fonction après tout, j'ai rendu possible de prendre une collection comme argument ** depuis le début.

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)

Faiblesses

Tout d'abord, parce qu'il est implémenté de force par une fonction, ** le nombre de caractères augmentera inévitablement **. L'opérateur «??» est composé de 2 caractères, mais «qq (,)» est composé de 5 caractères. «?.» Etc. sont encore plus misérables à cause de citations inutiles.

Une autre chose est que contrairement à l'opérateur **, il ne peut pas être incrusté **, donc la ** chaîne a l'air terrible **.

Ci-dessous, un exemple de chaîne en Swift.

foo?.bar()?.baz?.qux() ?? default_value

C'est très rafraîchissant, mais quand j'essaye d'écrire la même chose avec la fonction que j'ai créée cette fois, ça devient comme ça.

qq(q_(q_(q_(foo,'bar',()),'baz'),'qux',()), default_value)

Ce n'est plus une chaîne mais un ** imbriqué, et je n'ai aucune idée de ce qui l'entoure. Trop terrible. Quand vous venez ici

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

Ça a toujours l'air mieux.

qq(
    q_(
        q_(
            q_(
                foo, 'bar', ()
            ), 'baz'
        ), 'qux', ()
    ), default_value
)

Si vous l'écrivez ainsi, c'est un peu plus facile à voir **.

Recommended Posts

[Python] Aucun J'ai créé nullutil.py car il était encombré de vérifications et de branchements.
J'ai essayé d'utiliser Google Translate à partir de Python et c'était trop facile
Le lru_cache de Python était lent (je l'ai étudié parce que je l'ai mal compris)
J'ai essayé de faire LINE BOT avec Python et Heroku
Je me suis demandé si Python 3.4 était plus rapide, mais c'était plus lent
Python: peut être répété en lambda
[Python] J'ai créé une fonction qui déchiffre et décrypte AES simplement en le lançant avec pycrypto.
J'ai créé un serveur avec socket Python et ssl et j'ai essayé d'y accéder depuis le navigateur
[Python] J'ai installé le jeu depuis pip et j'ai essayé de jouer
[Je l'ai fait avec Python] Outil pour la sortie par lots de données XML
J'ai créé Chatbot en utilisant l'API LINE Messaging et Python
Je pensais ne pas avoir vu le fichier pyc récemment, mais il a été isolé dans pycache par python3
Je l'ai fait parce que je veux des données JSON qui peuvent être utilisées librement dans les démos et les prototypes