Y a - t-il une surcharge lors de l'imbrication des fonctions en Python?

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? Y a-t-il des frais généraux associés à l'imbrication d'une fonction dans une autre?

50
demandé sur XåpplI'-I0llwlg'I - 2011-10-20 21:14:56

5 réponses

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
34
répondu Daenyth 2011-10-29 09:30:46

L'objet code est pré-compilé afin que cette partie n'ait pas de surcharge. L'objet function est construit sur chaque appel - 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 
34
répondu Raymond Hettinger 2011-10-20 17:20:40

Il y a un impact, mais dans la plupart des situations, il est si petit que vous ne devriez pas vous en inquiéter - la plupart des applications non triviales ont probablement déjà des goulots d'étranglement de performance dont les impacts sont plusieurs ordres de grandeur plus importants que celui-ci. Vous inquiétez plutôt de la lisibilité et de la réutilisabilité du code.

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

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)]

Quand je lance ceci Python 2.7 sur mon Macbook air fonctionnant sous OS X Lion je reçois:

** Test "FunctionDefinedEachTime" took 0:00:01.138531 **
** Test "FunctionDefinedOnce" took 0:00:00.270347 **
12
répondu Thanos Baskous 2012-05-31 23:52:47

J'étais curieux à ce sujet aussi, alors j'ai décidé de comprendre Combien frais généraux cela a encouru. TL; DR, la réponse n'est pas beaucoup.

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 était de regarder les pourcentages (with_inner est 24% plus lent), mais ce nombre est trompeur dans ce cas, puisque nous ne retournerons jamais la valeur d'une fonction interne à partir d'une fonction externe, en particulier avec des fonctions qui ne font rien.
Après avoir fait cette erreur, j'ai décidé de la comparer à d'autres choses communes, pour voir quand cela fait et n'a pas d'importance:

    >>> 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 manière rapide), donc si vous faites quelque chose de non-trivial, cela n'a probablement pas d'importance du tout.

2
répondu Bengerman 2018-05-03 21:20:13

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 de génération,etc.

Prenons l'exemple suivant:

def foo():
    # I need to execute some function on two sets of arguments:
    argSet1 = (arg1, arg2, arg3, arg4)
    argSet2 = (arg1, arg2, arg3, arg4)

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

    total =  bar(argSet1)
    total += bar(argSet2)

    # 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

Cet exemple est un peu loufoque, mais j'espère que vous pouvez voir mon point néanmoins. Les fonctions internes ne sont souvent pas nécessaires.

1
répondu Cory B 2014-06-24 19:11:10