133 votes

Importation à partir d'une bibliothèque intégrée alors qu'il existe un module du même nom

Situation : - Il y a un module dans mon dossier de projet appelé calendrier. - Je voudrais utiliser la classe Calendar intégrée dans les bibliothèques Python. - Lorsque j'utilise from calendar import Calendar, il se plaint car il essaie de charger à partir de mon module.

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

Une idée sans avoir à renommer mon module ?

25 votes

C'est une bonne pratique de ne pas nommer les modules pour cacher les modules intégrés.

3 votes

La solution est de "choisir un autre nom". Votre approche consistant à ne pas changer de nom est une mauvaise idée. Pourquoi ne pouvez-vous pas renommer votre module ? Quel est le problème avec le renommage ?

0 votes

En effet. C'est parce qu'il y a pas de bonne réponse à cette question que l'observation des modules stdlib est si fortement déconseillée.

143voto

Damian Points 759

Il n'est pas nécessaire de changer le nom de votre module. Vous pouvez plutôt utiliser absolute_import 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 plus ; il s'agit d'activer un comportement qui est par défaut dans Python 3.0 et plus. Pylint se plaindra de ce code mais il est parfaitement valide.

4 votes

Cela me semble être la bonne réponse. Voir le 2.5 changelog o PEP328 pour plus.

6 votes

C'est la bonne solution. Malheureusement, elle ne fonctionne pas lorsque du code provenant du paquet est lancé, car alors le paquet n'est pas reconnu comme tel, et le chemin local est ajouté en préambule à PYTHONPATH . Une autre question montre comment résoudre ce problème.

5 votes

Voici la solution. J'ai vérifié pour Python 2.7.6 et c'est requis, ce n'est toujours pas la valeur par défaut.

38voto

Boaz Yaniv Points 4012

En fait, la résolution de ce problème est plutôt facile, mais l'implémentation sera toujours un peu fragile, parce qu'elle dépend des internes du mécanisme d'importation de python et qu'ils sont sujets à des changements dans les futures versions.

(le code suivant montre comment charger à la fois 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, si possible, est d'éviter de nommer vos modules avec le même nom que les noms de modules standard ou intégrés.

0 votes

Comment cela va-t-il interagir avec sys.modules et les tentatives ultérieures de charger le module local ?

0 votes

@Omnifarious : Il ajoutera le module à sys.modules avec son nom, ce qui empêchera le chargement du module local. Vous pouvez toujours utiliser un nom personnalisé pour éviter cela.

0 votes

@Boaz Yaniv : Vous devriez utiliser un nom personnalisé pour le calendrier local, et non le nom standard. D'autres modules Python pourraient essayer d'importer le calendrier standard. Et si vous faites cela, ce que vous obtenez avec cela est essentiellement de renommer le module local sans avoir à renommer le fichier.

15voto

Omnifarious Points 25666

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

Renommez plutôt votre module.

Si vous voulez apprendre à détourner le mécanisme d'importation interne, c'est ici que vous devez trouver comment le faire :

Il y a parfois de bonnes raisons de se lancer dans ce péril. La raison que vous donnez n'en fait pas partie. Renommez votre module.

Si vous empruntez cette voie périlleuse, vous rencontrerez un problème : lorsque vous chargez un module, celui-ci se voit attribuer un "nom officiel" afin que Python puisse éviter de devoir analyser le contenu de ce module. Une correspondance entre le "nom officiel" d'un module et l'objet module lui-même peut être trouvée dans le document suivant sys.modules .

Cela signifie que si vous import calendar en un seul endroit, quel que soit le module importé, il sera considéré comme le module portant le nom officiel. calendar et toutes les autres tentatives de import calendar partout ailleurs, y compris dans d'autres codes faisant partie de la bibliothèque principale de Python, obtiendra ce calendrier.

Il est possible de concevoir un importateur de clients en utilisant l'option module imputil dans Python 2.x qui faisait que les modules chargés à partir de certains chemins recherchaient les modules qu'ils importaient dans autre chose que sys.modules d'abord ou quelque chose comme ça. Mais c'est une chose extrêmement dangereuse à faire, et cela ne fonctionnera pas dans Python 3.x de toute façon.

Il y a une chose extrêmement laide et horrible que vous pouvez faire qui n'implique pas d'accrocher le mécanisme d'importation. C'est quelque chose que vous ne devriez probablement pas faire, mais qui fonctionnera probablement. Cela transforme votre calendar en un hybride du module de calendrier du système et de votre module de calendrier. Grâce à 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__])

0 votes

Imputil est considéré comme déprécié. Vous devriez utiliser le imp module.

0 votes

Ce qui est parfaitement compatible avec Python 3, d'ailleurs. Et n'est pas du tout difficile à utiliser. Mais vous devez toujours être conscient que le code qui repose sur le fait que python traite les chemins d'une certaine manière ou recherche les modules dans cet ordre peut se casser tôt ou tard.

0 votes

@Boaz Yaniv : Je sais, mais il ne semble pas qu'il y ait de remplacement pour cette fonctionnalité avant Python 3. Pour autant que je sache, le imp fournit simplement une implémentation du mécanisme d'importation standard, et non un moyen d'accrocher globalement le mécanisme d'importation pour lui faire faire ce que vous voulez.

1voto

casey Points 697

J'aimerais proposer ma version, qui est une combinaison de la solution de Boaz Yaniv et de celle d'Omnifarious. Elle permet d'importer la version système d'un module, avec deux différences principales par rapport aux réponses précédentes :

  • Supporte la notation "point", par exemple package.module
  • Il s'agit d'un remplacement immédiat de l'instruction d'importation des modules du système, ce qui signifie qu'il suffit de remplacer cette ligne et que si des appels sont déjà faits au module, ils fonctionneront tels quels.

Mettez-le dans un endroit 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 à partir 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)

-2voto

linuts Points 2358

Modifiez le chemin d'importation :

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path

0 votes

Cela ne fonctionnera pas parce qu'après avoir fait cela, il n'y aura aucun moyen d'importer le module local sans avoir à manipuler vous-même la machinerie d'importation.

0 votes

@Omnifarious : c'est un problème différent, que vous pouvez contourner avec un troisième module qui fait un from calendar import *.

0 votes

Non, cela ne fonctionnerait probablement pas car python met en cache le nom du module dans le fichier sys.modules et il n'importera pas à nouveau un module portant le même nom.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X