Como importar um módulo, considerando o caminho completo?

1141

Como posso carregar um módulo Python devido ao seu caminho completo? Observe que o arquivo pode estar em qualquer lugar do sistema de arquivos, pois é uma opção de configuração.

derfred
fonte
21
Pergunta agradável e simples - e respostas úteis, mas elas me fazem pensar no que aconteceu com o mantra python "Existe uma maneira óbvia " de fazer isso. Não parece nada parecido com uma resposta simples ou óbvia. Parece ridiculamente hacky e depende da versão para uma operação tão fundamental (e parece e mais inchada em versões mais recentes ..).
11409

Respostas:

1282

Para o Python 3.5+, use:

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()

Para Python 3.3 e 3.4, use:

from importlib.machinery import SourceFileLoader

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

(Embora isso tenha sido descontinuado no Python 3.4.)

Para Python 2, use:

import imp

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

Existem funções de conveniência equivalentes para arquivos e DLLs Python compilados.

Veja também http://bugs.python.org/issue21436 .

Sebastian Rittau
fonte
53
Se eu soubesse o namespace - 'module.name' - eu já usaria __import__.
Sridhar Ratnakumar
62
@SridharRatnakumar, o valor do primeiro argumento imp.load_sourcedefine apenas .__name__o módulo retornado. isso não afeta o carregamento.
Dan D.
17
@DanD. - o primeiro argumento de imp.load_source()determina a chave da nova entrada criada no sys.modulesdicionário; portanto, o primeiro argumento realmente afeta o carregamento.
Brandon Rhodes
9
O impmódulo está obsoleto desde a versão 3.4: O imppacote está com obsoleta pendente em favor de importlib.
Chiel ten Brinke 08/12/2013
9
@AXO e mais, ao ponto, nos perguntamos por que algo tão simples e básico como isso tem que ser tão complicado. Não está em muitos outros idiomas.
rochoso
423

A vantagem de adicionar um caminho ao sys.path (sobre o uso de imp) é que ele simplifica as coisas ao importar mais de um módulo de um único pacote. Por exemplo:

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
Daryl Spitzer
fonte
13
Como usamos sys.path.appendpara apontar para um único arquivo python em vez de um diretório?
Phani
28
:-) Talvez sua pergunta seja mais adequada como uma pergunta StackOverflow, não como um comentário em uma resposta.
Daryl Spitzer
3
O caminho do python pode conter arquivos zip, "eggs" (um tipo complexo de arquivos zip), etc. Os módulos podem ser importados deles. Portanto, os elementos do caminho são de fato recipientes de arquivos, mas não são necessariamente diretórios.
21915 Alexis
12
Cuidado com o fato de o Python armazenar em cache as instruções de importação. No caso raro de você ter duas pastas diferentes compartilhando um único nome de classe (classX), a abordagem de adicionar um caminho ao sys.path, importar o classX, remover o caminho e repetir os caminhos restantes não funcionará. O Python sempre carrega a classe do primeiro caminho do cache. No meu caso, pretendi criar um sistema de plugins em que todos os plugins implementassem uma classeX específica. Acabei usando o SourceFileLoader , observe que sua depreciação é controversa .
ComFreek
3
Observe que essa abordagem permite que o módulo importado importe outros módulos do mesmo diretório, o que os módulos costumam fazer, enquanto a abordagem da resposta aceita não (pelo menos na versão 3.7). importlib.import_module(mod_name)pode ser usado em vez da importação explícita aqui, se o nome do módulo não for conhecido em tempo de execução, eu adicionaria um sys.path.pop()no final, assumindo que o código importado não tente importar mais módulos à medida que é usado.
Eli_B
43

Se o seu módulo de nível superior não for um arquivo, mas estiver empacotado como um diretório com __init__.py, a solução aceita quase funcionará, mas não completamente. No Python 3.5+, o seguinte código é necessário (observe a linha adicionada que começa com '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)

Sem essa linha, quando exec_module é executado, ele tenta vincular importações relativas no seu nível superior __init__.py ao nome do módulo de nível superior - nesse caso "mymodule". Mas "mymodule" ainda não foi carregado, então você receberá o erro "SystemError: Módulo pai 'mymodule' não carregado, não pode executar importação relativa". Então, você precisa vincular o nome antes de carregá-lo. A razão para isso é a invariante fundamental do sistema de importação relativo: "A retenção invariável é que, se você tiver sys.modules ['spam'] e sys.modules ['spam.foo'] (como faria após a importação acima ), o último deve aparecer como o atributo foo do primeiro ", conforme discutido aqui .

Sam Grondahl
fonte
Muito obrigado! Este método permite importações relativas entre submódulos. Ótimo!
Tbanep 10/10/19
Esta resposta corresponde à documentação aqui: docs.python.org/3/library/… .
Tim Ludwinski
1
mas o que é mymodule?
Gulzar 20/01
@Gulzar, é o nome que você deseja atribuir ao seu módulo, para que você possa fazer posteriormente: "from mymodule import myclass"
Idodo 16/02
Então ... /path/to/your/module/é realmente /path/to/your/PACKAGE/? e mymodulevocê quer dizer myfile.py?
Gulzar
37

Para importar seu módulo, você precisa adicionar seu diretório à variável de ambiente, temporária ou permanentemente.

Temporariamente

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

Permanentemente

Adicionando a seguinte linha ao seu .bashrcarquivo (no linux) e execute source ~/.bashrcno terminal:

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

Crédito / Fonte: saarrrr , outra questão de stackexchange

Miladiouss
fonte
3
Essa solução "temp" é uma ótima resposta se você deseja produzir um projeto em um notebook jupiter em outro lugar.
Fordy
Mas ... é perigoso mexer com o caminho
Shai Alon
@ShaiAlon Você está adicionando caminhos; portanto, não há perigo, exceto quando você transfere códigos de um computador para outro, os caminhos podem ficar confusos. Portanto, para o desenvolvimento de pacotes, importo apenas pacotes locais. Além disso, os nomes dos pacotes devem ser exclusivos. Se você estiver preocupado, use a solução temporária.
Miladiouss
28

Parece que você não deseja importar especificamente o arquivo de configuração (que tem muitos efeitos colaterais e complicações adicionais envolvidas), apenas deseja executá-lo e acessar o espaço de nome resultante. A biblioteca padrão fornece uma API especificamente para isso na forma de runpy.run_path :

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

Essa interface está disponível no Python 2.7 e Python 3.2 ou superior

ncoghlan
fonte
Eu gosto desse método, mas quando eu recebo o resultado do run_path é um dicionário que eu não consigo acessar?
Stephen Ellwood
O que você quer dizer com "não pode acessar"? Você não pode importar a partir dele (é por isso que esta é apenas uma boa opção quando o acesso ao estilo de importação não é realmente necessário), mas o conteúdo deve estar disponível através da API dict regulares ( result[name], result.get('name', default_value), etc)
ncoghlan
Esta resposta é muito subestimada. É muito curto e simples! Melhor ainda, se você precisar de um namespace de módulo adequado, poderá fazer algo como from runpy import run_path; from argparse import Namespace; mod = Namespace(**run_path('path/to/file.py'))
RuRo 18/03
20

Você também pode fazer algo assim e adicionar o diretório em que o arquivo de configuração está localizado no caminho de carregamento do Python e, em seguida, fazer uma importação normal, supondo que você saiba o nome do arquivo com antecedência, neste caso "config".

Confuso, mas funciona.

configfile = '~/config.py'

import os
import sys

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

import config
ctcherry
fonte
Isso não é dinamicamente.
Shai Alon
Eu tentei: config_file = 'setup-for-chats', setup_file = get_setup_file (config_file + ".py"), sys.path.append (os.path.dirname (os.path.expanduser (setup_file))), import config_file >> "ImportError: nenhum módulo chamado config_file"
Shai Alon
17

Você pode usar o

load_source(module_name, path_to_file) 

método do módulo imp .

zuber
fonte
... e imp.load_dynamic(module_name, path_to_file)para DLLs
HEKTO
34
heads-up que imp é preterido agora.
precisa saber é
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')
Chris Calloway
fonte
37
Por que escrever 14 linhas de código de buggy quando isso já é abordado pela biblioteca padrão? Você não fez a verificação de erros no formato ou no conteúdo do full_path_to_module ou nas operações os.whatever; e usar uma except:cláusula abrangente raramente é uma boa ideia.
Chris Johnson
Você deve usar mais "tentativa-finalmente" aqui. Por exemplosave_cwd = os.getcwd() try: … finally: os.chdir(save_cwd)
kay - SE is evil
11
@ChrisJohnson this is already addressed by the standard librarysim, mas o python tem o hábito desagradável de não ser compatível com versões anteriores ... como a resposta verificada diz que existem duas maneiras diferentes antes e depois do 3.3. Nesse caso, eu gostaria de escrever minha própria função universal do que verificar a versão em tempo real. E sim, talvez esse código não esteja muito bem protegido por erros, mas mostra uma ideia (que é os.chdir (), ainda não pensei nisso), com base na qual posso escrever um código melhor. Portanto, +1.
Sushi271
13

Aqui está um código que funciona em todas as versões do Python, de 2.7 a 3.5 e provavelmente até outras.

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

Eu testei. Pode ser feio, mas até agora é o único que funciona em todas as versões.

sorin
fonte
1
Esta resposta funcionou para mim onde load_sourcenão funcionou porque importa o script e fornece ao script acesso aos módulos e globais no momento da importação.
Klik
13

Eu vim com uma versão ligeiramente modificada da maravilhosa resposta de @ SebastianRittau (para Python> 3.4, eu acho), que permitirá que você carregue um arquivo com qualquer extensão como módulo usando, em spec_from_loadervez de 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)

A vantagem de codificar o caminho de forma explícita SourceFileLoaderé que o mecanismo não tentará descobrir o tipo do arquivo a partir da extensão. Isso significa que você pode carregar algo como um .txtarquivo usando este método, mas você não poderia fazê-lo com spec_from_file_locationsem especificar o carregador, porque .txtnão está na importlib.machinery.SOURCE_SUFFIXES.

Físico louco
fonte
13

Você quer dizer carregar ou importar?

Você pode manipular a sys.pathlista, especificar o caminho para o seu módulo e depois importá-lo. Por exemplo, dado um módulo em:

/foo/bar.py

Você poderia fazer:

import sys
sys.path[0:0] = ['/foo'] # puts the /foo directory at the start of your path
import bar
Trigo
fonte
1
@Wheat Por que sys.path [0: 0] em vez de sys.path [0]?
user618677
5
B / c sys.path [0] = xy substitui o primeiro item de caminho enquanto caminho [0: 0] = xy é equivalente a path.insert (0, xy)
dom0
2
hm o path.insert funcionou para mim, mas o truque [0: 0] não.
JSH
11
sys.path[0:0] = ['/foo']
Kevin Edwards
6
Explicit is better than implicit.Então, por que não em sys.path.insert(0, ...)vez de sys.path[0:0]?
Winklerrr 8/05/19
8

Eu acredito que você pode usar imp.find_module()e imp.load_module()carregar o módulo especificado. Você precisará dividir o nome do módulo fora do caminho, ou seja, se você deseja carregar, /home/mypath/mymodule.pyprecisará fazer:

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

... mas isso deve fazer o trabalho.

Matt
fonte
6

Você pode usar o pkgutilmódulo (especificamente o walk_packagesmétodo) para obter uma lista dos pacotes no diretório atual. A partir daí, é trivial usar o importlibmaquinário para importar os módulos que você deseja:

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!
bob_twinkles
fonte
5

Criar módulo python test.py

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

Criar módulo python test_check.py

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

Podemos importar o módulo importado do módulo.

abhimanyu
fonte
4

Esta área do Python 3.4 parece ser extremamente tortuosa de entender! No entanto, com um pouco de hacking usando o código de Chris Calloway como um começo, consegui fazer algo funcionar. Aqui está a função básica.

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

Parece usar módulos não obsoletos do Python 3.4. Não pretendo entender o porquê, mas parece funcionar de dentro de um programa. Eu achei a solução de Chris funcionada na linha de comando, mas não de dentro de um programa.

Redlegjed
fonte
4

Não estou dizendo que é melhor, mas por uma questão de completude, eu queria sugerir a execfunção, disponível nos python 2 e 3., que execpermite executar código arbitrário no escopo global ou interno, fornecido como um dicionário.

Por exemplo, se você tiver um módulo armazenado em "/path/to/module"com a função foo(), poderá executá-lo da seguinte maneira:

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

Isso torna um pouco mais explícito que você esteja carregando o código dinamicamente e concede a você poder adicional, como a capacidade de fornecer recursos personalizados.

E se o acesso por meio de atributos, em vez de chaves, é importante para você, você pode criar uma classe de dict personalizada para os globais, que fornece esse acesso, por exemplo:

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

Para importar um módulo de um determinado nome de arquivo, você pode estender temporariamente o caminho e restaurar o caminho do sistema na referência finalmente ao bloco :

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
Peter Zhu
fonte
4

Se tivermos scripts no mesmo projeto, mas em diferentes diretórios, podemos resolver esse problema pelo seguinte método.

Nesta situação utils.pyestá emsrc/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
Kumar Immanuel
fonte
o mais simples IMO
Ardhi
3

Isso deve funcionar

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)
Hengjie
fonte
4
Uma maneira mais geral para cortar o para fora extensão é: name, ext = os.path.splitext(os.path.basename(infile)). Seu método funciona porque a restrição anterior à extensão .py. Além disso, você provavelmente deve importar o módulo para alguma entrada de variável / dicionário.
ReneSac
2

Eu fiz um pacote que usa imppara você. Eu chamo import_filee é assim que é usado:

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

Você pode obtê-lo em:

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

ou em

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

ubershmekel
fonte
1
os.chdir? (caracteres mínimos para aprovar comentário).
ychaouche
Passei o dia inteiro solucionando problemas de importação em um exe gerado pelo pyinstaller. No final, essa é a única coisa que funcionou para mim. Muito obrigado por fazer isso!
frakman1
2

Importar módulos de pacote em tempo de execução (receita 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)
user10370
fonte
2

No Linux, adicionar um link simbólico no diretório em que seu script python está localizado funciona.

ou seja:

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

O python criará /absolute/path/to/script/module.pyce atualizará se você alterar o conteúdo de/absolute/path/to/module/module.py

inclua o seguinte em mypythonscript.py

from module import *
user2760152
fonte
1
Este é o truque que usei e me causou alguns problemas. Um dos mais dolorosos foi que o IDEA tem um problema em que não captura o código alterado no link, mas ainda tenta salvar o que acha que está lá. Uma condição de corrida em que a última a economizar é o que fica ... Perdi uma quantidade razoável de trabalho por causa disso.
Gripp
@Gripp não tem certeza se estou entendendo seu problema, mas frequentemente (quase exclusivamente) edito meus scripts em um servidor remoto da minha área de trabalho via SFTP com um cliente como o CyberDuck, e nesse caso também é uma má idéia tentar edite o arquivo com link simbólico; é muito mais seguro editar o arquivo original. Você pode detectar alguns desses problemas usando gite verificando o seu git statuspara verificar se suas alterações no script estão realmente retornando ao documento de origem e não se perdendo no éter.
user5359531
2

Eu escrevi minha própria função de importação global e portátil, com base no importlibmódulo, para:

  • Poder importar o módulo como um submódulo e importar o conteúdo de um módulo para um módulo pai (ou para um global se não houver módulo pai).
  • Poder importar módulos com caracteres de ponto em um nome de arquivo.
  • Poder importar módulos com qualquer extensão.
  • Poder usar um nome autônomo para um submódulo em vez de um nome de arquivo sem extensão, que é por padrão.
  • Ser capaz de definir a ordem de importação com base no módulo importado anteriormente, em vez de depender sys.pathde um armazenamento de caminho de pesquisa que nunca.

A estrutura de diretório de exemplos:

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

Dependência e ordem de inclusão:

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

Implementação:

Armazenamento das últimas alterações: 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')

Saída ( 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

Testado em Python 3.7.4, 3.2.5,2.7.16

Prós :

  • Pode importar o módulo como um submódulo e pode importar o conteúdo de um módulo para um módulo pai (ou para um global se não houver módulo pai).
  • Pode importar módulos com pontos em um nome de arquivo.
  • Pode importar qualquer módulo de extensão de qualquer módulo de extensão.
  • Pode usar um nome autônomo para um submódulo em vez de um nome de arquivo sem extensão que é por padrão (por exemplo, testlib.std.pycomo testlib, testlib.blabla.pycomo testlib_blablae assim por diante).
  • Não depende de um sys.patharmazenamento de caminho de pesquisa que nunca.
  • Não requer salvar / restaurar variáveis ​​globais como SOURCE_FILEe SOURCE_DIRentre chamadas para tkl_import_module.
  • [for 3.4.xe superior] Pode misturar os namespaces do módulo em tkl_import_modulechamadas aninhadas (ex: named->local->namedou local->named->localassim por diante).
  • [for 3.4.xand later] Pode exportar automaticamente variáveis ​​/ funções / classes globais de onde está declarada para todos os módulos filhos importados por meio da tkl_import_module(por meio da tkl_declare_globalfunção).

Contras :

  • [for 3.3.xand lower] Exigir declarar tkl_import_moduleem todos os módulos que chama tkl_import_module(duplicação de código)

Atualização 1,2 ( 3.4.xsomente para versões superiores):

No Python 3.4 e superior, você pode ignorar o requisito de declarar tkl_import_moduleem cada módulo, declarando tkl_import_moduleem um módulo de nível superior, e a função se injetaria em todos os módulos filhos em uma única chamada (é um tipo de importação de auto-implantação).

Atualização 3 :

Adicionada função tkl_source_modulecomo analógica ao bash sourcecom suporte à execução de proteção na importação (implementada através da mesclagem de módulos em vez de importação).

Atualização 4 :

Função adicionada tkl_declare_globalpara exportar automaticamente uma variável global do módulo para todos os módulos filhos em que uma variável global do módulo não está visível porque não faz parte de um módulo filho.

Atualização 5 :

Todas as funções foram transferidas para a biblioteca tacklelib, veja o link acima.

Andry
fonte
2

Há um pacote dedicado a isso especificamente:

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')

Ele foi testado em versões do Python (Jython e PyPy também), mas pode ser um exagero, dependendo do tamanho do seu projeto.

fny
fonte
1

Adicionando isso à lista de respostas, pois não consegui encontrar nada que funcionasse. Isso permitirá a importação de módulos python compilados (pyd) no 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()
David
fonte
1

maneira bem simples: suponha que você queira importar o arquivo com o caminho relativo ../../MyLibs/pyfunc.py


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

Mas se você conseguir sem um guarda, poderá finalmente ter um caminho muito longo

Andrei Keino
fonte
1

Uma solução simples usando em importlibvez do imppacote (testado para Python 2.7, embora deva funcionar também para 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")

Agora você pode usar diretamente o espaço para nome do módulo importado, assim:

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

A vantagem desta solução é que nem precisamos saber o nome real do módulo que gostaríamos de importar , para usá-lo em nosso código. Isso é útil, por exemplo, caso o caminho do módulo seja um argumento configurável.

Ataxias
fonte
Dessa forma, você está modificando o sys.path, que não se encaixa em todos os casos de uso.
bgusach
@bgusach Isso pode ser verdade, mas também é desejável em alguns casos (adicionar um caminho ao sys.path simplifica as coisas ao importar mais de um módulo de um único pacote). De qualquer forma, se isso não é desejável, pode-se imediatamente depois fazersys.path.pop()
Ataxias
0

Esta resposta é um complemento à resposta de Sebastian Rittau, que responde ao comentário: "mas e se você não tiver o nome do módulo?" Essa é uma maneira rápida e suja de obter o nome provável do módulo python com um nome de arquivo - ele sobe na árvore até encontrar um diretório sem um __init__.pyarquivo e depois o transforma novamente em um nome de arquivo. Para o Python 3.4+ (usa pathlib), o que faz sentido, já que as pessoas do Py2 podem usar "imp" ou outras maneiras de fazer importações relativas:

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))

Certamente, existem possibilidades de aprimoramento, e os __init__.pyarquivos opcionais podem exigir outras alterações, mas se você tiver, __init__.pyem geral, isso funciona.

Michael Scott Cuthbert
fonte
-1

A melhor maneira, eu acho, é da documentação oficial ( 29.1. Imp - Acesse as informações internas de importação ):

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()
Zompa
fonte
1
Esta solução não permite que você forneça o caminho, que é o que a pergunta pede.
Micah Smith