Importando da Biblioteca Incorporada quando o Módulo com o Mesmo Nome Existe

121

Situação: - Há um módulo na pasta project_ chamado calendar - Gostaria de usar a classe Calendar incorporada das bibliotecas Python - Quando uso a importação de calendário, o Calendar reclama porque está tentando carregar do meu módulo.

Fiz algumas pesquisas e não consigo encontrar uma solução para o meu problema.

Alguma idéia sem ter que renomear meu módulo?

galho
fonte
24
É uma prática recomendada não nomear módulos para ocultar módulos internos.
17111
3
A solução é "escolha um nome diferente". Sua abordagem de não renomear é uma má ideia. Por que você não pode renomear seu módulo? O que há de errado em renomear?
S.Lott 17/05
De fato. É porque não há uma boa resposta para essa pergunta que a sombra dos módulos stdlib é tão fortemente desencorajada.
Ncoghlan
Evitei usar o mesmo nome de módulo, pois as soluções pareciam mais problemas do que vale a pena. Obrigado!
galho
9
@the_drow Este conselho não é escalável, puro e simples. O PEP328 reconhece prontamente isso.
precisa saber é o seguinte

Respostas:

4

A solução aceita contém uma abordagem agora obsoleta.

A documentação importlib aqui fornece um bom exemplo da maneira mais apropriada de carregar um módulo diretamente de um caminho de arquivo para 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)

Portanto, você pode carregar qualquer arquivo .py a partir de um caminho e definir o nome do módulo para o que quiser. Portanto, apenas ajuste-o module_namecomo qualquer nome personalizado que você gostaria que o módulo tivesse ao importar.

Para carregar um pacote em vez de um único arquivo, file_pathdeve ser o caminho para a raiz do pacote__init__.py

Brandon Squizzato
fonte
Funciona como um encanto ... Usei isso para testar durante o desenvolvimento de uma biblioteca, para que meus testes estivessem sempre usando a versão em desenvolvimento e não a publicada (e instalada). No windows 10 eu tive que escrever o caminho para o meu módulo como este: file_path=r"C:\Users\My User\My Path\Module File.py". Então chamei module_nameexatamente como o módulo lançado, para ter um script de trabalho completo que, retirado esse trecho, poderia ser usado em outros computadores
Luke Savefrogs
141

Não é necessário alterar o nome do seu módulo. Em vez disso, você pode usar absolute_import para alterar o comportamento de importação. Por exemplo, com stem / socket.py , importo o módulo de soquete da seguinte maneira:

from __future__ import absolute_import
import socket

Isso funciona apenas com o Python 2.5 e superior; está permitindo um comportamento padrão no Python 3.0 e superior. O Pylint irá reclamar do código, mas é perfeitamente válido.

Damian
fonte
4
Esta parece a resposta correta para mim. Veja o changelog 2.5 ou PEP328 para mais.
21412 Pieter Ennes
5
Esta é a solução correta. Infelizmente, ele não funciona quando o código de dentro do pacote é iniciado, porque o pacote não é reconhecido como tal e o caminho local é anexado PYTHONPATH. Outra pergunta mostra como resolver isso.
precisa saber é o seguinte
5
Essa é a solução. Eu verifiquei o Python 2.7.6 e isso é necessário, ainda não é o padrão.
Havok
3
De fato: A primeira versão python, onde este comportamento é o padrão foi de 3,0, de acordo com docs.python.org/2/library/__future__.html
misnomer
1
Em seguida, não nomeie seu módulo principal como aquele que colide com um módulo interno.
Antti Haapala
38

Na verdade, resolver isso é bastante fácil, mas a implementação sempre será um pouco frágil, porque depende do interior do mecanismo de importação python e eles estão sujeitos a alterações em versões futuras.

(o código a seguir mostra como carregar módulos locais e não locais e como eles podem coexistir)

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

A melhor solução, se possível, é evitar nomear seus módulos com o mesmo nome da biblioteca padrão ou dos nomes dos módulos internos.

Boaz Yaniv
fonte
Como isso irá interagir sys.modulese as tentativas subseqüentes de carregar o módulo local?
Onipotente 17/05
@ Oniforme: Ele adicionará o módulo ao sys.modules com seu nome, o que impedirá o carregamento do módulo local. Você sempre pode usar um nome personalizado para evitar isso.
Boaz Yaniv
@ Boo Yaniv: Você deve usar um nome personalizado para o calendário local, não o padrão. Outros módulos Python podem tentar importar o padrão. E se você fizer isso, o que você conseguirá com isso é basicamente renomear o módulo local sem precisar renomear o arquivo.
omniforme
@ Onnifarious: Você poderia fazê-lo de qualquer maneira. Algum outro código pode tentar carregar o módulo local e obter o mesmo erro. Você precisará fazer um compromisso, e cabe a você decidir qual módulo apoiar.
Boaz Yaniv
2
Obrigado por isso Boaz! Embora seu snippet seja mais curto (e documento), acho que é mais fácil renomear o módulo do que ter um código hacky que possa confundir as pessoas (ou a mim) no futuro.
galho
15

A única maneira de resolver esse problema é seqüestrar o maquinário interno de importação. Isso não é fácil e cheio de perigos. Você deve evitar o farol em forma de graal a todo custo, porque o perigo é muito perigoso.

Renomeie seu módulo.

Se você quiser aprender a seqüestrar o mecanismo interno de importação, é aqui que você descobrirá como fazer isso:

Às vezes, existem boas razões para entrar nesse perigo. A razão que você dá não está entre eles. Renomeie seu módulo.

Se você seguir o caminho perigoso, um problema que você encontrará é que, quando você carrega um módulo, ele acaba com um 'nome oficial', para que o Python evite ter que analisar o conteúdo desse módulo novamente. Um mapeamento do 'nome oficial' de um módulo para o próprio objeto do módulo pode ser encontrado em sys.modules.

Isso significa que, se você estiver import calendarem um lugar, qualquer módulo importado será considerado o módulo com o nome oficial calendare todas as outras tentativas para import calendarqualquer outro lugar, incluindo outro código que faça parte da biblioteca principal do Python, receberá esse calendário.

Pode ser possível projetar um importador de clientes usando o módulo imputil no Python 2.x que fez com que os módulos carregados de determinados caminhos procurassem os módulos que estavam importando em algo diferente do sys.modulesprimeiro ou algo parecido. Mas isso é algo extremamente complicado de se fazer, e de qualquer maneira não funcionará no Python 3.x.

Há uma coisa extremamente feia e horrível que você pode fazer que não envolve enganchar o mecanismo de importação. Isso é algo que você provavelmente não deve fazer, mas provavelmente funcionará. Transforma seu calendarmódulo em um híbrido do módulo de calendário do sistema e seu módulo de calendário. Agradeço a Boaz Yaniv pelo esqueleto da função que uso . Coloque isso no início do seu calendar.pyarquivo:

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__])
Omniforme
fonte
imputil é considerado obsoleto. Você deve usar o módulo imp .
Boaz Yaniv
O que é perfeitamente compatível com o Python 3, a propósito. E não tão peludo de usar. Mas você deve sempre estar ciente de que o código que depende do python tratar caminhos de uma maneira ou procurar módulos nessa ordem pode quebrar mais cedo ou mais tarde.
Boaz Yaniv
1
Certo, mas em um caso isolado (colisão de nome de módulo) ligar o mecanismo de importação é um exagero. E como é peludo e incompatível, é melhor deixá-lo sozinho.
Boaz Yaniv
1
@jspacek não, até agora tudo bem, mas a colisão só ocorreria ao usar o debbuger do PyDev, não em uso regular. E certifique-se de verificar o código mais recente (o URL no github), pois ele mudou um pouco em relação à resposta acima
MestreLion
1
@jspacek: É um jogo, não uma biblioteca, portanto, no meu caso, a compatibilidade com versões anteriores não é uma preocupação. E a colisão de namespace ocorre apenas quando a execução é executada via PyDev IDE (que usa o codemódulo std do Python ), o que significa que apenas uma fração dos desenvolvedores pode ter problemas com esse "hack de mesclagem". Os usuários não seriam afetados.
MestreLion
1

Eu gostaria de oferecer minha versão, que é uma combinação da solução de Boaz Yaniv e Omnifarious. Importará a versão do sistema de um módulo, com duas principais diferenças em relação às respostas anteriores:

  • Suporta a notação 'ponto', por exemplo. package.module
  • É um substituto para a declaração de importação nos módulos do sistema, o que significa que você só precisa substituir essa linha e, se já houver chamadas sendo feitas para o módulo, elas funcionarão como estão

Coloque isso em algum lugar acessível para que você possa chamá-lo (eu tenho o meu no meu arquivo __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

Exemplo

Eu queria importar o mysql.connection, mas já tinha um pacote local chamado mysql (os utilitários oficiais do mysql). Então, para obter o conector do pacote mysql do sistema, substituí este:

import mysql.connector

Com isso:

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

Resultado

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

Mude o caminho de importação:

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path
linuts
fonte
Isso não funcionará porque, após fazer isso, não haverá como importar o módulo local sem mexer nas máquinas de importação.
Onipotente 17/05
@ Oniforme: esse é um problema diferente, que você pode contornar com um terceiro módulo que faz uma importação de calendário *.
Linux 17/05/11
Não, isso provavelmente não funcionaria porque o python armazena em cache o nome do módulo sys.modulese não importará um módulo com o mesmo nome novamente.
Boaz Yaniv