[PYTHON] Vérifiez la valeur de retour avec PEP 380

Cette fois, utilisez PEP 380 pour rendre yield None équivalent à return, et la valeur de retour doit être une valeur non-None. Présentation du décorateur pour vérifier.

PEP 380 est une nouvelle spécification ajoutée dans Python 3.3, qui est un [rendement de] pour les générateurs multi-étapes. Le but principal est d'ajouter Syntaxe, mais cette fois, combien ont été ajoutés en plus du rendement de la syntaxe. J'ai écrit un décorateur qui rend «yield None» équivalent à «return» en utilisant cette fonction.

La plupart des gens

Quelle est la relation entre rendre yield None équivalent à return et vérifier que la valeur de retour est une valeur non-None?

Je pense que vous avez la question, alors laissez-moi vous donner une fonction simple à titre d'exemple.

python


    def get_version(self, config_path=None):
        if config_path is None:
            config_path = self.get_default_config_path()
        config = self.parse_config(config_path)
        program_name = config.get("program_name")
        match = RE_VERSION.match(program_name)
        return match.group("version")

C'est difficile à comprendre car ce n'est qu'une partie, mais cette get_version () récupère le chemin du fichier de configuration, analyse le fichier et extrait la partie version de l'élément` "nom_programme" ʻ comme une expression régulière. La fonction à retourner.

Je suis habitué aux expressions régulières, donc je n'hésite pas à les utiliser, mais si j'utilise des expressions régulières pour ce type de traitement,

Un tel code n'est pas pythonique!

Veuillez noter que les personnes Pythonista peuvent se mettre en colère.

Eh bien, ce code ne vérifie pas du tout la valeur de retour, comme vous pouvez le voir en un coup d'œil.

C'est un problème, alors

Le code ci-dessous est modifié pour vérifier la valeur de retour si nécessaire dans la règle.

python


    def get_version(self, config_path=None):
            if config_path is None:
                config_path = self.get_default_config_path()
            if config_path is None:
                return None
            config = self.parse_config(config_path)
            if config is None:
                return None
            program_name = config.get("program_name", None)
            if program_name is None:
                return None
            match = RE_PROGRAM_VERSION.match(program_name)
            if match is None:
                return None
            return match.group("version")

En conséquence, le code est plein de «si xxx vaut None: return None», et la fonction, qui faisait 7 lignes, fait maintenant 15 lignes.

J'ai toujours voulu faire quelque chose à ce sujet, alors j'ai écrit un décorateur appelé yield_none_becomes_return () en utilisant les fonctionnalités ajoutées dans PEP 380 comme mentionné précédemment.

Voici le code avec yield_none_becomes_return () appliqué à la première version.

python


    @yield_none_becomes_return
    def get_version(self, config_path=None):
        if config_path is None
            config_path = yield self.get_default_config_path()
        config = yield self.parse_config(config_path)
        program_name = yield config.get("program_name")
        match = yield RE_VERSION.match(program_name)
        return match.group("version")

Le code, qui était de 15 lignes dans la version précédente, est maintenant de 8 lignes.

La seule différence par rapport à la première version est l'ajout de décorateurs et de yield, et rien d'autre n'a changé.

Le processus de yield_none_becomes_return () est simple. Par exemple

python


        config_path = yield self.get_default_config_path()

Dans le cas de, si la valeur de retour de self.get_default_config_path () est None, yield None est établi, donc immédiatement return, si ce n'est pas None, renvoie la valeur à config_path. Remplacez et continuez le traitement tel quel.

S'il n'y a pas d'argument dans le décorateur, il sera équivalent à return, donc None sera retourné,

python


    @yield_none_becomes_return("")

En donnant un argument comme, il est également possible de renvoyer une valeur arbitraire lorsque yield None est satisfait.

Cependant, si vous voulez retourner un objet qui s'évalue avec callable () et est vrai

python


    @yield_none_becomes_return(value=function)

Veuillez décrire comme. Ceci est une limitation due à diverses raisons.

La seule autre chose à noter est que StopIteration () ne se propage pas à l'appelant de la fonction décorée.

La source de yield_none_becomes_return () est ci-dessous, alors faites-nous savoir si vous rencontrez d'autres problèmes.

ynbr.py


#!/usr/bin/env python3
# vim:fileencoding=utf-8

# Copyright (c) 2014 Masami HIRATA <[email protected]>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     1. Redistributions of source code must retain the above copyright notice,
#        this list of conditions and the following disclaimer.
#
#     2. Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import sys

if sys.version_info < (3, 3):  # pragma: no cover
    raise ImportError("Python >= 3.3 is required")

from functools import partial, wraps
from inspect import isgeneratorfunction

DEFAULT = object()

__all__ = ['yield_none_becomes_return']


def yield_none_becomes_return(function=DEFAULT, *, value=DEFAULT):
    """This decorator changes the yield statement to None checker for
       avoiding "if xxx is None: return" statements

    For example:
    # without this decorator:
    def get_version(self, config_path=None):
        if config_path is None:
            config_path = self.get_default_config_path()
        if config_path is None:
            return ""
        config = self.parse_config(config_path)
        if config is None:
            return ""
        program_name = config.get("program_name")
        if program_name is None:
            return ""
        match = RE_PROGRAM_VERSION.match(program_name)
        if match is None:
            return ""
        return match.group("version")

    # with this decorator:
    @yield_none_becomes_return("")
    def get_version(self, config_path=None):
        if config_path is None:
            config_path = yield self.get_default_config_path()
        config = yield self.parse_config(config_path)
        program_name = yield config.get("program_name")
        match = yield RE_VERSION.match(program_name)
        return match.group("version")
    """

    if not isgeneratorfunction(function):
        if function is DEFAULT:
            if value is DEFAULT:
                # @yield_none_becomes_return()  # CORRECT
                value = None
        else:
            if callable(function):
                raise TypeError("@yield_none_becomes_return is used only " +
                                "for generator functions")

            if value is not DEFAULT:
                # @yield_none_becomes_return("B", value="C")  # WRONG
                raise TypeError("yield_none_becomes_return() takes " +
                                "1 argument but 2 were given.")

            # @yield_none_becomes_return("A")  # CORRECT
            value = function
        return partial(yield_none_becomes_return, value=value)
    else:
        if value is DEFAULT:
            value = None

    @wraps(function)
    def _yield_none_becomes_return(*args, **kwargs):
        generator = function(*args, **kwargs)
        try:
            return_value = next(generator)
            while True:
                if return_value is not None:
                    return_value = generator.send(return_value)
                else:
                    return value
        except StopIteration as exception:
            return exception.value

    return _yield_none_becomes_return

Recommended Posts

Vérifiez la valeur de retour avec PEP 380
À propos de la valeur de retour de pthread_mutex_init ()
À propos de la valeur de retour de l'histogramme.
Vérifiez le style de code python à l'aide de pep8
Attention à la valeur de retour de __len__
Générez des valeurs de hachage à l'aide de la méthode HMAC.
Vérifiez l'état des données à l'aide de pandas_profiling
Renvoie la quantité d'entités encodées à chaud à la valeur de catégorie d'origine
L'histoire selon laquelle la valeur de retour de tape.gradient () était None
Essayez d'utiliser l'API Twitter
Cloner à l'aide de la commande dd
Essayez d'utiliser l'API Twitter
Essayez d'utiliser l'API PeeringDB 2.0
Vérifiez le code avec flake8
Décrivez le visage avec Dlib (1)
[Python] Vérifiez les bibliothèques installées
Obtenez et définissez la valeur du menu déroulant en utilisant Python et Selenium
Vérifiez le résultat du dessin à l'aide de Plotly en incorporant CodePen dans Qiita
Trouvez la valeur optimale de la fonction à l'aide d'un algorithme génétique (partie 1)