Импорт класса в подпакет Python импортирует больше, чем запрошено

Обзор

Я запускаю некоторые научные симуляции и хочу обработать полученные данные в Python. Моделирование создает пользовательский тип данных, который не используется за пределами цепочки программ, созданных авторами моделирования, поэтому, к сожалению, мне нужно то, что они мне предоставляют.

Они хотят, чтобы я установил два файла:

  • Модуль с именем sdds.py, который определяет класс, предоставляющий все пользовательские функции и две демонстрации.
  • Скомпилированный модуль с именем sddsdatamodule.so, который предоставляет только вспомогательные функции для sdds.py.

(Мне кажется странным, что они предлагают мне два модуля, которые настолько неразрывно связаны, что мне это не кажется хорошей практикой кодирования, но использование их кода, вероятно, лучше, чем переписывать все с нуля.) Я бы предпочел не делать этого. чтобы установить их прямо на моем пути, бок о бок. Они созданы одной компанией и предназначены для совместного выполнения одной конкретной задачи: доступа к файлам типа SDDS и манипулирования ими.

Вот я и подумал, что положу их в пакет. Я мог бы установить его на моем пути, он был бы автономным, и я мог бы легко найти и удалить или обновить модули из одного места. Тогда я мог бы спрятать их не-Pythonic-решение в более-Pythonic-пакете без существенного переписывания вещей. Кажется элегантным.

Подробности

Пакет, который я на самом деле использую, находится здесь:

http://www.aps.anl.gov/Accelerator_Systems_Division/Accelerator_Operations_Physics/software.shtml#PythonBinaries

К сожалению, сейчас они поддерживают только Windows и Mac OS X. Компиляция исходного кода довольно обременительна, и, по-видимому, у них нет существенных запросов для Linux/Unix. У меня Mac, так что, к счастью, для меня это не проблема.

Итак, мое дерево каталогов выглядит так:

SDDSPython/                   My toplevel package
    __init__.py               Designed to only import the SDDS class
    sdds.py                   Defines SDDS class and two demo methods
    sddsdatamodule.so         Defines sddsdata module used by SDDS class.

Мой файл __init__.py буквально содержит только это:

from sdds import SDDS

Файл sdds.py содержит определение класса и два демонстрационных определения. Единственный другой код в файле sdds.py:

import sddsdata, sys, time

class SDDS:
    (lots of code here)

def demo(output):
    (lots of code here)

def demo2(output):
    (lots of code here)

Затем я могу импортировать SDDSPython и проверить, используя dir:

>>> import SDDSPython
>>> dir(SDDSPython)
['SDDS', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'sdds', 'sddsdata']

Итак, теперь я могу получить доступ к классу SDDS через SDDSPython.SDDS

Вопрос

Как, черт возьми, SDDSPython.sdds и SDDSPython.sddsdata загрузились в пространство имен SDDSPython??

>>> SDDSPython.sdds
<module 'SDDSPython.sdds' from 'SDDSPython/sdds.pyc'>
>>> SDDSPython.sddsdata
<module 'SDDSPython.sddsdata' from 'SDDSPython/sddsdatamodule.so'>

Я думал, что, создавая файл __init__.py, я специально исключал модули sdds и sddsdata из загрузки в пространство имен SDDSPython. Что здесь происходит? Я могу только предположить, что это происходит из-за чего-то в файле sddsdatamodule.so? Но как модуль может так влиять на пространство имен своего родителя? Я довольно потерян, и я не знаю, с чего начать. Я просмотрел код C, но не вижу ничего подозрительного. Честно говоря, я, вероятно, не знаю, как может выглядеть что-то подозрительное, я, вероятно, недостаточно знаком с программированием расширений C для Python.


person Joel    schedule 29.01.2015    source источник


Ответы (2)


Любопытный вопрос - я провел для вас небольшое расследование, используя аналогичный тестовый пример.

XML/
    __init__.py       -from indent import XMLIndentGenerator
    indent.py         -contains class XMLIndentGenerator, and Xml
    Sink.py      

Похоже, что при импорте класса из модуля, даже если вы импортируете только часть, весь модуль доступен так, как вы описали, а именно:

>>>import XML
>>>XML.indent
<module 'XML.indent' from 'XML\indent.py'>
>>>XML.indent.Xml   #did not include this in the from
<class 'XML.indent.Xml'>
>>>XML.Sink
Traceback (most recent call last):
AttributeError:yadayada no attribute 'Sink'

Это ожидаемо, так как я не import Sink в __init__.py.....НО!

Я добавил строку в indent.py:

import Sink

class XMLIndentGenerator(XMLGenerator):
    (code)

Теперь, поскольку этот класс импортирует модуль, содержащийся в пакете XML, если я сделаю:

>>>import XML
>>>XML.Sink
<module 'XML.Sink' from 'XML\Sink.pyc'>

Итак, получается, что поскольку ваш импортированный модуль sdds также импортирует sddsdata, вы можете получить к нему доступ. Это отвечает на часть «Как» вашего вопроса, но «почему» это так, я уверен, что где-то в документах есть ответ :)

Я надеюсь, что это поможет - я буквально делал это, когда печатал ответ! Поучительный опыт и для меня.

person tenwest    schedule 29.01.2015
comment
Спасибо за кропотливую работу над этим! Теперь я вижу, как это происходит, и я действительно хотел бы знать, почему. - person Joel; 29.01.2015

Это происходит потому, что импорт python работает не так, как вы думаете. Они работают следующим образом:

  • механизм импорта ищет файл, который должен быть модулем, запрошенным из импорта
  • создается экземпляр types.ModuleType, несколько атрибутов в нем устанавливаются для соответствующего файла (__file__, __name__ и т. д.), и этот объект вставляется в sys.modules под полным именем модуля, которое он должен иметь.
  • если это импорт подмодуля (т. е. sdds.py, который является подмодулем в SDDSPython), вновь созданный модуль прикрепляется в качестве атрибута к существующему модулю python родительского пакета.
  • файл «выполняется» с этим модулем в качестве его глобальной области; все имена, определенные в этом файле, отображаются как атрибуты модуля.
  • в случае импорта from атрибут из модуля может быть возвращен сценарию импорта.

Это означает, что если я импортирую модуль (скажем, foo.py), который имеет в качестве источника только:

import bar

затем в foo есть глобальная переменная с именем bar, и я могу получить к ней доступ как foo.bar.

В python нет возможности «выполнить только ту часть этого скрипта python, которую я хочу использовать прямо сейчас». Все работает.

person SingleNegationElimination    schedule 29.01.2015
comment
Верно, но в моем случае я сделал from sdds import SDDS в __init__.py. Если бы вместо этого я сделал import sdds, я бы ожидал, что dir(SDDSPython) вернет только sdds, но dir(SDDSPython.sdds) должен включать sddsdata, sys и time. from sdds import SDDS должен, как я думал, импортировать только класс из подмодуля sdds. Но почему-то это ведет себя так, как будто я тоже сделал import sdds, sddsdata. Этот from sdds import SDDS не должен импортировать подмодуль, верно? - person Joel; 29.01.2015
comment
SDDSPython — это пакет на вашем пути, а sdds в нем. когда вы импортируете sdds, независимо от того, как вы это сделали, пока python находит его как подмодуль, этот подмодуль безоговорочно добавляется в качестве атрибута к содержащему его пакету. это делается механизмом импорта, независимо от того, что еще делает ваш __init__.py. - person SingleNegationElimination; 29.01.2015
comment
Оооооооооооооооооооооооооооооооооооооо. Есть ли где-нибудь документация по этому поводу? - person Joel; 30.01.2015