¿Deben las declaraciones de importación estar siempre en la parte superior de un módulo?

403

PEP 08 dice:

Las importaciones siempre se colocan en la parte superior del archivo, justo después de los comentarios de cualquier módulo y cadenas de documentos, y antes de las constantes y globales de módulo.

Sin embargo, si la clase / método / función que estoy importando solo se usa en casos raros, ¿seguramente es más eficiente hacer la importación cuando sea necesario?

No es esto:

class SomeClass(object):

    def not_often_called(self)
        from datetime import datetime
        self.datetime = datetime.now()

más eficiente que esto?

from datetime import datetime

class SomeClass(object):

    def not_often_called(self)
        self.datetime = datetime.now()
Adam J. Forster
fuente

Respuestas:

283

La importación de módulos es bastante rápida, pero no instantánea. Esto significa que:

  • Poner las importaciones en la parte superior del módulo está bien, porque es un costo trivial que solo se paga una vez.
  • Poner las importaciones dentro de una función hará que las llamadas a esa función demoren más.

Entonces, si le importa la eficiencia, coloque las importaciones en la parte superior. Solo muévalos a una función si su perfil muestra que ayudaría ( hizo un perfil para ver dónde mejorar mejor el rendimiento, ¿verdad?)


Las mejores razones por las que he visto realizar importaciones perezosas son:

  • Soporte opcional de biblioteca. Si su código tiene múltiples rutas que usan bibliotecas diferentes, no se rompa si no hay una biblioteca opcional instalada.
  • En el __init__.pyde un complemento, que podría importarse pero no utilizarse realmente. Algunos ejemplos son los complementos de Bazaar, que utilizan bzrlibel marco de carga diferida.
John Millikin
fuente
17
John, esta era una pregunta completamente teórica, así que no tenía ningún código para perfilar. En el pasado, siempre he seguido el PEP, pero hoy escribí un código que me hizo preguntarme si eso era lo correcto. Gracias por tu ayuda.
Adam J. Forster
43
> Poner las importaciones dentro de una función hará que las llamadas a esa función demoren más. En realidad, creo que este costo solo se paga una vez. He leído que Python almacena en caché un módulo importado para que solo haya un costo mínimo para importarlo nuevamente.
Moltenform
24
@halfhourhacks Python no volverá a importar el módulo, pero aún debe realizar algunas instrucciones solo para ver si el módulo existe / está en sys.modules / etc.
John Millikin
24
-1. Poner importaciones en una función no necesariamente hace que tarde más. Por favor vea mi respuesta en otra pregunta.
aaronasterling
44
Un caso de uso es evitar las importaciones circulares (generalmente no es razonable, pero a veces es adecuado). A veces, la clase A en el módulo m1 llama a un método en la clase B en el módulo m2 que construye otra instancia de la clase A. Si el método en la clase B que construye una instancia de la clase A ejecuta la importación solo al ejecutar la función que construye una instancia, Se evita la importación circular.
Sam Svenbjorgchristiensensen
80

Poner la declaración de importación dentro de una función puede evitar dependencias circulares. Por ejemplo, si tiene 2 módulos, X.py e Y.py, y ambos necesitan importarse entre sí, esto causará una dependencia circular cuando importe uno de los módulos, causando un bucle infinito. Si mueve la declaración de importación en uno de los módulos, entonces no intentará importar el otro módulo hasta que se llame a la función, y ese módulo ya se importará, por lo que no habrá bucle infinito. Lea aquí para más información: effbot.org/zone/import-confusion.htm

Moe
fuente
3
Sí, pero uno puede entrar en el infierno de la dependencia.
eigenein
8
Si dos módulos necesitan importarse entre sí, algo está muy mal con el código.
Anna
La programación orientada a objetos a menudo me lleva a dependencias circulares. Se puede importar una clase de objeto vital en varios módulos. Para que este objeto realice sus propias tareas, es posible que deba llegar a uno o más de esos módulos. Hay formas de evitarlo, como enviar datos al objeto a través de argumentos de funciones, para permitirle acceder al otro módulo. Pero hay momentos en que hacer esto se siente muy contra-intuitivo para OOP (el mundo exterior no debería necesitar saber cómo está cumpliendo la tarea en esa función).
Robert
44
Cuando X necesita Y e Y necesita X, son dos partes de la misma idea (es decir, deben definirse juntas) o falta una abstracción.
GLRoman
59

He adoptado la práctica de poner todas las importaciones en las funciones que las usan, en lugar de ubicarlas en la parte superior del módulo.

El beneficio que obtengo es la capacidad de refactorizar de manera más confiable. Cuando muevo una función de un módulo a otro, sé que la función continuará funcionando con todo su legado de pruebas intacto. Si tengo mis importaciones en la parte superior del módulo, cuando muevo una función, encuentro que termino pasando mucho tiempo haciendo que las importaciones del nuevo módulo sean completas y mínimas. Un IDE refactorizador puede hacer que esto sea irrelevante.

Hay una penalización de velocidad como se menciona en otra parte. He medido esto en mi solicitud y me pareció insignificante para mis propósitos.

También es bueno poder ver todas las dependencias de los módulos por adelantado sin recurrir a la búsqueda (por ejemplo, grep). Sin embargo, la razón por la que me importan las dependencias de los módulos es generalmente porque estoy instalando, refactorizando o moviendo un sistema completo que comprende múltiples archivos, no solo un solo módulo. En ese caso, voy a realizar una búsqueda global de todos modos para asegurarme de tener las dependencias a nivel del sistema. Por lo tanto, no he encontrado importaciones mundiales para ayudarme a comprender un sistema en la práctica.

Normalmente pongo la importación de sysdentro del if __name__=='__main__'cheque y luego paso argumentos (como sys.argv[1:]) a una main()función. Esto me permite usar mainen un contexto donde sysno se ha importado.


fuente
44
Muchos IDEs facilitan la refactorización del código optimizando e importando automáticamente los módulos necesarios en su archivo. En la mayoría de los casos, PyCharm y Eclipse han tomado las decisiones correctas por mí. Apuesto a que hay una manera de obtener el mismo comportamiento en emacs o vim.
brent.payne
3
Una importación dentro de una instrucción if en el espacio de nombres global sigue siendo una importación global. Esto imprimirá los argumentos (usando Python 3): def main(): print(sys.argv); if True: import sys; main();Tendría que ajustar if __name__=='__main__'una función para crear un nuevo espacio de nombres.
Darcinon el
44
Esto me parece una excelente razón para importar dentro de las funciones más que en el ámbito global. Estoy bastante sorprendido de que nadie más haya mencionado hacerlo por esta misma razón. ¿Existen desventajas significativas, además del rendimiento y la verbosidad?
algal
@algal el inconveniente es que muchas personas python odian esto porque violas el codex pep. Tienes que convencer a los miembros de tu equipo. La penalización de rendimiento es mínima. A veces es aún más rápido, consulte stackoverflow.com/a/4789963/362951
mit
Me pareció extremadamente útil para la refactorización poner las importaciones cerca de donde las uso. Ya no es necesario desplazarse hacia arriba y hacia atrás en tantos tonos. Utilizo IDEs como pycharm o wing ide y también utilizo su refactorización, pero no siempre quiero confiar en ellos. Mover funciones a otro módulo se vuelve mucho más fácil con este estilo de importación alternativo, como consecuencia, refactorizo ​​mucho más.
mit
39

La mayoría de las veces esto sería útil por claridad y sensata, pero no siempre es así. A continuación se presentan algunos ejemplos de circunstancias en las que las importaciones de módulos podrían vivir en otro lugar.

En primer lugar, podría tener un módulo con una prueba unitaria de la forma:

if __name__ == '__main__':
    import foo
    aa = foo.xyz()         # initiate something for the test

En segundo lugar, es posible que tenga que importar condicionalmente algún módulo diferente en tiempo de ejecución.

if [condition]:
    import foo as plugin_api
else:
    import bar as plugin_api
xx = plugin_api.Plugin()
[...]

Probablemente haya otras situaciones en las que podría colocar importaciones en otras partes del código.

Preocupado por TunbridgeWells
fuente
14

La primera variante es de hecho más eficiente que la segunda cuando la función se llama cero o una vez. Sin embargo, con la segunda y posteriores invocaciones, el enfoque de "importar cada llamada" es en realidad menos eficiente. Consulte este enlace para obtener una técnica de carga diferida que combina lo mejor de ambos enfoques al realizar una "importación diferida".

Pero hay otras razones además de la eficiencia por las que podría preferir una sobre la otra. Un enfoque es que sea mucho más claro para alguien que lea el código en cuanto a las dependencias que tiene este módulo. También tienen características de falla muy diferentes: la primera fallará en el momento de la carga si no hay un módulo "datetime", mientras que la segunda no fallará hasta que se llame al método.

Nota agregada: en IronPython, las importaciones pueden ser bastante más caras que en CPython porque el código se está compilando básicamente a medida que se importa.

Curt Hagenlocher
fuente
1
No es cierto que el primero tenga
Jason Baker
Funciona mejor si nunca se llama al método porque la importación nunca ocurre.
Curt Hagenlocher
Es cierto, pero funciona peor si el método se llama más de una vez. Y los beneficios de rendimiento que obtendría al no importar el módulo de inmediato son insignificantes en la mayoría de los casos. Las excepciones serían si el módulo es muy grande o si hay muchas funciones de este tipo.
Jason Baker,
En el mundo IronPython, las importaciones iniciales son mucho más caras que en CPython;). El ejemplo de "importación diferida" en su enlace es probablemente la mejor solución genérica general.
Curt Hagenlocher
Espero que no te importe, pero lo edité en tu publicación. Esa es información útil para saber.
Jason Baker,
9

Curt hace un buen punto: la segunda versión es más clara y fallará en el momento de la carga en lugar de más tarde, e inesperadamente.

Normalmente no me preocupa la eficacia de cargar módulos, ya que (a) es bastante rápido y (b) solo ocurre principalmente en el inicio.

Si tiene que cargar módulos pesados ​​en momentos inesperados, probablemente tenga más sentido cargarlos dinámicamente con la __import__función, y asegúrese de detectar ImportErrorexcepciones y manejarlas de manera razonable.

Dan Lenski
fuente
8

No me preocuparía demasiado la eficacia de cargar el módulo por adelantado. La memoria que ocupa el módulo no será muy grande (suponiendo que sea lo suficientemente modular) y el costo de inicio será insignificante.

En la mayoría de los casos, desea cargar los módulos en la parte superior del archivo fuente. Para alguien que lea su código, es mucho más fácil saber qué función u objeto provino de qué módulo.

Una buena razón para importar un módulo en otra parte del código es si se usa en una declaración de depuración.

Por ejemplo:

do_something_with_x(x)

Podría depurar esto con:

from pprint import pprint
pprint(x)
do_something_with_x(x)

Por supuesto, la otra razón para importar módulos en otra parte del código es si necesita importarlos dinámicamente. Esto se debe a que prácticamente no tienes otra opción.

No me preocuparía demasiado la eficacia de cargar el módulo por adelantado. La memoria que ocupa el módulo no será muy grande (suponiendo que sea lo suficientemente modular) y el costo de inicio será insignificante.

Jason Baker
fuente
Estamos hablando de decenas de milisegundos de costo de inicio por módulo (en mi máquina). Eso no siempre es insignificante, por ejemplo, si afecta la capacidad de respuesta de una aplicación web a un clic del usuario.
Evgeni Sergeev
6

Es una compensación, que solo el programador puede decidir hacer.

El caso 1 ahorra algo de memoria y tiempo de inicio al no importar el módulo de fecha y hora (y hacer cualquier inicialización que pueda requerir) hasta que sea necesario. Tenga en cuenta que hacer la importación 'solo cuando se llama' también significa hacerlo 'cada vez que se llama', por lo que cada llamada después de la primera aún genera la sobrecarga adicional de realizar la importación.

El caso 2 ahorra algo de tiempo de ejecución y latencia al importar datetime de antemano para que not_often_called () regrese más rápidamente cuando sea llama, y también por no incurrir en los gastos generales de una importación en cada llamada.

Además de la eficiencia, es más fácil ver las dependencias del módulo por adelantado si las declaraciones de importación son ... por adelantado. Ocultarlos en el código puede hacer que sea más difícil encontrar fácilmente de qué módulos depende algo.

Personalmente, generalmente sigo el PEP, excepto para cosas como pruebas unitarias y que no quiero cargar siempre porque que no se usarán, excepto para el código de prueba.

pjz
fuente
2
-1. La sobrecarga principal de importación solo ocurre la primera vez. El costo de buscar el módulo sys.modulespuede compensarse fácilmente con el ahorro de tener que buscar solo un nombre local en lugar de un nombre global.
aaronasterling
6

Aquí hay un ejemplo donde todas las importaciones están en la parte superior (esta es la única vez que necesito hacer esto). Quiero poder terminar un subproceso tanto en Un * x como en Windows.

import os
# ...
try:
    kill = os.kill  # will raise AttributeError on Windows
    from signal import SIGTERM
    def terminate(process):
        kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
    try:
        from win32api import TerminateProcess  # use win32api if available
        def terminate(process):
            TerminateProcess(int(process._handle), -1)
    except ImportError:
        def terminate(process):
            raise NotImplementedError  # define a dummy function

(En revisión: lo que dijo John Millikin ).

Giltay
fuente
6

Esto es como muchas otras optimizaciones: sacrifica algo de legibilidad por la velocidad. Como John mencionó, si has hecho tu tarea de elaboración de perfiles y has encontrado que este es un cambio bastante útil y necesitas la velocidad extra, entonces hazlo. Probablemente sería bueno poner una nota con todas las demás importaciones:

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below
Drew Stephens
fuente
4

La inicialización del módulo solo ocurre una vez, en la primera importación. Si el módulo en cuestión es de la biblioteca estándar, es probable que también lo importe desde otros módulos en su programa. Para un módulo tan frecuente como datetime, también es probable que dependa de una gran cantidad de otras bibliotecas estándar. La declaración de importación costaría muy poco, ya que la inicialización del módulo ya habría sucedido. Todo lo que está haciendo en este momento es vincular el objeto del módulo existente al ámbito local.

Combine esa información con el argumento de legibilidad y yo diría que es mejor tener la declaración de importación en el alcance del módulo.

Jeremy Brown
fuente
4

Solo para completar la respuesta de Moe y la pregunta original:

Cuando tenemos que lidiar con dependencias circulares podemos hacer algunos "trucos". Asumiendo que estamos trabajando con módulos a.pyy b.pyque contienen x()yb y(), respectivamente. Entonces:

  1. Podemos mover uno de los from importsen la parte inferior del módulo.
  2. Podemos mover uno de los from importsdentro de la función o método que realmente requiere la importación (esto no siempre es posible, ya que puede usarlo desde varios lugares).
  3. Podemos cambiar uno de los dos from importspara que sea una importación que se vea así:import a

Entonces, para concluir. Si no está lidiando con dependencias circulares y está haciendo algún tipo de truco para evitarlas, entonces es mejor colocar todas sus importaciones en la parte superior debido a las razones ya explicadas en otras respuestas a esta pregunta. Y por favor, cuando hagas estos "trucos" incluye un comentario, ¡siempre es bienvenido! :)

Caumons
fuente
4

Además de las excelentes respuestas ya dadas, vale la pena señalar que la colocación de importaciones no es simplemente una cuestión de estilo. A veces, un módulo tiene dependencias implícitas que deben importarse o inicializarse primero, y una importación de nivel superior podría provocar violaciones del orden de ejecución requerido.

Este problema a menudo surge en la API Python de Apache Spark, donde necesita inicializar el SparkContext antes de importar cualquier paquete o módulo de pyspark. Es mejor colocar las importaciones de pyspark en un ámbito donde se garantice que SparkContext esté disponible.

Pablo
fuente
4

Me sorprendió no ver los números de costos reales para las verificaciones de carga repetidas publicadas, aunque hay muchas buenas explicaciones de qué esperar.

Si importa en la parte superior, recibe el golpe de carga sin importar qué. Eso es bastante pequeño, pero comúnmente en los milisegundos, no en nanosegundos.

Si importa dentro de una función (s), a continuación, sólo toma el golpe para la carga si y cuando una de esas funciones se llama en primer lugar. Como muchos han señalado, si eso no sucede en absoluto, ahorrará el tiempo de carga. Pero si las funciones se llaman mucho, recibe un golpe repetido aunque mucho más pequeño (para comprobar que se ha cargado; no para volver a cargar realmente). Por otro lado, como señaló @aaronasterling, también ahorras un poco porque importar dentro de una función permite que la función use búsquedas de variables locales ligeramente más rápidas para identificar el nombre más tarde ( http://stackoverflow.com/questions/477096/python- import-coding-style / 4789963 # 4789963 ).

Aquí están los resultados de una prueba simple que importa algunas cosas desde dentro de una función. Los tiempos informados (en Python 2.7.14 en un Intel Core i7 de 2.3 GHz) se muestran a continuación (la segunda llamada que toma más llamadas posteriores parece consistente, aunque no sé por qué).

 0 foo:   14429.0924 µs
 1 foo:      63.8962 µs
 2 foo:      10.0136 µs
 3 foo:       7.1526 µs
 4 foo:       7.8678 µs
 0 bar:       9.0599 µs
 1 bar:       6.9141 µs
 2 bar:       7.1526 µs
 3 bar:       7.8678 µs
 4 bar:       7.1526 µs

El código:

from __future__ import print_function
from time import time

def foo():
    import collections
    import re
    import string
    import math
    import subprocess
    return

def bar():
    import collections
    import re
    import string
    import math
    import subprocess
    return

t0 = time()
for i in xrange(5):
    foo()
    t1 = time()
    print("    %2d foo: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
for i in xrange(5):
    bar()
    t1 = time()
    print("    %2d bar: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
TextGeek
fuente
Los cambios en el tiempo de ejecución probablemente se deban a la escala de frecuencia de la CPU en respuesta a la carga. Es mejor comenzar las pruebas de velocidad con un segundo de trabajo ocupado para aumentar la velocidad del reloj de la CPU.
Han-Kwang Nienhuys
3

No aspiro a proporcionar una respuesta completa, porque otros ya lo han hecho muy bien. Solo quiero mencionar un caso de uso cuando me parece especialmente útil importar módulos dentro de funciones. Mi aplicación utiliza paquetes y módulos de Python almacenados en cierta ubicación como complementos. Durante el inicio de la aplicación, la aplicación recorre todos los módulos en la ubicación y los importa, luego mira dentro de los módulos y si encuentra algunos puntos de montaje para los complementos (en mi caso, es una subclase de cierta clase base que tiene una única ID) los registra. La cantidad de complementos es grande (ahora docenas, pero tal vez cientos en el futuro) y cada uno de ellos se usa con bastante poca frecuencia. Tener importaciones de bibliotecas de terceros en la parte superior de mis módulos de complementos fue una pequeña penalización durante el inicio de la aplicación. Especialmente algunas bibliotecas de terceros son pesadas de importar (por ejemplo, la importación de plotly incluso intenta conectarse a Internet y descargar algo que agregaba aproximadamente un segundo al inicio). Al optimizar las importaciones (llamándolas solo en las funciones donde se usan) en los complementos, logré reducir el inicio de 10 segundos a unos 2 segundos. Esa es una gran diferencia para mis usuarios.

Entonces mi respuesta es no, no siempre coloque las importaciones en la parte superior de sus módulos.

VK
fuente
3

Es interesante que ni una sola respuesta mencione el procesamiento paralelo hasta el momento, donde podría ser REQUERIDO que las importaciones estén en la función, cuando el código de función serializado es lo que se está enviando a otros núcleos, por ejemplo, en el caso de ipyparallel.

K.-Michael Aye
fuente
1

Puede haber una ganancia de rendimiento al importar variables / alcance local dentro de una función. Esto depende del uso de lo importado dentro de la función. Si realiza un bucle muchas veces y accede a un objeto global del módulo, importarlo como local puede ayudar.

prueba.py

X=10
Y=11
Z=12
def add(i):
  i = i + 10

runlocal.py

from test import add, X, Y, Z

    def callme():
      x=X
      y=Y
      z=Z
      ladd=add 
      for i  in range(100000000):
        ladd(i)
        x+y+z

    callme()

run.py

from test import add, X, Y, Z

def callme():
  for i in range(100000000):
    add(i)
    X+Y+Z

callme()

Un tiempo en Linux muestra una pequeña ganancia

/usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python run.py 
    0:17.80 real,   17.77 user, 0.01 sys
/tmp/test$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python runlocal.py 
    0:14.23 real,   14.22 user, 0.01 sys

real es el reloj de pared. El usuario es tiempo en el programa. sys es hora de llamadas al sistema.

https://docs.python.org/3.5/reference/executionmodel.html#resolution-of-names

quiet_penguin
fuente
1

Legibilidad

Además del rendimiento de inicio, hay un argumento de legibilidad para localizar las importdeclaraciones. Por ejemplo, tome los números de línea de python 1283 a 1296 en mi primer proyecto actual de python:

listdata.append(['tk font version', font_version])
listdata.append(['Gtk version', str(Gtk.get_major_version())+"."+
                 str(Gtk.get_minor_version())+"."+
                 str(Gtk.get_micro_version())])

import xml.etree.ElementTree as ET

xmltree = ET.parse('/usr/share/gnome/gnome-version.xml')
xmlroot = xmltree.getroot()
result = []
for child in xmlroot:
    result.append(child.text)
listdata.append(['Gnome version', result[0]+"."+result[1]+"."+
                 result[2]+" "+result[3]])

Si la importdeclaración estuviera en la parte superior del archivo, tendría que desplazarme hacia arriba o presionar Homepara averiguar qué ETera. Entonces tendría que volver a la línea 1283 para continuar leyendo el código.

De hecho, incluso si la importdeclaración estuviera en la parte superior de la función (o clase) como muchos la colocarían, se requeriría paginación hacia arriba y hacia abajo.

Rara vez se mostrará el número de versión de Gnome, por lo que la importparte superior del archivo presenta un retraso de inicio innecesario.

WinEunuuchs2Unix
fuente
0

Me gustaría mencionar un caso de uso mío, muy similar a los mencionados por @John Millikin y @VK:

Importaciones opcionales

Hago análisis de datos con Jupyter Notebook, y uso el mismo cuaderno de IPython como plantilla para todos los análisis. En algunas ocasiones, necesito importar Tensorflow para hacer algunas ejecuciones rápidas del modelo, pero a veces trabajo en lugares donde el tensorflow no está configurado / es lento para importar. En esos casos, encapsulo mis operaciones dependientes de Tensorflow en una función auxiliar, importo tensorflow dentro de esa función y lo ato a un botón.

De esta manera, podría hacer "reiniciar y ejecutar todo" sin tener que esperar a la importación, o tener que reanudar el resto de las celdas cuando falla.

Cedro
fuente
0

Esta es una discusión fascinante. Como muchos otros, nunca había considerado este tema. Me arrinconaron para tener que importar las funciones porque quería usar el Django ORM en una de mis bibliotecas. Tenía que llamar django.setup()antes de importar mis clases de modelo y debido a que esto estaba en la parte superior del archivo, se estaba arrastrando a un código de biblioteca que no era Django debido a la construcción del inyector IoC.

De alguna manera pirateé un poco y terminé colocando el django.setup()constructor singleton y la importación relevante en la parte superior de cada método de clase. Ahora esto funcionó bien, pero me hizo sentir incómodo porque las importaciones no estaban en la cima y también comencé a preocuparme por el tiempo extra de las importaciones. Luego vine aquí y leí con gran interés que todo el mundo tomara esto.

Tengo un largo historial de C ++ y ahora uso Python / Cython. Mi opinión sobre esto es que, por qué no poner las importaciones en la función a menos que te cause un cuello de botella perfilado. Es solo como declarar espacio para variables justo antes de que las necesite. ¡El problema es que tengo miles de líneas de código con todas las importaciones en la parte superior! Así que creo que lo haré de ahora en adelante y cambiaré el archivo extraño aquí y allá cuando esté de paso y tenga tiempo.

LJHW
fuente