Importaciones relativas por millonésima vez.

Yo he estado aquí:

  • http://www.python.org/dev/peps/pep-0328/
  • http://docs.python.org/2/tutorial/modules.html#packages
  • Paquetes de Python: importaciones relativas
  • El código de ejemplo de importación relativa de Python no funciona
  • Última respuesta a las importaciones relativas de python
  • Importaciones relativas en Python
  • Python: Deshabilitando la importación relativa

y muchas URL que no copié, algunas en SO, otras en otros sitios, cuando pensé que tendría la solución rápidamente.

La pregunta que siempre se repite es la siguiente: con Windows 7, Python 2.7.3 de 32 bits, ¿cómo resuelvo este mensaje “Intento de importación relativa en un paquete que no es un paquete”? Construí una réplica exacta del paquete en pep-0328:

package/ __init__.py subpackage1/ __init__.py moduleX.py moduleY.py subpackage2/ __init__.py moduleZ.py moduleA.py 

Hice funciones llamadas spam y huevos en sus módulos apropiados. Naturalmente, no funcionó. La respuesta está aparentemente en la 4ta URL que he enumerado, pero para mí todos son antiguos alumnos. Hubo esta respuesta en una de las URL que visité:

Las importaciones relativas utilizan el atributo de nombre de un módulo para determinar la posición de ese módulo en la jerarquía de paquetes. Si el nombre del módulo no contiene ninguna información de paquete (por ejemplo, está configurado como ‘principal’), las importaciones relativas se resuelven como si el módulo fuera un módulo de nivel superior, independientemente de dónde se encuentre el módulo en el sistema de archivos.

La respuesta anterior parece prometedora, pero para mí todos son jeroglíficos. Entonces, mi pregunta, ¿cómo puedo hacer que Python no me devuelva “Intento de importación relativa en no paquete”? Tiene una respuesta que implica -m, supuestamente.

¿Alguien puede decirme por qué Python da ese mensaje de error, lo que significa no empaquetado? , por qué y cómo se define un ‘paquete’, y la respuesta precisa se expresa de manera lo suficientemente fácil para que un estudiante de kindergarten pueda entender .

Edición: Las importaciones se realizaron desde la consola.

Guión vs. Módulo

Aquí hay una explicación. La versión corta es que hay una gran diferencia entre ejecutar directamente un archivo de Python e importar ese archivo desde otro lugar. El solo hecho de saber en qué directorio está el archivo no determina en qué paquete Python cree que está. Eso depende, además, de cómo se carga el archivo en Python (ejecutándolo o importándolo).

Hay dos formas de cargar un archivo de Python: como la secuencia de comandos de nivel superior o como un módulo. Un archivo se carga como la secuencia de comandos de nivel superior si lo ejecuta directamente, por ejemplo, escribiendo python myfile.py en la línea de comandos. Se carga como un módulo si haces python -m myfile , o si se carga cuando se encuentra una statement de import dentro de algún otro archivo. Solo puede haber una secuencia de comandos de nivel superior a la vez; El script de nivel superior es el archivo Python que ejecutó para comenzar.

Nombrar

Cuando se carga un archivo, se le da un nombre (que se almacena en su atributo __name__ ). Si se cargó como la secuencia de comandos de nivel superior, su nombre es __main__ . Si se cargó como un módulo, su nombre es el nombre del archivo, precedido por los nombres de los paquetes / subpaquetes de los que forma parte, separados por puntos.

Así, por ejemplo, en tu ejemplo:

 package/ __init__.py subpackage1/ __init__.py moduleX.py moduleA.py 

si importó moduleX (nota: importado , no ejecutado directamente), su nombre sería package.subpackage1.moduleX . Si importó el moduleA , su nombre sería package.moduleA . Sin embargo, si ejecuta directamente el moduleX desde la línea de comandos, su nombre será __main__ , y si ejecuta directamente el moduleA desde la línea de comandos, su nombre será __main__ . Cuando un módulo se ejecuta como la secuencia de comandos de nivel superior, pierde su nombre normal y su nombre es __main__ .

Accediendo a un módulo NO a través de su paquete que contiene

Existe un problema adicional: el nombre del módulo depende de si se importó “directamente” desde el directorio en el que se encuentra o si se importó a través de un paquete. Esto solo hace una diferencia si ejecuta Python en un directorio e intenta importar un archivo en ese mismo directorio (o un subdirectorio de este). Por ejemplo, si inicia el intérprete de Python en el directorio package/subpackage1 y luego import moduleX , el nombre de moduleX será solo moduleX , y no package.subpackage1.moduleX . Esto se debe a que Python agrega el directorio actual a su ruta de búsqueda en el inicio; Si encuentra el módulo a importar en el directorio actual, no sabrá que ese directorio es parte de un paquete, y la información del paquete no se convertirá en parte del nombre del módulo.

Un caso especial es si ejecuta el intérprete de manera interactiva (por ejemplo, simplemente escriba python y comience a ingresar el código de Python sobre la marcha). En este caso, el nombre de esa sesión interactiva es __main__ .

Ahora, aquí está lo crucial para su mensaje de error: si el nombre de un módulo no tiene puntos, no se considera parte de un paquete . No importa dónde esté el archivo en el disco. Lo único que importa es cuál es su nombre y su nombre depende de cómo lo cargó.

Ahora mire la cita que incluyó en su pregunta:

Las importaciones relativas utilizan el atributo de nombre de un módulo para determinar la posición de ese módulo en la jerarquía de paquetes. Si el nombre del módulo no contiene ninguna información de paquete (por ejemplo, está configurado como ‘principal’), las importaciones relativas se resuelven como si el módulo fuera un módulo de nivel superior, independientemente de dónde se encuentre el módulo en el sistema de archivos.

Importaciones relativas …

Las importaciones relativas utilizan el nombre del módulo para determinar dónde se encuentra en un paquete. Cuando se utiliza una importación relativa como from .. import foo , los puntos indican que se debe boost la cantidad de niveles en la jerarquía de paquetes. Por ejemplo, si el nombre de su módulo actual es package.subpackage1.moduleX , entonces ..moduleA significaría package.moduleA . Para que una from .. import funcione, el nombre del módulo debe tener al menos tantos puntos como hay en la statement de import .

… son solo familiares en un paquete

Sin embargo, si el nombre de su módulo es __main__ , no se considera que esté en un paquete. Su nombre no tiene puntos y, por lo tanto, no puede utilizar from .. import sentencias de from .. import que contiene. Si intenta hacerlo, obtendrá el error “relativo a la importación en un paquete que no es”.

Los scripts no pueden importar parientes

Lo que probablemente hizo es intentar ejecutar moduleX o similar desde la línea de comandos. Cuando hizo esto, su nombre se estableció en __main__ , lo que significa que las importaciones relativas en él fallarán, porque su nombre no revela que está en un paquete. Tenga en cuenta que esto también ocurrirá si ejecuta Python desde el mismo directorio donde se encuentra un módulo, y luego intenta importar ese módulo porque, como se describió anteriormente, Python encontrará el módulo en el directorio actual “demasiado pronto” sin darse cuenta de que es parte de un paquete.

También recuerde que cuando ejecuta el intérprete interactivo, el “nombre” de esa sesión interactiva siempre es __main__ . Por lo tanto, no puede realizar importaciones relativas directamente desde una sesión interactiva . Las importaciones relativas son solo para uso dentro de los archivos de módulo.

Dos soluciones:

  1. Si realmente desea ejecutar el moduleX directamente, pero aún quiere que se considere parte de un paquete, puede hacer python -m package.subpackage1.moduleX . La -m le dice a Python que la cargue como un módulo, no como el script de nivel superior.

  2. O quizás no desee ejecutar el moduleX , solo desea ejecutar otro script, por ejemplo, myfile.py , que usa funciones dentro del moduleX . Si ese es el caso, ponga myfile.py en otro lugar , no dentro del directorio del package , y ejecútelo. Si dentro de myfile.py haces cosas como from package.moduleA import spam , funcionará bien.

Notas

  • Para cualquiera de estas soluciones, el directorio del paquete ( package en su ejemplo) debe ser accesible desde la ruta de búsqueda del módulo Python ( sys.path ). Si no es así, no podrá utilizar nada en el paquete de forma confiable.

  • Desde Python 2.6, el “nombre” del módulo para propósitos de resolución de paquetes está determinado no solo por sus atributos __name__ sino también por el atributo __package__ . Es por eso que evito usar el símbolo explícito __name__ para referirme al “nombre” del módulo. Como Python 2.6, el “nombre” de un módulo es efectivamente __package__ + '.' + __name__ __package__ + '.' + __name__ , o simplemente __name__ si __package__ es None .)

Esto es realmente un problema dentro de python. El origen de la confusión es que la gente toma erróneamente la importación relativa como una ruta relativa que no lo es.

Por ejemplo, cuando escribes en faa.py :

 from .. import foo 

Esto tiene un significado solo si faa.py fue identificado y cargado por python, durante la ejecución, como parte de un paquete. En ese caso, el nombre del módulo para faa.py sería, por ejemplo, some_packagename.faa . Si el archivo se cargó solo porque está en el directorio actual, cuando se ejecuta python, entonces su nombre no se referirá a ningún paquete y eventualmente la importación relativa fallará.

Una solución simple para referir módulos en el directorio actual, es usar esto:

 if __package__ is None or __package__ == '': # uses current directory visibility import foo else: # uses current package visibility from . import foo 

Aquí hay una receta general, modificada como ejemplo, que estoy usando ahora para tratar con las bibliotecas de Python escritas como paquetes, que contienen archivos interdependientes, donde quiero poder probar partes de ellos por partes. Llamemos a este lib.foo y digamos que necesita acceso a lib.fileA para las funciones f1 y f2 , y lib.fileB para la clase Class3 .

He incluido algunas llamadas print para ayudar a ilustrar cómo funciona esto. En la práctica, querrá eliminarlos (y tal vez también from __future__ import print_function línea de from __future__ import print_function de from __future__ import print_function ).

Este ejemplo en particular es demasiado simple para mostrar cuando realmente necesitamos insertar una entrada en sys.path . (Consulte la respuesta de Lars para un caso en el que la necesitamos, cuando tenemos dos o más niveles de directorios de paquetes, y luego usamos os.path.dirname(os.path.dirname(__file__)) —pero no es así realmente me duele aquí.) También es lo suficientemente seguro como para hacer esto sin la prueba if _i in sys.path . Sin embargo, si cada archivo importado inserta la misma ruta, por ejemplo, si tanto el fileA como el fileA fileB quieren importar utilidades del paquete, esto sys.path la ruta sys.path muchas veces, por lo que es bueno tener la if _i not in sys.path en la if _i not in sys.path de if _i not in sys.path .

 from __future__ import print_function # only when showing how this works if __package__: print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__)) from .fileA import f1, f2 from .fileB import Class3 else: print('Not a package; __name__ is {!r}'.format(__name__)) # these next steps should be used only with care and if needed # (remove the sys.path manipulation for simple cases!) import os, sys _i = os.path.dirname(os.path.abspath(__file__)) if _i not in sys.path: print('inserting {!r} into sys.path'.format(_i)) sys.path.insert(0, _i) else: print('{!r} is already in sys.path'.format(_i)) del _i # clean up global name space from fileA import f1, f2 from fileB import Class3 ... all the code as usual ... if __name__ == '__main__': import doctest, sys ret = doctest.testmod() sys.exit(0 if ret.failed == 0 else 1) 

La idea aquí es esta (y tenga en cuenta que todos estos funcionan de la misma manera en python2.7 y python 3.x):

  1. Si se ejecuta como import lib o from lib import foo como una importación de paquete normal desde un código ordinario, __package es lib y __name__ es lib.foo . Tomamos la primera ruta de código, importando desde .fileA , etc.

  2. Si se ejecuta como python lib/foo.py , __package__ será None y __name__ será __main__ .

    Tomamos el segundo camino del código. El directorio lib ya estará en sys.path por lo que no es necesario agregarlo. Importamos desde fileA , etc.

  3. Si se ejecuta dentro del directorio lib como python foo.py , el comportamiento es el mismo que para el caso 2.

  4. Si se ejecuta dentro del directorio lib como python -m foo , el comportamiento es similar a los casos 2 y 3. Sin embargo, la ruta al directorio lib no se encuentra en sys.path , por lo que lo agregamos antes de importar. Lo mismo se aplica si ejecutamos Python y luego import foo .

    (Ya que . Está en sys.path , no necesitamos agregar la versión absoluta de la ruta aquí. Aquí es donde se encuentra una estructura de anidamiento de paquetes más profunda, desde la que queremos hacer from ..otherlib.fileC import ... , hace una diferencia. Si no está haciendo esto, puede omitir toda la manipulación de sys.path completo.)

Notas

Todavía hay una peculiaridad. Si manejas todo esto desde fuera:

 $ python2 lib.foo 

o:

 $ python3 lib.foo 

el comportamiento depende del contenido de lib/__init__.py . Si eso existe y está vacío , todo está bien:

 Package named 'lib'; __name__ is '__main__' 

Pero si lib/__init__.py importa la routine para que pueda exportar routine.name directamente como lib.name , obtendrá:

 $ python2 lib.foo Package named 'lib'; __name__ is 'lib.foo' Package named 'lib'; __name__ is '__main__' 

Es decir, el módulo se importa dos veces, una vez a través del paquete y luego otra vez como __main__ para que ejecute su código main . Python 3.6 y posteriores advierten sobre esto:

 $ python3 lib.routine Package named 'lib'; __name__ is 'lib.foo' [...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules after import of package 'lib', but prior to execution of 'lib.foo'; this may result in unpredictable behaviour warn(RuntimeWarning(msg)) Package named 'lib'; __name__ is '__main__' 

La advertencia es nueva, pero el comportamiento advertido no lo es. Es parte de lo que algunos llaman la trampa de doble importación . (Para detalles adicionales vea el número 27487 ). Nick Coghlan dice:

Esta siguiente trampa existe en todas las versiones actuales de Python, incluida la 3.3, y se puede resumir en la siguiente guía general: “Nunca agregue un directorio de paquetes, o cualquier directorio dentro de un paquete, directamente a la ruta de Python”.

Tenga en cuenta que aunque violamos esa regla aquí, lo hacemos solo cuando el archivo que se está cargando no se está cargando como parte de un paquete, y nuestra modificación está diseñada específicamente para permitirnos acceder a otros archivos en ese paquete. (Y, como señalé, probablemente no deberíamos hacer esto para los paquetes de un solo nivel). Si quisiéramos ser más limpios, podríamos reescribir esto como, por ejemplo:

  import os, sys _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if _i not in sys.path: sys.path.insert(0, _i) else: _i = None from sub.fileA import f1, f2 from sub.fileB import Class3 if _i: sys.path.remove(_i) del _i 

Es decir, modificamos sys.path tiempo suficiente para lograr nuestras importaciones, luego lo devolvemos a la forma en que estaba (eliminando una copia de _i si y solo si agregamos una copia de _i ).

Así que después de hablar sobre esto junto con muchos otros, encontré una nota publicada por Dorian B en este artículo que resolvía el problema específico que tenía donde desarrollar módulos y clases para usar con un servicio web, pero también quiero estar capaz de probarlos mientras estoy codificando, usando las facilidades de depuración en PyCharm. Para ejecutar pruebas en una clase autónoma, incluiría lo siguiente al final de mi archivo de clase:

 if __name__ == '__main__': # run test code here... 

pero si quisiera importar otras clases o módulos en la misma carpeta, tendría que cambiar todas mis declaraciones de importación de la notación relativa a referencias locales (es decir, eliminar el punto (.)) Pero después de leer la sugerencia de Dorian, intenté su ‘ one-liner ‘y funcionó! ¡Ahora puedo probar en PyCharm y dejar mi código de prueba en su lugar cuando uso la clase en otra clase en prueba, o cuando lo uso en mi servicio web!

 # import any site-lib modules first, then... import sys parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__'] if __name__ == '__main__' or parent_module.__name__ == '__main__': from codex import Codex # these are in same folder as module under test! from dblogger import DbLogger else: from .codex import Codex from .dblogger import DbLogger 

La instrucción if comprueba si estamos ejecutando este módulo como principal o si se está utilizando en otro módulo que se está probando como principal . Quizás esto sea obvio, pero ofrezco esta nota aquí en caso de que alguien más frustrado por los problemas de importación relativos mencionados arriba pueda hacer uso de ella.

Aquí hay una solución que no recomendaría, pero podría ser útil en algunas situaciones donde los módulos simplemente no se generaron:

 import os import sys parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) sys.path.append(parent_dir_name + "/your_dir") import your_script your_script.a_function() 

__name__ cambia dependiendo de si el código en cuestión se ejecuta en el espacio de nombres global o como parte de un módulo importado.

Si el código no se está ejecutando en el espacio global, __name__ será el nombre del módulo. Si se está ejecutando en el espacio de nombres global, por ejemplo, si lo escribe en una consola, o ejecuta el módulo como un script usando python.exe yourscriptnamehere.py , __name__ convierte en "__main__" .

Verá una gran cantidad de código de Python if __name__ == '__main__' se usa para probar si el código se está ejecutando desde el espacio de nombres global, lo que le permite tener un módulo que se duplica como un script.

¿Intentaste hacer estas importaciones desde la consola?

Tuve un problema similar en el que no quería cambiar la ruta de búsqueda del módulo Python y necesitaba cargar un módulo relativamente desde una secuencia de comandos (a pesar de que “las secuencias de comandos no se pueden importar en relación con todas”, como BrenBarn explicó muy bien).

Así que utilicé el siguiente hack. Desafortunadamente, se basa en el módulo imp que se ha desaprobado desde la versión 3.4 para ser eliminado en favor de importlib . (¿Es esto posible con importlib , también? No lo sé.) Sin embargo, el truco funciona por ahora.

Ejemplo para acceder a miembros de moduleX en subpackage1 desde un script que reside en la carpeta subpackage2 :

 #!/usr/bin/env python3 import inspect import imp import os def get_script_dir(follow_symlinks=True): """ Return directory of code defining this very function. Should work from a module as well as from a script. """ script_path = inspect.getabsfile(get_script_dir) if follow_symlinks: script_path = os.path.realpath(script_path) return os.path.dirname(script_path) # loading the module (hack, relying on deprecated imp-module) PARENT_PATH = os.path.dirname(get_script_dir()) (x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1']) module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc) # importing a function and a value function = module_x.my_function VALUE = module_x.MY_CONST 

Un enfoque más limpio parece ser modificar el sys.path utilizado para cargar módulos como lo menciona Federico.

 #!/usr/bin/env python3 if __name__ == '__main__' and __package__ is None: from os import sys, path # __file__ should be defined in this case PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__))) sys.path.append(PARENT_DIR) from subpackage1.moduleX import * 

Para que Python no me devuelva “Intento de importación relativa en un no paquete”. paquete/

init .py subpackage1 / init .py moduleX.py moduleY.py subpackage2 / init .py moduleZ.py moduleA.py

Este error se produce solo si está aplicando una importación relativa al archivo principal. Por ejemplo, el archivo principal ya devuelve main después de codificar “imprimir ( nombre )” en el módulo A.py. Así ESTO este archivo ya es principal, no puede devolver ningún paquete principal más adelante. se requieren importaciones relativas en los archivos de los paquetes subpaquete1 y subpaquete2, puede usar “..” para referirse al módulo o al directorio principal. Pero el principal es si el paquete de nivel superior ya no puede ir más allá de ese directorio principal (paquete). Los archivos en los que está aplicando la importación relativa a los padres solo pueden trabajar con la aplicación de importación absoluta. Si va a utilizar ABSOLUTE IMPORT IN PARENT PACKAGE, NO ERROR aparecerá porque Python sabe quién está en el nivel superior del paquete, incluso si su archivo está en subpaquetes debido al concepto de PYTHON PATH que define el nivel superior del proyecto.

Las importaciones relativas utilizan el atributo de nombre de un módulo para determinar la posición de ese módulo en la jerarquía de paquetes. Si el nombre del módulo no contiene ninguna información de paquete (por ejemplo, está configurado como ‘principal’), las importaciones relativas se resuelven como si el módulo fuera un módulo de nivel superior, independientemente de dónde se encuentre el módulo en el sistema de archivos.

Escribió un pequeño paquete de Python a PyPi que podría ayudar a los espectadores de esta pregunta. El paquete actúa como solución alternativa si uno desea poder ejecutar archivos de Python que contengan importaciones que contengan paquetes de nivel superior desde un paquete / proyecto sin estar directamente en el directorio del archivo de importación. https://pypi.org/project/import-anywhere/