82 votes

L'imbrication de fonctions en Python entraîne-t-elle une surcharge ?

En Python, si j'ai une fonction enfant dans une fonction parent, la fonction enfant est-elle "initialisée" (créée) chaque fois que la fonction parent est appelée ? L'imbrication d'une fonction dans une autre entraîne-t-elle une surcharge de performances ?

64voto

Raymond Hettinger Points 50330

L'objet code est précompilé de sorte que cette partie n'a pas de surcharge. L'objet fonction est construit à chaque invocation -- il lie le nom de la fonction à l'objet code, enregistre les variables par défaut, etc.

Résumé : Ce n'est pas gratuit.

>>> from dis import dis
>>> def foo():
        def bar():
                pass
        return bar

>>> dis(foo)
  2           0 LOAD_CONST               1 (<code object bar at 0x1017e2b30, file "<pyshell#5>", line 2>)
              3 MAKE_FUNCTION            0
              6 STORE_FAST               0 (bar)

  4           9 LOAD_FAST                0 (bar)
             12 RETURN_VALUE

52voto

Daenyth Points 11297

Oui, un nouvel objet serait créé à chaque fois. Ce n'est probablement pas un problème, sauf si vous l'avez dans une boucle serrée. Le profilage vous dira si c'est un problème.

In [80]: def foo():
   ....:     def bar():
   ....:         pass
   ....:     return bar
   ....: 

In [81]: id(foo())
Out[81]: 29654024

In [82]: id(foo())
Out[82]: 29651384

47 votes

Pour être clair, un nouvel objet fonction est créé à chaque fois. L'objet de code sous-jacent est réutilisé. Ainsi, l'overhead est constant quelle que soit la longueur de la fonction interne.

5 votes

Pour information, si la fonction est décorée, le décorateur est également appelé chaque fois que l'objet fonction est recréé.

0 votes

... bien que dans de nombreux cas, cela signifie simplement que vous obtenez deux ou trois... O(1) les créations d'objets fonctionnels. Les décorateurs qui effectuent des tâches lourdes lors de la création sont rares, la plupart se contentent de créer un petit objet ou une fermeture.

21voto

Thanos Baskous Points 51

Il y a un impact, mais dans la plupart des situations, il est si faible que vous ne devriez pas vous en préoccuper - la plupart des applications non triviales ont probablement déjà des goulots d'étranglement des performances dont les impacts sont plusieurs ordres de grandeur plus grands que celui-ci. Préoccupez-vous plutôt de la lisibilité et de la réutilisation du code.

Voici un code qui compare les performances de la redéfinition d'une fonction à chaque fois dans une boucle à la réutilisation d'une fonction prédéfinie.

import gc
from datetime import datetime

class StopWatch:
     def __init__(self, name):
         self.name = name

     def __enter__(self):
         gc.collect()
         self.start = datetime.now()

     def __exit__(self, type, value, traceback):
         elapsed = datetime.now()-self.start
         print '** Test "%s" took %s **' % (self.name, elapsed)

def foo():
     def bar():
          pass
     return bar

def bar2():
    pass

def foo2():
    return bar2

num_iterations = 1000000

with StopWatch('FunctionDefinedEachTime') as sw:
    result_foo = [foo() for i in range(num_iterations)]

with StopWatch('FunctionDefinedOnce') as sw:
    result_foo2 = [foo2() for i in range(num_iterations)]

Lorsque je l'exécute dans Python 2.7 sur mon Macbook Air exécutant OS X Lion, j'obtiens :

** Test "FunctionDefinedEachTime" took 0:00:01.138531 **
** Test "FunctionDefinedOnce" took 0:00:00.270347 **

7voto

Bengerman Points 791

J'étais aussi curieux à ce sujet, alors j'ai décidé de trouver combien les frais généraux ainsi encourus. En résumé, la réponse est peu de chose.

Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from timeit import timeit
>>> def subfunc():
...     pass
... 
>>> def no_inner():
...     return subfunc()
... 
>>> def with_inner():
...     def s():
...         pass
...     return s()
... 
>>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__     import no_inner', number=1)
0.22971350199986773
>>> timeit('[with_inner() for _ in range(1000000)]', setup='from __main__ import with_inner', number=1)
0.2847519510000893

Mon instinct m'a poussé à regarder les pourcentages (with_inner est 24% plus lent), mais ce chiffre est trompeur dans ce cas, puisque nous ne retournerons jamais la valeur d'une fonction interne à partir d'une fonction externe, surtout avec des fonctions qui ne font rien.
Après avoir fait cette erreur, j'ai décidé de la comparer à d'autres choses courantes, pour voir quand cela a de l'importance et quand cela n'en a pas :

    >>> def no_inner():
    ...     a = {}
    ...     return subfunc()
    ... 
    >>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__ import no_inner', number=1)
    0.3099582109998664

En regardant cela, nous pouvons voir que cela prend moins de temps que de créer un dict vide ( la méthode rapide ), donc si vous faites quelque chose de non trivial, cela n'a probablement pas d'importance du tout.

2voto

Cory B Points 98

Les autres réponses sont excellentes et répondent vraiment bien à la question. Je voulais ajouter que la plupart des fonctions internes peuvent être évitées en python en utilisant des boucles for, des fonctions génératrices, etc.

Considérons l'exemple suivant :

def foo():
    # I need to execute a function on two sets of arguments:
    argSet1 = (1, 3, 5, 7)
    argSet2 = (2, 4, 6, 8)

    # A Function could be executed on each set of args
    def bar(arg1, arg2, arg3, arg4):
        return (arg1 + arg2 + arg3 + arg4)

    total = 0
    for argSet in [argSet1, argSet2]:
      total += bar(*argSet)
    print( total )

    # Or a loop could be used on the argument sets
    total = 0
    for arg1, arg2, arg3, arg4 in [argSet1, argSet2]:
        total += arg1 + arg2 + arg3 + arg4
    print( total )

Cet exemple est un peu loufoque, mais j'espère que vous comprenez néanmoins ce que je veux dire. Les fonctions internes ne sont souvent pas nécessaires.

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