¿Es pitónico importar funciones internas?

126

PEP 8 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 del módulo.

En occation, violo PEP 8. Algunas veces importo cosas dentro de funciones. Como regla general, hago esto si hay una importación que solo se usa dentro de una sola función.

Alguna opinión?

EDITAR (la razón por la que siento que importar funciones puede ser una buena idea):

Motivo principal: puede aclarar el código.

  • Al mirar el código de una función, podría preguntarme: "¿Qué es la función / clase xxx?" (xxx se utiliza dentro de la función). Si tengo todas mis importaciones en la parte superior del módulo, tengo que ir a buscar allí para determinar qué es xxx. Esto es más un problema cuando se usa from m import xxx. Ver m.xxxen la función probablemente me diga más. Dependiendo de lo que msea: ¿Es un conocido módulo / paquete de nivel superior ( import m)? ¿O es un submódulo / paquete ( from a.b.c import m)?
  • En algunos casos, tener esa información adicional ("¿Qué es xxx?") Cerca de donde se usa xxx puede facilitar la comprensión de la función.
codeape
fuente
2
y haces eso por rendimiento?
Macarse
44
Siento que aclara el código en algunos casos. Supongo que el rendimiento bruto cae al importar en una función (ya que la instrucción de importación se ejecutará cada vez que se llame a la función).
codeape
Puede responder "¿Qué es la función / clase xxx?" mediante el uso de la sintaxis import xyz en lugar de la sintaxis from xyz import abc
Tom Leys
1
Si la claridad es el único factor, U también podría incluir un comentario relevante, a tal efecto. ;)
Lakshman Prasad
55
@becomingGuru: Claro, pero los comentarios se pueden perder la sincronización con la realidad ...
codeape

Respuestas:

88

A la larga, creo que apreciará tener la mayoría de sus importaciones en la parte superior del archivo, de esa manera puede ver de un vistazo lo complicado que es su módulo por lo que necesita importar.

Si estoy agregando un nuevo código a un archivo existente, generalmente haré la importación donde sea necesario y luego, si el código permanece, haré las cosas más permanentes moviendo la línea de importación a la parte superior del archivo.

Otro punto, prefiero obtener una ImportErrorexcepción antes de que se ejecute cualquier código, como un control de cordura, por lo que esa es otra razón para importar en la parte superior.

Yo uso pyCheckerpara verificar los módulos no utilizados.

Peter Ericson
fuente
47

Hay dos ocasiones en las que violo la PEP 8 a este respecto:

  • Importaciones circulares: el módulo A importa el módulo B, pero algo en el módulo B necesita el módulo A (aunque esto es a menudo una señal de que necesito refactorizar los módulos para eliminar la dependencia circular)
  • Inserción de un punto de interrupción de pdb: import pdb; pdb.set_trace()esto es útil b / c que no quiero colocar import pdben la parte superior de cada módulo que quiera depurar, y es fácil recordar eliminar la importación cuando elimino el punto de interrupción.

Fuera de estos dos casos, es una buena idea poner todo en la parte superior. Hace las dependencias más claras.

Rick Copeland
fuente
77
Estoy de acuerdo en que aclara las dependencias con respecto al módulo en su conjunto. Pero creo que puede hacer que el código sea menos claro a nivel de función para importar todo en la parte superior. Cuando mira el código de una función, puede preguntarse: "¿Qué es la función / clase xxx?" (xxx se usa dentro de la función). Y tienes que mirar la parte superior del archivo para ver de dónde viene xxx. Esto es más un problema cuando se usa desde m import xxx. Ver m.xxx te dice más, al menos si no hay dudas sobre lo que es m.
codeape
20

Aquí están los cuatro casos de uso de importación que usamos

  1. import(y from x import yy import x as y) en la parte superior

  2. Opciones para la importación. En la cima.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
    
  3. Importación Condicional. Usado con JSON, bibliotecas XML y similares. En la cima.

    try:
        import this as foo
    except ImportError:
        import that as foo
    
  4. Importación dinámica. Hasta ahora, solo tenemos un ejemplo de esto.

    import settings
    module_stuff = {}
    module= __import__( settings.some_module, module_stuff )
    x = module_stuff['x']
    

    Tenga en cuenta que esta importación dinámica no trae código, pero trae estructuras de datos complejas escritas en Python. Es algo así como una pieza de datos en escabeche, excepto que la dejamos a mano.

    Esto también es, más o menos, en la parte superior de un módulo


Esto es lo que hacemos para aclarar el código:

  • Mantenga los módulos cortos.

  • Si tengo todas mis importaciones en la parte superior del módulo, tengo que ir a buscar allí para determinar qué nombre es. Si el módulo es corto, eso es fácil de hacer.

  • En algunos casos, tener esa información adicional cerca de donde se usa un nombre puede hacer que la función sea más fácil de entender. Si el módulo es corto, eso es fácil de hacer.

S.Lott
fuente
Mantener los módulos cortos es, por supuesto, una muy buena idea. Pero para obtener el beneficio de tener siempre "información de importación" para las funciones disponibles, la longitud máxima del módulo debería ser una pantalla (probablemente 100 líneas como máximo). Y eso probablemente sería demasiado corto para ser práctico en la mayoría de los casos.
codeape
Supongo que podrías llevar esto a un extremo lógico. Creo que puede haber un punto de equilibrio en el que su módulo sea "lo suficientemente pequeño" para que no necesite técnicas de importación sofisticadas para gestionar la complejidad. Nuestro tamaño de módulo promedio es, por coincidencia, alrededor de 100 líneas.
S.Lott
8

Una cosa a tener en cuenta: las importaciones innecesarias pueden causar problemas de rendimiento. Entonces, si esta es una función que se llamará con frecuencia, es mejor que coloque la importación en la parte superior. Por supuesto, esta es una optimización, por lo que si hay un caso válido por hacer que importar dentro de una función es más claro que importar en la parte superior de un archivo, eso supera el rendimiento en la mayoría de los casos.

Si está haciendo IronPython, me dicen que es mejor importar funciones internas (ya que compilar código en IronPython puede ser lento). Por lo tanto, puede obtener una forma de importar funciones internas en ese momento. Pero aparte de eso, diría que simplemente no vale la pena luchar contra la convención.

Como regla general, hago esto si hay una importación que solo se usa dentro de una sola función.

Otro punto que me gustaría destacar es que este puede ser un problema potencial de mantenimiento. ¿Qué sucede si agrega una función que usa un módulo que anteriormente solo usaba una función? ¿Vas a recordar agregar la importación a la parte superior del archivo? ¿O va a escanear todas y cada una de las funciones en busca de importaciones?

FWIW, hay casos en los que tiene sentido importar dentro de una función. Por ejemplo, si desea establecer el idioma en cx_Oracle, debe establecer una _variable de entorno NLS LANG antes de que se importe. Por lo tanto, puede ver código como este:

import os

oracle = None

def InitializeOracle(lang):
    global oracle
    os.environ['NLS_LANG'] = lang
    import cx_Oracle
    oracle = cx_Oracle
Jason Baker
fuente
2
Estoy de acuerdo con su problema de mantenimiento. Refactorizar el código puede ser un poco problemático. Si agrego una segunda función que usa un módulo usado anteriormente solo por una función, muevo la importación al principio o rompo mi propia regla general al importar también el módulo en la segunda función.
codeape
2
Creo que el argumento del rendimiento también puede ser al revés. Importar un módulo puede llevar mucho tiempo. En sistemas de archivos distribuidos como los de las supercomputadoras, importar un módulo grande como numpy puede llevar varios segundos. Si solo se necesita un módulo para una función única y poco utilizada, la importación en la función acelerará significativamente el caso común.
amaurea
6

He roto esta regla antes para los módulos que se autoevalúan. Es decir, normalmente solo se usan como soporte, pero defino una principal para ellos, de modo que si los ejecuta por sí mismos puede probar su funcionalidad. En ese caso, a veces importo getopty cmdsolo en main, porque quiero que sea claro para alguien que lee el código que estos módulos no tienen nada que ver con el funcionamiento normal del módulo y solo se incluyen para las pruebas.

Dan Lew
fuente
5

Viniendo de la pregunta sobre cómo cargar el módulo dos veces , ¿por qué no ambos?

Una importación en la parte superior del script indicará las dependencias y otra importación en la función hará que esta función sea más atómica, mientras que aparentemente no causa ninguna desventaja de rendimiento, ya que una importación consecutiva es barata.

IljaBek
fuente
3

Mientras sea importy no from x import *, debes ponerlos en la parte superior. Agrega solo un nombre al espacio de nombres global, y se adhiere a PEP 8. Además, si luego lo necesita en otro lugar, no tiene que mover nada.

No es gran cosa, pero como casi no hay diferencia, sugiero hacer lo que dice PEP 8.

Javier
fuente
3
En realidad, poner from x import *dentro de una función generará un SyntaxWarning, al menos en 2.5.
Rick Copeland
3

Eche un vistazo al enfoque alternativo que se usa en sqlalchemy: inyección de dependencia:

@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
    #...
    query.Query(...)

¡Observe cómo la biblioteca importada se declara en un decorador y se pasa como argumento a la función !

¡Este enfoque hace que el código sea más limpio, y también funciona 4,5 veces más rápido que una importdeclaración!

Punto de referencia: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796

Kolypto
fuente
2

En los módulos que son módulos 'normales' y pueden ejecutarse (es decir, tienen una if __name__ == '__main__':sección-), generalmente importo módulos que solo se usan al ejecutar el módulo dentro de la sección principal.

Ejemplo:

def really_useful_function(data):
    ...


def main():
    from pathlib import Path
    from argparse import ArgumentParser
    from dataloader import load_data_from_directory

    parser = ArgumentParser()
    parser.add_argument('directory')
    args = parser.parse_args()
    data = load_data_from_directory(Path(args.directory))
    print(really_useful_function(data)


if __name__ == '__main__':
    main()
codeape
fuente
1

Hay otro caso (probablemente "de esquina") en el que puede ser beneficioso importutilizar funciones raramente utilizadas: acortar el tiempo de inicio.

Llegué a ese muro una vez con un programa bastante complejo que se ejecuta en un pequeño servidor IoT que acepta comandos de una línea en serie y realiza operaciones, posiblemente operaciones muy complejas.

Colocar importdeclaraciones en la parte superior de los archivos destinados a tener todas las importaciones procesadas antes del inicio del servidor; ya que importla lista incluye jinja2, lxml, signxmly otros "pesos pesados" (SoC y no era muy potente) Esto significaba minutos antes de la primera instrucción se ejecuta realmente.

OTOH colocando la mayoría de las importaciones en funciones pude tener el servidor "vivo" en la línea serial en segundos. Por supuesto, cuando los módulos eran realmente necesarios, tenía que pagar el precio (Nota: esto también puede mitigarse generando una tarea en segundo plano haciendo imports en tiempo de inactividad).

ZioByte
fuente