C'est Halloween donc je vais essayer de le cacher avec Python

Préface

La troisième histoire d'Halloween.


Par exemple, supposons que vous ayez ce code. C'est un code Fizzbuzz courant.

Fizzbuzz sans aucun changement


def fizzbuzz(n):
    if n % 15 == 0:
        return 'fizzbuzz'
    if n % 3 == 0:
        return 'fizz'
    if n % 5 == 0:
        return 'buzz'
    
    return str(n)


for i in range(20):
    print(fizzbuzz(i+1))
** Résultat de l'exécution ** (Cliquez pour ouvrir)
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz

Cependant, ʻi + 1 de fizzbuzz (i + 1) `a l'air moche.

Puisque vous êtes coincé dans l'instruction for, ne voulez-vous pas que je lise les arguments et devine? [^ 1] Je veux que cela fonctionne de la même manière même si je l'appelle comme suit.

for _ in range(20):
    print(fizzbuzz())

Contester l'argument réel pour se cacher

[^ 1]: Je ne pense pas.

Produit final de cet article

J'ai fait quelque chose comme ça.

Produit final


#↓ Ne vous inquiétez pas du droit à partir d'ici
def fizzbuzz(n=(lambda mi: (__import__('sys').setprofile(mi.hook), mi)[-1])(type('MyInt', (object, ), {'__init__': (lambda self, n: setattr(self, '_n', n)), 'hook': (lambda self, frame, event, _: setattr(self, '_n', self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return')))), '__mod__': (lambda self, other: self._n % other), '__str__': (lambda self: str(self._n)),})(1))):
    if n % 15 == 0:
        return 'fizzbuzz'
    if n % 3 == 0:
        return 'fizz'
    if n % 5 == 0:
        return 'buzz'

    return str(n)


for _ in range(20):
    print(fizzbuzz())

** Résultat de l'exécution ** ⇒ Wandbox

Bien entendu, il peut être exécuté normalement et le résultat de l'exécution est le même qu'avant la modification. Cet article explique comment ce code a été complété.

Faisons-le: tout d'abord, honnêtement

Étape 0: Confirmer la politique

Si vous omettez simplement les arguments réels, il existe de nombreuses façons de procéder. Dans cette section, je voudrais clarifier les objectifs en écrivant chaque solution simple et ses difficultés ~~ (ou habitudes difficiles) ~~.

Solution utilisant des variables de module

default_n = 1

def fizzbuzz(n=None):
    global default_n
    if n is None:
        n = default_n
        default_n += 1

    if n % 15 == 0:
    ...

Une mauvaise déclaration globale est apparue et l'implémentation de la fonction doit être modifiée. C'est hors de question.

Solution utilisant la fermeture

def make_fizzbuzz(n_start=1):
    default_n = n_start
    
    def _fizzbuzz(n=None):
        nonlocal default_n
        if n is None:
            n = default_n
            default_n += 1
            
        if n % 15 == 0:
        ...  
    
    return _fizzbuzz


fizzbuzz = make_fizzbuzz()

Il y a quelques avantages par rapport à la méthode utilisant la variable de module précédente.

--La valeur initiale de n (n_start) peut être spécifiée lorsque la fonction fizzbuzz est générée.

Cependant, l'implémentation de la fonction fizzbuzz doit être considérablement modifiée.

Solution utilisant la classe

class CountedUpInt:
    def __init__(self, start_n=1):
        self._n = start_n
        
    def __int__(self):
        n = self._n
        self._n += 1
        
        return n

    
def fizzbuzz(n=CountedUpInt()):
    n = int(n)

    if n % 15 == 0:
    ...

Une solution simple. Facile à comprendre [^ 2] et facile à réparer.

[^ 2]: Cependant, vous devez comprendre comment gérer les arguments par défaut de Python. La ** valeur ** par défaut n'est évaluée qu'une seule fois lorsque la fonction est définie. Eh bien, même avec l'expression ** par défaut **, vous pouvez créer un code similaire en utilisant des variables de classe.

Cependant, même avec cette méthode, l'intérieur de l'implémentation de la fonction fizzbuzz doit être modifié. ** Le but est de réaliser que Dieu se cache simplement en concevant les arguments par défaut sans toucher à la partie code de la fonction fizzbuzz. ** **

Solution / modification par fermeture

Si vous faites bon usage des fonctions d'ordre supérieur, vous n'aurez pas à toucher à l'implémentation de la fonction fizzbuzz. La lisibilité est également améliorée lorsqu'un décorateur est utilisé ensemble.

def countup_wrapper(n_start):
    default_n = n_start
    
    def _inner1(func):
        def _inner2(n=None):
            nonlocal default_n
            
            if n is None:
                n = default_n
                default_n += 1
                
            return func(n)
        return _inner2
    return _inner1


@countup_wrapper(1)
def fizzbuzz(n):
    ...

Je l'ai inventé après avoir fini d'écrire la plupart des articles. ~~ ** Est-ce que ça va? ** ~~ L'article que j'ai écrit sera gaspillé, alors prenez l'allergie non locale comme un bouclier et ignorez-la.

C'est une liaison qui ne modifie que l'argument par défaut.

Étape 1: Créez un objet qui se comporte comme un nombre

Tant que vous ne touchez pas la partie de code, la valeur par défaut doit pouvoir être traitée de la même manière qu'une valeur numérique. Par exemple, afin de réaliser le calcul du surplus, la classe doit être organisée comme suit. [^ 3]

[^ 3]: Si vous voulez mettre l'objet MyInt dans l'opérande droit du calcul du reste, vous devez également implémenter la méthode __rmod__.

class MyInt:
    def __init__(self, n):
         self._n = n

    def __mod__(self, other):
         return self._n % other

    def __str__(self):
         return str(self._n)


print(16 % 6)             # => 4
print(MyInt(16) % 6)      # => 4

«mod» est une méthode spéciale responsable du calcul du reste, et «a% b» équivaut à «a .__ mod__ (b)». ** Référence **: [Référence du langage Python »3. Modèle de données» 3.3.8. Émuler les types numériques](https://docs.python.org/ja/3/reference/datamodel.html#emulating- numeric-types)

À ce stade, vous pouvez écrire du code comme celui-ci: Cependant, comme le processus de comptage n'a pas été mis en œuvre, le même nombre sera naturellement déterminé de manière répétée.

def fizzbuzz(n=MyInt(1)):
    if n % 15 == 0:
    ...

Étape 2: Réfléchissez à la façon de compter au bon moment

Si vous n'êtes pas impliqué dans l'implémentation de la fonction fizzbuzz, vous n'avez pas d'autre choix que de raccrocher l'appel / échappement de la fonction. Ici, réalisons le hook en utilisant la fonction sys.setprofile ~~ abuse ~~ ** diversion **. Je pense.

class MyInt:
    ...
    def succ(self):
        self._n += 1
            
    ...
 

import sys

myInt = MyInt(1)
def _hook(frame, event, arg):
    # frame.f_code.co_nom Nom de la fonction de l'événement
    # event                  'call'Ou'return'
    if (frame.f_code.co_name, event) == ('fizzbuzz', 'return'):
        myInt.succ()        

sys.setprofile(_hook)


#
def fizzbuzz(n=myInt):
    if n % 15 == 0:
    ...

** Référence **: bibliothèque standard Python »inspect --types and members

A ce stade, l'objectif mentionné ci-dessus "sans toucher à la partie de code de la fonction fizzbuzz" a déjà été atteint.

Livrable α: mise en œuvre simple (?)

Livrable α


class MyInt:
    def __init__(self, n):
         self._n = n

    def succ(self):
        self._n += 1
            
    def __mod__(self, other):
         return self._n % other

    def __str__(self):
         return str(self._n)        
 

import sys

myInt = MyInt(1)
def _hook(frame, event, arg):
    if (frame.f_code.co_name, event) == ('fizzbuzz', 'return'):
        myInt.succ()        

sys.setprofile(_hook)


#
def fizzbuzz(n=myInt):
    if n % 15 == 0:
        return 'fizzbuzz'
    if n % 3 == 0:
        return 'fizz'
    if n % 5 == 0:
        return 'buzz'
    
    return str(n)


for _ in range(20):
    print(fizzbuzz())

** Résultat de l'exécution ** ⇒ Wandbox

Allons-y: réduisez le nombre de lignes

Cependant, il y a trop de descriptions autres que la fonction fizzbuzz dans le produit α précédent. Pour souligner que fizzbuzz est le but principal, ** je vais l'écrire avec le moins de lignes possible ** [^ 4].

[^ 4]: Ne dites pas que le style artistique est restreint. La silhouette est trop étoilée et blessée.

Étape 1: assemblez les objets

Afin de rendre la description ultérieure aussi simple que possible, je vais résumer les objets dans une certaine mesure. Par exemple, la fonction _hook peut être incorporée en tant que méthode de la classe MyInt.

class MyInt:
    ...    
    def hook(self, frame, event, arg):
        if (frame.f_code.co_name, event) == ('fizzbuzz', 'return'):
            self._n += 1

myInt = MyInt(1)
sys.setprofile(myInt.hook)

La méthode succ a été supprimée pour faciliter les modifications ultérieures.

Étape 2: Classe MyInt sur une ligne

Étape 2.1: Traitement en une ligne dans la méthode

Implémentez la méthode avec une seule instruction d'expression ou une seule instruction de retour.

[^ 5]: Python 3.8 a introduit l'opérateur d'affectation (communément appelé opérateur Seiuchi), mais il semble y avoir une limite au contexte dans lequel il peut être utilisé. [^ 6]: Je veux écrire un traitement sans état sans l'utiliser autant que possible. Est-ce que le sentiment de perdre si j'utilise setattr facilement n'est que pour moi?

class MyInt:
    def __init__(self, n):
        setattr(self, '_n', n)
    
    def hook(self, frame, event, arg):
        setattr(self, '_n',
            #Soi quand la condition est vraie._n +Équivalent à 1, soi si faux._n +Équivaut à 0.
            self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return'))
        )
    ...

Étape 2.2: Éliminer les instructions de définition de fonction

Redéfinissez chaque méthode à l'aide d'une expression lambda.

class MyInt:
    __init__ = (lambda self, n:
        setattr(self, '_n', n)
    )
    hook = (lambda self, frame, event, _:
        setattr(
            self, '_n',
            self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return'))
        )
    )
    __mod__ = (lambda self, other:
        self._n % other
    )
    __str__ = (lambda self: 
        str(self._n)
    )

Puisqu'une méthode est une variable de classe après tout, son remplacement est étonnamment facile.

Étape 2.3: Éliminer les instructions de définition de classe

Vous pouvez utiliser la fonction type pour créer un objet de classe dans une expression. Les attributs de classe sont transmis ensemble dans un dictionnaire.

MyInt = type('MyInt', (object, ), {
    '__init__': (lambda self, n: 
        setattr(self, '_n', n)
    ), 
    'hook': (lambda self, frame, event, _:
        setattr(
            self, '_n',
            self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return'))
        )
    ), 
    '__mod__': (lambda self, other: 
        self._n % other
    ), 
    '__str__': (lambda self: 
        str(self._n)
    ),
})

À ce stade, la définition de classe est une ligne.

MyInt = type('MyInt', (object, ), {'__init__': (lambda self, n: setattr(self, '_n', n)), 'hook': (lambda self, frame, event, _: setattr(self, '_n', self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return')))), '__mod__': (lambda self, other: self._n % other), '__str__': (lambda self: str(self._n)),})

Étape 3: sys.setprofile sur une ligne (~)

Vous pouvez utiliser la fonction __import__ au lieu de l'instruction d'importation et écrire:

__import__('sys').setprofile(MyInt(1).hook)

En fait, ce code est incomplet. L'instance MyInt que j'ai créée a été supprimée et ne peut plus être récupérée [^ 7].

[^ 7]: Strictement parlant, c'est un mensonge. Cependant, collecter avec myInt = sys.getprofile () .__ self__ est ennuyeux.

Il est courant de faire bon usage de la formule lambda comme suit.

myInt = (lambda mi:
    #Créez un taple et renvoyez son dernier élément.
    (__import__('sys').setprofile(mi.hook), mi)[-1]
#Créez un objet une seule fois.
)(MyInt(1))

Étape 4: Une ligne pour le tout

Intégrez simplement l'objet type précédent directement dans la partie MyInt.

myInt = (lambda mi:
    (__import__('sys').setprofile(mi.hook), mi)[-1]
)(type('MyInt', (object, ), {'__init__': (lambda self, n: setattr(self, '_n', n)), 'hook': (lambda self, frame, event, _: setattr(self, '_n', self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return')))), '__mod__': (lambda self, other: self._n % other), '__str__': (lambda self: str(self._n)),})(1))

Livrable β: Implémentation qui n'ajoute pas de lignes supplémentaires

Utilisez l'objet ci-dessus pour la valeur par défaut myInt de l'argument n pour obtenir le produit final mentionné ci-dessus.

Livrable β(Produit final)


def fizzbuzz(n=(lambda mi: (__import__('sys').setprofile(mi.hook), mi)[-1])(type('MyInt', (object, ), {'__init__': (lambda self, n: setattr(self, '_n', n)), 'hook': (lambda self, frame, event, _: setattr(self, '_n', self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return')))), '__mod__': (lambda self, other: self._n % other), '__str__': (lambda self: str(self._n)),})(1))):
    if n % 15 == 0:
        return 'fizzbuzz'
    if n % 3 == 0:
        return 'fizz'
    if n % 5 == 0:
        return 'buzz'

    return str(n)


for _ in range(20):
    print(fizzbuzz())

** Résultat de l'exécution ** ⇒ Wandbox

Résumé

Comme mentionné ci-dessus, j'ai pu écrire le code sans les arguments réels. Si vous pouvez omettre les arguments évidents lors de l'écriture de code ... N'aimeriez-vous en aucun cas le faire?

** Je ne pense pas. ** **

Conseils d'expansion

Répertoriez les fonctions qui pourront être étendues à l'avenir.

  1. ** Réinitialiser le compte **
  2. ** Si vous passez un argument explicitement, faites en sorte que la valeur par défaut le suive **
  3. ** Assurez-vous que la modification du nom de la fonction n'affecte pas la valeur par défaut **

Image de l'extension 1


print(fizzbuzz())   # => 1
print(fizzbuzz())   # => 2
print(fizzbuzz())   # => fizz

fizzbuzz.reset(10)

print(fizzbuzz())   # => buzz
print(fizzbuzz())   # => 11

Image de l'extension 2


print(fizzbuzz())   # => 1
print(fizzbuzz())   # => 2
print(fizzbuzz())   # => fizz
print(fizzbuzz(10)) # => buzz
print(fizzbuzz())   # => 11

La mise en œuvre temporaire de l'extension 3 est terminée à ce stade. Je suis également intéressé par 1 et 2, donc j'écrirai bientôt à leur sujet. Je ne sais pas si ce sera un article.

Recommended Posts

C'est Halloween donc je vais essayer de le cacher avec Python
Essayez d'exploiter Facebook avec Python
C'est Noël, donc je vais essayer de dessiner la généalogie de Jésus-Christ avec Cabocha
C'est un problème d'écrire "coding: utf-8" en Python, donc je vais faire quelque chose avec Shellscript
Essayez de vous connecter à qiita avec Python
3.14 π jour, alors essayez de sortir en Python
Essayez-le avec JupyterLab en Python japonais Word Cloud.
Essayez de résoudre le diagramme homme-machine avec Python
Essayez de dessiner une courbe de vie avec python
Essayez de créer un code de "décryptage" en Python
Essayez de générer automatiquement des documents Python avec Sphinx
Essayez de créer un groupe de dièdre avec Python
Essayez de détecter les poissons avec python + OpenCV2.4 (inachevé)
J'ai pu me moquer d'AWS-Batch avec python, moto, donc je vais le laisser
Essayez de gratter avec Python.
Essayez de résoudre le livre des défis de programmation avec python3
Faisons un outil de veille de commande avec python
Installez le sélénium sur votre Mac et essayez-le avec python
Essayez le fonctionnement de la base de données avec Python et visualisez avec d3
Il est trop difficile d'afficher le japonais avec le python3 de Vim.
[Mémorandum] python + vscode + pipenv C'est courant, mais c'était un désordre d'avertissement, donc un mémorandum
Essayez de défier le sol par récursif
Essayez de le faire avec GUI, PyQt en Python
Connectez-vous à BigQuery avec Python
Après tout, il est faux de chat avec le sous-processus python.
Essayez de comprendre Python soi
Quand j'essaye de pousser avec heroku, ça ne marche pas
Essayez la sortie Python avec Haxe 3.2
Connectez-vous à Wikipedia avec Python
[AWS] Essayez d'ajouter la bibliothèque Python à la couche avec SAM + Lambda (Python)
Publiez sur Slack avec Python 3
Essayez d'ouvrir une sous-fenêtre avec PyQt5 et Python
Essayez d'automatiser le fonctionnement des périphériques réseau avec Python
Essayez d'exécuter Python avec Try Jupyter
Basculer python vers 2.7 avec des alternatives
Écrire en csv avec Python
Essayez la reconnaissance faciale avec Python
Essayez de déchiffrer les caractères déformés dans le nom du fichier joint avec Python
Activé pour convertir PNG en JPG avec Pillow of Python
N'écrivez pas Python si vous voulez l'accélérer avec Python
Les personnes familiarisées avec les programmes Android essaient le traitement multi-thread avec Python
Essayez de créer un environnement python avec Visual Studio Code et WSL
Essayez d'extraire une chaîne de caractères d'une image avec Python3
Essayez d'afficher la carte google et la carte géographique avec python
Essayez de résoudre l'itinéraire le plus court avec les données sociales Python + NetworkX +
Essayez d'obtenir des métriques CloudWatch avec la source de données python re: dash
Essayez d'ajouter un mur à votre fichier IFC avec IfcOpenShell python
[Python] Essayez de reconnaître les caractères des images avec OpenCV et pyocr
Essayez de gratter avec Python + Beautiful Soup
Python: comment utiliser async avec
Lien pour commencer avec python
[Python] Ecrire dans un fichier csv avec Python
Ravi de vous rencontrer avec python
Essayez la décomposition de valeurs singulières avec Python
Sortie vers un fichier csv avec Python
Essayez de profiler avec ONNX Runtime
Convertir la liste en DataFrame avec python