Importazione dalla libreria incorporata quando esiste un modulo con lo stesso nome


121

Situazione: - C'è un modulo nella mia cartella project_folder chiamato calendar - Vorrei usare la classe Calendar incorporata dalle librerie Python - Quando uso dall'importazione del calendario Calendar si lamenta perché sta cercando di caricare dal mio modulo.

Ho effettuato alcune ricerche e non riesco a trovare una soluzione al mio problema.

Qualche idea senza dover rinominare il mio modulo?


24
È una buona pratica non nominare i moduli per nascondere i moduli incorporati.
the_drow

3
La soluzione è "scegli un nome diverso". Il tuo approccio di non rinominare è una cattiva idea. Perché non puoi rinominare il tuo modulo? Cosa c'è di sbagliato nel rinominare?
S.Lott

Infatti. È perché non esiste una buona risposta a questa domanda che l'ombreggiatura dei moduli stdlib è così fortemente sconsigliata.
ncoghlan

Ho evitato di usare lo stesso nome del modulo poiché le soluzioni sembravano più problemi di quanto valesse. Grazie!
ramoscello

9
@the_drow Questo consiglio non è scalabile, puro e semplice. PEP328 lo riconosce prontamente.
Konrad Rudolph,

Risposte:


4

La soluzione accettata contiene un approccio ormai deprecato.

La documentazione importlib qui fornisce un buon esempio del modo più appropriato per caricare un modulo direttamente da un percorso di file per 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)

Quindi, puoi caricare qualsiasi file .py da un percorso e impostare il nome del modulo come desideri. Quindi regola solo il module_namenome personalizzato che desideri che il modulo abbia durante l'importazione.

Per caricare un pacchetto invece di un singolo file, file_pathdovrebbe essere il percorso alla radice del pacchetto__init__.py


Funziona a meraviglia ... Ho usato questo per testare mentre sviluppavo una libreria, in modo che i miei test usassero sempre la versione di sviluppo e non quella pubblicata (e installata). In Windows 10 ho dovuto scrivere il percorso per il mio modulo in questo modo: file_path=r"C:\Users\My User\My Path\Module File.py". Quindi ho chiamato module_nameproprio come il modulo rilasciato in modo da avere uno script completo funzionante che, tolto questo frammento, poteva essere utilizzato su altri PC
Luke Savefrogs

141

Non è necessario modificare il nome del modulo. Piuttosto, puoi usare absolute_import per modificare il comportamento di importazione. Ad esempio con stem / socket.py importo il modulo socket come segue:

from __future__ import absolute_import
import socket

Funziona solo con Python 2.5 e versioni successive; sta abilitando il comportamento che è l'impostazione predefinita in Python 3.0 e versioni successive. Pylint si lamenterà del codice ma è perfettamente valido.


4
Questa mi sembra la risposta corretta. Vedi il log delle modifiche 2.5 o PEP328 per ulteriori informazioni.
Pieter Ennes

5
Questa è la soluzione corretta. Sfortunatamente, non funziona quando viene avviato il codice dall'interno del pacchetto perché in tal caso il pacchetto non viene riconosciuto come tale e il percorso locale è anteposto a PYTHONPATH. Un'altra domanda mostra come risolverlo.
Konrad Rudolph

5
Questa è la soluzione. Ho controllato per Python 2.7.6 e questo è richiesto, non è ancora l'impostazione predefinita.
Havok

3
Infatti: la prima versione di Python in cui questo comportamento è l'impostazione predefinita era 3.0, secondo docs.python.org/2/library/__future__.html
nome improprio

1
Quindi non denominare il modulo principale come uno che si scontra con un modulo integrato.
Antti Haapala

38

In realtà, risolverlo è piuttosto semplice, ma l'implementazione sarà sempre un po 'fragile, perché dipende dagli interni del meccanismo di importazione di Python e sono soggetti a modifiche nelle versioni future.

(il codice seguente mostra come caricare moduli sia locali che non locali e come possono coesistere)

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 soluzione migliore, se possibile, è evitare di nominare i moduli con lo stesso nome della libreria standard o dei nomi dei moduli incorporati.


Come interagirà questo con i sys.modulessuccessivi tentativi di caricare il modulo locale?
Omnifarious

@Omnifarious: aggiungerà il modulo a sys.modules con il suo nome, il che impedirà il caricamento del modulo locale. Puoi sempre usare un nome personalizzato per evitarlo.
Boaz Yaniv

@ Boaz Yaniv: dovresti usare un nome personalizzato per il calendario locale, non quello standard. Altri moduli Python potrebbero provare a importare quello standard. E se lo fai, ciò che ottieni con questo è fondamentalmente rinominare il modulo locale senza dover rinominare il file.
Omnifario

@ Onnifario: potresti farlo in entrambi i casi. Qualche altro codice potrebbe provare a caricare il modulo locale e ottenere lo stesso errore. Dovrai scendere a compromessi e spetta a te decidere quale modulo supportare.
Boaz Yaniv

2
Grazie per quel Boaz! Sebbene il tuo snippet sia più breve (e documento), penso che sia più semplice rinominare il modulo che avere del codice hacky che potrebbe confondere le persone (o me stesso) in futuro.
ramoscello

15

L'unico modo per risolvere questo problema è dirottare da soli i macchinari di importazione interni. Questo non è facile e pieno di pericoli. Dovresti evitare a tutti i costi il ​​faro a forma di graal perché il pericolo è troppo pericoloso.

Rinomina invece il tuo modulo.

Se vuoi imparare come dirottare il meccanismo di importazione interno, ecco dove dovresti andare per scoprire come farlo:

A volte ci sono buone ragioni per entrare in questo pericolo. La ragione che dai non è tra queste. Rinomina il tuo modulo.

Se prendi il percorso pericoloso, un problema che incontrerai è che quando carichi un modulo finisce con un "nome ufficiale" in modo che Python possa evitare di dover analizzare il contenuto di quel modulo mai più. Una mappatura del 'nome ufficiale' di un modulo all'oggetto del modulo stesso può essere trovata in sys.modules.

Ciò significa che se ti trovi import calendarin un posto, qualsiasi modulo importato verrà considerato come il modulo con il nome ufficiale calendare tutti gli altri tentativi in import calendarqualsiasi altro luogo, incluso in altro codice che fa parte della libreria Python principale, otterranno quel calendario.

Potrebbe essere possibile progettare un importatore di clienti usando il modulo imputil in Python 2.x che ha fatto sì che i moduli caricati da determinati percorsi cercassero i moduli che stavano importando in qualcosa di diverso dal sys.modulesprimo o qualcosa del genere. Ma è una cosa estremamente complicata da fare, e comunque non funzionerà in Python 3.x.

C'è una cosa estremamente brutta e orribile che puoi fare che non implica l'aggancio del meccanismo di importazione. Questo è qualcosa che probabilmente non dovresti fare, ma probabilmente funzionerà. Trasforma il tuo calendarmodulo in un ibrido tra il modulo calendario del sistema e il modulo calendario. Grazie a Boaz Yaniv per lo scheletro della funzione che utilizzo . Metti questo all'inizio del tuo calendar.pyfile:

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

imputil è considerato deprecato. Dovresti usare il modulo imp .
Boaz Yaniv

Che è perfettamente compatibile con Python 3, tra l'altro. E non così peloso da usare. Ma dovresti sempre essere consapevole del fatto che il codice che si basa su python che tratta i percorsi in un modo o che cerca i moduli in quell'ordine potrebbe prima o poi interrompersi.
Boaz Yaniv

1
Giusto, ma in un caso così isolato (collisione del nome del modulo) agganciare il meccanismo di importazione è eccessivo. E poiché è peloso e incompatibile, è meglio lasciarlo stare.
Boaz Yaniv

1
@jspacek no, finora tutto bene, ma la collisione si verificava solo quando si utilizza il debbuger di PyDev, non in uso regolare. E assicurati di controllare l'ultimo codice (l'URL in GitHub), poiché è cambiato leggermente rispetto alla risposta sopra
MestreLion

1
@jspacek: è un gioco, non una libreria, quindi nel mio caso la compatibilità con le versioni precedenti non è affatto un problema. E la collisione dello spazio dei nomi si verifica solo quando si utilizza l'esecuzione tramite PyDev IDE (che utilizza il codemodulo std di Python ), il che significa che solo una frazione degli sviluppatori potrebbe mai avere problemi con questo "merging hack". Gli utenti non ne sarebbero affatto influenzati.
MestreLion

1

Vorrei offrire la mia versione, che è una combinazione della soluzione di Boaz Yaniv e di Omnifarious. Importerà la versione di sistema di un modulo, con due differenze principali rispetto alle risposte precedenti:

  • Supporta la notazione "punto", ad es. package.module
  • È un sostituto immediato per l'istruzione import sui moduli di sistema, il che significa che devi solo sostituire quella riga e se ci sono già chiamate fatte al modulo funzioneranno così com'è

Mettilo da qualche parte accessibile in modo da poterlo chiamare (ho il mio nel mio file __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

Esempio

Volevo importare mysql.connection, ma avevo già un pacchetto locale chiamato mysql (le utility mysql ufficiali). Quindi, per ottenere il connettore dal pacchetto mysql di sistema, ho sostituito questo:

import mysql.connector

Con questo:

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

Risultato

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

-2

Modifica il percorso di importazione:

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

Questo non funzionerà perché dopo averlo fatto non ci sarà modo di importare il modulo locale senza andare a giocherellare con il meccanismo di importazione da soli.
Omnifarious

@Omnifarious: questo è un problema diverso, che puoi aggirare con un terzo modulo che esegue un'importazione dal calendario *.
Linuts

No, probabilmente non funzionerebbe perché python memorizza nella cache il nome del modulo sys.modulese non importerà più un modulo con lo stesso nome.
Boaz Yaniv
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.