Як імпортувати модуль з урахуванням повного шляху?


1139

Як я можу завантажити модуль Python з урахуванням його повного шляху? Зауважте, що файл може знаходитися в будь-якій точці файлової системи, оскільки це варіант конфігурації.


21
Приємне і просте запитання - і корисні відповіді, але вони змушують мене замислитися, що сталося з мантрою пітона "Є один очевидний спосіб" це зробити. Це не схоже на щось, як єдиний чи простий і очевидний відповідь на нього. Здається смішно хакітним і залежним від версій для такої фундаментальної операції (і вона виглядає і більш роздутою в нових версіях ..).
Інгер

Відповіді:


1281

Для використання Python 3.5+:

import importlib.util
spec = importlib.util.spec_from_file_location("module.name", "/path/to/file.py")
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)
foo.MyClass()

Для Python 3.3 та 3.4 використовуйте:

from importlib.machinery import SourceFileLoader

foo = SourceFileLoader("module.name", "/path/to/file.py").load_module()
foo.MyClass()

(Хоча це було застаріло в Python 3.4.)

Для Python 2 використовуйте:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

Існують еквівалентні функції зручності для компільованих файлів Python та DLL.

Дивіться також http://bugs.python.org/issue21436 .


53
Якби я знав простір імен - 'module.name' - я б уже користувався __import__.
Шрідхар Ратнакумар

62
@SridharRatnakumar значення першого аргументу встановлює imp.load_sourceлише .__name__повернений модуль. це не впливає на завантаження.
Ден Д.

17
@DanD. - перший аргумент imp.load_source()визначає ключ нового запису, створеного у sys.modulesсловнику, тому перший аргумент дійсно впливає на завантаження.
Брендон Родос

9
impМодуль є застарілим , починаючи з версії 3.4: impпакет знаходиться на розгляді Deprecation на користь importlib.
Chiel ten Brinke

9
@AXO і більше до точки питається , чому - то , як простий і основний , так як це було настільки складним. Це не в багатьох інших мовах.
скелястий

422

Перевага додавання шляху до sys.path (над використанням imp) полягає в тому, що воно спрощує речі при імпорті більш ніж одного модуля з одного пакету. Наприклад:

import sys
# the mock-0.3.1 dir contains testcase.py, testutils.py & mock.py
sys.path.append('/foo/bar/mock-0.3.1')

from testcase import TestCase
from testutils import RunTests
from mock import Mock, sentinel, patch

13
Як ми використовуємо sys.path.appendдля вказівки на один файл python замість каталогу?
Фані

28
:-) Можливо, ваше питання краще підійде як питання StackOverflow, а не коментар до відповіді.
Дарил Спітцер

3
Шлях python може містити zip-архіви, "яйця" (складний вид zip-архівів) тощо. Модулі можуть бути імпортовані з них. Таким чином, елементи контуру дійсно є контейнерами файлів, але вони не обов'язково є каталогами.
alexis

12
Остерігайтеся того, що Python кешує імпорт заяв. У рідкісному випадку, якщо у вас є дві різні папки, які мають спільне ім’я одного класу (classX), підхід додавання шляху до sys.path, імпорту classX, видалення контуру та повторення для перейменування шляхів не буде працювати. Python завжди буде завантажувати клас із першого шляху зі свого кешу. У моєму випадку я мав на меті створити систему плагінів, де всі плагіни реалізують певний classX. Я в кінцевому рахунку використовував SourceFileLoader , зауважте, що його депресія є суперечливою .
ComFreek

3
Зверніть увагу, що цей підхід дозволяє імпортному модулю імпортувати інші модулі з того самого режиму, що і модулі часто роблять, тоді як прийнятий відповідь не підходить (принаймні на 3.7). importlib.import_module(mod_name)тут можна використовувати замість явного імпорту, якщо ім'я модуля невідоме під час виконання, я б додав sys.path.pop()в кінці кінців, хоча, припускаючи, що імпортований код не намагається імпортувати більше модулів, як він використовується.
Eli_B

43

Якщо ваш модуль верхнього рівня не є файлом, але пакується як каталог з __init__.py, прийняте рішення майже працює, але не зовсім. У Python 3.5+ потрібен наступний код (зверніть увагу на доданий рядок, який починається з 'sys.modules'):

MODULE_PATH = "/path/to/your/module/__init__.py"
MODULE_NAME = "mymodule"
import importlib
import sys
spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module 
spec.loader.exec_module(module)

Без цього рядка, коли виконується exec_module, він намагається прив’язати відносний імпорт вашого верхнього рівня __init__.py до назви модуля верхнього рівня - у цьому випадку "mymodule". Але "mymodule" ще не завантажений, тому ви отримаєте помилку "SystemError: батьківський модуль" mymodule "не завантажений, не може виконати відносний імпорт". Тож вам потрібно зв’язати ім'я, перш ніж завантажувати його. Причиною цього є фундаментальний інваріант відносної системи імпорту: "Інваріантне тримання полягає в тому, що якщо у вас є sys.modules ['spam'] та sys.modules ['spam.foo'] (як ви б після вищевказаного імпорту ), останній повинен фігурувати як атрибут foo колишнього ", про який йде мова тут .


Дуже дякую! Цей метод дозволяє відносний імпорт між підмодулями. Чудово!
tebanep

Ця відповідь відповідає документації тут: docs.python.org/3/library / ... .
Тім Людвинський

1
але що таке mymodule?
Гульзар

@Gulzar, це будь-яке ім'я, яке ви хотіли б дати своєму модулю, таким чином, щоб ви могли пізніше зробити: "from mymodule import myclass"
Idodo

Отже ... /path/to/your/module/насправді /path/to/your/PACKAGE/? а mymoduleти маєш на увазі myfile.py?
Гульзар

37

Щоб імпортувати ваш модуль, вам потрібно додати його каталог до змінної середовища, тимчасово або постійно.

Тимчасово

import sys
sys.path.append("/path/to/my/modules/")
import my_module

Постійно

Додавання наступного рядка до вашого .bashrcфайлу (в Linux) та виведення source ~/.bashrcу терміналі:

export PYTHONPATH="${PYTHONPATH}:/path/to/my/modules/"

Кредит / Джерело: saarrrr , ще одне питання обміну ставок


3
Це "темп" рішення - чудова відповідь, якщо ви хочете подати проект у зошит з юпітером в іншому місці.
форди

Але ... небезпечно
Шай Алон

@ShaiAlon Ви додаєте шляхи, тому немає небезпеки, крім перенесення кодів з одного комп'ютера на інший, шляхи можуть зіпсуватися. Отже, для розробки пакунків я імпортую лише локальні пакети. Також назви пакунків повинні бути унікальними. Якщо ви переживаєте, використовуйте тимчасове рішення.
Miladiouss

28

Це здається, що ви не хочете спеціально імпортувати файл конфігурації (який містить безліч побічних ефектів та додаткових ускладнень), ви просто хочете запустити його та мати доступ до отриманого простору імен. Стандартна бібліотека надає API спеціально для цього у вигляді runpy.run_path :

from runpy import run_path
settings = run_path("/path/to/file.py")

Цей інтерфейс доступний в Python 2.7 та Python 3.2+


Мені подобається цей метод, але коли я отримую результат run_path, це словник, до якого я не можу отримати доступ?
Стівен Елвуд

Що ви маєте на увазі під "не має доступу"? Ви не можете імпортувати з нього (саме тому це лише варіант хороший , коли насправді не потрібен доступ імпорту-стиль), але зміст має бути доступне через регулярний Dict API ( result[name], result.get('name', default_value), і т.д.)
ncoghlan

Ця відповідь є недооціненою. Це дуже коротко і просто! Ще краще, якщо вам потрібен належний простір імен модулів, ви можете зробити щось на кшталт from runpy import run_path; from argparse import Namespace; mod = Namespace(**run_path('path/to/file.py'))
RuRo

20

Ви також можете зробити щось подібне і додати каталог, у якому знаходиться файл конфігурації, на шлях завантаження Python, а потім просто виконати звичайний імпорт, якщо ви заздалегідь знаєте ім'я файлу, у цьому випадку "config".

Брудний, але це працює.

configfile = '~/config.py'

import os
import sys

sys.path.append(os.path.dirname(os.path.expanduser(configfile)))

import config

Це не динамічно.
Шай Алон

Я спробував: config_file = 'setup-for-chats', setup_file = get_setup_file (config_file + ".py"), sys.path.append (os.path.dirname (os.path.expanduser (setup_file))), імпортувати config_file >> "ImportError: Немає модуля з іменем config_file"
Шай Алон


13
def import_file(full_path_to_module):
    try:
        import os
        module_dir, module_file = os.path.split(full_path_to_module)
        module_name, module_ext = os.path.splitext(module_file)
        save_cwd = os.getcwd()
        os.chdir(module_dir)
        module_obj = __import__(module_name)
        module_obj.__file__ = full_path_to_module
        globals()[module_name] = module_obj
        os.chdir(save_cwd)
    except:
        raise ImportError

import_file('/home/somebody/somemodule.py')

37
Навіщо писати 14 рядків баггі-коду, коли це вже вирішено стандартною бібліотекою? Ви не робили перевірки помилок у форматі чи вмісті full_path_to_module або будь-яких операцій; а також використовувати except:застереження " загальний вигляд" - це рідко корисна ідея.
Кріс Джонсон

Ви повинні використовувати більше "пробувати" тут. Наприкладsave_cwd = os.getcwd() try: … finally: os.chdir(save_cwd)
кай - SE зла

11
@ChrisJohnson this is already addressed by the standard libraryТак, але python має неприємну звичку бути невідповідним ... як зазначено у перевіреній відповіді, існує два різних способи до та після 3.3. У такому випадку я б хотів написати власну універсальну функцію, ніж перевірити версію на льоту. І так, можливо, цей код не надто добре захищений від помилок, але він показує ідею (яка є os.chdir (), я не хоч про це), на основі якої я можу написати кращий код. Звідси +1.
Sushi271

13

Ось код, який працює у всіх версіях Python, починаючи з 2.7-3.5 і, мабуть, навіть в інших.

config_file = "/tmp/config.py"
with open(config_file) as f:
    code = compile(f.read(), config_file, 'exec')
    exec(code, globals(), locals())

Я тестував це. Це може бути некрасиво, але поки що це єдине, що працює у всіх версіях.


1
Ця відповідь працювала для мене куди load_sourceні, тому що вона імпортує скрипт і забезпечує доступ сценарію до модулів та глобальних мереж під час імпорту.
Клік

13

Я придумав трохи модифіковану версію чудової відповіді @ SebastianRittau (для Python> 3.4 я думаю), яка дозволить вам завантажити файл з будь-яким розширенням як модуль, використовуючи spec_from_loaderзамість spec_from_file_location:

from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader 

spec = spec_from_loader("module.name", SourceFileLoader("module.name", "/path/to/file.py"))
mod = module_from_spec(spec)
spec.loader.exec_module(mod)

Перевага кодування шляху в явному SourceFileLoaderполягає в тому, що механізми не намагаються з'ясувати тип файлу з розширення. Це означає, що ви можете завантажити щось на зразок .txtфайлу за допомогою цього методу, але ви не могли це зробити, spec_from_file_locationне вказавши завантажувач, оскільки .txtвін не входить importlib.machinery.SOURCE_SUFFIXES.


13

Ви маєте на увазі завантаження чи імпорт?

Ви можете маніпулювати sys.pathсписком із зазначенням шляху до вашого модуля, а потім імпортувати модуль. Наприклад, заданий модуль за адресою:

/foo/bar.py

Ви можете зробити:

import sys
sys.path[0:0] = ['/foo'] # puts the /foo directory at the start of your path
import bar

1
@Wheat Чому sys.path [0: 0] замість sys.path [0]?
user618677

5
B / c sys.path [0] = xy замінює перший елемент шляху, тоді як шлях [0: 0] = xy еквівалентний path.insert (0, xy)
dom0

2
гм path.insert працював на мене, але хитрість [0: 0] не зробила цього.
jsh

11
sys.path[0:0] = ['/foo']
Кевін Едвардс

6
Explicit is better than implicit.То чому б не sys.path.insert(0, ...)замість цього sys.path[0:0]?
winklerrr

8

Я вважаю, що ви можете використовувати imp.find_module()та imp.load_module()завантажити вказаний модуль. Вам потрібно буде розділити ім'я модуля від шляху, тобто, якщо ви хочете завантажити, /home/mypath/mymodule.pyвам потрібно буде зробити:

imp.find_module('mymodule', '/home/mypath/')

... але це повинно зробити роботу.


6

Ви можете використовувати pkgutilмодуль (конкретно walk_packagesметод), щоб отримати список пакетів у поточному каталозі. Звідси нереально використовувати importlibмашину для імпорту потрібних модулів:

import pkgutil
import importlib

packages = pkgutil.walk_packages(path='.')
for importer, name, is_package in packages:
    mod = importlib.import_module(name)
    # do whatever you want with module now, it's been imported!

5

Створіть модуль python test.py

import sys
sys.path.append("<project-path>/lib/")
from tes1 import Client1
from tes2 import Client2
import tes3

Створіть модуль python test_check.py

from test import Client1
from test import Client2
from test import test3

Ми можемо імпортувати імпортний модуль з модуля.


4

Ця область Python 3.4 здається надзвичайно тужливою для розуміння! Однак, трохи зламавши, використовуючи код від Кріса Каллоуей, я почав щось працювати. Ось основна функція.

def import_module_from_file(full_path_to_module):
    """
    Import a module given the full path/filename of the .py file

    Python 3.4

    """

    module = None

    try:

        # Get module name and path from full path
        module_dir, module_file = os.path.split(full_path_to_module)
        module_name, module_ext = os.path.splitext(module_file)

        # Get module "spec" from filename
        spec = importlib.util.spec_from_file_location(module_name,full_path_to_module)

        module = spec.loader.load_module()

    except Exception as ec:
        # Simple error printing
        # Insert "sophisticated" stuff here
        print(ec)

    finally:
        return module

Це здається, що використовуються непридатні модулі з Python 3.4. Я не претендую на те, що розумію чому, але, здається, це працює з програми. Я виявив, що рішення Кріса працює в командному рядку, але не з програми.


4

Я не кажу, що це краще, але заради повноти я хотів запропонувати execфункцію, доступну як в python 2, так і 3. execдозволяє виконувати довільний код або в глобальній області, або у внутрішній області, подано як словник.

Наприклад, якщо у вас модуль зберігається в "/path/to/module"з функцією" foo(), ви можете запустити його, виконавши наступні дії:

module = dict()
with open("/path/to/module") as f:
    exec(f.read(), module)
module['foo']()

Це робить трохи більш очевидним, що ви завантажуєте код динамічно, і надає вам додаткову потужність, наприклад, можливість надання користувальницьких вбудованих файлів.

І якщо для вас важливий доступ через атрибути, замість ключів, ви можете створити призначений для глобального класу клас диктантів, який забезпечує такий доступ, наприклад:

class MyModuleClass(dict):
    def __getattr__(self, name):
        return self.__getitem__(name)

4

Щоб імпортувати модуль із заданого імені файлу, ви можете тимчасово розширити шлях і відновити системний шлях у остаточному блоці посилання:

filename = "directory/module.py"

directory, module_name = os.path.split(filename)
module_name = os.path.splitext(module_name)[0]

path = list(sys.path)
sys.path.insert(0, directory)
try:
    module = __import__(module_name)
finally:
    sys.path[:] = path # restore

3

Це має спрацювати

path = os.path.join('./path/to/folder/with/py/files', '*.py')
for infile in glob.glob(path):
    basename = os.path.basename(infile)
    basename_without_extension = basename[:-3]

    # http://docs.python.org/library/imp.html?highlight=imp#module-imp
    imp.load_source(basename_without_extension, infile)

4
Більш загальний спосіб скоротити розкладний є: name, ext = os.path.splitext(os.path.basename(infile)). Ваш метод працює, оскільки попереднє обмеження на .py розширення. Крім того, вам, мабуть, слід імпортувати модуль до якоїсь записи змінної / словника.
ReneSac

3

Якщо у нас в одному проекті є сценарії, але в різних засобах каталогу, ми можемо вирішити цю проблему наступним методом.

У цій ситуації utils.pyвsrc/main/util/

import sys
sys.path.append('./')

import src.main.util.utils
#or
from src.main.util.utils import json_converter # json_converter is example method

2

Я зробив пакет, який використовує impдля вас. Я називаю це, import_fileі ось як це використовується:

>>>from import_file import import_file
>>>mylib = import_file('c:\\mylib.py')
>>>another = import_file('relative_subdir/another.py')

Ви можете отримати його за адресою:

http://pypi.python.org/pypi/import_file

або в

http://code.google.com/p/import-file/


1
os.chdir? (мінімум символів для затвердження коментаря).
ychaouche

Я витратив цілий день на усунення помилки з імпортом у програмі, що генерується у pyinstaller. Зрештою, це єдине, що працювало на мене. Дуже дякую вам за це!
frakman1

2

Імпорт модулів пакета під час виконання (рецепт Python)

http://code.activestate.com/recipes/223972/

###################
##                #
## classloader.py #
##                #
###################

import sys, types

def _get_mod(modulePath):
    try:
        aMod = sys.modules[modulePath]
        if not isinstance(aMod, types.ModuleType):
            raise KeyError
    except KeyError:
        # The last [''] is very important!
        aMod = __import__(modulePath, globals(), locals(), [''])
        sys.modules[modulePath] = aMod
    return aMod

def _get_func(fullFuncName):
    """Retrieve a function object from a full dotted-package name."""

    # Parse out the path, module, and function
    lastDot = fullFuncName.rfind(u".")
    funcName = fullFuncName[lastDot + 1:]
    modPath = fullFuncName[:lastDot]

    aMod = _get_mod(modPath)
    aFunc = getattr(aMod, funcName)

    # Assert that the function is a *callable* attribute.
    assert callable(aFunc), u"%s is not callable." % fullFuncName

    # Return a reference to the function itself,
    # not the results of the function.
    return aFunc

def _get_class(fullClassName, parentClass=None):
    """Load a module and retrieve a class (NOT an instance).

    If the parentClass is supplied, className must be of parentClass
    or a subclass of parentClass (or None is returned).
    """
    aClass = _get_func(fullClassName)

    # Assert that the class is a subclass of parentClass.
    if parentClass is not None:
        if not issubclass(aClass, parentClass):
            raise TypeError(u"%s is not a subclass of %s" %
                            (fullClassName, parentClass))

    # Return a reference to the class itself, not an instantiated object.
    return aClass


######################
##       Usage      ##
######################

class StorageManager: pass
class StorageManagerMySQL(StorageManager): pass

def storage_object(aFullClassName, allOptions={}):
    aStoreClass = _get_class(aFullClassName, StorageManager)
    return aStoreClass(allOptions)

2

В Linux, додавання символьної посилання в каталог працює ваш скрипт python, працює.

тобто:

ln -s /absolute/path/to/module/module.py /absolute/path/to/script/module.py

python створить /absolute/path/to/script/module.pycі оновить його, якщо ви зміните вміст/absolute/path/to/module/module.py

потім включіть наступне в mypythonscript.py

from module import *

1
Це хак, який я використовував, і це викликало у мене певні проблеми. Одним з найбільш болючих було те, що IDEA має проблему, коли він не підбирає змінений код із посилання, але все ж намагається зберегти те, що, на його думку, є там. Умова перегонів, де останнім врятувати - це те, що палить… Я через це втратив гідну кількість роботи.
Гріпп

@Gripp не впевнений, чи розумію я вашу проблему, але я часто (майже виключно) редагую свої сценарії на віддаленому сервері зі свого робочого столу через SFTP з таким клієнтом, як CyberDuck, і в цьому випадку також погана ідея спробувати редагуйте зв'язаний файл, замість цього набагато безпечніше редагувати вихідний файл. Деякі з цих проблем ви можете зафіксувати, скориставшись gitі перевіривши свої дані, git statusщоб переконатися, що зміни в сценарії насправді повертають його до вихідного документа та не втрачаються в ефірі.
користувач5359531

2

Я написав власну глобальну та портативну функцію імпорту на основі importlibмодуля для:

  • Уміти імпортувати обидва модулі як підмодуль та імпортувати вміст модуля до батьківського модуля (або у глобальну галузь, якщо немає батьківського модуля).
  • Уміти імпортувати модулі з символами періоду у назві файлу.
  • Вміти імпортувати модулі з будь-яким розширенням.
  • Уміти використовувати окреме ім'я для підмодуля замість імені файлу без розширення, яке за замовчуванням.
  • Уміти визначати порядок імпорту на основі раніше імпортованого модуля замість того, щоб залежати від sys.pathзберігання будь-якого шляху пошуку.

Структура каталогу каталогів:

<root>
 |
 +- test.py
 |
 +- testlib.py
 |
 +- /std1
 |   |
 |   +- testlib.std1.py
 |
 +- /std2
 |   |
 |   +- testlib.std2.py
 |
 +- /std3
     |
     +- testlib.std3.py

Залежність та порядок включення:

test.py
  -> testlib.py
    -> testlib.std1.py
      -> testlib.std2.py
    -> testlib.std3.py 

Впровадження:

Магазин останніх змін: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/python/tacklelib/tacklelib.py

test.py :

import os, sys, inspect, copy

SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("test::SOURCE_FILE: ", SOURCE_FILE)

# portable import to the global space
sys.path.append(TACKLELIB_ROOT) # TACKLELIB_ROOT - path to the library directory
import tacklelib as tkl

tkl.tkl_init(tkl)

# cleanup
del tkl # must be instead of `tkl = None`, otherwise the variable would be still persist
sys.path.pop()

tkl_import_module(SOURCE_DIR, 'testlib.py')

print(globals().keys())

testlib.base_test()
testlib.testlib_std1.std1_test()
testlib.testlib_std1.testlib_std2.std2_test()
#testlib.testlib.std3.std3_test()                             # does not reachable directly ...
getattr(globals()['testlib'], 'testlib.std3').std3_test()     # ... but reachable through the `globals` + `getattr`

tkl_import_module(SOURCE_DIR, 'testlib.py', '.')

print(globals().keys())

base_test()
testlib_std1.std1_test()
testlib_std1.testlib_std2.std2_test()
#testlib.std3.std3_test()                                     # does not reachable directly ...
globals()['testlib.std3'].std3_test()                         # ... but reachable through the `globals` + `getattr`

testlib.py :

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("1 testlib::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/std1', 'testlib.std1.py', 'testlib_std1')

# SOURCE_DIR is restored here
print("2 testlib::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/std3', 'testlib.std3.py')

print("3 testlib::SOURCE_FILE: ", SOURCE_FILE)

def base_test():
  print('base_test')

testlib.std1.py :

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std1::SOURCE_FILE: ", SOURCE_FILE)

tkl_import_module(SOURCE_DIR + '/../std2', 'testlib.std2.py', 'testlib_std2')

def std1_test():
  print('std1_test')

testlib.std2.py :

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std2::SOURCE_FILE: ", SOURCE_FILE)

def std2_test():
  print('std2_test')

testlib.std3.py :

# optional for 3.4.x and higher
#import os, inspect
#
#SOURCE_FILE = os.path.abspath(inspect.getsourcefile(lambda:0)).replace('\\','/')
#SOURCE_DIR = os.path.dirname(SOURCE_FILE)

print("testlib.std3::SOURCE_FILE: ", SOURCE_FILE)

def std3_test():
  print('std3_test')

Вихід ( 3.7.4):

test::SOURCE_FILE:  <root>/test01/test.py
import : <root>/test01/testlib.py as testlib -> []
1 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std1/testlib.std1.py as testlib_std1 -> ['testlib']
import : <root>/test01/std1/../std2/testlib.std2.py as testlib_std2 -> ['testlib', 'testlib_std1']
testlib.std2::SOURCE_FILE:  <root>/test01/std1/../std2/testlib.std2.py
2 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std3/testlib.std3.py as testlib.std3 -> ['testlib']
testlib.std3::SOURCE_FILE:  <root>/test01/std3/testlib.std3.py
3 testlib::SOURCE_FILE:  <root>/test01/testlib.py
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'os', 'sys', 'inspect', 'copy', 'SOURCE_FILE', 'SOURCE_DIR', 'TackleGlobalImportModuleState', 'tkl_membercopy', 'tkl_merge_module', 'tkl_get_parent_imported_module_state', 'tkl_declare_global', 'tkl_import_module', 'TackleSourceModuleState', 'tkl_source_module', 'TackleLocalImportModuleState', 'testlib'])
base_test
std1_test
std2_test
std3_test
import : <root>/test01/testlib.py as . -> []
1 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std1/testlib.std1.py as testlib_std1 -> ['testlib']
import : <root>/test01/std1/../std2/testlib.std2.py as testlib_std2 -> ['testlib', 'testlib_std1']
testlib.std2::SOURCE_FILE:  <root>/test01/std1/../std2/testlib.std2.py
2 testlib::SOURCE_FILE:  <root>/test01/testlib.py
import : <root>/test01/std3/testlib.std3.py as testlib.std3 -> ['testlib']
testlib.std3::SOURCE_FILE:  <root>/test01/std3/testlib.std3.py
3 testlib::SOURCE_FILE:  <root>/test01/testlib.py
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'os', 'sys', 'inspect', 'copy', 'SOURCE_FILE', 'SOURCE_DIR', 'TackleGlobalImportModuleState', 'tkl_membercopy', 'tkl_merge_module', 'tkl_get_parent_imported_module_state', 'tkl_declare_global', 'tkl_import_module', 'TackleSourceModuleState', 'tkl_source_module', 'TackleLocalImportModuleState', 'testlib', 'testlib_std1', 'testlib.std3', 'base_test'])
base_test
std1_test
std2_test
std3_test

Випробувано в Python 3.7.4, 3.2.5,2.7.16

Плюси :

  • Може імпортувати обидва модулі як підмодуль та може імпортувати вміст модуля до батьківського модуля (або в глобальний регістр, якщо у нього немає батьківського модуля).
  • Можна імпортувати модулі з періодами в імені файлу.
  • Можна імпортувати будь-який модуль розширення з будь-якого модуля розширення.
  • Можна використовувати окреме ім'я для підмодуля замість імені файлу без розширення, яке є за замовчуванням (наприклад, testlib.std.pyяк testlib, testlib.blabla.pyяк testlib_blablaі так далі).
  • Не залежить від того, sys.pathчи зберігається в ньому шлях пошуку.
  • Не вимагає збереження / відновлення глобальних змінних, таких як SOURCE_FILEі SOURCE_DIRміж дзвінками до tkl_import_module.
  • [для 3.4.xі вище] Може змішувати простори імен модулів у вкладені tkl_import_moduleвиклики (наприклад: named->local->namedабо local->named->localтощо).
  • [для 3.4.xі вище] Може автоматично експортувати глобальні змінні / функції / класи, звідки декларується, до всіх дочірніх модулів, імпортованих через tkl_import_module(через tkl_declare_globalфункцію).

Мінуси :

  • [для 3.3.xі нижче] Потрібно оголосити tkl_import_moduleу всіх модулях, до яких звертається tkl_import_module(дублювання коду)

Оновлення 1,2 (лише 3.4.xта вище):

У Python 3.4 та новіших версіях ви можете обійти вимогу декларування tkl_import_moduleу кожному модулі шляхом декларування tkl_import_moduleв модулі вищого рівня, і функція буде вводити себе всім дочірнім модулям за один виклик (це свого роду імпорт самостійного розгортання).

Оновлення 3 :

Додана функція tkl_source_moduleяк аналог bash sourceіз захистом виконання підтримки при імпорті (реалізується через злиття модуля замість імпорту).

Оновлення 4 :

Додана функція tkl_declare_globalавтоматичного експорту глобальної змінної модуля до всіх дочірніх модулів, де глобальна змінна модуля не видима, оскільки не є частиною дочірнього модуля.

Оновлення 5 :

Усі функції перемістилися в бібліотеку снастей, див. Посилання вище.


2

Є пакет , присвячений саме цьому:

from thesmuggler import smuggle

# À la `import weapons`
weapons = smuggle('weapons.py')

# À la `from contraband import drugs, alcohol`
drugs, alcohol = smuggle('drugs', 'alcohol', source='contraband.py')

# À la `from contraband import drugs as dope, alcohol as booze`
dope, booze = smuggle('drugs', 'alcohol', source='contraband.py')

Це тестується у версіях Python (також Jython та PyPy), але це може бути надмірним, залежно від розміру вашого проекту.


1

Додавши це до списку відповідей, оскільки я не зміг знайти нічого, що працювало. Це дозволить імпортувати складені (pyd) модулі python у 3.4:

import sys
import importlib.machinery

def load_module(name, filename):
    # If the Loader finds the module name in this list it will use
    # module_name.__file__ instead so we need to delete it here
    if name in sys.modules:
        del sys.modules[name]
    loader = importlib.machinery.ExtensionFileLoader(name, filename)
    module = loader.load_module()
    locals()[name] = module
    globals()[name] = module

load_module('something', r'C:\Path\To\something.pyd')
something.do_something()

1

досить простий спосіб: припустимо, ви хочете імпортувати файл з відносним шляхом ../../MyLibs/pyfunc.py


libPath = '../../MyLibs'
import sys
if not libPath in sys.path: sys.path.append(libPath)
import pyfunc as pf

Але якщо ви зробите це без охорони, нарешті, ви можете отримати дуже довгий шлях


1

Просте рішення, використовуючи importlibзамість impпакета (перевірено для Python 2.7, хоча воно також повинно працювати і для Python 3):

import importlib

dirname, basename = os.path.split(pyfilepath) # pyfilepath: '/my/path/mymodule.py'
sys.path.append(dirname) # only directories should be added to PYTHONPATH
module_name = os.path.splitext(basename)[0] # '/my/path/mymodule.py' --> 'mymodule'
module = importlib.import_module(module_name) # name space of defined module (otherwise we would literally look for "module_name")

Тепер ви можете безпосередньо використовувати простір імен імпортованого модуля, наприклад:

a = module.myvar
b = module.myfunc(a)

Перевага цього рішення полягає в тому, що нам навіть не потрібно знати фактичну назву модуля, який ми хотіли б імпортувати , щоб використовувати його в нашому коді. Це корисно, наприклад у випадку, якщо шлях модуля є настроюваним аргументом.


Таким чином ви модифікуєте sys.path, що не відповідає кожному випадку використання.
bgusach

@bgusach Це може бути правдою, але бажано в деяких випадках (додавання шляху до sys.path спрощує речі при імпорті більше одного модуля з одного пакету). У будь-якому випадку, якщо це не бажано, можна відразу після цього зробитиsys.path.pop()
Атаксія

0

Ця відповідь є доповненням до відповіді Себастьяна Ріттау на коментар: "але що робити, якщо у вас немає назви модуля?" Це швидкий і брудний спосіб отримання ймовірного імені модуля python з назвою файлу - він просто піднімається вгору по дереву, поки не знайде каталог без __init__.pyфайлу, а потім перетворить його назад у ім’я файлу. Для Python 3.4+ (використовує pathlib), що має сенс, оскільки люди з Py2 можуть використовувати "імп" або інші способи здійснення відносного імпорту:

import pathlib

def likely_python_module(filename):
    '''
    Given a filename or Path, return the "likely" python module name.  That is, iterate
    the parent directories until it doesn't contain an __init__.py file.

    :rtype: str
    '''
    p = pathlib.Path(filename).resolve()
    paths = []
    if p.name != '__init__.py':
        paths.append(p.stem)
    while True:
        p = p.parent
        if not p:
            break
        if not p.is_dir():
            break

        inits = [f for f in p.iterdir() if f.name == '__init__.py']
        if not inits:
            break

        paths.append(p.stem)

    return '.'.join(reversed(paths))

Звичайно, є можливості для вдосконалення, і необов'язкові __init__.pyфайли можуть потребувати інших змін, але якщо у вас є __init__.pyзагалом, це робить фокус.


-1

Я вважаю, що найкращий спосіб - з офіційної документації ( 29.1. Imp - доступ до внутрішніх даних імпорту ):

import imp
import sys

def __import__(name, globals=None, locals=None, fromlist=None):
    # Fast path: see if the module has already been imported.
    try:
        return sys.modules[name]
    except KeyError:
        pass

    # If any of the following calls raises an exception,
    # there's a problem we can't handle -- let the caller handle it.

    fp, pathname, description = imp.find_module(name)

    try:
        return imp.load_module(name, fp, pathname, description)
    finally:
        # Since we may exit via an exception, close fp explicitly.
        if fp:
            fp.close()

1
Це рішення не дозволяє вам надати шлях, саме про це і задається питання.
Міхей Сміт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.