Импортировать произвольный исходный файл Python. (Python 3.3+)

Как я могу импортировать произвольный исходный файл Python (имя которого может содержать любые символы, и не всегда заканчивается .py) в Python 3.3+?

я использовал imp.load_module следующее:

>>> import imp
>>> path = '/tmp/a-b.txt'
>>> with open(path, 'U') as f:
...     mod = imp.load_module('a_b', f, path, ('.py', 'U', imp.PY_SOURCE))
...
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

Это все еще работает в Python 3.3, но в соответствии с imp.load_module Документация, устарела:

Устаревший с версии 3.3: Не требуется, так как загрузчики должны использоваться для загрузки модулей, а find_module() устарела.

а также imp Модуль документации рекомендует использовать importlib:

Примечание. Новые программы должны использовать importlib, а не этот модуль.

Как правильно загрузить произвольный исходный файл Python в Python 3.3+ без использования устаревших imp.load_module функционировать?

5 ответов

Решение

Нашел решение от importlib тестовый код.

Использование importlib.machinery.SourceFileLoader:

>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = loader.load_module()
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

ПРИМЕЧАНИЕ: работает только в Python 3.3+.

ОБНОВИТЬ Loader.load_module устарела начиная с Python 3.4. использование Loader.exec_module вместо:

>>> import types
>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = types.ModuleType(loader.name)
>>> loader.exec_module(mod)
>>> mod
<module 'a_b'>

>>> import importlib.machinery
>>> import importlib.util
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> spec = importlib.util.spec_from_loader(loader.name, loader)
>>> mod = importlib.util.module_from_spec(spec)
>>> loader.exec_module(mod)
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

Укороченная версия решения @falsetrue:

>>> import importlib.util
>>> spec = importlib.util.spec_from_file_location('a_b', '/tmp/a-b.py')
>>> mod = importlib.util.module_from_spec(spec)
>>> spec.loader.exec_module(mod)
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

Я проверил это с Python 3.5 и 3.6.

Согласно комментариям, он не работает с произвольными расширениями файлов.

Аналогично @falsetru, но для Python 3.5+ и учета того, что importlib Док заявляет об использовании importlib.util.module_from_spec над types.ModuleType:

Эта функция [ importlib.util.module_from_spec ] предпочтительнее использования types.ModuleType создать новый модуль, так как спецификация используется для установки максимально возможного количества атрибутов, контролируемых импортом.

Мы можем импортировать любой файл с importlib в одиночку, изменив importlib.machinery.SOURCE_SUFFIXES список.

import importlib

importlib.machinery.SOURCE_SUFFIXES.append('') # empty string to allow any file
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# if desired: importlib.machinery.SOURCE_SUFFIXES.pop()

importlib вспомогательная функция

Вот удобный, готовый к использованию помощник для замены imp, с примером. Техника такая же, как и у /questions/34391763/importirovat-proizvolnyij-ishodnyij-fajl-python-python-33/34391775#34391775, просто обеспечивает более удобную функцию.

main.py

#!/usr/bin/env python3

import os
import importlib

def import_path(path):
    module_name = os.path.basename(path).replace('-', '_')
    spec = importlib.util.spec_from_loader(
        module_name,
        importlib.machinery.SourceFileLoader(module_name, path)
    )
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    sys.modules[module_name] = module
    return module

notmain = import_path('not-main')
print(notmain)
print(notmain.x)

не-основной

x = 1

Бежать:

python3 main.py

Вывод:

<module 'not_main' from 'not-main'>
1

Я заменяю - с участием _потому что у моих импортируемых исполняемых файлов Python без расширения есть дефисы. Это не обязательно, но дает более точные имена модулей.

Этот шаблон также упоминается в документации по адресу: https://docs.python.org/3.7/library/importlib.html#importing-a-source-file-directly

В итоге я перешел на него, потому что после обновления до Python 3.7 import imp печатает:

DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses

и я не знаю, как это выключить, это спросили на:

Протестировано в Python 3.7.3.

после многих неудачных решений это работает для меня

      def _import(func,*args):
    import os
    from importlib import util
    module_name = "my_module"
    BASE_DIR = "wanted module directory path"
    path =  os.path.join(BASE_DIR,module_name)
    spec = util.spec_from_file_location(func, path)
    mod = util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return getattr(mod,func)(*args)

и чтобы вызвать его, просто напишите имя функции и ее параметры _import("function",*args)

Другие вопросы по тегам