¿Se compilan las funciones internas de Python?

Entonces, AFAIK en CPython, las definiciones de funciones se compilan en objetos de función cuando se ejecutan en el momento del análisis. Pero, ¿qué pasa con las funciones internas? ¿Se compilan en objetos de función en el momento del análisis o se compilan (o interpretan) cada vez que se llama a la función? ¿Las funciones internas incurren en alguna penalización de desempeño?

preguntado el 16 de mayo de 11 a las 16:05

¿Alguna posibilidad de que pueda proporcionar un ejemplo del tipo de funciones internas a las que se refiere? Me inclino a decir que habrá una caída en el rendimiento si está definiendo una función en el cuerpo de otra función, principalmente porque esperaría que se redefiniera cada llamada de la función externa, pero quiero asegurarme de que capte completamente su pregunta. También el timeit módulo sería una excelente manera de probarlo. -

5 Respuestas

Para dar una explicación general, asumiendo que tiene el siguiente código en un módulo:

def outer(x=1):
    def inner(y=2):
        return x+y

Cuando el archivo es analizado por python a través de compile(), el texto anterior se convierte en código de bytes para saber cómo ejecutar el módulo. En el código de bytes del módulo, hay dos "objetos de código", uno para el código de bytes de outer() y uno para el bytecode inner(). Tenga en cuenta que dije objetos de código, no funciones: los objetos de código contienen poco más que el código de bytes utilizado por la función y cualquier información que pueda conocerse en el momento de la compilación, como el código de bytes para outer() que contiene una referencia al bytecode para inner().

Cuando el módulo se carga realmente, al evaluar el objeto de código asociado con el módulo, una cosa que sucede es que se crea un "objeto de función" real para outer(), y almacenado en el módulo outer atributo. El objeto de función actúa como una colección del código de bytes y todas las cosas relacionadas con el contexto que se necesitan para llamar a la función (por ejemplo, de qué dictado global debe extraerse, etc.) que no se pueden conocer en el momento de la compilación. En cierto modo, un objeto de código es una plantilla para una función, que es una plantilla para la ejecución del código de bytes real con todas las variables completadas.

Nada de esto involucrado inner()-como función todavía: cada vez que empiezas a llamar outer(), ahí es cuando un nuevo inner() se crea el objeto de función para esa invocación de exterior, que vincula el objeto de código de bytes interno ya creado a una lista de globales, incluido el valor de x como pasó a esa llamada al exterior. Como puede imaginar, esto es bastante rápido, ya que no es necesario analizar, solo completar una estructura rápida con algunos punteros a otros objetos ya existentes.

contestado el 16 de mayo de 11 a las 21:05

Respuesta muy clara que es consistente con los resultados de los experimentos anteriores. ¡Gracias! - YH Wong

Prueba fácil: los argumentos predeterminados de una función se invocan una vez, en el momento de definir.

>>> def foo():
...     def bar(arg=count()):
...             pass
...     pass
...
>>> def count():
...     print "defined"
...
>>> foo()
defined
>>> foo()
defined

Entonces sí: este es un golpe de rendimiento menor (muy, menor).

contestado el 16 de mayo de 11 a las 20:05

>>> import dis
>>> def foo():
...     def bar():
...             print "stuff"
...     return bar
... 
>>> b = foo()
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (<code object bar at 0x20bf738, file "<stdin>", line 2>)
              3 MAKE_FUNCTION            0
              6 STORE_FAST               0 (bar)

  4           9 LOAD_FAST                0 (bar)
             12 RETURN_VALUE        
>>> dis.dis(b)
  3           0 LOAD_CONST               1 ('stuff')
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE        

Sospecho que esto depende en gran medida de la implementación, pero eso fue CPython 2.6.6, y la función interna parece que fue compilada. Aquí hay otro ejemplo:

>>> def foo():
...     def bar():
...             return 1
...     return dis.dis(bar)
... 
>>> foo()
  3           0 LOAD_CONST               1 (1)
              3 RETURN_VALUE

Entonces podemos concluir que están compilados. En cuanto a sus características de rendimiento, utilícelos. Si comienza a tener problemas de rendimiento, profile. Sé que en realidad no es una respuesta, pero casi nunca importa y cuando lo hace, las respuestas generales no son suficientes. Las llamadas a funciones generan una sobrecarga y parece que las funciones internas son como funciones.

contestado el 16 de mayo de 11 a las 20:05

Para extender la función interna de respuesta de nmichaels se compilan en compilar tiempo como él adivinó y el código de bytes se guarda en el foo.func_code.co_consts y se accede a ellos mediante el código de operación LOAD_CONST como se puede ver en el desmontaje de la función.

Ejemplo:

>>> def foo():
...     def inner():
...         pass
>>> print foo.func_code.co_consts
(None, <code object inner at 0x249c6c0, file "<ipython console>", line 2>)

contestado el 16 de mayo de 11 a las 21:05

Para más información, ver: akaptur.com/blog/2013/11/15/… - btown

Llego tarde en esto, pero como un pequeño complemento experimental de estas completas respuestas: puede usar el función incorporada id para verificar si se crea un nuevo objeto o no:

In []: # inner version
       def foo():
           def bar():
               return id(bar)
           return bar()

       foo(), foo()

Out[]: (4352951432, 4352952752)

Los números reales pueden diferir, pero su diferencia indica que dos instancias distintas de bar son de hecho creados.

In []: # outer version
       def bar():
           return id(bar)

       def foo():
           return bar()

       foo(), foo()

Out[]: (4352950952, 4352950952)

Esta vez, como se esperaba, los dos ids son iguales.

Ahora, para algunos timeit mediciones. Interior primero, exterior segundo:

100000 loops, best of 3: 1.93 µs per loop
1000000 loops, best of 3: 1.25 µs per loop

Entonces, en mi máquina, parece que la versión interna es un 50% más lenta (Python 2.7, IPython Notebook).

contestado el 26 de mayo de 16 a las 19:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.