Как импортировать дополнительный модуль без exec __init__.py в пакете

При импортировании вспомогательного модуля из пакета файл __init__.py в папке пакета сначала будет выполняться exec, как я могу отключить это. Иногда мне нужна только одна функция в пакете, импорт всего пакета немного тяжелый.

Например, модуль pandas.util.clipboard не зависит от каких-либо других функций в pandas.

from pandas.util.clipboard import clipboard_get импортирует эту функцию, но также импортирует все pandas общие модули. Есть ли какой-то метод, который просто импортирует буферный модуль, так как он является модулем в моей собственной папке приложения.

Ответ 1

Нет, по дизайну нет. Если вы хотите избежать больших накладных расходов при импорте подмодулей, вы просто используете пустой __init__.py для определения пакетов. Таким образом, накладные расходы на импорт пакета практически равны нулю.

Если pandas не делает этого, у вас нет способа импортировать pandas.util.clipboard без импорта pandas и util. Что вы можете сделать, однако это огромный взлом и эквивалент не, заключается в том, чтобы импортировать модуль clipboard в качестве обычного модуля, а не в качестве подмодуля. Вам просто нужно найти место, где установлен pandas (например, /usr/lib/pythonX.Y/dist-packages/) и вставить путь родительского пакета в sys.path (/usr/lib/pythonX.Y/dist-packages/pandas/util в вашем случае). Затем вы можете импортировать пакет clipboard, выполнив:

import clipboard

Обратите внимание, что:

import clipboard
from pandas.util import clipboard as clipboard2
print(clipboard == clipboard2)

Будет напечатан False. На самом деле это может нарушить много кода, поскольку вы принципиально нарушаете некоторые инварианты, которые предполагает механизм import.

В частности, если подмодуль ссылается на другие подмодули, используя относительные импорты, импорт будет терпеть неудачу, и есть другие ситуации, когда он будет вести себя неправильно. Другой пример, когда это не удается, - это если вы имеете дело с маринованными объектами. Если у вас есть какие-то объекты, замаринованные с помощью модуля, импортированного как pandas.util.clipboard, вы сможете не разрывать их, используя импортированный модуль clipboard, как указано выше.

Итак, не! Я предлагаю либо:

  • Живи с ним, если время, затраченное на импорт пакета, не является реальной проблемой.
  • Или: попробуйте найти замену. Если вам нужно только pandas.util.clipboard, но не остальная часть pandas, возможно, вам не следует использовать pandas в первую очередь, и вы должны использовать меньший пакет, который реализует только функциональные возможности clipboard.

Если вы посмотрите pandas.util.clipboard исходный код, вы обнаружите, что на самом деле это просто pyperclip версия модуля 1.3. Вы можете просто добавить этот модуль в свой site-packages и использовать его вместо того, который предоставляется pandas. Фактически команда pandas только добавила следующую часть в конец исходного кода:

## pandas aliases
clipboard_get = paste
clipboard_set = copy

Развернуть немного о том, почему импорт python работает таким образом.

Как известно, в модулях python есть объекты. И также случается, что пакеты являются модулями, хотя не каждый модуль является пакетом. Когда вы импортируете пакет, как в:

import pandas.util.clipboard

Python должен:

  • Создайте экземпляр module pandas
  • Создайте экземпляр module util и добавьте его как атрибут в pandas
  • Создайте экземпляр module clipboard и добавьте его как атрибут в util.

Чтобы создать экземпляр module, python должен выполнить код в модуле.

Импорт формы:

from pandas.util import clipboard

Только синтаксический сахар для:

import pandas.util.clipboard
clipboard = pandas.util.clipboard
del pandas.util

Обратите внимание, что в случае from clipboard может быть либо module/package, либо просто что-то определенное внутри util. Чтобы проверить это, интерпретатор должен также импортировать util, и для этого он также должен импортировать pandas.

Ответ 2

Я нашел метод, который использует sys.meta_path для подключения процесса импорта:

import imp, sys

class DummyLoader(object):

    def load_module(self, name):
        names = name.split("__")
        path = None
        for name in names:
            f, path, info = imp.find_module(name, path)
            path = [path]
        return imp.load_module(name, f, path[0], info)

    def find_module(self, name, path=None):
        if "__" in name and not name.startswith("__"):
            return DummyLoader()
        else:
            return None

if not sys.meta_path:
    sys.meta_path.append(DummyLoader())
else:
    sys.meta_path[0] = DummyLoader()

Используйте "__" вместо ".". для загрузки только файла:

import pandas__util__clipboard as clip

или используйте функцию для загрузки файла:

import imp

def load_module(name):
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)    

clip = load_module("pandas.util.clipboard")

Ответ 3

Я пробовал эти методы, но не мог заставить их работать. По-видимому, по дизайну он не должен работать.

Если вам нужно это сделать, создайте новую ветку в репо, откуда вы пытаетесь импортировать, или инициализируете репо:

git checkout -b without_init

.. затем удалите __init__.py!

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

import subprocess
print ("Current branch is:", subprocess.check_output(["git rev-parse --abbrev-ref HEAD"], shell=True).strip().decode())

>> without_init

Ответ 4

С помощью Python3 вы можете импортировать файл, если знаете его путь, не выполняя __init__ в каталоге, в котором находится файл. Из [importlib example] :(Как импортировать только субмодуль без exec __init__.py в пакет)

import importlib.util
import sys

# For illustrative purposes.
import tokenize
file_path = tokenize.__file__
module_name = tokenize.__name__

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)