xticks barplot python coding-style

python - barplot - pandas plot



Estilo de codificación de importación Python (11)

Implementaciones de seguridad

Considere un entorno donde todo su código Python se encuentra dentro de una carpeta a la que solo tiene acceso un usuario con privilegios. Para evitar ejecutar todo su programa como usuario con privilegios, decide dejar privilegios a un usuario no privilegiado durante la ejecución. Tan pronto como utilice una función que importa otro módulo, su programa arrojará un ImportError ya que el usuario sin privilegios no puede importar el módulo debido a los permisos del archivo.

Descubrí un nuevo patrón. ¿Este patrón es bien conocido o cuál es la opinión al respecto?

Básicamente, me cuesta mucho fregar los archivos fuente hacia arriba y hacia abajo para averiguar qué módulos de importación están disponibles, etc., así que ahora, en lugar de

import foo from bar.baz import quux def myFunction(): foo.this.that(quux)

Muevo todas mis importaciones a la función en la que realmente se usan., Así:

def myFunction(): import foo from bar.baz import quux foo.this.that(quux)

Esto hace algunas cosas. En primer lugar, rara vez contamino accidentalmente mis módulos con los contenidos de otros módulos. Podría establecer la variable __all__ para el módulo, pero luego tendría que actualizarlo a medida que el módulo evoluciona, y eso no ayuda a la contaminación del espacio de nombres para el código que realmente vive en el módulo.

En segundo lugar, rara vez termino con una letanía de importaciones en la parte superior de mis módulos, la mitad o más de las cuales ya no necesito porque las he refabricado. Finalmente, encuentro que este patrón MUCHO es más fácil de leer, ya que cada nombre referenciado está allí mismo en el cuerpo de la función.


Algunos problemas con este enfoque:

  • No es inmediatamente obvio al abrir el archivo de qué módulos depende.
  • py2app programas que tienen que analizar dependencias, como py2exe , py2app , etc.
  • ¿Qué hay de los módulos que usas en muchas funciones? Terminará con muchas importaciones redundantes o tendrá que tener algunas en la parte superior del archivo y algunas funciones internas.

Entonces ... la forma preferida es colocar todas las importaciones en la parte superior del archivo. Descubrí que si mis importaciones se vuelven difíciles de controlar, generalmente significa que tengo demasiados códigos que sería mejor dividirlos en dos o más archivos.

Algunas situaciones en las que he encontrado que las importaciones dentro de las funciones son útiles:

  • Para tratar con dependencias circulares (si realmente no puede evitarlas)
  • Código específico de plataforma

Además, poner importaciones dentro de cada función no es mucho más lento que en la parte superior del archivo. La primera vez que se carga cada módulo, se coloca en sys.modules , y cada importación posterior cuesta solo el tiempo para buscar el módulo, que es bastante rápido (no se recarga).


Ambas variantes tienen sus usos. Sin embargo, en la mayoría de los casos, es mejor importar fuera de las funciones, no dentro de ellas.

Actuación

Se ha mencionado en varias respuestas, pero en mi opinión, todas carecen de una discusión completa.

La primera vez que se importa un módulo en un intérprete de Python será lento, sin importar si está en el nivel superior o dentro de una función. Es lento porque Python (me estoy enfocando en CPython, podría ser diferente para otras implementaciones de Python) hace varios pasos:

  • Ubica el paquete.
  • Comprueba si el paquete ya se convirtió en bytecode (el famoso directorio __pycache__ o los archivos .pyx ) y si no los convierte a bytecode.
  • Python carga el bytecode.
  • El módulo cargado se coloca en sys.modules .

Las importaciones posteriores no tendrán que hacer todo esto porque Python simplemente puede devolver el módulo desde sys.modules . Por lo tanto, las importaciones posteriores serán mucho más rápidas.

Puede ser que una función de su módulo no se use muy a menudo, pero depende de una import que lleva bastante tiempo. Entonces podrías mover la import dentro de la función. Eso hará que la importación de su módulo sea más rápida (porque no tiene que importar el paquete de carga larga inmediatamente), sin embargo, cuando la función finalmente se use, será lenta en la primera llamada (porque entonces el módulo debe ser importado). Eso puede tener un impacto en el rendimiento percibido porque, en lugar de ralentizar a todos los usuarios, solo ralentiza aquellos que usan la función que depende de la dependencia de carga lenta.

Sin embargo, la búsqueda en sys.modules no es gratuita. Es muy rápido, pero no es gratis. Por lo tanto, si realmente llama a una función que import un paquete de sa muy a menudo, notará un rendimiento levemente degradado:

import random import itertools def func_1(): return random.random() def func_2(): import random return random.random() def loopy(func, repeats): for _ in itertools.repeat(None, repeats): func() %timeit loopy(func_1, 10000) # 1.14 ms ± 20.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit loopy(func_2, 10000) # 2.21 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Eso es casi dos veces más lento.

Es muy importante darse cuenta de que aaronasterling "engañó" un poco en la respuesta . Dijo que hacer la importación en la función realmente hace que la función sea más rápida. Y hasta cierto punto esto es verdad. Eso es porque cómo Python busca nombres:

  • Primero verifica el alcance local.
  • Comprueba el alcance circundante a continuación.
  • Luego se verifica el siguiente alcance circundante
  • ...
  • El alcance global está marcado.

Entonces, en lugar de verificar el alcance local y luego verificar el alcance global, basta con verificar el alcance local porque el nombre del módulo está disponible en el ámbito local. ¡Eso realmente lo hace más rápido! Pero esa es una técnica llamada "movimiento de código invariante de bucle" . Básicamente significa que se reduce la sobrecarga de algo que se hace en un bucle (o repetidamente) al almacenarlo en una variable antes del bucle (o las llamadas repetidas). Entonces, en lugar de import en la función, simplemente podría usar una variable y asignarla al nombre global:

import random import itertools def f1(repeats): "Repeated global lookup" for _ in itertools.repeat(None, repeats): random.random() def f2(repeats): "Import once then repeated local lookup" import random for _ in itertools.repeat(None, repeats): random.random() def f3(repeats): "Assign once then repeated local lookup" local_random = random for _ in itertools.repeat(None, repeats): local_random.random() %timeit f1(10000) # 588 µs ± 3.92 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f2(10000) # 522 µs ± 1.95 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f3(10000) # 527 µs ± 4.51 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Si bien puede ver claramente que hacer búsquedas repetidas para el random global es lento, prácticamente no hay diferencia entre importar el módulo dentro de la función o asignar el módulo global en una variable dentro de la función.

Esto podría llevarse al extremo al evitar también la búsqueda de funciones dentro del ciclo:

def f4(repeats): from random import random for _ in itertools.repeat(None, repeats): random() def f5(repeats): r = random.random for _ in itertools.repeat(None, repeats): r() %timeit f4(10000) # 364 µs ± 9.34 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f5(10000) # 357 µs ± 2.73 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

De nuevo, mucho más rápido, pero casi no hay diferencia entre la importación y la variable.

Dependencias opcionales

A veces, tener una importación a nivel de módulo puede ser un problema. Por ejemplo, si no desea agregar otra dependencia de tiempo de instalación, el módulo sería realmente útil para algunas funcionalidades adicionales . Decidir si una dependencia debería ser opcional no debe hacerse a la ligera porque afectará a los usuarios (ya sea que obtengan un ImportError inesperado o de otra manera se pierdan las "características interesantes") y hace que la instalación del paquete con todas las funciones sea más complicada, para Las dependencias normales pip o conda (solo para mencionar dos gestores de paquetes) funcionan de manera conda , pero para las dependencias opcionales, los usuarios tienen que instalar manualmente los paquetes más adelante (hay algunas opciones que hacen posible personalizar los requisitos pero, una vez más, la carga de instalarlo "correctamente" se pone al usuario).

Pero de nuevo, esto podría hacerse de ambas maneras:

try: import matplotlib.pyplot as plt except ImportError: pass def function_that_requires_matplotlib(): plt.plot()

o:

def function_that_requires_matplotlib(): import matplotlib.pyplot as plt plt.plot()

Esto podría ser más personalizado al proporcionar implementaciones alternativas o personalizar la excepción (o mensaje) que el usuario ve, pero esta es la esencia principal.

El enfoque de nivel superior podría ser un poco mejor si se quiere proporcionar una "solución" alternativa a la dependencia opcional, sin embargo, generalmente las personas usan la importación en función. Principalmente porque conduce a un stacktrace más limpio y es más corto.

Importaciones circulares

Las importaciones en función pueden ser muy útiles para evitar ImportErrors debido a importaciones circulares. En muchos casos, las importaciones circulares son un signo de una estructura de paquete "mala", pero si no hay forma de evitar una importación circular, el "círculo" (y por lo tanto los problemas) se resuelve colocando las importaciones que conducen al círculo dentro las funciones que realmente lo usan.

No te repitas

Si realmente coloca todas las importaciones en la función en lugar del alcance del módulo, introducirá la redundancia, porque es probable que las funciones requieran las mismas importaciones. Eso tiene algunas desventajas:

  • Ahora tiene varios lugares para verificar si alguna importación se ha vuelto obsoleta.
  • En caso de que hayas extraviado alguna importación, solo lo sabrás cuando ejecutes la función específica y no la carga. Debido a que tiene más declaraciones de importación, la probabilidad de un error aumenta (no mucho) y se vuelve un poco más esencial para probar todas las funciones.

Pensamientos adicionales:

Raramente termino con una letanía de importaciones en la parte superior de mis módulos, la mitad o más de las cuales ya no necesito porque las he refabricado.

La mayoría de los IDE ya tienen un corrector para las importaciones no utilizadas, por lo que es probable que sea solo unos pocos clics para eliminarlos. Incluso si no usa un IDE, puede usar una secuencia de comandos de código estático de vez en cuando y corregirlo manualmente. Otra respuesta mencionó a la pileta, pero hay otras (por ejemplo, pyflakes).

Raramente contamino accidentalmente mis módulos con los contenidos de otros módulos

Es por eso que normalmente usa __all__ y / o define los submódulos de sus funciones y solo importa las clases / funciones relevantes / ... en el módulo principal, por ejemplo __init__.py .

Además, si crees que contaminaste demasiado el espacio de nombres del módulo, probablemente deberías considerar dividir el módulo en submódulos, pero eso solo tiene sentido para docenas de importaciones.

Un punto adicional (muy importante) que debe mencionarse si desea reducir la contaminación del espacio de nombres es evitar la from module import * un from module import * . Pero también es posible que desee evitar las from module import a, b, c, d, e, ... que importan demasiados nombres e importan el módulo y acceden a las funciones con module.c .

Como último recurso, siempre puede usar alias para evitar contaminar el espacio de nombres con importaciones "públicas" utilizando: import random as _random . Eso hará que el código sea más difícil de entender, pero deja muy en claro qué debe ser públicamente visible y qué no. No es algo que recomiende, solo debe mantener la lista __all__ actualizada (que es el enfoque recomendado y razonable).

Resumen

  • El impacto en el rendimiento es visible, pero casi siempre será micro-optimizador, por lo que no deje que la decisión de colocar las importaciones se guíe por micro-puntos de referencia. Excepto si la dependencia es realmente lenta en la primera import y solo se usa para un pequeño subconjunto de la funcionalidad. Entonces puede tener un impacto visible en el rendimiento percibido de su módulo para la mayoría de los usuarios.

  • Use las herramientas comúnmente entendidas para definir la API pública, me refiero a la variable __all__ . Puede ser un poco molesto mantenerlo actualizado, pero también lo es comprobar todas las funciones en busca de importaciones obsoletas o cuando agrega una nueva función para agregar todas las importaciones relevantes en esa función. A largo plazo, probablemente tendrá que hacer menos trabajo actualizando __all__ .

  • Realmente no importa cuál prefieres, ambos funcionan. Si trabajas solo, puedes razonar sobre los pros y contras y decidir cuál crees que es el mejor. Sin embargo, si trabajas en un equipo, probablemente deberías apegarte a patrones conocidos (que serían importaciones de nivel superior con __all__ ) porque les permite hacer lo que (probablemente) siempre han hecho.


Creo que este es un enfoque recomendado en algunos casos / escenarios. Por ejemplo, en Google App Engine se recomiendan los grandes módulos de carga diferida, ya que minimizarán el costo de preparación de nuevas VM / intérpretes de Python. Eche un vistazo a la presentación de un Ingeniero de Google describiendo esto. Sin embargo, tenga en cuenta que esto no significa que deba cargar todos sus módulos.



Es posible que desee echar un vistazo a la sobrecarga de declaración de importación en la wiki de python. En resumen: si el módulo ya se ha cargado (mira sys.modules ) tu código se ejecutará más despacio. Si su módulo no se ha cargado todavía, y foo solo se cargará cuando sea necesario, que puede ser cero veces, entonces el rendimiento general será mejor.


Esto tiene algunas desventajas.

Pruebas

En el caso de que quiera probar su módulo a través de la modificación del tiempo de ejecución, puede hacerlo más difícil. En lugar de hacer

import mymodule mymodule.othermodule = module_stub

Tendrás que hacer

import othermodule othermodule.foo = foo_stub

Esto significa que tendrá que parchear el otro módulo de forma global, en lugar de simplemente cambiar lo que indica la referencia en mymodule.

Seguimiento de dependencia

Esto hace que no sea obvio de qué módulos depende su módulo. Esto es especialmente irritante si utiliza muchas bibliotecas de terceros o está reorganizando el código.

Tuve que mantener algún código heredado que usaba importaciones en línea en todo el lugar, hizo que el código fuera extremadamente difícil de refactorizar o reempaquetar.

Notas sobre el rendimiento

Debido a la forma en que Python almacena los módulos en caché, no hay un golpe de rendimiento. De hecho, dado que el módulo está en el espacio de nombres local, hay un pequeño beneficio de rendimiento para importar módulos en una función.

Importación superior

import random def f(): L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(10000): f() $ time python test.py real 0m1.569s user 0m1.560s sys 0m0.010s

Importar en el cuerpo de la función

def f(): import random L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(10000): f() $ time python test2.py real 0m1.385s user 0m1.380s sys 0m0.000s


La respuesta (anteriormente) mejor votado a esta pregunta está muy bien formateada pero absolutamente incorrecta sobre el rendimiento. Déjame demostrar

Actuación

Importación superior

import random def f(): L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(1000): f() $ time python import.py real 0m0.721s user 0m0.412s sys 0m0.020s

Importar en el cuerpo de la función

def f(): import random L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(1000): f() $ time python import2.py real 0m0.661s user 0m0.404s sys 0m0.008s

Como puede ver, puede ser más eficiente importar el módulo en la función. La razón de esto es simple. Mueve la referencia de una referencia global a una referencia local. Esto significa que, para CPython al menos, el compilador emitirá instrucciones LOAD_FAST lugar de instrucciones LOAD_GLOBAL . Estos son, como su nombre lo indica, más rápido. El otro respondedor infló artificialmente el impacto en el rendimiento de buscar en sys.modules mediante la importación en cada iteración del ciclo .

Como regla general, es mejor importar en la parte superior, pero el rendimiento no es la razón si está accediendo al módulo muchas veces. Las razones son que uno puede hacer un seguimiento de lo que un módulo depende más fácilmente y que hacerlo es consistente con la mayoría del resto del universo de Python.


La gente ha explicado muy bien por qué para evitar las importaciones en línea, pero no flujos de trabajo realmente alternativos para abordar las razones que los quieren en primer lugar.

Me cuesta mucho fregar los archivos fuente ascendentes y descendentes para descubrir qué módulos de importación están disponibles, etc.

Para verificar las importaciones no utilizadas, uso pylint . Realiza un análisis estático (ish) del código de Python, y una de las (muchas) cosas que verifica son las importaciones no utilizadas. Por ejemplo, el siguiente script ...

import urllib import urllib2 urllib.urlopen("http://.com")

..would generar el siguiente mensaje:

example.py:2 [W0611] Unused import urllib2

En cuanto a comprobar las importaciones disponibles, generalmente confío en la finalización de TextMate (bastante simplista): cuando presiona Esc, completa la palabra actual con otras personas en el documento. Si he hecho la import urllib , urll[Esc] se expandirá a urllib , si no, saltaré al inicio del archivo y agregaré la importación.


Otra cosa útil a tener en cuenta es que la sintaxis from module import * dentro de una función ha sido eliminada en Python 3.0.

Hay una breve mención de ello en "Sintaxis eliminada" aquí:

http://docs.python.org/3.0/whatsnew/3.0.html


Sugeriría que intentes evitar las from foo import bar importación from foo import bar . Solo los uso dentro de paquetes, donde la división en módulos es un detalle de implementación y de todos modos no habrá muchos.

En todos los demás lugares, donde importa un paquete, simplemente use import foo y luego foo.bar nombre completo foo.bar . De esta forma, siempre puedes saber de dónde viene un determinado elemento y no tienes que mantener la lista de elementos importados (en realidad, esto siempre estará desactualizado e importar elementos que ya no se usan).

Si foo es un nombre realmente largo, puedes simplificarlo import foo as f y luego escribir f.bar . Esto es mucho más conveniente y explícito que mantener todas las importaciones.