[Feature Poem] Je ne comprends pas le langage fonctionnel Vous pouvez le comprendre avec Python: Partie 2 La fonction qui génère la fonction est incroyable.

introduction

Ceci est un ** poème caractéristique ** qui décrit les fonctionnalités de la programmation fonctionnelle en Python. Je l'ai écrit pour que vous puissiez le comprendre sans connaître Python, alors veuillez le lire même si vous êtes PHP, Java ou JavaScript.

【objectif】

[Motif d'écriture]

[Lecteurs cibles]

[Article de la série]

Fonction pour générer une fonction

Révision: Qu'est-ce qu'une fonction d'ordre supérieur?

Les fonctions d'ordre supérieur sont des fonctions qui traitent la fonction comme des données. En particulier:

Générer une autre fonction dans une fonction

Comme je l'ai expliqué la dernière fois, les fonctions sont également des objets en Python, elles peuvent donc être traitées comme des données. Par conséquent, vous pouvez créer une fonction à l'intérieur d'une ** fonction **. Par exemple:

def create_func():
  def newfunc(i):      #Créer une nouvelle fonction dans la fonction
    if i % 2 == 0:
      return "even"
    else:
      return "odd"
  return newfunc       #Rends le

##Essayez d'utiliser
fn = create_func()     #Créez une nouvelle fonction et affectez-la à la variable fn
print(fn(2))           #2 est pair"even"Est sortie
print(fn(3))           #Parce que 3 est étrange"odd"Est sortie

Une fonction qui crée et renvoie une fonction de cette manière est également appelée fonction d'ordre supérieur. De plus, la fonction créée dans la fonction est parfois appelée «fonction intra-fonction» (ci-après, elle est appelée «fonction interne» pour une meilleure compréhension).

Dans d'autres livres, il est expliqué comme "une fonction qui renvoie une fonction" au lieu de "une fonction qui crée une fonction". Mais ** l'important est de créer une nouvelle fonction plutôt que de renvoyer une fonction **. Donc, dans ce poème, je vais l'expliquer comme "une fonction qui génère une fonction".

Se référer aux variables locales de l'intérieur vers l'extérieur

Les deux fonctions suivantes sont toutes deux très similaires. La seule différence est la valeur renvoyée lorsqu'elle est paire et la valeur renvoyée lorsqu'elle est impaire.

def create_func1():
  def newfunc(i):
    if i % 2 == 0:
      return "even"    #Quand même"even"rends le
    else:
      return "odd"     #Quand c'est bizarre"odd"rends le
  return newfunc

def create_func2():
  def newfunc(i):
    if i % 2 == 0:
      return "#fcc"    #Quand même"#fcc" (Rouge clair)rends le
    else:
      return "#ccf"    #Quand c'est bizarre"#ccf" (Bleu clair)rends le
  return newfunc

Partageons ces deux fonctions similaires.

def create_func(even_val, odd_val):  #Ajouter un argument à la fonction externe
  def newfunc(i):      #Fonction intérieure(Fonction générée)Est ...
    if i % 2 == 0:
      return even_val  #Quand même_Changé pour retourner val
    else:
      return odd_val   #Quand c'est bizarre_Changé pour retourner val
  return newfunc

Il y a deux points:

En faisant cela, vous pouvez ** générer un certain nombre de fonctions avec des comportements légèrement différents comme suit **.

## "even"Quand"odd"Générer une fonction qui retourne
fn1 = create_func("even", "odd")
print(fn1(0))    #=> even
print(fn1(1))    #=> odd
print(fn1(2))    #=> even
print(fn1(3))    #=> odd

## "#fcc"Quand"#ccf"Générer une fonction qui retourne
fn2 = create_func("#fcc", "#ccf")
print(fn2(0))    #=> #fcc
print(fn2(1))    #=> #ccf
print(fn2(2))    #=> #fcc
print(fn2(3))    #=> #ccf

Comparons cela avec les fonctions de la section précédente.

Le point important est que ** la fonction interne accède aux variables locales (dans ce cas, les arguments) de la fonction externe **. Une telle fonction est appelée un "plus proche". Ceci est expliqué dans la section suivante.

<Supplément> L'accès aux variables locales de la fonction interne à partir de la fonction externe n'est généralement possible dans aucun langage, pas seulement Python. </ Supplément>

Résumé à ce jour

fermeture

Qu'est-ce qu'une fermeture?

Qu'est-ce qu'une fermeture? D'après Wikipedia:

Fermeture (plus proche, anglais: fermeture), la fermeture de fonction est un type d'objet fonction dans le langage de programmation. Dans certains langages, il est réalisé par des expressions lambda et des fonctions anonymes. Il se caractérise par la résolution de variables autres que des arguments dans l'environnement dans lequel il est défini (portée statique), et non dans l'environnement au moment de l'exécution. On peut dire que c'est une paire d'une fonction et d'un environnement qui l'évalue. Ce concept remonte au moins aux machines SECD des années 1960.

(... omis ...)

La clôture est un mécanisme de partage de l'environnement au sein d'un programme. Les variables lexicales diffèrent des variables globales en ce qu'elles n'occupent pas l'espace de noms global. Elle diffère également d'une variable d'instance d'objet en ce qu'elle est liée à un appel de fonction plutôt qu'à une instance d'objet.

Eh bien, je ne sais pas ce que c'est. Il semble que vous ayez besoin d'environ 145 QI pour comprendre cette explication.

Pour le moment, pensez à une fermeture comme une fonction interne ** qui accède aux variables locales de la fonction externe (ce qui peut être inexact sur le plan académique, mais à des fins pratiques, ce niveau de compréhension est suffisant. ).

Par exemple, dans les fonctions de la section précédente, la fonction interne accède aux variables locales de la fonction externe (Remarque: l'argument est également l'une des variables locales). Par conséquent, la fonction intérieure est une fermeture.

##C'est la fonction extérieure
def create_func(even_val, odd_val):  #Notez que les arguments sont également des variables locales
  ##C'est la fonction intérieure
  ##  (Puisque nous accédons à la variable locale externe, cette fonction est une fermeture)
  def newfunc(i):
    if i % 2 == 0:
      return even_val     #Accéder à la variable locale externe
    else:
      return odd_val      #Accéder à la variable locale externe
  return newfunc

En revanche, le cas suivant n'est pas une clôture. C'est parce que la fonction interne n'accède pas à la fonction locale externe.

def create_func(even_val, odd_val):
  ##Ce n'est pas une fermeture
  ## (Parce que la fonction interne n'accède pas à la variable locale externe)
  def newfunc(i):
    if i % 2 == 0:
      return "even"
    else:
      return "odd"
  return newfunc

De plus, le cas suivant n'est pas une clôture. C'est parce que la fonction interne accède à une variable globale.

##Ce sont des variables globales
even_val = "#fcc"
odd_val  = "#ccf"

def create_func():
  ##Ce n'est pas non plus une fermeture
  ## (J'accède à une variable globale)
  def newfunc(i):
    if i % 2 == 0:
      return even_val
    else:
      return odd_val
  return newfunc

Comment fonctionne la fermeture

Pour mieux comprendre, examinons le fonctionnement des fermetures.

Du point de vue de l'implémentation, ** l'état réel de fermeture est "une fonction avec une table de variables attachée" **. La différence entre les fermetures et les fonctions régulières est la présence ou l'absence de cette table de variables.

Voici un exemple en Python3. Vous pouvez voir que la table des variables est collée à la fermeture.

##Fonction qui renvoie une fermeture
def create_func(even_val, odd_val):
  def newfunc(i):
    if i % 2 == 0: return even_val
    else:          return odd_val
  return newfunc   # (Pas seulement une fonction)Retourner la fermeture

##Lorsque vous créez une fermeture ...
fn = create_func("red", "blue")
## __closure__Vous pouvez voir que la table des variables est attachée
print(fn.__closure__[0].cell_contents)   #=> red
print(fn.__closure__[1].cell_contents)   #=> blue

##Il semble que vous ne pouvez pas le changer arbitrairement
fn.__closure__[0].cell_contents = "white"
   #=> AttributeError: attribute 'cell_contents' of 'cell' objects is not writable

De plus, les fonctions normales (non fermées) n'ont pas cette table de variables.

##Une fonction qui renvoie une fonction normale
def create_func():
  def newfunc(i):
    if i % 2 == 0: return "even"
    else:          return "odd"
  return newfunc   # (Pas une fermeture)Renvoie juste une fonction

##Pour les fonctions normales, il n'y a pas de table de variables
fn = create_func()
print(fn.__closure__)    #=> None

Comme vous pouvez le voir, la fermeture est associée à une table de variables. En d'autres termes, ** la fermeture est une fonction basée sur l'état **. On dit souvent que "les fonctions n'ont pas d'état", mais notez que les fermetures n'en ont pas.

<Supplément> L'accès aux variables locales externes se fait via la table des variables, qui est plus lente que l'accès aux variables locales régulières. C'est comme si l'accès à une variable d'instance était plus lent que l'accès à une variable locale. </ Supplément>

<Ajout 1> Il a commenté: "Est-ce que cela dépend de l'implémentation qui est plus lent que les variables locales?" C'est vrai, c'est plus lent que les variables locales en Python, mais il est fort possible que ce ne soit pas dans d'autres langages et processeurs. Merci d'avoir souligné. </ Ajout 1>

<Ajout 2> Il a souligné que "N'est-il pas bon de dire qu'une fermeture a un" état "? Il semble préférable de dire" attribut "ou" environnement "" (Merci). Personnellement, je pense que l'expression «état» convient pour les raisons suivantes. ・ Le mot «état» n'inclut pas s'il peut ou non être mis à jour. ・ C'est juste une question de perspective de faire d'un "état" un "attribut" (une fonction peut être considérée comme une formule de calcul pour renvoyer une valeur et une formule de discrimination pour renvoyer une valeur booléenne, mais c'est quand même une fonction. Pareil que) ・ «Environnement» est un terme que vous pouvez voir dans les livres spécialisés, mais sa définition est vague et déroute les débutants, et la substance n'est qu'un tableau variable. Cependant, ce que j'appelle «état» peut être quelque chose que les débutants appellent Wikipédia Wiki. Si vous êtes intéressé, ici, series, [tweet]( Veuillez consulter https://twitter.com/tanakahisateru/status/606098308411453442). </ Ajout 2>

Imiter une fermeture avec un objet

Au fait, avez-vous déjà entendu l'explication selon laquelle "un objet est une variable avec une fonction attachée"? ** Si une fonction est attachée à une variable est un objet et qu'une variable est attachée à une fonction est une fermeture **, alors l'objet et la fermeture sont susceptibles d'être très similaires.

En fait, les ** fermetures peuvent être imitées par des objets **. Ce qui suit est un exemple.

##La fonction qui renvoie la fermeture apparue dans la section précédente
def create_func(even_val, odd_val):
  def func(i):
    if i % 2 == 0:
      return even_val
    else:
      return odd_val
  return func

##Créez une classe qui l'imite
class CreateFunc(object):

  def __init__(self, even_val, odd_val):  #constructeur
    self.even_val = even_val
    self.odd_val  = odd_val

  def __call__(self, i):
    if i % 2 == 0:
      return self.even_val
    else:
      return self.odd_val

##Comment utiliser
obj = CreteFunc("red", "blue")   #Créer un objet(Note 1)
print(obj.__call__(0))    #=> red
print(obj.__call__(1))    #=> blue
print(obj.__call__(2))    #=> red
print(obj.__call__(3))    #=> blue
##C'est bon(Note 2)
print(obj(0))             #=> red
print(obj(1))             #=> blue
print(obj(2))             #=> red
print(obj(3))             #=> blue

## (Note 1)En Python, vous n'avez pas besoin d'un nouvel opérateur lors de la création d'un objet.
##Vous pouvez créer un objet simplement en appelant la classe comme s'il s'agissait d'une fonction.
## (Note 2)Obj en Python.__call__()Obj()Peut être appelé comme.

De cette manière, ce que fait la fermeture peut être imité par l'objet. Cela signifie que les langues qui ne prennent pas en charge les fermetures peuvent faire la même chose que les fermetures.

Cependant, comme vous pouvez le voir dans la comparaison suivante, les fermetures sont plus concises (dans ce cas).

##Version plus proche##Version objet
def create_func(even, odd):       class CreateFunc(object):
                                    def __init__(self, even, odd):
                                      self.even = even
                                      self.odd  = odd

  def func(i):                      def __call__(self, i):
    if i % 2 == 0:                    if i % 2 == 0:
      return even                       return self.even
    else:                             else:
      return odd                        return self.odd

  return func

fn = create_func("red", "blue")   obj = CreateFunc("red", "blue")
fn(0)                             obj.__call__(0)   # or obj(0)
fn(1)                             obj.__call__(1)   # or obj(1)

Comme vous pouvez le voir ci-dessus, les fermetures et les objets sont très similaires. S'il y a une belle fille IQ145 qui insiste sur le fait que "l'orientation de l'objet qui contient l'état est fausse! Le langage fonctionnel est correct!", "Mais la fermeture du langage fonctionnel senior est très similaire à un objet, n'est-ce pas? Y compris le point d'avoir un état. " Je suis sûr que IQ145 vous donnera une excellente réponse.

Résumé à ce jour

Fonction wrapper

Les «fonctions d'ordre supérieur qui génèrent des fonctions» sont très utiles lors de la création de fonctions wrapper. Ici, afin de montrer la commodité des «fonctions d'ordre supérieur qui génèrent des fonctions», des «fonctions d'encapsulation» sont décrites.

Qu'est-ce qu'une fonction wrapper?

** Les fonctions wrapper sont des fonctions qui ajoutent d'autres traitements lors de l'appel d'une fonction existante **. Par exemple:

Bien sûr, il existe d'autres possibilités, mais pour le moment, vous devez conserver les trois types ci-dessus.

Regardons un exemple concret.

table = [
  {"name": "Haruhi", "size": "C"},
  {"name": "Mikuru", "size": "E"},
  {"name": "Yuki",   "size": "A"},
]

##Fonction d'origine
def search(name):             #Fonction pour récupérer des données
  for d in table:
    if d["name"] == name:
      return d
  return None

## (A)Ajouter ou modifier des arguments avant d'appeler la fonction d'origine
def search1(name):
  if name == "Michiru":
    name = "Mikuru"           #Élaborez les arguments
  return search(name)         #Appeler la fonction d'origine

## (B)Appelez la fonction d'origine, puis créez la valeur de retour
def search2(name, targets=["Mikuru"]):
  ret = search(name)          #Appeler la fonction d'origine
  if name in targets:
    ret["size"] = "C'est une question interdite"   #Créer la valeur de retour
  return ret

## (C)Ajouter un prétraitement et un post-traitement lors de l'appel de la fonction d'origine
def search3(name):
  print("** name=%s" % name)  #Prétraitement
  ret = search(name)          #Appeler la fonction d'origine
  print("** ret=%s" % ret)    #Post-traitement
  return ret

De cette façon, la fonction wrapper vous permet de créer des arguments, de créer des valeurs de retour et d'ajouter un traitement avant et après. En d'autres termes, la fonction wrapper est une fonction ajoutée à la fonction d'origine.

Fonctions d'ordre supérieur qui génèrent des fonctions wrapper

Maintenant, écrivons une fonction d'ordre supérieur qui génère ces fonctions wrapper. Les points sont les suivants.

def normalize(func):           #Recevoir la fonction
  def wrapper(name):
    if name == "Michiru":
      name = "Mikuru"          #Après avoir élaboré les arguments
    return func(name)          #Appeler la fonction d'origine
  return wrapper               #Fonctions comme(fermeture)rends le.

def censor(func, targets):     #Recevoir la fonction
  def wrapper(name):
    ret = func(name)           #Après avoir appelé la fonction d'origine
    if name in targets:
      ret["size"] = "Les interdictions" #Créer la valeur de retour
    return ret
  return wrapper               #Fonctions comme(fermeture)rends le.

def trace(func):               #Recevoir la fonction
  def wrapper(name):
    print("** name=%s" % name) #Ajouter un prétraitement
    ret = func(name)           #Appeler la fonction d'origine
    print("** ret=%s" % ret)   #Ajouter un post-traitement
    return ret
  return wrapper               #Fonctions comme(fermeture)rends le.

Leur utilisation est la suivante.

table = [
  {"name": "Haruhi", "size": "C"},
  {"name": "Mikuru", "size": "E"},
  {"name": "Yuki",   "size": "A"},
]

def search(name):            #Fonction pour récupérer des données
  for d in table:
    if d["name"] == name:
      return d
  return None

##Fonction wrapper pour créer des arguments
search1 = normalize(search)

##Fonction wrapper pour créer la valeur de retour
search2 = censor(search, ["Mikuru"])

##Fonction wrapper avec pré-traitement et post-traitement ajoutés
search3 = trace(search)

Les fonctions d'ordre supérieur facilitent la génération de fonctions wrapper avec des fonctions supplémentaires.

Non seulement que. Vous pouvez facilement combiner ces fonctions librement. Par exemple:

##Fonction wrapper qui crée à la fois des arguments et des valeurs de retour
search12 = censor(normalize(search), ["Mikuru"])

##Fonction wrapper avec des arguments et des valeurs de retour spécialement conçus, ainsi qu'un pré-traitement et un post-traitement ajoutés
search123 = trace(censor(normalize(search), ["Mikuru"]))

Ou:

##Fonction wrapper avec des arguments et des valeurs de retour spécialement conçus, ainsi qu'un pré-traitement et un post-traitement ajoutés
search = normalize(search)
search = censor(search, ["Mikuru"])
search = trace(search)

Pourquoi est-il possible de combiner librement? C'est parce que les fonctions d'ordre supérieur prennent la fonction d'origine comme argument.

###Par exemple, dans le premier exemple, la fonction d'origine a été incorporée.
###Par conséquent, la fonction est fixe et ne peut pas être remplacée par une autre fonction.
def search3(name):
  print("** name=%s" % name)
  ret = search(name)    #La fonction est fixe
  print("** ret=%s" % ret)
  return ret

###D'un autre côté, la fonction d'ordre supérieur n'a pas la fonction d'origine incorporée.
###Puisqu'il est reçu comme argument, il peut être facilement changé en une autre fonction.
def trace(func):        #Recevoir la fonction d'origine comme argument
  def wrapper(name):
    print("** name=%s" % name)
    ret = func(name)    #La fonction n'est pas fixe!
    print("** ret=%s" % ret)
    return ret
  return wrapper

Application partielle

En passant, dans la programmation fonctionnelle, il y a le mot «application partielle». Si vous pensez que c'est "l'une des fonctions qui génèrent une fonction wrapper", c'est très bien.

def add(x, y):        #Exemple: supposons que vous ayez une fonction qui ajoute deux nombres
  return x + y

def add1(x):          #Si vous fixez un argument à 1, c'est
  return add(x, 1)    #Cela devient une "fonction qui ajoute 1 à un certain nombre".

def adder(y):         #Si vous souhaitez spécifier un nombre arbitraire au lieu de le fixer à 1.
  def wrapper(x):     #Une telle fonction d'ordre supérieur peut être utilisée.
    return add(x, y)
  return wrapper

add3 = adder(3)       #Exemple d'utilisation(add(x, y)Dont y est fixé à 3)
print(add3(7))        #=> 10

def apply(func, y):   #Rendons possible la spécification de la fonction elle-même.
  def wrapper(x):
    return func(x, y)
  return wrapper

add3 = apply(add, 3)  #Exemple d'utilisation(add(x, y)Dont y est fixé à 3)
print(add3(7))        #=> 10

De cette manière, la création d'une nouvelle fonction à partir d'une certaine fonction avec une partie de l'argument donné est appelée "application partielle".

Cependant, l'application partielle n'est pas souvent utilisée en Python. Puisqu'il est rafraîchissant au curry, l'explication est omise.

Résumé à ce jour

Décorateur de fonction

Python a une fonctionnalité appelée "décorateurs de fonctions" pour vous aider à mieux utiliser les fonctions d'encapsulation. En Python, les «fonctions d'ordre supérieur qui génèrent des fonctions wrapper» sont rarement appelées directement, et sont généralement utilisées via ce «décorateur de fonctions». Ce "décorateur de fonctions" est une fonction indispensable et importante pour utiliser Python.

Qu'est-ce qu'un décorateur de fonction?

À propos, l'exemple de code de la «fonction d'ordre supérieur qui génère la fonction wrapper» était comme ceci.

def search(name):
  ....

search = trace(search)

C'est un code correct, mais le nom de la fonction «recherche» est apparu trois fois. Par conséquent, vous devez modifier au moins trois emplacements lorsque vous modifiez le nom de la fonction. C'est un état dit "non sec".

Dans un tel cas, en Python, vous pouvez écrire: C'est ce qu'on appelle un ** décorateur de fonctions **. (Cela ressemble à une annotation Java, mais c'est complètement différent.)

@trace               #← Ceci est un décorateur de fonction
def search(name):
  ....

Avec le décorateur de fonction, il suffit d'écrire le nom de la fonction à un seul endroit (c'est très "DRY"). Cela peut sembler étrange au premier abord, mais @trace def search (): ... est identique à def search (): ...; search = trace (search), donc Ne réfléchissez pas dur.

Vous pouvez également spécifier plusieurs décorateurs de fonction si vous le souhaitez.

##c'est,
@deco1
@deco2
@deco3
def func():
  ....

##Identique à ça
def func():
  ....
func = deco1(deco2(deco3(func)))

Les décorateurs de fonctions ne se trouvent pas dans les langages autres que Python. Par conséquent, cela peut sembler étrange, mais cela ne devrait pas être difficile si le contenu jusqu'à présent.

Décorateur de fonction avec arguments

Les décorateurs de fonctions sont difficiles lorsqu'ils prennent des arguments. Je vais l'expliquer maintenant, mais si vous ne le comprenez pas (si vous ne voulez pas étudier Python sérieusement), ne vous inquiétez pas et ignorez-le.

Tout d'abord, considérons le cas où "une fonction d'ordre supérieur qui reçoit la fonction d'origine et renvoie une nouvelle fonction" prend des arguments autres que la fonction d'origine * *.

##Cette fonction d'ordre supérieur a des arguments autres que func
def censor(func, targets):
  def wrapper(name):
    ret = func(name)
    if name in targets:
      ret["size"] = "C'est une question interdite"
    return ret
  return wrapper

Tenter de l'utiliser comme décorateur de fonction entraînera une erreur. Savez-vous pourquoi vous obtenez une erreur?

##Cela entraînera une erreur
@censor
def search(name):
  ...

##Parce que la censure()Parce qu'il n'y a pas de deuxième argument cible de
search = censor(search)
   #=> TypeError: censor() missing 1 required positional argument: 'targets'

Le code ci-dessus est un décorateur de fonctions, donc Python essaiera de faire search = censor (search). Cependant, le deuxième argument («cibles») de «censor ()» n'est pas spécifié, donc une erreur se produit.

Pour éviter cela, créez une "fonction qui prend un argument et renvoie un" décorateur de fonction "". Un décorateur de fonction est une "fonction qui reçoit une fonction et retourne une nouvelle fonction", c'est-à-dire qu'il crée une "fonction qui reçoit un argument et renvoie une" fonction qui reçoit une fonction et renvoie une nouvelle fonction "" (!!).

def censor(targets):    #Une fonction qui prend un argument et renvoie un décorateur de fonction
  def deco(func):         #Faire un décorateur de fonction
    def wrapper(name):      #Créer une fonction wrapper
      ret = func(name)        #Appeler la fonction d'origine
      if name in targets:     #Se réfère à l'argument le plus extérieur
        ret["size"] = "C'est une question interdite"
      return ret              #Renvoie la valeur de retour
    return wrapper          #Renvoie une fonction wrapper
  return deco             #Renvoie un décorateur de fonction

Wow, c'est compliqué ...

Voici comment l'utiliser.

##c'est
@censor(["Mikuru"])
def search(name):
  ....

##Identique à ça
def search(name):
  ....
deco = censor(["Mikuru"])
search = deco(search)

##Notez que c'est différent!!
search = censor(search, ["Mikuru"])

C'est tout pour le décorateur de fonction avec des arguments, mais avez-vous compris?

Si vous n'avez pas compris: vous n'avez pas à vous en soucier. Un jour, nous le saurons. Sinon, vous constaterez que «même si vous ne comprenez pas les petites choses, cela n'a pas un grand impact sur votre vie».

Ceux qui comprennent: Je suis sûr que vous pensez simplement que vous comprenez. Quand un tel «je comprends» est extrême, j'écris des poèmes géniaux et beaux. Faisons attention.

<Mode de plainte à partir d'ici>

Les décorateurs de fonctions de Python sont certainement utiles si vous pouvez les maîtriser. Cependant, la méthode de fabrication est différente selon qu'elle prend ou non un argument, et définir un décorateur de fonction qui prend un argument est très gênant et difficile à enseigner. Pour être clair, ** les décorateurs de fonctions Python sont une erreur de conception **, comme ceci.

Simplement en permettant au décorateur de fonction d'accepter des arguments non-fonction, ces problèmes peuvent être résolus à la fois. Pourquoi tu ne fais pas ça ...

##Par exemple, si vous pouviez écrire comme ça
@censor ["Mikuru"], "C'est une question interdite"
def search(name):
  ....

##Si ce ↑ devient le même que ce ↓
def search(arg):
  ....
search = censor(search, ["Mikuru"], "C'est une question interdite")

##Il s'écrit de la même manière qu'un décorateur de fonction qui prend un argument et un décorateur qui ne prend pas d'argument.
##Même si vous le modifiez pour prendre un décorateur de fonction qui ne prend pas d'argument, la compatibilité du côté utilisateur est
##Je souhaite que tout le monde puisse être heureux car ils ont été conservés.
def censor(func, targets, seal):
  def wrapper(name):
    ret = func(arg)
    if name in targets:
      ret["size"] = seal
    return ret
  return wrapper

</ Fin>

Avez-vous besoin d'un décorateur de fonction?

Je n'ai pas vu la fonction de décorateur de fonction en dehors de Python (c'est peut-être le cas, mais ce n'est pas populaire). En conséquence, les utilisateurs d'autres langues peuvent dire: "Il ne s'agit que d'un sucre de syntaxe pour les fonctions d'ordre supérieur, n'est-ce pas nécessaire?"

Cependant, la fonction de décorateur de fonction est une fonction très intéressante qui n'a pas à être **. C'est parce que la fonction de décorateur de fonctions rend certains codes très lisibles.

Par exemple, considérez le code JavaScript suivant:

var compile = taskdef("*.html", ["*.txt"], function() {
  var html = this.product;
  var txt  = this.materials[0];
  system.run("txt2html " + html + " > " + txt);
});

Si JavaScript avait une fonctionnalité de décorateur de fonction, cela pourrait être écrit: Ne pensez-vous pas que celui-ci est beaucoup plus facile à lire?

@taskdef("*.html", ["*.txt"])
function compile() {
  var html = this.product;
  var txt  = this.materials[0];
  system.run("txt2html " + html + " > " + txt);
}

Considérez également un autre code comme celui-ci: function () {...} est multiple imbriqué.

function test_it_should_exit_with_0_when_same_contents() {
  with_dummyfile("A.txt", "aaa", function() {
    with_dummyfile("B.txt", "aaa", function() {
      status = system.run("compare A.txt B.txt");
      asssert_equal(status, 0);
    });
  });
};

Si vous pouviez utiliser un décorateur de fonctions en JavaScript, vous pourriez probablement écrire: C'est très rafraîchissant car il n'y a pas d'imbrication multiple.

@with_dummyfile("B.txt", "aaa")
@with_dummyfile("A.txt", "aaa")
function test_it_should_exit_with_0_when_same_contents() {
  status = system.run("compare A.txt B.txt");
  asssert_equal(status, 0);
};

Puisque le décorateur de fonction n'est qu'un sucre de syntaxe, il est facile à mettre en œuvre, mais l'effet obtenu est assez important. C'est une fonctionnalité rentable pour les langues, donc j'espère qu'elle sera également introduite dans d'autres langues.

Est-ce différent du motif du décorateur?

L'un des modèles de conception dans la conception de classe est le "modèle de décorateur". Je pense que le terme «décorateur de fonction» en Python est probablement dérivé de ce nom de modèle. De plus, aux fins du décorateur * motif *, nous utilisons souvent la fonction décorateur * fonction * comme moyen.

Mais il y a une différence entre les deux.

Cela montre que les décorateurs de fonction de Python sont différents des modèles de décorateurs. Si c'est vrai, cela aurait dû être un nom plus précis pour la fonction.

Résumé à ce jour

Autres histoires spécifiques à Python

Changer la variable locale externe

Lors de l'accès aux variables locales de la fonction externe à partir de la fonction interne en Python, la lecture de la variable peut être effectuée sans aucune préparation, mais l'écriture de la variable a une limitation.

##Pour Python3
def outer():
  x = 10
  y = 20
  def inner():
    x = 15      #C'est intérieur()Est affecté à la variable locale de.
    nonlocal y  #Mais en utilisant non local,
    y = 25      # outer()Peut être affecté à une variable locale de.
  inner()
  print(x)   #=> 10  (Inchangé)
  print(y)   #=> 25  (a été modifié)

outer()

##Pour Python2
def outer():
  x = 10
  y = [20]      #Python2 n'a pas de non local, vous avez donc besoin d'une astuce comme celle-ci
  def inner():
    x = 15      #C'est intérieur()Est affecté à la variable locale de.
    y[0] = 25   #Notez que cela n'a pas changé la variable y!
  inner()
  print(x)      #=> 10  (Inchangé)
  print(y[0])   #=> 25  (y n'a pas changé, mais y[0]A été modifié)

outer()

C'est un peu différent des autres langues. Il existe de nombreuses raisons pour lesquelles Python a une telle spécification.

<Supplément> En principe, même si la grammaire de la définition de fonction et de la définition de fermeture est la même, il est possible de faire la distinction entre les deux. Tu devrais être capable de. En principe. </ Supplément>

Décorateur de fonction et décorateur de classe

En plus des décorateurs de fonctions, Python a également une fonctionnalité appelée "décorateurs de classe".

@publish              #← Ceci est un décorateur de classe
class Hello(object):
  ...

##C'est la même chose que
class Hello(object):
  ...
Hello = publish(Hello)

En Python, le terme «décorateur» fait généralement référence à un décorateur de fonction. Cependant, il existe deux types de décorateurs, et il peut être confondu avec l'un des modèles de conception (comme mentionné ci-dessus), le "modèle de décorateur", il est donc préférable de l'appeler un "décorateur de fonction" autant que possible pour éviter toute ambiguïté. ..

Arguments arbitraires

L'exemple de code pour les décorateurs de fonction a jusqu'à présent supposé que la fonction d'origine n'a qu'un seul argument.

def trace(func):
  def newfunc(name):
    print("** name=%s" % name)
    ret = func(name)    #← En supposant qu'un seul argument
    print("** ret=%s" % ret)
    return ret
  return newfunc

Par conséquent, ce décorateur de fonction ne peut être appliqué qu'aux fonctions avec un seul argument.

@trace
def search(name):   #← Applicable car il n'y a qu'un seul argument
  for d in tables:
    if d["name"] == name: return d
  return None

@trace
def add(x, y):      #← Puisqu'il y a deux arguments, une erreur d'exécution se produit.
  return x+y

add(10, 20)
  #=> TypeError: newfunc() takes 1 positional argument but 2 were given

Pour rendre cela applicable à n'importe quel nombre d'arguments:

def trace(func):
  def newfunc(*args):    #← Recevoir n'importe quel nombre d'arguments
    print("** args=%r" % (name,))
    ret = func(*args)    #← Passez n'importe quel nombre d'arguments
    print("** ret=%r" % (ret,))
    return ret
  return newfunc

@trace
def add(x, y):      #← Aucune erreur ne se produit même avec deux arguments
  return x+y

add(x, y)

Pour accepter des arguments de mot-clé arbitraires, procédez comme suit:

def trace(func):
  def newfunc(*args, **kwargs):    #← Recevoir des arguments de mots clés arbitraires
    print("** args=%r, %kwargs=%r" % (name, kwargs))
    ret = func(*args, **kwargs)    #← Passer des arguments de mots clés arbitraires
    print("** ret=%r" % (ret,))
    return ret
  return newfunc

@trace
def add(x, y):
  return x+y

add(y=20, x=10)     #← L'appel en utilisant des arguments de mots-clés est OK

Nom de la fonction et documentation de la fonction

Lorsque vous définissez une fonction en Python, l'objet fonction est rempli avec le nom de la fonction et la documentation.

def search(name):
  """Search table by name."""
  for d in table:
    if d["name"] == name:
      return d
  return None

##Afficher le nom de la fonction et la documentation
print(search.__name__)  #=> search
print(search.__doc__)   #=> Search table by name.

Mais avec des fonctions d'ordre supérieur et des décorateurs de fonctions, ils disparaissent ou portent des noms différents.

def trace(func):
  def newfunc(name):
    print("** name=%s" % name)
    ret = func(name)
    print("** ret=%s" % ret)
    return ret
  return newfunc

@trace
def search(name):
  ....

print(search.__name__)   #=> newfunc  (Le nom de la fonction a changé!)
print(search.__doc__)    #=> None (Le document a disparu!)

Ce n'est pas très bon. Par conséquent, il est judicieux de copier le nom de la fonction et la documentation de la fonction d'origine vers la nouvelle fonction.

def trace(func):
  def newfunc(name):
    ....
  #Copiez le nom de la fonction et la documentation de la fonction d'origine
  newfunc.__name__ = func.__name__
  newfunc.__doc__  = func.__doc__
  return newfunc

@trace
def search(name):
  ....

print(search.__name__)   #=> search
print(search.__doc__)    #=> Search table by name.

Un utilitaire qui effectue cette copie est fourni en standard en Python, c'est donc une bonne idée de l'utiliser.

import functools

def trace(func):
  @functools.wraps(func)   #← Copies de la fonction d'origine
  def newfunc(name):
    ....
  return newfunc

@trace
def search(name):
  ....

print(search.__name__)   #=> search
print(search.__doc__)    #=> Search table by name.

Résumé

Dans ce poème, nous avons expliqué "les fonctions qui génèrent des fonctions" parmi les fonctions d'ordre supérieur. Vous pouvez l'utiliser pour générer des fonctions qui se comportent légèrement différemment. L'important ici est le "plus proche", qui est une fonction à laquelle sont attachées des variables.

Il a également expliqué qu'une "fonction wrapper" qui ajoute des fonctionnalités à une fonction existante est utile. Les fonctions d'ordre supérieur sont également utiles lors de la génération de fonctions wrapper, et nous avons également introduit une fonction appelée "décorateur de fonctions" pour les rendre plus faciles à lire.

Je pense que IQ145 peut l'expliquer plus facilement qu'un beau senior, mais si vous remarquez quelque chose, veuillez commenter.

Des exercices

J'écrirai un exemple de réponse dans la section commentaire.

【Question 1】 Considérez une fonction qui alterne entre deux valeurs à chaque fois qu'elle est appelée.

## 'red'Quand'blue'Une fonction qui retourne alternativement
print(toggle_color())  #=> 'red'
print(toggle_color())  #=> 'blue'
print(toggle_color())  #=> 'red'
print(toggle_color())  #=> 'blue'

##Une fonction qui renvoie alternativement 1 et 0
print(toggle_on_off())  #=> 1
print(toggle_on_off())  #=> 0
print(toggle_on_off())  #=> 1
print(toggle_on_off())  #=> 0

## 'show'Quand'hide'Une fonction qui retourne alternativement
print(toggle_visible())  #=> 'show'
print(toggle_visible())  #=> 'hide'
print(toggle_visible())  #=> 'show'
print(toggle_visible())  #=> 'hide'

Définissez une fonction d'ordre supérieur new_toggle () qui produit une telle fonction. Comment utiliser:

toggle_color   = new_toggle("red", "blue")
toggle_on_off  = new_toggle(1, 0)
toggle_visible = new_toggle("show", "hide")

** [Problème 2] ** Définissons une fonction qui prend un tableau de chaînes et les convertit toutes en majuscules et minuscules en utilisant la fonction d'ordre supérieur map () (pour map (), previous. kwatch / items / 03fd035a955235681577)) ::

def uppers(strings):
  return map(lambda s: s.upper(), strings)

def lowers(strings):
  return map(lambda s: s.lower(), strings)

print(uppers(['a', 'b', 'c']))   #=> ['A', 'B', 'C']
print(lowers(['A', 'B', 'C']))   #=> ['a', 'b', 'c']

Aussi, définissons une fonction pour trouver la longueur de toutes les chaînes en utilisant map () de la même manière:

def lengths(strings):
  return map(lambda s: len(s), strings)
  ##Ou retourner la carte(len(s), strings)

print(lengths(["Haruhi", "Mikuru", "Yuki"]))  #=> [6, 6, 4]

Tous ont le même code, à l'exception de l'expression lambda.

Alors, essayez de définir un mappeur de fonctions d'ordre supérieur () qui génère facilement ces fonctions. Comment utiliser:

uppers = mapper(lambda s: s.upper())
lowers = mapper(lambda s: s.lower())
lengths = mapper(lambda s: len(s))   #Ou mappeur(len)

print(uppers(['a', 'b', 'c']))   #=> ['A', 'B', 'C']
print(lowers(['A', 'B', 'C']))   #=> ['a', 'b', 'c']
print(lengths(["Haruhi", "Mikuru", "Yuki"]))  #=> [6, 6, 4]

** [Problème 3] ** Supposons que vous ayez une fonction qui envoie une requête HTTP, telle que:

##Fonction pour envoyer une requête HTTP
def http(method, url, params={}, headers={}):
  ....

##Fonction pour envoyer une requête HTTPS
def https(method, url, params={}, headers={}):
  ....

##Comment utiliser
response = http("GET", "http://www.google.com/", {"q": "python"})

J'ai créé une fonction wrapper pour cela pour chacune des méthodes GET / POST / PUT / DELETE / HEAD.

def GET(url, params={}, headers={}):
  return http("GET", url, params, headers)

def POST(url, params={}, headers={}):
  return http("POST", url, params, headers)

def PUT(url, params={}, headers={}):
  return http("PUT", url, params, headers)

def DELETE(url, params={}, headers={}):
  return http("DELETE", url, params, headers)

def HEAD(url, params={}, headers={}):
  return http("HEAD", url, params, headers)

Mais c'est une itération de code similaire. Existe-t-il un moyen de l'écrire de manière plus concise?

** [Problème 4] ** Le code suivant est un code de test écrit en Python. Vous pouvez voir que le fichier factice est généré / supprimé dans le test.

import os, unittest

class FileTest(unittest.TestCase):

  def test_read(self):
    ##Créer un fichier factice
    filename = "file1.txt"
    File.open(filename, "w") as f:
      f.write("FOO\n")
    ##Lancer le test
    try:
      with open(filename) as f:
        content = f.read()
      self.assertEqual(content, "FOO\n")
    ##Supprimer le fichier factice
    finally:
      os.unlink(filename)

Pour écrire ceci de manière plus concise, définissez une fonction décoratrice appelée @with_dummyfile ():

import os, unittest

class FileTest(unittest.TestCase):

  @with_dummyfile("file1.txt", "FOO\n")   #← Définissez ceci
  def test_read(self):
    with open("file1.txt") as f:
      content = f.read()
    self.assertEqual(content, "FOO\n")

** [Problème 5] ** Un certain cadre d'application Web a été utilisé comme suit. J'utilise un décorateur de fonctions appelé @view_config (), mais il semble que cela ait été long à lire et à écrire.

##C'est trop long à lire et à écrire
@view_config(request_method="GET",
             route_urlpath="/api/books/{book_id}/comments/{comment_id}")
def show_book(self):
  book_id    = self.request.matched['book_id']
  comment_id = self.request.matched['comment_id']
  ....

Par conséquent, définissez un décorateur de fonction appelé @ on () qui peut être écrit de manière plus concise. Comment utiliser:

@on("GET", "/api/books/{book_id}/comments/{comment_id}")
def show_book(self):
  book_id    = self.request.matched['book_id']
  comment_id = self.request.matched['comment_id']
  ....

Définissez également un décorateur @ api qui accepte les paramètres du modèle de chemin d'URL comme arguments de fonction. Comment utiliser:

@on("GET", "/api/books/{book_id}/comments/{comment_id}")
@api
def show_book(self, book_id, comment_id):
  #Livre d'arguments_ça c'est moi.request.matched['book_id']Est passé.
  #Argument féliciter_ça c'est moi.request.matched['comment_id']Est passé.
  ....

Si vous le pouvez, intégrez la fonctionnalité de @ api dans @ on (). Comment utiliser:

@on("GET", "/api/books/{book_id}/comments/{comment_id}")
def show_book(self, book_id, comment_id):
  #Livre d'arguments_ça c'est moi.request.matched['book_id']Est passé.
  #Argument féliciter_ça c'est moi.request.matched['comment_id']Est passé.
  ....

Pointe:

##c'est
keyword_args = {"book_id": "123", "comment_id": "98765"}
show_book(self, **keyword_args)
##Identique à ça
show_book(self, book_id="123", comment_id="98765")

Recommended Posts

[Feature Poem] Je ne comprends pas le langage fonctionnel Vous pouvez le comprendre avec Python: Partie 2 La fonction qui génère la fonction est incroyable.
[Feature Poem] Je ne comprends pas le langage fonctionnel Vous pouvez le comprendre avec Python: Partie 1 Les fonctions qui reçoivent des fonctions sont pratiques.
python> "Je ne comprends pas la signification de ="> (Solution) Tuples à un élément: Vous avez répondu au commentaire
Introduction à Python que même les singes peuvent comprendre (partie 3)
Introduction à Python que même les singes peuvent comprendre (partie 1)
Introduction à Python que même les singes peuvent comprendre (partie 2)
Si vous pensez que la personne que vous mettez avec pip ne fonctionne pas → Utilisez-vous python3 par hasard?
J'ai essayé d'en savoir le plus possible sur GIL que vous devriez savoir si vous faites un traitement parallèle avec Python