Importieren aus der integrierten Bibliothek, wenn ein Modul mit demselben Namen vorhanden ist

121

Situation: - In meinem Projektordner befindet sich ein Modul namens "Kalender". - Ich möchte die integrierte Kalenderklasse aus den Python-Bibliotheken verwenden. - Wenn ich den Kalenderimportkalender verwende, beschwert es sich, weil versucht wird, von meinem Modul zu laden.

Ich habe einige Suchanfragen durchgeführt und kann anscheinend keine Lösung für mein Problem finden.

Irgendwelche Ideen, ohne mein Modul umbenennen zu müssen?

Zweig
quelle
24
Es wird empfohlen, keine Module zu benennen, um integrierte Module auszublenden.
the_drow
3
Die Lösung lautet "Wählen Sie einen anderen Namen". Ihr Ansatz, nicht umzubenennen, ist eine schlechte Idee. Warum können Sie Ihr Modul nicht umbenennen? Was ist falsch am Umbenennen?
S.Lott
Tatsächlich. Weil es keine gute Antwort auf diese Frage gibt, wird davon abgeraten, stdlib-Module zu beschatten.
Ncoghlan
Ich habe es vermieden, denselben Modulnamen zu verwenden, da die Lösungen mehr Probleme zu bereiten schienen, als es wert ist. Vielen Dank!
Zweig
9
@the_drow Dieser Rat ist schlicht und einfach nicht skalierbar. PEP328 erkennt dies ohne weiteres an.
Konrad Rudolph

Antworten:

4

Die akzeptierte Lösung enthält einen inzwischen veralteten Ansatz.

Die importlib-Dokumentation hier gibt ein gutes Beispiel für die geeignetere Methode zum Laden eines Moduls direkt aus einem Dateipfad für 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)

Sie können also jede .py-Datei aus einem Pfad laden und den Modulnamen so einstellen, wie Sie möchten. Passen Sie also einfach den module_namebenutzerdefinierten Namen an, den das Modul beim Importieren haben soll.

Um ein Paket anstelle einer einzelnen Datei zu laden, file_pathsollte der Pfad zum Stammverzeichnis des Pakets angegeben werden__init__.py

Brandon Squizzato
quelle
Funktioniert wie ein Zauber ... Wird zum Testen während der Entwicklung einer Bibliothek verwendet, sodass meine Tests immer die Entwicklungsversion und nicht die veröffentlichte (und installierte) Version verwendeten. In Windows 10 musste ich den Pfad zu meinem Modul folgendermaßen schreiben : file_path=r"C:\Users\My User\My Path\Module File.py". Dann rief ich module_namegenau wie das veröffentlichte Modul an, so dass ich ein voll funktionsfähiges Skript hatte, das, entfernt von diesem Snippet, auf anderen PCs verwendet werden konnte
Luke Savefrogs
141

Das Ändern des Namens Ihres Moduls ist nicht erforderlich. Sie können vielmehr absolute_import verwenden, um das Importverhalten zu ändern. Zum Beispiel importiere ich mit stem / socket.py das Socket-Modul wie folgt:

from __future__ import absolute_import
import socket

Dies funktioniert nur mit Python 2.5 und höher. Es aktiviert das Verhalten, das in Python 3.0 und höher standardmäßig verwendet wird. Pylint wird sich über den Code beschweren, aber er ist vollkommen gültig.

Damian
quelle
4
Dies scheint mir die richtige Antwort zu sein. Weitere Informationen finden Sie im 2.5-Änderungsprotokoll oder in PEP328 .
Pieter Ennes
5
Dies ist die richtige Lösung. Leider funktioniert es nicht, wenn Code aus dem Paket heraus gestartet wird, da das Paket dann nicht als solches erkannt wird und dem lokalen Pfad vorangestellt wird PYTHONPATH. Eine andere Frage zeigt, wie man das löst.
Konrad Rudolph
5
Das ist die Lösung. Ich habe nach Python 2.7.6 gesucht und dies ist erforderlich, es ist immer noch nicht die Standardeinstellung.
Havok
3
In der Tat: Die erste Python-Version, in der dieses Verhalten die Standardeinstellung ist, war laut docs.python.org/2/library/__future__.html
falsche Bezeichnung
1
Dann nicht nennen Ihr Hauptmodul als ein , dass Kollisionen mit einem eingebauten Modul.
Antti Haapala
38

Eigentlich ist es ziemlich einfach, dies zu lösen, aber die Implementierung wird immer etwas fragil sein, da dies von den Interna des Python-Importmechanismus abhängt und sie in zukünftigen Versionen Änderungen unterliegen.

(Der folgende Code zeigt, wie sowohl lokale als auch nicht lokale Module geladen werden und wie sie nebeneinander existieren können.)

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

Die beste Lösung besteht nach Möglichkeit darin, zu vermeiden, dass Ihre Module mit demselben Namen wie die Standardbibliothek oder die integrierten Modulnamen benannt werden.

Boaz Yaniv
quelle
Wie wird dies mit sys.modulesund nachfolgenden Versuchen, das lokale Modul zu laden, interagieren ?
Omnifarious
@Omnifarious: Das Modul wird sys.modules mit seinem Namen hinzugefügt, wodurch das Laden des lokalen Moduls verhindert wird. Sie können jederzeit einen benutzerdefinierten Namen verwenden, um dies zu vermeiden.
Boaz Yaniv
@Boaz Yaniv: Sie sollten einen benutzerdefinierten Namen für den lokalen Kalender verwenden, nicht den Standardnamen. Andere Python-Module versuchen möglicherweise, das Standardmodul zu importieren. Und wenn Sie das tun, erreichen Sie damit im Grunde das lokale Modul umbenennen, ohne die Datei umbenennen zu müssen.
Omnifarious
@ Omnifarious: Du könntest es so oder so machen. Ein anderer Code versucht möglicherweise, das lokale Modul zu laden und den gleichen Fehler zu erhalten. Sie müssen einen Kompromiss eingehen, und es liegt an Ihnen, zu entscheiden, welches Modul unterstützt werden soll.
Boaz Yaniv
2
Danke für diesen Boaz! Obwohl Ihr Snippet kürzer (und dokumentierter) ist, ist es meiner Meinung nach einfacher, das Modul umzubenennen, als Hacky-Code zu haben, der die Leute (oder mich selbst) in Zukunft verwirren könnte.
Zweig
15

Die einzige Möglichkeit, dieses Problem zu lösen, besteht darin, die internen Importmaschinen selbst zu entführen. Das ist nicht einfach und voller Gefahren. Sie sollten das gralsförmige Leuchtfeuer um jeden Preis meiden, da die Gefahr zu gefährlich ist.

Benennen Sie stattdessen Ihr Modul um.

Wenn Sie lernen möchten, wie Sie die internen Importmaschinen entführen, erfahren Sie hier, wie Sie dies tun können:

Es gibt manchmal gute Gründe, in diese Gefahr zu geraten. Der Grund, den Sie angeben, ist nicht unter ihnen. Benennen Sie Ihr Modul um.

Wenn Sie den gefährlichen Pfad einschlagen, wird ein Problem darin bestehen, dass beim Laden eines Moduls ein 'offizieller Name' angezeigt wird, sodass Python vermeiden kann, den Inhalt dieses Moduls jemals wieder analysieren zu müssen. Eine Zuordnung des 'offiziellen Namens' eines Moduls zum Modulobjekt selbst finden Sie in sys.modules.

Dies bedeutet, dass, wenn Sie sich import calendaran einem Ort befinden, jedes importierte Modul als das Modul mit dem offiziellen Namen betrachtet wird calendarund alle anderen Versuche, import calendarirgendwo anders, einschließlich in anderem Code, der Teil der Python-Hauptbibliothek ist, diesen Kalender erhalten.

Es ist möglicherweise möglich, einen Kundenimporter mithilfe des imputil-Moduls in Python 2.x zu entwerfen, das dazu führte , dass Module, die aus bestimmten Pfaden geladen wurden, die von ihnen importierten Module in etwas anderem als dem sys.modulesersten oder ähnlichem nachschlagen . Aber das ist eine extrem haarige Sache, und es wird in Python 3.x sowieso nicht funktionieren.

Es gibt eine extrem hässliche und schreckliche Sache, die Sie tun können, ohne den Importmechanismus anzuschließen. Dies ist etwas, was Sie wahrscheinlich nicht tun sollten, aber es wird wahrscheinlich funktionieren. Es verwandelt Ihr calendarModul in eine Mischung aus dem Systemkalendermodul und Ihrem Kalendermodul. Vielen Dank an Boaz Yaniv für das Skelett der Funktion, die ich benutze . Fügen Sie dies am Anfang Ihrer calendar.pyDatei ein:

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__])
Allgegenwärtig
quelle
imputil gilt als veraltet. Sie sollten das imp- Modul verwenden.
Boaz Yaniv
Was übrigens perfekt mit Python 3 kompatibel ist. Und gar nicht so haarig. Sie sollten sich jedoch immer bewusst sein, dass Code, der darauf basiert, dass Python Pfade auf eine Weise behandelt oder Module in dieser Reihenfolge nachschlägt, früher oder später beschädigt werden kann.
Boaz Yaniv
1
Richtig, aber in einem solchen Einzelfall (Kollision mit Modulnamen) ist das Einbinden des Importmechanismus ein Overkill. Und da es haarig und inkompatibel ist, ist es besser, es in Ruhe zu lassen.
Boaz Yaniv
1
@jspacek Nein, soweit so gut, aber eine Kollision würde nur auftreten, wenn der Debbuger von PyDev verwendet wird, nicht bei regelmäßiger Verwendung. Und stellen Sie sicher, dass Sie den neuesten Code (die URL in Github) überprüfen, da er sich ein wenig von der obigen Antwort
geändert hat
1
@jspacek: Es ist ein Spiel, keine Bibliothek, also ist in meinem Fall die Abwärtskompatibilität überhaupt kein Problem. Und die Namespace-Kollision tritt nur bei Verwendung über PyDev IDE (das Pythons Standardmodul verwendet code) auf, was bedeutet, dass nur ein Bruchteil der Entwickler jemals Probleme mit diesem "Zusammenführungs-Hack" haben könnte. Benutzer wären überhaupt nicht betroffen.
MestreLion
1

Ich möchte meine Version anbieten, die eine Kombination aus der Lösung von Boaz Yaniv und Omnifarious ist. Es wird die Systemversion eines Moduls importiert, mit zwei Hauptunterschieden zu den vorherigen Antworten:

  • Unterstützt die 'Punkt'-Notation, z. package.module
  • Ist ein Drop-In-Ersatz für die Importanweisung für Systemmodule, dh Sie müssen nur diese eine Zeile ersetzen, und wenn bereits Aufrufe an das Modul erfolgen, funktionieren diese unverändert

Stellen Sie dies an einen zugänglichen Ort, damit Sie es aufrufen können (ich habe meine in meiner __init__.py-Datei):

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

Beispiel

Ich wollte mysql.connection importieren, hatte aber bereits ein lokales Paket namens mysql (die offiziellen mysql-Dienstprogramme). Um den Connector aus dem System-MySQL-Paket zu erhalten, habe ich Folgendes ersetzt:

import mysql.connector

Mit diesem:

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

Ergebnis

# 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
quelle
-2

Ändern Sie den Importpfad:

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path
Linuts
quelle
Dies funktioniert nicht, da es danach nicht mehr möglich ist, das lokale Modul zu importieren, ohne selbst an den Importmaschinen herumzuspielen.
Omnifarious
@ Omnifarious: Das ist ein anderes Problem, das Sie mit einem dritten Modul umgehen können, das einen Import aus dem Kalender * ausführt.
Linuts
Nein, dies würde wahrscheinlich nicht funktionieren, da Python den Modulnamen zwischenspeichert sys.modulesund kein Modul mit demselben Namen erneut importiert.
Boaz Yaniv