183 votes

La fonction imbriquée est-elle une bonne approche lorsqu'elle est requise par une seule fonction ?

Disons qu'un function A n'est requis que par function B A doit-il être défini à l'intérieur de B ?

Un exemple simple. Deux méthodes, l'une appelée par l'autre :

def method_a(arg):
    some_data = method_b(arg)

def method_b(arg):
    return some_data

En Python, nous pouvons déclarer def à l'intérieur d'un autre def . Donc, si method_b est nécessaire et n'est appelé que par method_a dois-je déclarer method_b à l'intérieur de method_a ? comme ceci :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

Ou dois-je éviter de le faire ?

8 votes

Vous ne devriez pas avoir besoin de définir une fonction à l'intérieur d'une autre, à moins que vous ne fassiez quelque chose de VRAIMENT funky. Cependant, veuillez préciser ce que vous essayez de faire, afin que nous puissions vous fournir une réponse plus utile.

6 votes

Vous réalisez que le second exemple est différent, parce que vous n'avez pas appelez method_b ? (@inspector : Vous devez le faire, à proprement parler, mais c'est immensément utile lorsque vous vous lancez dans la programmation fonctionnelle, en particulier les fermetures).

3 votes

@delnan : Je pense que vous vouliez dire "Vous Ne le fais pas. pas besoin, à proprement parler, mais..."

136voto

user225312 Points 22699
>>> def sum(x, y):
...     def do_it():
...             return x + y
...     return do_it
... 
>>> a = sum(1, 3)
>>> a
<function do_it at 0xb772b304>
>>> a()
4

C'est ce que tu cherchais ? C'est ce qu'on appelle un fermeture .

0 votes

Pourquoi ne pas simplement faire def sum(x,y) : return x+y ?

5 votes

@mango : Il s'agit juste d'un exemple pour illustrer le concept - dans l'utilisation réelle ce qui do_it() serait vraisemblablement un peu plus compliquée que ce qui peut être traité par de l'arithmétique dans une simple return déclaration.

2 votes

@mango A répondu à votre question avec un exemple légèrement plus utile. stackoverflow.com/a/24090940/2125392

55voto

martineau Points 21665

Vous ne gagnez pas grand chose en faisant cela, en fait cela ralentit method_a car elle définira et recompilera l'autre fonction à chaque fois qu'elle sera appelée. Dans ces conditions, il serait probablement préférable de préfixer le nom de la fonction par un trait de soulignement pour indiquer qu'il s'agit d'une méthode privée -- c'est à dire _method_b .

Je suppose que vous pourrait Vous pourriez vouloir le faire si la définition de la fonction imbriquée variait à chaque fois pour une raison quelconque, mais cela pourrait indiquer un défaut dans votre conception. Cela dit, il y a est Une raison valable de faire cela est de permettre à la fonction imbriquée d'utiliser des arguments qui ont été passés à la fonction extérieure mais qui ne leur ont pas été explicitement transmis, ce qui se produit parfois lors de l'écriture de décorateurs de fonctions, par exemple. C'est ce qui est montré dans la réponse acceptée bien qu'un décorateur ne soit pas défini ou utilisé.

Mise à jour :

Voici la preuve que leur imbrication est plus lente (avec Python 3.6.1), même si elle n'est pas très importante dans ce cas trivial :

setup = """
class Test(object):
    def separate(self, arg):
        some_data = self._method_b(arg)

    def _method_b(self, arg):
        return arg+1

    def nested(self, arg):

        def method_b2(self, arg):
            return arg+1

        some_data = method_b2(self, arg)

obj = Test()
"""
from timeit import Timer
print(min(Timer(stmt='obj.separate(42)', setup=setup).repeat()))  # -> 0.24479823284461724
print(min(Timer(stmt='obj.nested(42)', setup=setup).repeat()))    # -> 0.26553459700452575

Notez que j'ai ajouté quelques self à vos fonctions d'exemple pour les rendre plus proches des méthodes réelles (bien que method_b2 n'est toujours pas, techniquement, une méthode de l'outil de gestion de l'environnement. Test classe). De plus, la fonction imbriquée est réellement appelée dans cette version, contrairement à la vôtre.

26 votes

En fait, la fonction interne n'est pas entièrement compilée à chaque fois que la fonction externe est appelée, mais elle doit créer un objet fonction, ce qui prend un peu de temps. D'un autre côté, le nom de la fonction devient local plutôt que global, et il est donc plus rapide d'appeler la fonction. D'après mes essais, en termes de temps, c'est un jeu d'enfant la plupart du temps ; cela peut même être plus rapide avec la fonction interne si vous l'appelez plusieurs fois.

0 votes

@kindall : Il ne semble pas qu'il soit plus rapide d'utiliser une fonction imbriquée -- voir le code de test dans ma réponse mise à jour -- donc au moins dans ce cas, ce que vous dites ne semble pas vrai.

7 votes

Oui, vous aurez besoin de plusieurs appels à la fonction interne. Si vous l'appelez dans une boucle, ou plus d'une poignée de fois, l'avantage d'avoir un nom local pour la fonction commencera à l'emporter sur le coût de la création de la fonction. Dans mes essais, cela se produit lorsque vous appelez la fonction interne environ 3-4 fois. Bien sûr, vous pourriez obtenir le même avantage (sans un coût aussi élevé) en définissant un nom local pour la fonction, par ex. method_b = self._method_b et ensuite appeler method_b pour éviter les recherches répétées d'attributs. (Il se trouve que j'ai fait BEAUCOUP de chronométrage ces derniers temps :)

29voto

CivFan Points 38

Une fonction à l'intérieur d'une fonction est généralement utilisée pour fermetures .

(Il existe un beaucoup de conflits sur quoi exactement fait une fermeture une fermeture .)

Voici un exemple utilisant la fonction intégrée sum() . Il définit start une fois et l'utilise à partir de ce moment-là :

def sum_partial(start):
    def sum_start(iterable):
        return sum(iterable, start)
    return sum_start

En cours d'utilisation :

>>> sum_with_1 = sum_partial(1)
>>> sum_with_3 = sum_partial(3)
>>> 
>>> sum_with_1
<function sum_start at 0x7f3726e70b90>
>>> sum_with_3
<function sum_start at 0x7f3726e70c08>
>>> sum_with_1((1,2,3))
7
>>> sum_with_3((1,2,3))
9

Fermeture intégrée en python

functools.partial est un exemple de fermeture.

Extrait de la documentation python c'est à peu près l'équivalent de :

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

(Bravo à @user225312 ci-dessous pour la réponse. Je trouve cet exemple plus facile à comprendre, et j'espère qu'il aidera à répondre au commentaire de @mango).

0 votes

-1 oui, ils sont couramment utilisés comme fermetures. Maintenant, relisez la question. Il demande essentiellement si le concept qu'il montre peut être utilisé pour le cas b. Lui dire que c'est souvent utilisé pour le cas a n'est pas une mauvaise réponse, mais la mauvaise pour cette question. Il veut savoir si c'est une bonne chose de faire ce qui précède pour l'encapsulation par exemple.

0 votes

Pour être juste, la question est plutôt ouverte - plutôt que d'essayer de répondre à tous les cas possibles, j'ai pensé qu'il valait mieux se concentrer sur un seul. La question n'est pas non plus très claire. Par exemple, elle implique la fermeture dans le deuxième exemple, même si ce n'est probablement pas ce qui était visé.

0 votes

@Mayou36 "Il a essentiellement demandé si le concept qu'il montre peut être utilisé pour le cas b." Non, la question demande si devrait être utilisé. L'OP sait déjà qu'il peut être utilisé.

10voto

vz0 Points 11605

Il est en fait possible de déclarer une fonction à l'intérieur d'une autre. Cela est particulièrement utile pour créer des décorateurs.

Toutefois, en règle générale, si la fonction est complexe (plus de 10 lignes), il est préférable de la déclarer au niveau du module.

2 votes

C'est possible, mais je suis d'accord, il faut une bonne raison pour le faire. Il serait plus python d'utiliser un trait de soulignement précédent pour une fonction destinée à être utilisée uniquement dans votre classe.

3 votes

À l'intérieur d'une classe, oui, mais qu'en est-il de l'intérieur d'une classe seulement ? fonction ? L'encapsulation est hiérarchique.

0 votes

@PaulDraper "L'encapsulation est hiérarchique" - Non ! Qui dit cela ? L'encapsulation est un principe beaucoup plus large qu'un simple héritage.

8voto

miracle173 Points 240

J'ai trouvé cette question parce que je voulais poser la question de savoir pourquoi il y a un impact sur les performances si on utilise des fonctions imbriquées. J'ai effectué des tests pour les fonctions suivantes en utilisant Python 3.2.5 sur un ordinateur portable Windows avec un processeur Quad Core 2.5 GHz Intel i5-2530M

def square0(x):
    return x*x

def square1(x):
    def dummy(y):
        return y*y
    return x*x

def square2(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    return x*x

def square5(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    def dummy3(y):
        return y*y
    def dummy4(y):
        return y*y
    def dummy5(y):
        return y*y
    return x*x

J'ai mesuré les éléments suivants 20 fois, également pour le carré 1, le carré 2 et le carré 5 :

s=0
for i in range(10**6):
    s+=square0(i)

et j'ai obtenu les résultats suivants

>>> 
m = mean, s = standard deviation, m0 = mean of first testcase
[m-3s,m+3s] is a 0.997 confidence interval if normal distributed

square? m     s       m/m0  [m-3s ,m+3s ]
square0 0.387 0.01515 1.000 [0.342,0.433]
square1 0.460 0.01422 1.188 [0.417,0.503]
square2 0.552 0.01803 1.425 [0.498,0.606]
square5 0.766 0.01654 1.979 [0.717,0.816]
>>> 

square0 n'a pas de fonction imbriquée, square1 a une fonction imbriquée, square2 a deux fonctions imbriquées et square5 comporte cinq fonctions imbriquées. Les fonctions imbriquées sont seulement déclarées mais pas appelées.

Ainsi, si vous avez défini 5 fonctions imbriquées dans une fonction que vous n'appelez pas, le temps d'exécution de la fonction est le double de celui de la fonction sans fonction imbriquée. Je pense qu'il faut être prudent lors de l'utilisation de fonctions imbriquées.

Le fichier Python pour l'ensemble du test qui génère cette sortie se trouve à l'adresse suivante idéone .

5 votes

La comparaison que vous faites n'est pas vraiment utile. C'est comme ajouter des instructions factices dans la fonction et dire que c'est plus lent. L'exemple de Martineau utilise en fait les fonctions encapsulées et je ne remarque aucune différence de performance en exécutant son exemple.

0 votes

-1 veuillez relire la question. Bien que vous puissiez constater qu'il est peut-être légèrement plus lent, il demande si c'est une bonne idée de faire cela, non pas principalement pour des raisons de performance mais de pratique générale.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X