Python: ¿por qué importar un paquete a veces otorga acceso a los módulos que se encuentran debajo pero a veces no?

El mecanismo de importación de Python siempre es un mito para mí. A veces, importar un paquete puede otorgar acceso a los módulos que se encuentran debajo de él. Por ejemplo,

import urllib urllib.parse.unquote 

da

  

lo que muestra que las funciones son accesibles incluso con solo el paquete (es decir, urllib en este caso) importado pero no hasta el archivo del módulo. Esto se hace dentro del cuaderno de Jupyter.

Pero cuando hago lo mismo en la terminal.

 >>> import urllib >>> urllib.parse.unquote Traceback (most recent call last): File "", line 1, in  AttributeError: module 'urllib' has no attribute 'parse' 

Ambas versiones de Python son 3.6.1.

¿Qué marca la diferencia y cuál es la buena práctica?

EDITAR para combinar las respuestas de @ user2357112 y @Tomoki.

Directamente desde @ user2357112

Para que un acceso a urllib.parse funcione, se deben cumplir las dos condiciones siguientes:

El objeto del módulo urllib debe estar vinculado al nombre de urllib , ya sea en el ámbito local, global o en algún ámbito que lo urllib . El submódulo urllib.parse debe haberse inicializado y vinculado al atributo parse del objeto del módulo urllib . Un urllib importación en el ámbito local o global actual (o cualquier ámbito adjunto) satisface la primera condición.

Un import urllib.parse ejecutado en cualquier parte del progtwig satisface la segunda condición, ya que carga el submódulo y lo enlaza al atributo parse en el objeto del módulo urllib , y solo hay un objeto del módulo urllib para todo el progtwig.

En los entornos donde urllib.parse era accesible después de un simple urllib importación, algún otro código debe haber cargado urllib.parse , causando que lo vea.

La evidencia es proporcionada por @Tomoki

 Test: "import IPython" └─IPython:┐ ┌────┘ ├──"from core.application import Application" │ └──IPython.core.application: "from IPython.core import release, crashhandler" │ └──IPython.core.crashhandler: "from IPython.core import ultratb" │ └──IPython.core.ultratb: "import pydoc" │ └──pydoc: "import urllib.parse" └──"from terminal.embed import embed" └──IPython.terminal.embed:┐ ┌───────────┘ ├──"from IPython.core import magic_arguments" │ └──IPython.core.magic_arguments: "from IPython.utils.text import dedent" │ └──IPython.utils.text: "from pathlib import Path" │ └──pathlib: "from urllib.parse import quote_from_bytes" ├──"from IPython.core.magic import Magics, magics_class, line_magic" │ └──IPython.core.magic: "from IPython.core import oinspect" │ └──IPython.core.oinspect: "from IPython.core import page" │ └──IPython.core.page: "from IPython.core.display import display" │ └──IPython.core.display: "import mimetypes" │ └──mimetypes: "import urllib.parse" └──"from IPython.terminal.interactiveshell import TerminalInteractiveShell" └──pygments.plugin: "import pkg_resources" └──pkg_resources: "import email.parser" └──email.parser: "from email.feedparser import FeedParser, BytesFeedParser" └──email.feedparser: "from email._policybase import compat32" └──email._policybase: "from email.utils import _has_surrogates" └──email.utils: "import urllib.parse" 

La última línea de hecho toca urllib.parse .

Otra evidencia

import scipy no proporciona acceso a scipy.stats.norm en una terminal o en una computadora portátil Jupyter porque ningún entorno afecta a scipy.stats .

¿Cuál es la buena práctica?

Podemos concluir desde arriba que no solo es una buena práctica, sino que de hecho es un requisito para ## importar todos los niveles del módulo ##.

“Importar siempre a nivel de archivo (módulo) para garantizar el acceso”

¡Gracias por todas las respuestas!

Para que un acceso a urllib.parse funcione, se deben cumplir las dos condiciones siguientes:

  1. El objeto del módulo urllib debe estar vinculado al nombre de urllib , ya sea en el ámbito local, global o en algún ámbito que lo urllib .
  2. El submódulo urllib.parse debe haberse inicializado y vinculado al atributo parse del objeto del módulo urllib .

Un import urllib en el ámbito local o global actual (o cualquier ámbito adjunto) satisface la primera condición.

Un import urllib.parse ejecutado en cualquier parte del progtwig satisface la segunda condición, ya que carga el submódulo y lo enlaza al atributo parse en el objeto del módulo urllib , y solo hay un objeto del módulo urllib para todo el progtwig.

En los entornos donde urllib.parse era accesible después de un simple import urllib , algún otro código debe haber cargado urllib.parse , causando que lo vea.

Como user2357112 dijo que se está importando; Creo que estos son los módulos y declaraciones específicas.

 Test: "import IPython" └─IPython:┐ ┌────┘ ├──"from core.application import Application" │ └──IPython.core.application: "from IPython.core import release, crashhandler" │ └──IPython.core.crashhandler: "from IPython.core import ultratb" │ └──IPython.core.ultratb: "import pydoc" │ └──pydoc: "import urllib.parse" └──"from terminal.embed import embed" └──IPython.terminal.embed:┐ ┌───────────┘ ├──"from IPython.core import magic_arguments" │ └──IPython.core.magic_arguments: "from IPython.utils.text import dedent" │ └──IPython.utils.text: "from pathlib import Path" │ └──pathlib: "from urllib.parse import quote_from_bytes" ├──"from IPython.core.magic import Magics, magics_class, line_magic" │ └──IPython.core.magic: "from IPython.core import oinspect" │ └──IPython.core.oinspect: "from IPython.core import page" │ └──IPython.core.page: "from IPython.core.display import display" │ └──IPython.core.display: "import mimetypes" │ └──mimetypes: "import urllib.parse" └──"from IPython.terminal.interactiveshell import TerminalInteractiveShell" └──pygments.plugin: "import pkg_resources" └──pkg_resources: "import email.parser" └──email.parser: "from email.feedparser import FeedParser, BytesFeedParser" └──email.feedparser: "from email._policybase import compat32" └──email._policybase: "from email.utils import _has_surrogates" └──email.utils: "import urllib.parse" 

Python 3 no carga los módulos auxiliares para urllib automáticamente. ( https://docs.python.org/2/library/urllib.html )

“Nota: el módulo urllib se ha dividido en partes y se le ha cambiado el nombre en Python 3 a urllib.request, urllib.parse y urllib.error. La herramienta 2to3 adaptará automáticamente las importaciones al convertir sus fonts a Python 3.”

“Nota urllib también expone ciertas funciones de utilidad como splittype, splithost y otras analizando la URL en varios componentes. Pero se recomienda usar urlparse para analizar las URL en lugar de usar estas funciones directamente. Python 3 no expone estas funciones de ayuda desde el módulo urllib.parse . ”


Si intenta consultar el directorio de espacio de nombres urllib (urllib) después de la importación, no hay submódulos. Después de escribir urllib.parse.unquote y obtener el error, se cargan los módulos de ayuda de urllib. (Lo digo en serio, eso suena loco e incorrecto, todas las cosas no son Python, “él es un n00b”, solo inténtalo). Puedes verlas en el espacio de nombres a través de dir (urllib) y puedes consultarlas para usarlas como si fueran todas. cargado inicialmente. A continuación, obtendrá el retorno de objeto de función.

Python 3.5.2 (default, Aug 18 2017, 17:48:00) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information.

>>> import urllib

>>> urllib.parse.unquote

Traceback (most recent call last): File "", line 1, in AttributeError: module 'urllib' has no attribute 'parse'

>>> urllib.parse.unquote


En el módulo seis, hay builtins.module (builtins.object)

  Module_six_moves_urllib _LazyDescr(builtins.object) MovedAttribute MovedModule _LazyModule(builtins.module) Module_six_moves_urllib_error Module_six_moves_urllib_parse Module_six_moves_urllib_request Module_six_moves_urllib_response Module_six_moves_urllib_robotparser 

Hay documentación adicional (por supuesto) tal como

class Module_six_moves_urllib (builtins.module) “| Cree un espacio de nombres six.moves.urllib que se parezca al espacio de nombres de Python 3”

Sospecho que el terminal no invoca el builtin para cargar los módulos auxiliares de forma automática como lo hace Jupyter, aunque, sinceramente, no lo sé.

Editar para agregar: Importar urllib, importar seis e invocarlo [incluso help (“six”)] cargará los módulos de análisis, solicitud, respuesta, robotparser al espacio de nombres de urllib. Además, la importación de urllib y las llamadas de ayuda en él cargarán el análisis pero no los otros módulos en el espacio de nombres. Es posible que Jupyter esté cargando la ayuda de forma proactiva, lo que hace que solo cargue el módulo de análisis. No tengo instalado IPython / conda / Jupyter, así que no puedo ayudar a probar.