Importation à partir de la bibliothèque intégrée lorsqu'un module avec le même nom existe

121

Situation: - Il y a un module dans mon project_folder appelé calendrier - Je voudrais utiliser la classe Calendar intégrée des bibliothèques Python - Lorsque j'utilise à partir de Calendar Import Calendar, il se plaint parce qu'il essaie de se charger à partir de mon module.

J'ai fait quelques recherches et je n'arrive pas à trouver une solution à mon problème.

Des idées sans avoir à renommer mon module?

brindille
la source
24
Il est recommandé de ne pas nommer les modules pour masquer les modules intégrés.
the_drow
3
La solution est de "choisir un nom différent". Votre approche de ne pas renommer est une mauvaise idée. Pourquoi ne pouvez-vous pas renommer votre module? Quel est le problème avec le changement de nom?
S.Lott
En effet. C'est parce qu'il n'y a pas de bonne réponse à cette question que l'observation des modules stdlib est si fortement déconseillée.
ncoghlan
J'ai évité d'utiliser le même nom de module car les solutions semblaient plus problématiques qu'elles n'en valaient la peine. Merci!
brindille
9
@the_drow Ce conseil n'est pas à l'échelle, pur et simple. PEP328 le reconnaît volontiers.
Konrad Rudolph

Réponses:

4

La solution acceptée contient une approche désormais obsolète.

La documentation importlib donne ici un bon exemple de la manière la plus appropriée de charger un module directement à partir d'un chemin de fichier pour python> = 3.5:

import importlib.util
import sys

# For illustrative purposes.
import tokenize
file_path = tokenize.__file__  # returns "/path/to/tokenize.py"
module_name = tokenize.__name__  # returns "tokenize"

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)

Ainsi, vous pouvez charger n'importe quel fichier .py à partir d'un chemin et définir le nom du module comme vous le souhaitez. Donc, ajustez simplement le module_namepour qu'il soit le nom personnalisé que vous souhaitez que le module ait lors de l'importation.

Pour charger un package au lieu d'un seul fichier, file_pathdevrait être le chemin vers la racine du package__init__.py

Brandon Squizzato
la source
Fonctionne comme un charme ... Utilisé pour tester lors du développement d'une bibliothèque, de sorte que mes tests utilisaient toujours la version en développement et non la version publiée (et installée). Dans les fenêtres 10 je devais écrire le chemin de mon module comme ceci: file_path=r"C:\Users\My User\My Path\Module File.py". Ensuite, j'ai appelé module_namecomme le module publié pour avoir un script de travail complet qui, enlevé cet extrait, pourrait être utilisé sur d'autres
ordinateurs
141

Il n'est pas nécessaire de changer le nom de votre module. Vous pouvez plutôt utiliser l’importation_absolue pour modifier le comportement d’importation. Par exemple, avec stem / socket.py, j'importe le module socket comme suit:

from __future__ import absolute_import
import socket

Cela ne fonctionne qu'avec Python 2.5 et supérieur; c'est un comportement d'activation qui est le comportement par défaut dans Python 3.0 et supérieur. Pylint se plaindra du code mais il est parfaitement valide.

Damian
la source
4
Cela me semble la bonne réponse. Voir le changelog 2.5 ou PEP328 pour plus.
Pieter Ennes
5
C'est la bonne solution. Malheureusement, cela ne fonctionne pas lorsque le code à partir du package est lancé car le package n'est pas reconnu en tant que tel et le chemin local est ajouté au début PYTHONPATH. Une autre question montre comment résoudre cela.
Konrad Rudolph
5
Voilà la solution. J'ai vérifié Python 2.7.6 et cela est obligatoire, ce n'est toujours pas la valeur par défaut.
Havok
3
En effet: La première version python où ce comportement est la valeur par défaut est de 3,0, selon docs.python.org/2/library/__future__.html
abus de langage
1
Ensuite, ne nommez pas votre module principal comme étant en conflit avec un module intégré.
Antti Haapala
38

En fait, résoudre ce problème est assez facile, mais l'implémentation sera toujours un peu fragile, car cela dépend des composants internes du mécanisme d'importation python et ils sont susceptibles de changer dans les versions futures.

(le code suivant montre comment charger les modules locaux et non locaux et comment ils peuvent coexister)

def import_non_local(name, custom_name=None):
    import imp, sys

    custom_name = custom_name or name

    f, pathname, desc = imp.find_module(name, sys.path[1:])
    module = imp.load_module(custom_name, f, pathname, desc)
    f.close()

    return module

# Import non-local module, use a custom name to differentiate it from local
# This name is only used internally for identifying the module. We decide
# the name in the local scope by assigning it to the variable calendar.
calendar = import_non_local('calendar','std_calendar')

# import local module normally, as calendar_local
import calendar as calendar_local

print calendar.Calendar
print calendar_local

La meilleure solution, si possible, est d'éviter de nommer vos modules avec le même nom que les noms de bibliothèque standard ou de module intégré.

Boaz Yaniv
la source
Comment cela interagira-t-il avec sys.modulesles tentatives ultérieures de chargement du module local?
Omnifariable
@Omnifarious: Il ajoutera le module à sys.modules avec son nom, ce qui empêchera de charger le module local. Vous pouvez toujours utiliser un nom personnalisé pour éviter cela.
Boaz Yaniv
@Boaz Yaniv: Vous devriez utiliser un nom personnalisé pour le calendrier local, pas le nom standard. D'autres modules Python peuvent essayer d'importer le module standard. Et si vous faites cela, ce que vous obtenez est de renommer le module local sans avoir à renommer le fichier.
Omnifariable
@Omnifarious: Vous pouvez le faire de toute façon. Un autre code peut essayer de charger le module local et obtenir la même erreur. Vous devrez faire un compromis, et c'est à vous de décider quel module prendre en charge.
Boaz Yaniv
2
Merci pour ce Boaz! Bien que votre extrait de code soit plus court (et documenté), je pense qu'il est simplement plus facile de renommer le module que d'avoir un code piraté qui pourrait dérouter les gens (ou moi-même) à l'avenir.
twig
15

La seule façon de résoudre ce problème est de détourner vous-même la machinerie d'importation interne. Ce n’est pas facile et risqué. Vous devez éviter à tout prix la balise en forme de graal car le péril est trop périlleux.

Renommez plutôt votre module.

Si vous voulez apprendre à détourner la machinerie d'importation interne, voici où vous iriez pour savoir comment procéder:

Il y a parfois de bonnes raisons de courir ce péril. La raison que vous donnez n'est pas parmi eux. Renommez votre module.

Si vous prenez le chemin périlleux, un problème que vous rencontrerez est que lorsque vous chargez un module, il se retrouve avec un `` nom officiel '' afin que Python puisse éviter d'avoir à analyser à nouveau le contenu de ce module. Un mappage du «nom officiel» d'un module à l'objet module lui-même peut être trouvé dans sys.modules.

Cela signifie que si vous êtes import calendarà un endroit, quel que soit le module importé, sera considéré comme le module avec le nom officiel calendaret toutes les autres tentatives vers import calendarn'importe où ailleurs, y compris dans un autre code faisant partie de la bibliothèque principale Python, obtiendront ce calendrier.

Il serait peut-être possible de concevoir un importateur client en utilisant le module imputil de Python 2.x qui obligeait les modules chargés à partir de certains chemins à rechercher les modules qu'ils importaient dans autre chose que le sys.modulespremier ou quelque chose du genre. Mais c'est une chose extrêmement épineuse à faire, et cela ne fonctionnera pas de toute façon dans Python 3.x.

Il y a une chose extrêmement laide et horrible que vous pouvez faire qui n'implique pas d'accrochage au mécanisme d'importation. C'est quelque chose que vous ne devriez probablement pas faire, mais cela fonctionnera probablement. Il transforme votre calendarmodule en un hybride du module de calendrier système et de votre module de calendrier. Merci à Boaz Yaniv pour le squelette de la fonction que j'utilise . Mettez ceci au début de votre calendar.pyfichier:

import sys

def copy_in_standard_module_symbols(name, local_module):
    import imp

    for i in range(0, 100):
        random_name = 'random_name_%d' % (i,)
        if random_name not in sys.modules:
            break
        else:
            random_name = None
    if random_name is None:
        raise RuntimeError("Couldn't manufacture an unused module name.")
    f, pathname, desc = imp.find_module(name, sys.path[1:])
    module = imp.load_module(random_name, f, pathname, desc)
    f.close()
    del sys.modules[random_name]
    for key in module.__dict__:
        if not hasattr(local_module, key):
            setattr(local_module, key, getattr(module, key))

copy_in_standard_module_symbols('calendar', sys.modules[copy_in_standard_module_symbols.__module__])
Très varié
la source
imputil est considéré comme obsolète. Vous devez utiliser le module imp .
Boaz Yaniv
Ce qui est parfaitement compatible avec Python 3, d'ailleurs. Et pas si poilu à utiliser du tout. Mais vous devez toujours être conscient que le code qui repose sur Python traitant les chemins d'une manière ou recherchant des modules dans cet ordre peut se rompre tôt ou tard.
Boaz Yaniv
1
Bien, mais dans un cas aussi isolé (collision de nom de module), accrocher le mécanisme d'importation est une exagération. Et comme il est poilu et incompatible, il vaut mieux le laisser seul.
Boaz Yaniv
1
@jspacek non, jusqu'ici tout va bien, mais la collision ne se produirait que lors de l'utilisation du débogueur de PyDev, pas en utilisation régulière. Et assurez-vous de vérifier le dernier code (l'URL dans github), car il a un peu changé par rapport à la réponse ci-dessus
MestreLion
1
@jspacek: C'est un jeu, pas une bibliothèque, donc dans mon cas, la rétrocompatibilité n'est pas du tout un problème. Et la collision d'espace de noms se produit uniquement lors de l'utilisation de PyDev IDE (qui utilise le codemodule std de Python ), ce qui signifie que seule une fraction des développeurs pourrait avoir des problèmes avec ce "hack de fusion". Les utilisateurs ne seraient pas du tout affectés.
MestreLion
1

Je voudrais proposer ma version, qui est une combinaison de la solution de Boaz Yaniv et Omnifarious. Il importera la version système d'un module, avec deux différences principales par rapport aux réponses précédentes:

  • Prend en charge la notation «point», par exemple. package.module
  • Est un remplacement instantané de l'instruction d'importation sur les modules système, ce qui signifie qu'il vous suffit de remplacer cette ligne et s'il y a déjà des appels vers le module, ils fonctionneront tels quels

Mettez ceci quelque part accessible afin que vous puissiez l'appeler (j'ai le mien dans mon fichier __init__.py):

class SysModule(object):
    pass

def import_non_local(name, local_module=None, path=None, full_name=None, accessor=SysModule()):
    import imp, sys, os

    path = path or sys.path[1:]
    if isinstance(path, basestring):
        path = [path]

    if '.' in name:
        package_name = name.split('.')[0]
        f, pathname, desc = imp.find_module(package_name, path)
        if pathname not in __path__:
            __path__.insert(0, pathname)
        imp.load_module(package_name, f, pathname, desc)
        v = import_non_local('.'.join(name.split('.')[1:]), None, pathname, name, SysModule())
        setattr(accessor, package_name, v)
        if local_module:
            for key in accessor.__dict__.keys():
                setattr(local_module, key, getattr(accessor, key))
        return accessor
    try:
        f, pathname, desc = imp.find_module(name, path)
        if pathname not in __path__:
            __path__.insert(0, pathname)
        module = imp.load_module(name, f, pathname, desc)
        setattr(accessor, name, module)
        if local_module:
            for key in accessor.__dict__.keys():
                setattr(local_module, key, getattr(accessor, key))
            return module
        return accessor
    finally:
        try:
            if f:
                f.close()
        except:
            pass

Exemple

Je voulais importer mysql.connection, mais j'avais déjà un paquet local appelé mysql (les utilitaires officiels de mysql). Donc, pour obtenir le connecteur du package mysql du système, j'ai remplacé ceci:

import mysql.connector

Avec ça:

import sys
from mysql.utilities import import_non_local         # where I put the above function (mysql/utilities/__init__.py)
import_non_local('mysql.connector', sys.modules[__name__])

Résultat

# This unmodified line further down in the file now works just fine because mysql.connector has actually become part of the namespace
self.db_conn = mysql.connector.connect(**parameters)
Casey
la source
-2

Modifiez le chemin d'importation:

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path
linuts
la source
Cela ne fonctionnera pas car après avoir fait cela, il n'y aura aucun moyen d'importer le module local sans avoir à jouer vous-même avec la machine d'importation.
Omnifariable
@Omnifarious: c'est un problème différent, que vous pouvez contourner avec un troisième module qui effectue une importation à partir du calendrier *.
linuts
Non, cela ne fonctionnerait probablement pas car python met en cache le nom du module dans sys.modules, et il n'importera pas à nouveau un module avec le même nom.
Boaz Yaniv