Почему импорт по имени файла не поддерживается? [Дубликат]

Когда ваши пути include разные

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

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

Возможно ли получить ошибку компоновщика, даже если объявление и определение, похоже, совпадают? Да! Они могут выглядеть одинаково в исходном коде, но это действительно зависит от того, что видит компилятор. По сути, вы можете столкнуться с такой ситуацией:

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

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

Вы можете спросить, как это получается в такой ситуации? Включите пути, конечно! Если при компиляции разделяемой библиотеки путь include приводит к header1.h, и вы в конечном итоге используете header2.h в своей собственной программе, вы оставите царапины на своем заголовке, задаваясь вопросом, что произошло (каламбур).

Пример того, как это может произойти в реальном мире, объясняется ниже.

Дальнейшая разработка с примером

У меня есть два проекта: graphics.lib и main.exe. Оба проекта зависят от common_math.h. Предположим, что библиотека экспортирует следующую функцию:

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

И затем вы идете вперед и включаете библиотеку в свой собственный проект.

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

Boom! Вы получаете ошибку компоновщика, и вы понятия не имеете, почему она терпит неудачу. Причина в том, что общая библиотека использует разные версии одного и того же include common_math.h (я сделал это очевидным здесь в этом примере, включив другой путь, но это может быть не всегда так очевидно. Возможно, путь include отличается в настройки компилятора).

Обратите внимание, что в этом примере компоновщик сказал бы вам, что не смог найти draw(), когда на самом деле вы знаете, что он явно экспортируется библиотекой. Вы могли часами царапать себе голову, думая, что пошло не так. Дело в том, что компоновщик видит другую подпись, потому что типы параметров немного отличаются. В этом примере vec3 является другим типом в обоих проектах в отношении компилятора. Это может произойти из-за того, что они происходят из двух немного разных файлов include (возможно, включенные файлы поступают из двух разных версий библиотеки).

Отладка компоновщика

DUMPBIN - ваш друг, если вы используете Visual Studio. Я уверен, что другие компиляторы имеют другие подобные инструменты.

Процесс выглядит следующим образом:

  1. Обратите внимание на странное искаженное имя, указанное в ошибке компоновщика. (например, draw @ graphics @ XYZ).
  2. Выгрузите экспортированные символы из библиотеки в текстовый файл.
  3. Найдите экспортированный символ, представляющий интерес, и обратите внимание, что искаженное имя
  4. Обратите внимание на то, почему искаженные имена оказались разными. Вы могли бы видеть, что типы параметров различны, хотя они выглядят одинаково в исходном коде.
  5. Причина, почему они разные. В приведенном выше примере они различаются из-за разных файлов include.

[1] По проекту я имею в виду набор исходных файлов, которые связаны друг с другом для создания либо библиотеки, либо исполняемого файла .

РЕДАКТИРОВАТЬ 1: Переписать первый раздел, который будет легче понять. Пожалуйста, прокомментируйте ниже, чтобы сообщить мне, нужно ли что-то еще исправлять. Спасибо!

784
задан ferkulat 16 February 2014 в 17:36
поделиться

22 ответа

Для использования 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 .

907
ответ дан Sebastian Rittau 28 August 2018 в 22:31
поделиться

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


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

Но если вы сделаете это без охраны, вы, наконец, сможете получить очень длинный путь

1
ответ дан Andrei Keino 28 August 2018 в 22:31
поделиться

Простое решение с использованием 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)

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

1
ответ дан Ataxias 28 August 2018 в 22:31
поделиться
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')
10
ответ дан Chris Calloway 28 August 2018 в 22:31
поделиться

Преимущество добавления пути к 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
314
ответ дан Daryl Spitzer 28 August 2018 в 22:31
поделиться

Добавление этого в список ответов, поскольку я не мог найти ничего, что сработало. Это позволит импортировать скомпилированные (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()
0
ответ дан David 28 August 2018 в 22:31
поделиться

Я придумал слегка измененную версию замечательного ответа @ 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 .

5
ответ дан Delgan 28 August 2018 в 22:31
поделиться

Импортировать модули модулей во время выполнения (рецепт 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
ответ дан Eric Leschinski 28 August 2018 в 22:31
поделиться

Вы имеете в виду загрузку или импорт?

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

/foo/bar.py

Вы можете сделать:

import sys
sys.path[0:0] = '/foo' # puts the /foo directory at the start of your path
import bar
9
ответ дан Eric Schoonover 28 August 2018 в 22:31
поделиться

Это должно работать

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)
3
ответ дан joran 28 August 2018 в 22:31
поделиться

Вы можете использовать модуль 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!
8
ответ дан Mathieu Rodic 28 August 2018 в 22:31
поделиться

В Linux, добавляя символическую ссылку в каталог, в котором находится ваш скрипт python, [.g0]

ie:

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 *
2
ответ дан Micah Smith 28 August 2018 в 22:31
поделиться

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

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

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

13
ответ дан ncoghlan 28 August 2018 в 22:31
поделиться

Вы также можете сделать что-то подобное и добавить каталог, в котором находится файл конфигурации, в путь загрузки Python, а затем просто сделать обычный импорт, предполагая, что вы знаете имя файла заранее, в этом случае " config ".

Беспокойно, но он работает.

configfile = '~/config.py'

import os
import sys

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

import config
18
ответ дан Peter Mortensen 28 August 2018 в 22:31
поделиться

Чтобы импортировать модуль из заданного имени файла, вы можете временно расширить путь и восстановить путь к системе в конце блока :

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
ответ дан Peter Zhu 28 August 2018 в 22:31
поделиться

Эта область 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. Я не претендую на то, чтобы понять, почему, но, похоже, работает изнутри программы. Я нашел решение Криса работало в командной строке, но не внутри программы.

3
ответ дан Redlegjed 28 August 2018 в 22:31
поделиться

Если ваш модуль верхнего уровня не является файлом, а упакован как каталог с __init__.py, то принятое решение почти работает, но не совсем. В Python 3.5+ необходим следующий код (обратите внимание на добавленную строку, начинающуюся с «sys.modules»):

MODULE_PATH = "/path/to/your/module/__init__.py"
MODULE_NAME = "mymodule"
spec = importlib.util.spec_from_file_location("mymodule", 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: Parent module« mymodule »не загружен, не может выполнять относительный импорт». Поэтому вам нужно привязать имя до его загрузки. Причиной этого является фундаментальный инвариант относительной системы импорта: «Инвариантный холдинг состоит в том, что если у вас есть sys.modules ['spam'] и sys.modules ['spam.foo'] (как и после указанного импорта ), последний должен появиться как атрибут foo предыдущего «, как обсуждалось здесь .

4
ответ дан Sam Grondahl 28 August 2018 в 22:31
поделиться

Вот код, который работает во всех версиях 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())

Я его протестировал. Это может быть уродливым, но до сих пор это единственное, что работает во всех версиях.

7
ответ дан sorin 28 August 2018 в 22:31
поделиться

Вы можете использовать метод

load_source(module_name, path_to_file) 

из модуля imp .

16
ответ дан twasbrillig 28 August 2018 в 22:31
поделиться

Я сделал пакет, который использует 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
ответ дан ubershmekel 28 August 2018 в 22:31
поделиться

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

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

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

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

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

class MyModuleClass(dict):
    def __getattr__(self, name):
        return self.__getitem__(name)
3
ответ дан yoniLavi 28 August 2018 в 22:31
поделиться

Лучший способ, я думаю, из официальной документации ( 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
ответ дан Zompa 28 August 2018 в 22:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: