Подтвердить что ты не робот

Как организовать несколько файлов python в одном модуле, не ведя себя как пакет?

Есть ли способ использовать __init__.py для организации нескольких файлов в модуле?

Причина: Модули проще в использовании, чем пакеты, потому что в них не так много слоев пространства имен.

Обычно это делает пакет, который я получаю. Проблема заключается в пакете, "import thepackage" дает мне пустое пространство имен. Затем пользователи должны либо использовать "из импорта пакета" (нахмурившись), либо точно знать, что содержится, и вручную вытащить его в удобное пространство имен.

Я хочу, чтобы пользователь "импортировал пакет" и имел красивые чистые пространства имен, которые выглядят так, раскрывая функции и классы, относящиеся к проекту для использования.

current_module
\
  doit_tools/
  \
   - (class) _hidden_resource_pool
   - (class) JobInfo
   - (class) CachedLookup
   - (class) ThreadedWorker
   - (Fn) util_a
   - (Fn) util_b
   - (Fn) gather_stuff
   - (Fn) analyze_stuff

Задача сопровождающего заключается в том, чтобы избежать указания одного и того же имени в разных файлах, что должно быть легким, когда проект мал, как мой.

Было бы неплохо, если бы люди могли сделать from doit_stuff import JobInfo и получить этот класс, а не модуль, содержащий класс.

Это легко, если весь мой код находится в одном гигантском файле, но мне нравится организовывать, когда все начинает становиться большим. Что у меня на диске выглядит примерно так:

place_in_my_python_path/
  doit_tools/
    __init__.py
    JobInfo.py
      - class JobInfo:
    NetworkAccessors.py
      - class _hidden_resource_pool:
      - class CachedLookup:
      - class ThreadedWorker:
    utility_functions.py
      - def util_a()
      - def util_b()
    data_functions.py
      - def gather_stuff()
      - def analyze_stuff()

Я разделяю их только так, чтобы мои файлы не были огромными и не поддающимися обработке. Все они связаны друг с другом, хотя кто-то (возможно, я) может захотеть использовать классы самостоятельно, не импортируя все.

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

Если я не использует __init__.py, я ничего не могу импортировать, потому что Python не спускается в папку из sys.path.

Если я использовать пустой __init__.py, когда я import doit_tools это пустое пространство имен, в котором ничего нет. Ни один из моих файлов не импортирован, что затрудняет его использование.

Если я перечислил подмодули в __all__, я могу использовать синтаксис (нахмурился??) from thing import *, но все мои классы снова отстают от лишних барьеров пространства имен. Пользователь должен (1) знать, что они должны использовать from x import * вместо import x, (2) вручную перетасовывать классы, пока они не смогут разумно подчиняться ограничениям ширины линии.

Если я добавить операторы from thatfile import X в __init__.py, я приближаюсь, но у меня есть конфликты пространства имен (?) и дополнительные пространства имен для вещей, которые я не хотел быть там. В приведенном ниже примере вы увидите, что:

  • Класс JobInfo перезаписывал объект модуля с именем JobInfo, потому что их имена были одинаковыми. Как-то Python может это понять, потому что JobInfo имеет тип <class 'doit_tools.JobInfo.JobInfo'>. (doit_tools.JobInfo - это класс, но doit_tools.JobInfo.JobInfo - это тот же класс... это запутано и кажется очень плохим, но, похоже, ничего не сломает.)
  • Каждое имя файла пробилось в пространство имен doit_tools, что затрудняет просмотр, если кто-то смотрит на содержимое модуля. Я хочу, чтобы doit_tools.utility_functions.py удерживал некоторый код, а не определял новое пространство имен.

.

current_module
\
  doit_tools/
  \
   - (module) JobInfo
      \
       - (class) JobInfo
   - (class) JobInfo
   - (module) NetworkAccessors
      \
       - (class) CachedLookup
       - (class) ThreadedWorker
   - (class) CachedLookup
   - (class) ThreadedWorker
   - (module) utility_functions
      \
       - (Fn) util_a
       - (Fn) util_b
   - (Fn) util_a
   - (Fn) util_b
   - (module) data_functions
      \
       - (Fn) gather_stuff
       - (Fn) analyze_stuff
   - (Fn) gather_stuff
   - (Fn) analyze_stuff

Также кто-то, импортирующий только класс абстракции данных, получит нечто иное, чем они ожидают, когда они "из doit_tools импортируют JobInfo":

current_namespace
\
 JobInfo (module)
  \
   -JobInfo (class)

instead of:

current_namespace
\
 - JobInfo (class)

Итак, это просто неправильный способ организовать код Python? Если нет, то какой правильный способ разделить связанный код вверх, но все же собрать его модульным способом?

Возможно, лучший сценарий заключается в том, что выполнение 'from doit_tools import JobInfo' немного сбивает с толку для кого-то, использующего пакет?

Может быть, файл python с именем "api", чтобы люди, использующие код, выполняли следующие действия:

import doit_tools.api
from doit_tools.api import JobInfo

============================================р >

Примеры в ответ на комментарии:

Возьмите следующее содержимое пакета, внутри папки 'foo', которая находится в пути python.

foo/__init__.py

__all__ = ['doit','dataholder','getSomeStuff','hold_more_data','SpecialCase']
from another_class import doit
from another_class import dataholder
from descriptive_name import getSomeStuff
from descriptive_name import hold_more_data
from specialcase import SpecialCase

foo/specialcase.py

class SpecialCase:
    pass

foo/more.py

def getSomeStuff():
    pass

class hold_more_data(object):
    pass

foo/stuff.py

def doit():
    print "I'm a function."

class dataholder(object):
    pass

Сделайте это:

>>> import foo
>>> for thing in dir(foo): print thing
... 
SpecialCase
__builtins__
__doc__
__file__
__name__
__package__
__path__
another_class
dataholder
descriptive_name
doit
getSomeStuff
hold_more_data
specialcase

another_class и descriptive_name существуют беспорядочные вещи, а также дополнительные копии, например. doit() под их пространствами имен.

Если у меня есть класс с именем Data внутри файла с именем Data.py, когда я делаю "из Data Import Data", тогда я получаю конфликт пространства имен, потому что Data - это класс в текущем пространстве имен, который находится внутри модуля Data, так или иначе также в текущем пространстве имен. (Но Python, похоже, справляется с этим.)

4b9b3361

Ответ 1

Вы можете сделать это, но это не очень хорошая идея, и вы боретесь с тем, как должны работать модули/пакеты Python. Импортируя соответствующие имена в __init__.py, вы можете сделать их доступными в пространстве имен пакетов. Удалив имена модулей, вы можете сделать их недоступными. (Для чего их нужно удалить, см. этот вопрос). Таким образом, вы можете приблизиться к тому, что хотите, с чем-то вроде этого (в __init__.py):

from another_class import doit
from another_class import dataholder
from descriptive_name import getSomeStuff
from descriptive_name import hold_more_data
del another_class, descriptive_name
__all__ = ['doit', 'dataholder', 'getSomeStuff', 'hold_more_data']

Однако это приведет к разрыву последующих попыток import package.another_class. В общем случае вы не можете импортировать что-либо из package.module, не делая package.module доступным как импортируемую ссылку на этот модуль (хотя с помощью __all__ вы можете заблокировать from package import module).

В более общем плане, разбивая код по классу/функции, вы работаете против системы пакета/модуля Python. Модуль Python обычно должен содержать материал, который вы хотите импортировать в качестве единицы. Необязательно импортировать компоненты субмодуля непосредственно в пространство имен пакетов верхнего уровня для удобства, но наоборот - пытаясь скрыть подмодули и разрешить доступ к их содержимому только через пространство имен пакетов верхнего уровня --- собирается привести к проблемам. Кроме того, ничего не получается, пытаясь "очистить" пространство имен пакетов модулей. Эти модули должны находиться в пространстве имен пакетов; что там, где они принадлежат.

Ответ 2

Определите __all__ = ['names', 'that', 'are', 'public'] в __init__.py например:

__all__ = ['foo']

from ._subpackage import foo

Пример реального мира: numpy/__init__.py.


У вас есть неправильное представление о том, как работают пакеты Python:

Если я не использую __init__.py, я ничего не могу импортировать, потому что Python не спускается в папку из sys.path.

Вам нужен __init__.py файл в версиях Python старше Python 3.3, чтобы пометить каталог как содержащий пакет Python.

Если я использую пустой __init__.py, когда я импортирую doit_tools, это пустое пространство имен, в котором ничего нет. Ни один из моих файлов не импортирован, что затрудняет его использование.

Это не предотвращает импорт:

from doit_tools import your_module

Работает так, как ожидалось.

Если я перечислил подмодули в __all__, я могу использовать синтаксис (нахмурился??) from thing import *, но все мои классы снова отстают от лишних барьеров пространства имен. Пользователь должен (1) знать, что они должны использовать from x import * вместо import x, (2) вручную перетасовывать классы, пока они не смогут разумно подчиняться ограничениям ширины линии.

(1) Ваши пользователи (в большинстве случаев) не должны использовать from your_package import * вне интерактивной оболочки Python.

(2) вы можете использовать () для разрыва длинной строки импорта:

from package import (function1, Class1, Class2, ..snip many other names..,
                     ClassN)

Если я добавляю from thatfile import X -операторы к __init__.py, я становлюсь ближе, но у меня есть конфликты пространства имен (?) и дополнительные пространства имен для вещей, которые я не хотел быть там.

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

Чтобы пометить имена как непубличные, вы можете префикс их с помощью _, например, package/_nonpublic_module.py.

Ответ 3

python не является java. Имя файла модуля не обязательно должно совпадать с именем класса. На самом деле python рекомендует использовать все строчные буквы для имени файла модуля.

Также "из math import sqrt" добавит только sqrt в пространство имен, а не математику.

Ответ 4

Существуют вполне обоснованные причины, чтобы скрыть подструктуру пакета (не только при отладке). Среди них удобство и эффективность. При попытке сделать быстрый прототип с пакетом крайне раздражает необходимость прерывать ход мыслей только для того, чтобы найти совершенно бесполезную информацию о том, какой именно точный подмодуль для конкретной функции или класса может быть.

Когда все доступно на верхнем уровне пакета, Идиома:

python -c 'import pkg; help(pkg)'

отображает всю справку, а не только некоторые изящные имена модулей.

Вы всегда можете отключить импорт подмодулей для производственного кода или очистить модули пакета после разработки.

Следующее - это лучший способ, с которым я пришел. Он максимизирует удобство при попытке не подавлять допустимые ошибки. См. Также полный источник с документацией о доктринах.


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

_package_ = 'flat_export'
_modules_ = ['sub1', 'sub2', 'sub3']

Используйте относительный импорт, когда он доступен (это необходимо, см. is_importing_package):

_loaded = False
if is_importing_package(_package_, locals()):
    for _module in _modules_:
        exec ('from .' + _module + ' import *')
    _loaded = True
    del(_module)

Попробуйте импортировать пакет, включая __all__.
Это происходит при выполнении файла модуля как script с помощью пакет в пути поиска (например, python flat_export/__init__.py)

if not _loaded:
    try:
        exec('from ' + _package_ + ' import *')
        exec('from ' + _package_ + ' import __all__')
        _loaded = True
    except (ImportError):
        pass

В крайнем случае попробуйте напрямую импортировать подмодули. Это происходит при выполнении файла модуля как script внутри каталог пакетов без пакета в пути поиска (например, cd flat_export; python __init__.py).

if not _loaded:
    for _module in _modules_:
        exec('from ' + _module + ' import *')
    del(_module)

Построить __all__ (оставить модули), если он не был импортирован раньше:

if not __all__:
    _module_type = type(__import__('sys'))
    for _sym, _val in sorted(locals().items()):
        if not _sym.startswith('_') and not isinstance(_val, _module_type) :
            __all__.append(_sym)
    del(_sym)
    del(_val)
    del(_module_type)

Вот функция is_importing_package:

def is_importing_package(_package_, locals_, dummy_name=None):
    """:returns: True, if relative package imports are working.

    :param _package_: the package name (unfortunately, __package__
      does not work, since it is None, when loading ``:(``).
    :param locals_: module local variables for auto-removing function
      after use.
    :param dummy_name: dummy module name (default: 'dummy').

    Tries to do a relative import from an empty module `.dummy`. This
    avoids any secondary errors, other than::

        ValueError: Attempted relative import in non-package
    """

    success = False
    if _package_:
        import sys
        dummy_name = dummy_name or 'dummy'
        dummy_module = _package_ + '.' + dummy_name
        if not dummy_module in sys.modules:
            import imp
            sys.modules[dummy_module] = imp.new_module(dummy_module)
        try:
            exec('from .' + dummy_name + ' import *')
            success = True
        except:
            pass
    if not 'sphinx.ext.autodoc' in __import__('sys').modules:
        del(locals_['is_importing_package'])
    return success