web-dev-qa-db-fra.com

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

Situation: - Il y a un module dans mon dossier projet_folder appelé calendar - J'aimerais utiliser la classe de calendrier intégrée à partir des bibliothèques Python - - Lorsque j'utilise un calendrier à partir de calendrier, il se plaint parce qu'il essaie charger 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?

105
twig

En fait, résoudre ce problème est plutôt facile, mais la mise en œuvre restera toujours un peu fragile, car cela dépend des éléments internes du mécanisme d'importation python et ils sont susceptibles de changer dans les versions futures.

(le code suivant montre comment charger des 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, dans la mesure du possible, consiste à éviter de nommer vos modules avec le même nom que les noms de modules de bibliothèque standard ou intégrés.

35
Boaz Yaniv

Changer le nom de votre module n'est pas nécessaire. Au lieu de cela, vous pouvez utiliser absolute_import pour modifier le comportement d'importation. Par exemple avec stem/socket.py J'importe le module de socket comme suit:

from __future__ import absolute_import
import socket

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

133
Damian

Le seul moyen de résoudre ce problème est de détourner vous-même la machine d'importation interne. Ce n'est pas facile, et périlleux. Vous devez éviter à tout prix le phare en forme de graal car le péril est trop périlleux.

Renommez votre module à la place.

Si vous souhaitez apprendre à détourner les machines d'importation internes, voici comment procéder:

Il y a parfois de bonnes raisons d'entrer dans ce péril. La raison pour laquelle 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 termine par un 'nom officiel' de sorte que Python peut éviter de devoir analyser le contenu de ce fichier.) module. Un mappage du 'nom officiel' d’un module sur son objet module se trouve dans sys.modules.

Cela signifie que si vous import calendar à un endroit, tout module importé sera considéré comme le module portant le nom officiel calendar et toutes les autres tentatives visant à import calendar n'importe où ailleurs, y compris dans un code qui fait partie de la bibliothèque principale Python), obtiendra ce calendrier.

Il serait peut-être possible de concevoir un importateur client à l'aide du module imputil dans Python 2.x à l'origine du chargement des modules à partir de certains chemins pour rechercher les modules qu'ils importaient dans autre chose que sys.modules premier ou quelque chose comme ça. Mais c'est une chose extrêmement poilue à faire, et cela ne fonctionnera pas dans Python 3.x de toute façon.

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

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__])
14
Omnifarious

J'aimerais proposer ma version, qui combine les solutions de Boaz Yaniv et d'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
  • Remplace-t-elle directement la déclaration d'importation sur les modules système, ce qui signifie que vous devez simplement remplacer cette ligne et que, si des appels sont déjà passés vers le module, ils fonctionneront tels quels.

Mettez ceci quelque part accessible pour 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 paquet 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)
1
casey