import urllib.parse не работает, когда Python запускается из командной строки

Я наблюдал следующее поведение в python 3.4.2, и я не могу его объяснить. Надеюсь, кто-нибудь сможет пролить свет на этот вопрос:

В IPython:

In [129]: import urllib

In [130]: print(urllib.parse)
<module 'urllib.parse' from '/Users/ashwin/.pyenv/versions/3.4.2/lib/python3.4/urllib/parse.py'>

Я импортировал модуль и распечатал один из его атрибутов. Все работает так, как ожидалось. Пока что жизнь удалась.

Теперь я делаю то же самое из командной строки:

$ python -c 'import urllib; print(urllib.parse)'  
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: 'module' object has no attribute 'parse'

Чего-чего?! это не так, как это должно работать.
Хорошо, может быть, это поведение всего python; возможно, модули не сразу импортируются при использовании флага -c. Попробуем другой модуль:

$ python -c 'import datetime; print(datetime.datetime)'
<class 'datetime.datetime'>

Какая?! Как это работает для datetime, а не для urllib? Я использую одну и ту же версию Python в обоих местах (3.4.2)

У кого-нибудь есть мысли по этому поводу?

ИЗМЕНИТЬ:

По одному из комментариев:

$ which -a ipython
/Users/ashwin/.pyenv/shims/ipython
/Library/Frameworks/Python.framework/Versions/2.7/bin/ipython
/usr/local/bin/ipython
/usr/local/bin/ipython

А также

$ which -a python
/Users/ashwin/.pyenv/shims/python
/Library/Frameworks/Python.framework/Versions/2.7/bin/python
/usr/bin/python
/usr/bin/python

person inspectorG4dget    schedule 19.10.2015    source источник
comment
вы можете добавить вывод which -a ipython и which -a python   -  person cel    schedule 19.10.2015
comment
@cel: я не понимаю, как это поможет, но я добавил это   -  person inspectorG4dget    schedule 19.10.2015
comment
Итак, python -m IPython -c 'import datetime; print(datetime.datetime)' и python -c 'import datetime; print(datetime.datetime)' дают разные результаты?   -  person cel    schedule 19.10.2015
comment
python -c с datetime дает другой результат, чем python -c с urllib, в том смысле, что первый выдает AttributeError, а второй нет. Тем не менее, код с urllib не вызывает ошибок при использовании в интерактивном IPython.   -  person inspectorG4dget    schedule 19.10.2015
comment
В основном python -c 'import urllib; print(urllib.parse)' и python -m IPython -c 'import urllib; print(urllib.parse)' дают разные результаты. Я тоже это вижу. Действительно, очень интересное наблюдение. Я бы добавил обычный тег python для повышения видимости.   -  person cel    schedule 19.10.2015
comment
Вы используете одну и ту же версию Python для обоих примеров? Используя python2 и python3 (эталонные реализации), я получаю ту же ошибку из командной строки и из интерпретатора. Может быть, библиотека urllib не та?   -  person skyking    schedule 19.10.2015
comment
@скайкинг: I'm using the same version of python in both places (3.4.2)   -  person inspectorG4dget    schedule 19.10.2015


Ответы (2)


Когда вы запускаете import urllib, он создает объект модуля urllib (который на самом деле является package) без импорта его подмодулей (parse, request и т. д.).

Вам нужно, чтобы объект родительского модуля (urllib) находился в вашем пространстве имен, если вы хотите получить доступ к его подмодулю, используя доступ к атрибуту. Кроме того, этот подмодуль должен уже быть загружен (импортирован). Из документации:

если пакет spam имеет подмодуль foo, после импорта spam.foo spam будет иметь атрибут foo, привязанный к подмодулю. [...] Инвариантное удержание состоит в том, что если у вас есть sys.modules['spam'] и sys.modules['spam.foo'] (как и после импорта выше), последний должен отображаться как атрибут foo первого.

Существует только один экземпляр каждого модуля, поэтому любые изменения, внесенные в объект модуля urllib (хранящийся в sys.modules['urllib']), отражаются везде.

Вы не импортируете urllib.parse, а IPython импортирует. Чтобы доказать это, я создам файл запуска:

import urllib
print('Running the startup file: ', end='')
try:
    # After importing  'urllib.parse' ANYWHERE,
    # 'urllib' will have the 'parse' attribute.
    # You could also do "import sys; sys.modules['urllib'].parse"
    urllib.parse
except AttributeError:
    print("urllib.parse hasn't been imported yet")
else:
    print('urllib.parse has already been imported')
print('Exiting the startup file.')

и запустите ipython

vaultah@base:~$ ipython
Running urllib/parse.py
Running the startup file: urllib.parse has already been imported
Exiting the startup file.
Python 3.6.0a0 (default:089146b8ccc6, Sep 25 2015, 14:16:56) 
Type "copyright", "credits" or "license" for more information.

IPython 4.0.0 -- An enhanced Interactive Python.

Это побочный эффект импорта pydoc во время запуска IPython (which ipython — это /usr/local/bin/ipython):

/usr/local/bin/ipython, line 7:
  from IPython import start_ipython
/usr/local/lib/python3.6/site-packages/IPython/__init__.py, line 47:
  from .core.application import Application
/usr/local/lib/python3.6/site-packages/IPython/core/application.py, line 24:
  from IPython.core import release, crashhandler
/usr/local/lib/python3.6/site-packages/IPython/core/crashhandler.py, line 28:
  from IPython.core import ultratb
/usr/local/lib/python3.6/site-packages/IPython/core/ultratb.py, line 90:
  import pydoc
/usr/local/lib/python3.6/pydoc.py, line 68:
  import urllib.parse

Это объясняет, почему приведенный ниже код не работает — вы импортируете только urllib, а urllib.parse ничего не импортирует:

$ python -c 'import urllib; print(urllib.parse)'

С другой стороны, следующая команда работает, поскольку datetime.datetime не является модулем. Это класс, который импортируется во время import datetime.

$ python -c 'import datetime; print(datetime.datetime)'
person vaultah    schedule 25.10.2015
comment
Но если я попробую pydoc в IPython, я получу NameError. Разве это не означает, что pydoc не был импортирован IPython при запуске? - person inspectorG4dget; 25.10.2015
comment
@spectorG4dget, он был импортирован, вы можете проверить, находится ли он уже в sys.modules: 'pydoc' in sys.modules возвращает True. Вам просто нужно импортировать его в текущее пространство имен. - person vaultah; 25.10.2015
comment
Хорошо, но urllib отсутствует в текущем пространстве имен ИЛИ в sys.modules в IPython. Так что я до сих пор не понимаю, как urllib.parse автоматически существует в пространстве имен. - person inspectorG4dget; 25.10.2015
comment
@ InspectorG4dget Я не уверен ... Запуск [x for x in sys.modules if 'urllib' in x] из IPython дает ['urllib.parse', 'urllib']. При выполнении import urllib.parse Python проверит, можно ли пропустить создание объекта модуля, заглянув в sys.modules. Когда объект модуля будет готов, он будет помещен в локальное пространство имен под выбранным вами именем (например, с помощью оператора import). Обратите внимание, что пространства имен не являются общими. - person vaultah; 25.10.2015
comment
Но я не пробую import urllib.parse в IPython. Я пробую import urllib, а затем вызываю urllib.parse.unquote без дальнейшего импорта. - person inspectorG4dget; 25.10.2015

urllib.parse доступен начиная с Python 3. Я думаю, вам может понадобиться import urllib.parse, а не import urllib. Не уверен, что (когда) импорт подмодуля является неявным.

Я предполагаю, что IPython импортирует urllib.parse при запуске, и поэтому он доступен.

parse — это модуль, а не атрибут:

Python 3.4.2 (default, Oct 15 2014, 22:01:37)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib
>>> urllib.parse
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'parse'
>>> import urllib.parse
>>> urllib.parse
<module 'urllib.parse' from '/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/urllib/parse.py'>
person Mikko Ohtamaa    schedule 19.10.2015
comment
Ваш анализ кажется неверным, так как я использую python3.4.2, даже когда я вызываю python из командной строки. Как видно из моих путей, первая запись относится к pyenv, которая в настоящее время указывает на v3.4.2. - person inspectorG4dget; 19.10.2015
comment
Попробуйте import urllib.parse вместо import urllib, как было предложено. - person Mikko Ohtamaa; 19.10.2015
comment
Также спасибо за разъяснение пути. Я обновил вопрос и удалил неверные биты. - person Mikko Ohtamaa; 19.10.2015
comment
IPython не импортирует urllib.parse при запуске. Я знаю это, потому что я сам настроил импорт запуска IPython. Но вы правы в том, что запуск этого в интерактивной оболочке также не работает должным образом. У меня такое ощущение, что IPython решает эту проблему, создавая невидимый import urllib.parse, чтобы избежать этой ошибки. - person inspectorG4dget; 19.10.2015