web-dev-qa-db-fra.com

Comment fonctionnent les fonctions imbriquées en Python?

def maker(n):
    def action(x):
        return x ** n
    return action

f = maker(2)
print(f)
print(f(3))
print(f(4))

g = maker(3)
print(g(3))

print(f(3)) # still remembers 2

Pourquoi la fonction imbriquée se souvient-elle de la première valeur 2 Même si maker() est retourné et a quitté au moment où action() est appelée?

54
3zzy

Vous pouvez le voir comme toutes les variables provenant de la fonction parent étant remplacées par leur valeur réelle à l'intérieur de la fonction enfant. De cette façon, il n'est pas nécessaire de suivre l'étendue de la fonction parent pour que la fonction enfant s'exécute correctement.

Voyez-le comme "créer dynamiquement une fonction".

def maker(n):
  def action(x):
    return x ** n
  return action

f = maker(2)
--> def action(x):
-->   return x ** 2

C'est un comportement de base en python, il fait de même avec plusieurs affectations.

a = 1
b = 2
a, b = b, a

Python lit ceci comme

a, b = 2, 1

Il insère essentiellement les valeurs avant de faire quoi que ce soit avec elles.

30
Tor Valamo

Vous créez essentiellement un fermeture .

En informatique, une fermeture est une fonction de première classe avec des variables libres qui sont liées dans l'environnement lexical. Une telle fonction est dite "fermée" sur ses variables libres.

Lecture connexe: Fermetures: pourquoi sont-elles si utiles?

Une fermeture est simplement un moyen plus pratique de donner accès à une fonction à l'état local.

De http://docs.python.org/reference/compound_stmts.html :

Remarque du programmeur: les fonctions sont des objets de première classe. Un formulaire 'def' exécuté dans une définition de fonction définit une fonction locale qui peut être retournée ou transmise. Les variables libres utilisées dans la fonction imbriquée peuvent accéder aux variables locales de la fonction contenant la déf. Voir la section Dénomination et liaison pour plus de détails.

38
miku

Vous définissez DEUX fonctions. Quand vous appelez

f = maker(2)

vous définissez une fonction qui renvoie deux fois le nombre, donc

f(2) --> 4
f(3) --> 6

Ensuite, vous définissez UNE AUTRE DIFFÉRENTE FONCTION

g = maker(3)

qui retournent trois fois le nombre

g(3) ---> 9

Mais ce sont DEUX fonctions différentes, ce n'est pas la même fonction référencée, chacune est indépendante. Même dans la portée à l'intérieur de la fonction, "maker" est appelé la même chose, ce n'est pas la même fonction, chaque fois que vous appelez maker() vous définissez une fonction différente. C'est comme une variable locale, chaque fois que vous appelez la fonction prend le même nom, mais peut contenir des valeurs différentes. Dans ce cas, la variable 'action' contient une fonction (qui peut être différente)

14
Khelben

C'est ce qu'on appelle " fermeture ". Autrement dit, pour la plupart sinon tous les langages de programmation qui traitent les fonctions comme objet de première classe , toutes les variables utilisées dans un objet fonction sont incluses (c'est-à-dire mémorisées) tant que la fonction est toujours en vie. C'est un concept puissant si vous savez vous en servir.

Dans votre exemple, la fonction imbriquée action utilise la variable n afin de former une fermeture autour de cette variable et de la mémoriser pour les appels de fonction ultérieurs.

9
Lukman

Examinons trois raisons courantes pour écrire des fonctions internes.

1. Fermetures et fonctions d'usine

La valeur dans la portée englobante est mémorisée même lorsque la variable sort de la portée ou que la fonction elle-même est supprimée de l'espace de noms actuel.

def print_msg(msg):
    """This is the outer enclosing function"""

    def printer():
        """This is the nested function"""
        print(msg)

    return printer  # this got changed

Essayons maintenant d'appeler cette fonction.

>>> another = print_msg("Hello")
>>> another()
Hello

C'est inhabituel. La fonction print_msg() a été appelée avec la chaîne "Hello" Et la fonction retournée était liée au nom another. Lors de l'appel de another(), le message était toujours mémorisé bien que nous ayons déjà fini d'exécuter la fonction print_msg(). Cette technique par laquelle certaines données ("Hello") Sont attachées au code est appelée fermeture en Python.

À quoi servent donc les fermetures? Les fermetures peuvent éviter l'utilisation de valeurs globales et fournissent une certaine forme de masquage des données. Il peut également fournir une solution orientée objet au problème. Lorsqu'il y a peu de méthodes (une méthode dans la plupart des cas) à implémenter dans une classe, les fermetures peuvent fournir des solutions alternatives et plus élégantes. Référence

2. Encapsulation:

Le concept général de l'encapsulation est de cacher et de protéger le monde intérieur de l'extérieur, ici les fonctions intérieures ne sont accessibles qu'à l'intérieur de l'extérieur et sont protégées contre tout ce qui se passe en dehors de la fonction.

3. Gardez-le au SEC

Peut-être avez-vous une fonction géante qui exécute le même morceau de code à de nombreux endroits. Par exemple, vous pouvez écrire une fonction qui traite un fichier et vous souhaitez accepter un objet fichier ouvert ou un nom de fichier:

def process(file_name):
    def do_stuff(file_process):
        for line in file_process:
            print(line)
    if isinstance(file_name, str):
        with open(file_name, 'r') as f:
            do_stuff(f)
    else:
        do_stuff(file_name)

Pour en savoir plus, vous pouvez consulter this blog.

3
sujit tiwari

Parce qu'au moment où vous créez la fonction, n était 2, donc votre fonction est:

def action(x):
    return x ** 2

Lorsque vous appelez f (3), x est défini sur 3, donc votre fonction renverra 3 ** 2

2
James Polley

Une utilisation consiste à renvoyer une fonction qui conserve un paramètre.

def outer_closure(a):
    #  parm = a               <- saving a here isn't needed
    def inner_closure():
        #return parm
        return a              # <- a is remembered 
    return inner_closure

# set parm to 5 and return address of inner_closure function
x5 = outer_closure(5)
x5()
>5

x6 = outer_closure(6)
x6()
>6

# x5 inner closure function instance of parm persists 
x5()
>5
1
kztd

Les gens ont répondu correctement à propos de la fermeture, c'est-à-dire: la valeur valide pour "n" à l'intérieur de l'action est la dernière valeur qu'elle avait à chaque fois que "maker" était appelé.

Une façon simple de surmonter cela est de faire de votre freevar (n) une variable à l'intérieur de la fonction "action", qui reçoit une copie de "n" au moment où elle est exécutée:

La façon la plus simple de procéder consiste à définir "n" comme paramètre dont la valeur par défaut est "n" au moment de la création. Cette valeur pour "n" reste fixe car les paramètres par défaut d'une fonction sont stockés dans un Tuple qui est un attribut de la fonction elle-même (action.func_defaults dans ce cas):

def maker(n):
    def action(x, k=n):
        return x ** k
    return action

Usage:

f = maker(2) # f is action(x, k=2)
f(3)   # returns 3^2 = 9
f(3,3) # returns 3^3 = 27
1
jsbueno

Lorsque vous créez une fonction avec le mot-clé def, vous faites exactement cela: vous créez un nouvel objet fonction et l'affectez à une variable. Dans le code que vous avez donné, vous affectez ce nouvel objet fonction à une variable locale appelée action.

Lorsque vous l'appelez une deuxième fois, vous créez un deuxième objet fonction. Donc, f pointe vers le premier objet fonction (carré la valeur) et g pointe vers le deuxième objet fonction (cube la valeur). Lorsque Python voit "f (3)", cela signifie "exécuter l'objet fonction pointé comme variable f et lui passer la valeur 3". F et g et différents objets fonction et ainsi retourner des valeurs différentes.

0
DavidG