Импорт из встроенной библиотеки при наличии модуля с таким же именем

121

Ситуация: - В моей папке project_folder есть модуль под названием calendar - Я хотел бы использовать встроенный класс Calendar из библиотек Python - Когда я использую календарь импорта из календаря, он жалуется, потому что пытается загрузить из моего модуля.

Я провел несколько поисков и не могу найти решение своей проблемы.

Есть идеи без переименования моего модуля?

прут
источник
24
Рекомендуется не называть модули, чтобы скрыть встроенные модули.
the_drow
3
Решение - «выберите другое имя». Ваш подход не переименовывать - плохая идея. Почему вы не можете переименовать свой модуль? Что не так с переименованием?
S.Lott
На самом деле. Именно из-за отсутствия хорошего ответа на этот вопрос использование теневых модулей stdlib настоятельно не рекомендуется.
ncoghlan
Я избегал использования одного и того же имени модуля, поскольку решения казались больше проблем, чем они того стоят. Спасибо!
веточка
9
@the_drow Этот совет не масштабируется, чистый и простой. PEP328 с готовностью признает это.
Конрад Рудольф

Ответы:

4

Принятое решение содержит устаревший подход.

Документация importlib здесь дает хороший пример более подходящего способа загрузки модуля непосредственно из пути к файлу для 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)

Таким образом, вы можете загрузить любой файл .py по пути и установить любое имя модуля. Так что просто измените его так, module_nameчтобы оно было любым пользовательским именем, которое вы хотите, чтобы модуль имел при импорте.

Чтобы загрузить пакет вместо одного файла, file_pathдолжен быть путь к корню пакета__init__.py

Брэндон Сквиццато
источник
Работает как шарм ... Использовал это для тестирования при разработке библиотеки, так что мои тесты всегда использовали развивающуюся версию, а не опубликованную (и установленную). В окнах 10 я должен был написать путь к моему модулю , как это: file_path=r"C:\Users\My User\My Path\Module File.py". Затем я позвонил module_nameточно так же, как выпущенный модуль, чтобы у меня был полностью рабочий сценарий, который, без этого фрагмента, можно было бы использовать на других компьютерах
Люк Сэйвфрогс
141

Менять имя вашего модуля не нужно. Скорее, вы можете использовать absolute_import для изменения поведения импорта. Например, с помощью stem / socket.py я импортирую модуль сокета следующим образом:

from __future__ import absolute_import
import socket

Это работает только с Python 2.5 и выше; это активирующее поведение, используемое по умолчанию в Python 3.0 и выше. Pylint будет жаловаться на код, но он абсолютно верен.

Damian
источник
4
Мне это кажется правильным ответом. См. Журнал изменений 2.5 или PEP328 для получения дополнительной информации.
Питер Эннес
5
Это правильное решение. К сожалению, это не работает, когда код из пакета запускается, потому что тогда пакет не распознается как таковой, и к нему добавляется локальный путь PYTHONPATH. Другой вопрос показывает, как это решить.
Конрад Рудольф
5
Это решение. Я проверил Python 2.7.6, и это обязательно, но все еще не по умолчанию.
Havok
3
Действительно: Первая версия питона , где это поведение по умолчанию было 3,0, в соответствии с docs.python.org/2/library/__future__.html
неправильное название
1
Тогда не называйте ваш основной модуль тем, который конфликтует со встроенным модулем.
Антти Хаапала
38

На самом деле решить эту проблему довольно просто, но реализация всегда будет немного хрупкой, потому что это зависит от внутренних компонентов механизма импорта Python, и они могут быть изменены в будущих версиях.

(следующий код показывает, как загружать локальные и нелокальные модули и как они могут сосуществовать)

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

Лучшее решение, если возможно, - избегать именования ваших модулей с тем же именем, что и имена стандартной библиотеки или встроенных модулей.

Боаз Янив
источник
Как это будет взаимодействовать с sys.modulesпоследующими попытками загрузки локального модуля?
Omnifarious
@Omnifarious: он добавит модуль в sys.modules с его именем, что предотвратит загрузку локального модуля. Вы всегда можете использовать собственное имя, чтобы этого избежать.
Боаз Янив
@Boaz Yaniv: Вы должны использовать собственное имя для местного календаря, а не стандартное. Другие модули Python могут попытаться импортировать стандартный. И если вы это сделаете, вы просто переименуете локальный модуль без необходимости переименовывать файл.
Omnifarious
@Omnifarious: вы можете сделать это в любом случае. Другой код может попытаться загрузить локальный модуль и получить ту же ошибку. Вам придется пойти на компромисс, и вам решать, какой модуль поддерживать.
Боаз Янив
2
Спасибо за это, Вооз! Хотя ваш фрагмент короче (и документ), я думаю, что проще переименовать модуль, чем иметь какой-то хакерский код, который может запутать людей (или меня) в будущем.
веточка
15

Единственный способ решить эту проблему - самостоятельно угнать внутреннюю импортную технику. Это непросто и чревато опасностями. Вы должны избегать маяка в форме Грааля любой ценой, потому что опасность слишком велика.

Вместо этого переименуйте свой модуль.

Если вы хотите узнать, как взломать механизм внутреннего импорта, вот где вы должны узнать, как это сделать:

Иногда есть веские причины попасть в такую ​​опасность. Причины, по которой вы говорите, среди них нет. Переименуйте свой модуль.

Если вы выберете опасный путь, одна проблема, с которой вы столкнетесь, заключается в том, что когда вы загружаете модуль, он получает «официальное имя», так что Python может никогда больше не анализировать содержимое этого модуля. Сопоставление «официального имени» модуля с самим объектом модуля можно найти в sys.modules.

Это означает, что если вы находитесь import calendarв одном месте, любой импортированный модуль будет рассматриваться как модуль с официальным именем, calendarи все другие попытки import calendarкуда-либо еще, в том числе в другом коде, который является частью основной библиотеки Python, получат этот календарь.

Можно было бы разработать клиентский импортер, используя модуль imputil в Python 2.x, который заставлял бы модули, загружаемые с определенных путей, искать модули, которые они импортируют, не в sys.modulesпервом или чем-то подобном. Но делать это крайне непросто, и в любом случае это не сработает в Python 3.x.

Вы можете сделать очень уродливую и ужасную вещь, которая не требует подключения механизма импорта. Вероятно, вам не стоит этого делать, но, скорее всего, это сработает. Он превращает ваш calendarмодуль в гибрид модуля системного календаря и модуля календаря. Спасибо Boaz Yaniv за скелет функции, которую я использую . Поместите это в начало вашего calendar.pyфайла:

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 считается устаревшим. Вам следует использовать модуль imp .
Боаз Янив
Который, кстати, полностью совместим с Python 3. И совсем не такой волосатый, чтобы использовать. Но вы всегда должны знать, что код, который полагается на Python, обрабатывающий пути одним способом или ищущий модули в этом порядке, может рано или поздно сломаться.
Боаз Янив
1
Верно, но в таком единичном случае (коллизия имени модуля) подключение механизма импорта - это излишне. А так как он волосатый и несовместимый, его лучше оставить в покое.
Боаз Янив
1
@jspacek нет, пока все хорошо, но коллизия произойдет только при использовании отладчика PyDev, а не при обычном использовании. И убедитесь, что вы проверили последний код (URL-адрес в github), поскольку он немного изменился по сравнению с приведенным выше ответом
MestreLion
1
@jspacek: это игра, а не библиотека, поэтому в моем случае обратная совместимость не вызывает никаких проблем. И конфликт пространства имен происходит только при использовании PyDev IDE (который использует codeмодуль Python std), а это означает, что только часть разработчиков может иметь какие-либо проблемы с этим «хаком слияния». Пользователи не пострадают.
MestreLion
1

Хочу предложить свою версию, которая представляет собой комбинацию решения Боаза Янива и Omnifarious. Он импортирует системную версию модуля с двумя основными отличиями от предыдущих ответов:

  • Поддерживает точечную нотацию, например. package.module
  • Является заменой для оператора импорта в системных модулях, то есть вам просто нужно заменить эту одну строку, и если в модуль уже поступают вызовы, они будут работать как есть

Поместите это где-нибудь под рукой, чтобы вы могли его вызвать (у меня есть мой в моем файле __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

пример

Я хотел импортировать mysql.connection, но у меня уже был локальный пакет с именем mysql (официальные утилиты mysql). Итак, чтобы получить коннектор из системного пакета mysql, я заменил это:

import mysql.connector

С этим:

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

результат

# 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
источник
-2

Измените путь импорта:

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path
linuts
источник
Это не сработает, потому что после этого не будет возможности импортировать локальный модуль без самостоятельной работы с механизмом импорта.
Omnifarious
@Omnifarious: это другая проблема, которую вы можете решить с помощью третьего модуля, который выполняет импорт из календаря *.
linuts
Нет, это, вероятно, не сработает, потому что python кэширует имя модуля sys.modulesи больше не импортирует модуль с тем же именем.
Боаз Янив