Comment importer un module avec le chemin complet?

1143

Comment puis-je charger un module Python compte tenu de son chemin complet? Notez que le fichier peut se trouver n'importe où dans le système de fichiers, car il s'agit d'une option de configuration.

derfred
la source
21
Une question agréable et simple - et des réponses utiles, mais elles me font me demander ce qui s'est passé avec le mantra en python "Il y a une façon évidente " de le faire .. Cela ne ressemble en rien à une réponse simple ou évidente. Semble ridiculement hacky et dépendant de la version pour une opération aussi fondamentale (et il semble et plus gonflé dans les versions plus récentes ..).
inger

Réponses:

1283

Pour l'utilisation de 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()

Pour Python 3.3 et 3.4, utilisez:

from importlib.machinery import SourceFileLoader

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

(Bien que cela soit obsolète dans Python 3.4.)

Pour Python 2, utilisez:

import imp

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

Il existe des fonctions de commodité équivalentes pour les fichiers et DLL Python compilés.

Voir également http://bugs.python.org/issue21436 .

Sebastian Rittau
la source
53
Si je connaissais l'espace de noms - 'module.name' - j'utiliserais déjà __import__.
Sridhar Ratnakumar
62
@SridharRatnakumar la valeur du premier argument imp.load_sourcedéfinit uniquement .__name__le module retourné. cela n'affecte pas le chargement.
Dan D.
17
@DanD. - le premier argument de imp.load_source()détermine la clé de la nouvelle entrée créée dans le sys.modulesdictionnaire, donc le premier argument affecte effectivement le chargement.
Brandon Rhodes
9
Le impmodule est obsolète depuis la version 3.4: Le imppackage est en attente de dépréciation en faveur de importlib.
Chiel ten Brinke
9
@AXO et plus au point on se demande pourquoi quelque chose d'aussi simple et basique doit être si compliqué. Ce n'est pas dans beaucoup d'autres langues.
rocheux
424

L'avantage d'ajouter un chemin d'accès à sys.path (au lieu d'utiliser imp) est qu'il simplifie les choses lors de l'importation de plusieurs modules à partir d'un même package. Par exemple:

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
la source
13
Comment utilisons-nous sys.path.appendpour pointer vers un seul fichier python au lieu d'un répertoire?
Phani
28
:-) Peut-être que votre question conviendrait mieux comme une question StackOverflow, pas comme un commentaire sur une réponse.
Daryl Spitzer
3
Le chemin python peut contenir des archives zip, des "oeufs" (un type complexe d'archives zip), etc. Des modules peuvent être importés d'eux. Les éléments de chemin sont donc bien des conteneurs de fichiers, mais ce ne sont pas nécessairement des répertoires.
alexis
12
Méfiez-vous du fait que Python met en cache les instructions d'importation. Dans les rares cas où vous avez deux dossiers différents partageant un nom de classe unique (classX), l'approche consistant à ajouter un chemin d'accès à sys.path, à importer classX, à supprimer le chemin d'accès et à répéter pour les chemins de réassemblage ne fonctionnera pas. Python chargera toujours la classe depuis le premier chemin de son cache. Dans mon cas, je visais à créer un système de plugins où tous les plugins implémentent un classX spécifique. J'ai fini par utiliser SourceFileLoader , notez que sa dépréciation est controversée .
ComFreek
3
Notez que cette approche permet au module importé d'importer d'autres modules à partir du même répertoire, ce que font souvent les modules, contrairement à l'approche de la réponse acceptée (au moins sur 3.7). importlib.import_module(mod_name)peut être utilisé à la place de l'importation explicite ici si le nom du module n'est pas connu au moment de l'exécution, j'ajouterais un sys.path.pop()à la fin, cependant, en supposant que le code importé n'essaye pas d'importer plus de modules tel qu'il est utilisé.
Eli_B
43

Si votre module de niveau supérieur n'est pas un fichier mais est empaqueté comme un répertoire avec __init__.py, alors la solution acceptée fonctionne presque, mais pas tout à fait. Dans Python 3.5+, le code suivant est nécessaire (notez la ligne ajoutée qui commence par '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)

Sans cette ligne, lorsque exec_module est exécuté, il essaie de lier les importations relatives de votre niveau supérieur __init__.py au nom du module de niveau supérieur - dans ce cas, "mymodule". Mais "mymodule" n'est pas encore chargé, vous obtiendrez donc l'erreur "SystemError: le module parent 'mymodule' n'est pas chargé, ne peut pas effectuer d'importation relative". Vous devez donc lier le nom avant de le charger. La raison en est l'invariant fondamental du système d'importation relatif: "La position invariante est que si vous avez sys.modules ['spam'] et sys.modules ['spam.foo'] (comme vous le feriez après l'importation ci-dessus ), ce dernier doit apparaître comme l'attribut foo du premier ", comme expliqué ici .

Sam Grondahl
la source
Merci beaucoup! Cette méthode permet des importations relatives entre les sous-modules. Génial!
tebanep
Cette réponse correspond à la documentation ici: docs.python.org/3/library/… .
Tim Ludwinski
1
mais qu'est-ce que c'est mymodule?
Gulzar
@Gulzar, c'est le nom que vous souhaitez donner à votre module, que vous pourrez ensuite faire: "from mymodule import myclass"
Idodo
Alors ... /path/to/your/module/c'est en fait /path/to/your/PACKAGE/? et mymoduletu veux dire myfile.py?
Gulzar
37

Pour importer votre module, vous devez ajouter son répertoire à la variable d'environnement, temporairement ou définitivement.

Temporairement

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

En permanence

Ajout de la ligne suivante à votre .bashrcfichier (sous linux) et exécution source ~/.bashrcdans le terminal:

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

Crédit / Source: saarrrr , une autre question d'échange de pile

Miladiouss
la source
3
Cette solution "temporaire" est une excellente réponse si vous souhaitez développer un projet dans un cahier jupyter ailleurs.
fordy
Mais ... c'est dangereux d'altérer le chemin
Shai Alon
@ShaiAlon Vous ajoutez des chemins, donc pas de danger autre que lorsque vous transférez des codes d'un ordinateur à un autre, les chemins peuvent être gâchés. Donc, pour le développement de packages, j'importe uniquement des packages locaux. De plus, les noms de package doivent être uniques. Si vous êtes inquiet, utilisez la solution temporaire.
Miladiouss
28

Il semble que vous ne souhaitiez pas importer spécifiquement le fichier de configuration (qui comporte de nombreux effets secondaires et complications supplémentaires), vous voulez simplement l'exécuter et pouvoir accéder à l'espace de noms résultant. La bibliothèque standard fournit une API spécifiquement pour cela sous la forme de runpy.run_path :

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

Cette interface est disponible dans Python 2.7 et Python 3.2+

ncoghlan
la source
J'aime cette méthode, mais quand j'obtiens le résultat de run_path, c'est un dictionnaire auquel je n'arrive pas à accéder?
Stephen Ellwood
Qu'entendez-vous par «ne peut pas accéder»? Vous ne pouvez pas importer de lui (c'est pourquoi cela est seulement une bonne option lorsque n'est pas réellement nécessaire l' accès de style à l'importation), mais le contenu devrait être disponible via l'API dict régulière ( result[name], result.get('name', default_value), etc.)
ncoghlan
Cette réponse est bien sous-estimée. C'est très court et simple! Encore mieux, si vous avez besoin d'un espace de noms de module approprié, vous pouvez faire quelque chose comme from runpy import run_path; from argparse import Namespace; mod = Namespace(**run_path('path/to/file.py'))
RuRo
20

Vous pouvez également faire quelque chose comme ça et ajouter le répertoire dans lequel se trouve le fichier de configuration dans le chemin de chargement Python, puis faire juste une importation normale, en supposant que vous connaissez le nom du fichier à l'avance, dans ce cas "config".

Désordonné, mais cela fonctionne.

configfile = '~/config.py'

import os
import sys

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

import config
ctcherry
la source
Ce n'est pas dynamiquement.
Shai Alon
J'ai essayé: 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: Aucun module nommé config_file"
Shai Alon
17

Vous pouvez utiliser le

load_source(module_name, path_to_file) 

méthode du module imp .

zuber
la source
... et imp.load_dynamic(module_name, path_to_file)pour les DLL
HEKTO
34
prévient que le lutin est obsolète maintenant.
t1m0
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
la source
37
Pourquoi écrire 14 lignes de code buggé alors que cela est déjà traité par la bibliothèque standard? Vous n'avez pas effectué de vérification d'erreur sur le format ou le contenu de full_path_to_module ou des opérations os.wwhat; et l'utilisation d'une except:clause fourre-tout est rarement une bonne idée.
Chris Johnson
Vous devriez utiliser plus de "try-finally" ici. Par exemplesave_cwd = os.getcwd() try: … finally: os.chdir(save_cwd)
kay - SE is evil
11
@ChrisJohnson this is already addressed by the standard libraryoui, mais python a la mauvaise habitude de ne pas être rétrocompatible ... comme la réponse vérifiée dit qu'il y a 2 façons différentes avant et après 3.3. Dans ce cas, je préfère écrire ma propre fonction universelle que de vérifier la version à la volée. Et oui, peut-être que ce code n'est pas trop bien protégé contre les erreurs, mais il montre une idée (qui est os.chdir (), je n'y ai pas pensé), sur la base de laquelle je peux écrire un meilleur code. D'où +1.
Sushi271
13

Voici du code qui fonctionne dans toutes les versions de Python, de 2.7 à 3.5 et probablement même d'autres.

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

Je l'ai testé. C'est peut-être moche mais jusqu'à présent c'est le seul qui fonctionne dans toutes les versions.

Sorin
la source
1
Cette réponse a fonctionné pour moi où load_sourcene l'a pas fait car elle importe le script et fournit l'accès au script aux modules et aux globaux au moment de l'importation.
Klik
13

J'ai trouvé une version légèrement modifiée de la merveilleuse réponse de @ SebastianRittau (pour Python> 3.4 je pense), qui vous permettra de charger un fichier avec n'importe quelle extension en tant que module en utilisant spec_from_loaderau lieu 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)

L'avantage d'encoder le chemin de manière explicite SourceFileLoaderest que la machine n'essaiera pas de comprendre le type du fichier à partir de l'extension. Cela signifie que vous pouvez charger quelque chose comme un .txtfichier à l'aide de cette méthode, mais vous ne pouvez pas le faire spec_from_file_locationsans spécifier le chargeur car il .txtn'est pas dans importlib.machinery.SOURCE_SUFFIXES.

Physicien fou
la source
13

Voulez-vous dire charger ou importer?

Vous pouvez manipuler la sys.pathliste, spécifier le chemin d'accès à votre module, puis importer votre module. Par exemple, étant donné un module à:

/foo/bar.py

Vous pourriez faire:

import sys
sys.path[0:0] = ['/foo'] # puts the /foo directory at the start of your path
import bar
Blé
la source
1
@Wheat Pourquoi sys.path [0: 0] au lieu de sys.path [0]?
user618677
5
B / c sys.path [0] = xy écrase le premier élément de chemin tandis que path [0: 0] = xy est équivalent à path.insert (0, xy)
dom0
2
hm le path.insert a fonctionné pour moi mais l'astuce [0: 0] n'a pas fonctionné.
jsh
11
sys.path[0:0] = ['/foo']
Kevin Edwards du
6
Explicit is better than implicit.Alors pourquoi pas à la sys.path.insert(0, ...)place de sys.path[0:0]?
winklerrr
8

Je pense que vous pouvez utiliser imp.find_module()et imp.load_module()charger le module spécifié. Vous devrez séparer le nom du module du chemin d'accès, c'est-à-dire que si vous souhaitez charger, /home/mypath/mymodule.pyvous devrez faire:

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

... mais cela devrait faire le travail.

Mat
la source
6

Vous pouvez utiliser le pkgutilmodule (en particulier la walk_packagesméthode) pour obtenir une liste des packages dans le répertoire courant. De là, il est trivial d'utiliser la importlibmachinerie pour importer les modules que vous souhaitez:

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
la source
5

Créer le module python test.py

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

Créer le module python test_check.py

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

Nous pouvons importer le module importé depuis le module.

abhimanyu
la source
4

Cette zone de Python 3.4 semble être extrêmement tortueuse à comprendre! Cependant, avec un peu de piratage en utilisant le code de Chris Calloway pour commencer, j'ai réussi à faire fonctionner quelque chose. Voici la fonction de base.

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

Cela semble utiliser des modules non obsolètes de Python 3.4. Je ne prétends pas comprendre pourquoi, mais cela semble fonctionner à partir d'un programme. J'ai trouvé que la solution de Chris fonctionnait sur la ligne de commande mais pas depuis l'intérieur d'un programme.

Redlegjed
la source
4

Je ne dis pas que c'est mieux, mais par souci d'exhaustivité, je voulais suggérer la execfonction, disponible en python 2 et 3. exec vous permet d'exécuter du code arbitraire dans la portée globale ou dans une portée interne, fourni sous forme de dictionnaire.

Par exemple, si vous avez un module stocké dans "/path/to/module"avec la fonction foo(), vous pouvez l'exécuter en procédant comme suit:

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

Cela rend un peu plus explicite le chargement dynamique du code et vous accorde une puissance supplémentaire, telle que la possibilité de fournir des fonctions intégrées.

Et si avoir accès via des attributs, au lieu de clés est important pour vous, vous pouvez concevoir une classe de dict personnalisée pour les globaux, qui fournit un tel accès, par exemple:

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

Pour importer un module à partir d'un nom de fichier donné, vous pouvez temporairement étendre le chemin d'accès et restaurer le chemin d'accès système dans la référence de bloc finalement :

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
la source
4

Si nous avons des scripts dans le même projet mais dans des répertoires différents, nous pouvons résoudre ce problème par la méthode suivante.

Dans cette situation , utils.pyest ensrc/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
la source
1
le plus simple OMI
Ardhi
3

Cela devrait fonctionner

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
la source
4
Une façon de réduire le hors d'extension plus générale est: name, ext = os.path.splitext(os.path.basename(infile)). Votre méthode fonctionne car la restriction précédente à l'extension .py. En outre, vous devriez probablement importer le module dans une entrée de variable / dictionnaire.
ReneSac
2

J'ai fait un package qui utilise imppour vous. Je l'appelle import_fileet voici comment il est utilisé:

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

Vous pouvez l'obtenir à:

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

ou à

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

ubershmekel
la source
1
os.chdir? (caractères minimaux pour approuver le commentaire).
ychaouche
J'ai passé toute la journée à dépanner un bogue d'importation dans un exe généré par pyinstaller. En fin de compte, c'est la seule chose qui a fonctionné pour moi. Merci beaucoup d'avoir fait ça!
frakman1
2

Importer des modules de package lors de l'exécution (recette 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
la source
2

Sous Linux, l'ajout d'un lien symbolique dans le répertoire où se trouve votre script python fonctionne.

c'est à dire:

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

python créera /absolute/path/to/script/module.pyc et le mettra à jour si vous modifiez le contenu de/absolute/path/to/module/module.py

incluez ensuite les éléments suivants dans mypythonscript.py

from module import *
user2760152
la source
1
C'est le hack que j'ai utilisé, et cela m'a causé des problèmes. L'une des plus douloureuses est que IDEA a un problème où il ne récupère pas le code modifié à partir du lien, mais tente néanmoins de sauvegarder ce qu'il pense être là. Une condition de course où le dernier à sauver est ce qui colle ... J'ai perdu une quantité décente de travail à cause de cela.
Gripp
@Gripp ne sais pas si je comprends votre problème, mais je modifie fréquemment (presque exclusivement) mes scripts sur un serveur distant depuis mon bureau via SFTP avec un client comme CyberDuck, et dans ce cas également, c'est une mauvaise idée d'essayer de modifier le fichier lié, au lieu de cela, il est beaucoup plus sûr de modifier le fichier d'origine. Vous pouvez attraper certains de ces problèmes en utilisant gitet en vérifiant votre git statuspour vérifier que vos modifications apportées au script sont effectivement en train de revenir au document source et ne se perdent pas dans l'éther.
user5359531
2

J'ai écrit ma propre fonction d'importation globale et portable, basée sur le importlibmodule, pour:

  • Être capable d'importer les deux modules en tant que sous-module et d'importer le contenu d'un module dans un module parent (ou dans un global s'il n'a pas de module parent).
  • Pouvoir importer des modules avec un point dans un nom de fichier.
  • Être capable d'importer des modules avec n'importe quelle extension.
  • Pouvoir utiliser un nom autonome pour un sous-module au lieu d'un nom de fichier sans extension qui est par défaut.
  • Être en mesure de définir l'ordre d'importation en fonction du module précédemment importé au lieu de dépendre de sys.pathou sur un stockage de chemin de recherche.

Les exemples de structure de répertoires:

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

Dépendance et ordre d'inclusion:

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

La mise en oeuvre:

Dernier magasin de modifications: 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')

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

Testé en Python 3.7.4, 3.2.5,2.7.16

Avantages :

  • Peut importer les deux modules en tant que sous-module et peut importer le contenu d'un module dans un module parent (ou dans un global s'il n'a pas de module parent).
  • Peut importer des modules avec des points dans un nom de fichier.
  • Peut importer n'importe quel module d'extension à partir de n'importe quel module d'extension.
  • Peut utiliser un nom autonome pour un sous-module au lieu d'un nom de fichier sans extension qui est par défaut (par exemple, testlib.std.pyas testlib, testlib.blabla.pyas testlib_blablaet ainsi de suite).
  • Ne dépend pas d'un sys.pathou d'un stockage de chemin de recherche.
  • Ne nécessite pas de sauvegarder / restaurer des variables globales comme SOURCE_FILEet SOURCE_DIRentre les appels à tkl_import_module.
  • [pour 3.4.xet supérieur] Peut mélanger les espaces de noms de module dans des tkl_import_moduleappels imbriqués (ex: named->local->namedoulocal->named->local et ainsi de suite).
  • [pour 3.4.xet supérieur] Peut exporter automatiquement les variables / fonctions / classes globales d'où elles sont déclarées à tous les modules enfants importés via tkl_import_module(via la tkl_declare_globalfonction).

Inconvénients :

  • [pour 3.3.xet inférieur] Obligation de déclarer tkl_import_moduledans tous les modules qui appelle tkl_import_module(duplication de code)

Mise à jour 1,2 (pour 3.4.xet supérieur uniquement):

Dans Python 3.4 et supérieur, vous pouvez contourner l'exigence de déclaration tkl_import_moduledans chaque module en déclarant tkl_import_moduledans un module de niveau supérieur et la fonction s'injecterait à tous les modules enfants en un seul appel (c'est une sorte d'importation auto-déployée).

Mise à jour 3 :

Ajout d'une fonction tkl_source_moduleanalogique à bash sourceavec prise en charge de l'exécution lors de l'importation (implémentée via la fusion du module au lieu de l'importation).

Mise à jour 4 :

Ajout d'une fonction tkl_declare_globalpour exporter automatiquement une variable globale de module vers tous les modules enfants où une variable globale de module n'est pas visible car ne fait pas partie d'un module enfant.

Mise à jour 5 :

Toutes les fonctions ont été déplacées dans la bibliothèque tacklelib, voir le lien ci-dessus.

Andry
la source
2

Il existe un package spécifiquement dédié à cela:

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

Il est testé sur toutes les versions de Python (Jython et PyPy aussi), mais il peut être excessif en fonction de la taille de votre projet.

fny
la source
1

Ajout de cela à la liste des réponses car je n'ai rien trouvé qui fonctionnait. Cela permettra d'importer des modules python compilés (pyd) en 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
la source
1

manière assez simple: supposons que vous vouliez importer un fichier avec un chemin relatif ../../MyLibs/pyfunc.py


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

Mais si vous le faites sans gardien, vous pouvez enfin obtenir un très long chemin

Andrei Keino
la source
1

Une solution simple utilisant à la importlibplace du imppackage (testée pour Python 2.7, bien qu'elle devrait également fonctionner pour 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")

Vous pouvez maintenant utiliser directement l'espace de noms du module importé, comme ceci:

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

L'avantage de cette solution est que nous n'avons même pas besoin de connaître le nom réel du module que nous souhaitons importer , pour l'utiliser dans notre code. Ceci est utile, par exemple dans le cas où le chemin du module est un argument configurable.

Ataxies
la source
De cette façon, vous modifiez le sys.path, qui ne convient pas à tous les cas d'utilisation.
bgusach
@bgusach Cela peut être vrai, mais cela est également souhaitable dans certains cas (l'ajout d'un chemin d'accès à sys.path simplifie les choses lors de l'importation de plusieurs modules à partir d'un même package). En tout cas, si cela n'est pas souhaitable, on peut le faire immédiatement aprèssys.path.pop()
Ataxias
0

Cette réponse est un complément à la réponse de Sebastian Rittau répondant au commentaire: "mais si vous n'avez pas le nom du module?" C'est un moyen rapide et sale d'obtenir le nom probable du module python avec un nom de fichier - il monte simplement dans l'arborescence jusqu'à ce qu'il trouve un répertoire sans __init__.pyfichier, puis le retourne en nom de fichier. Pour Python 3.4+ (utilise pathlib), ce qui est logique puisque les utilisateurs de Py2 peuvent utiliser "imp" ou d'autres façons de faire des importations relatives:

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

Il existe certainement des possibilités d'amélioration, et les __init__.pyfichiers optionnels peuvent nécessiter d'autres modifications, mais si vous en avez __init__.pyen général, cela fait l'affaire.

Michael Scott Cuthbert
la source
-1

La meilleure façon, je pense, est de la documentation officielle ( 29.1. Imp - Accéder aux importations internes ):

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
la source
1
Cette solution ne vous permet pas de fournir le chemin d'accès, ce que demande la question.
Micah Smith