Імпорт із вбудованої бібліотеки, коли існує модуль з такою ж назвою


121

Ситуація: - У моєму папці project_folder, який називається календар, є модуль - я хотів би використовувати вбудований клас Calendar з бібліотек Python. Коли я використовую календар імпорту календаря, він скаржиться, тому що він намагається завантажити з мого модуля.

Я здійснив кілька пошуків, і, здається, не знайшов рішення своєї проблеми.

Будь-які ідеї без перейменування мого модуля?


24
Найкраща практика не називати модулі для приховування вбудованих модулів.
the_drow

3
Рішення - «вибрати іншу назву». Ваш підхід до перейменування - погана ідея. Чому ви не можете перейменувати свій модуль? Що не так у перейменуванні?
С.Лотт

Справді. Саме тому, що немає хорошої відповіді на це питання, затінення модулів 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само, як випущений модуль, щоб у мене був повноцінний робочий сценарій, який, знімаючи цей фрагмент, coud можна використовувати на інших ПК
Лука Savefrogs

141

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

from __future__ import absolute_import
import socket

Це працює лише з Python 2.5 і вище; це уможливлення поведінки, яка є типовою для Python 3.0 і вище. Pylint скаржиться на код, але він абсолютно дійсний.


4
Це здається мені правильною відповіддю. Див. Розділ 2.5 змін або PEP328 для отримання додаткової інформації.
Пітер Еннес

5
Це правильне рішення. На жаль, він не працює, коли запускається код з пакету, тому що пакет не розпізнається як такий, і локальний шлях передбачений PYTHONPATH. Інше питання показує, як це вирішити.
Конрад Рудольф

5
Це рішення. Я перевірив на Python 2.7.6, і це потрібно, він все ще не є типовим.
Хавок

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: Він додасть модуль до sys.modules з назвою, що запобіжить завантаженню локального модуля. Ви завжди можете використовувати власну назву, щоб уникнути цього.
Боаз Янів

@Boaz Yaniv: Ви повинні використовувати власну назву для місцевого календаря, а не стандартну. Інші модулі Python можуть спробувати імпортувати стандартний. І якщо ви це зробите, то, чого ви досягаєте, це в основному перейменувати локальний модуль без необхідності перейменувати файл.
всезнайко

@Omnifarious: Ви можете це зробити в будь-якому випадку. Деякі інші коди можуть спробувати завантажити локальний модуль і отримати ту саму помилку. Вам доведеться піти на компроміс, і ви вирішуєте, який модуль підтримувати.
Боаз Янів

2
Дякую за це Боаз! Хоча ваш фрагмент коротший (і документ), я думаю, що просто перейменувати модуль просто простіше, ніж мати якийсь хакі-код, який може в майбутньому заплутати людей (або мене самого).
гілочка

15

Єдиний спосіб вирішити цю проблему - самостійно викрасти внутрішню імпортну техніку. Це непросто і загрожує небезпекою. Вам слід за будь-яку ціну уникати маяка у формі грааля, оскільки небезпека занадто небезпечна.

Перейменуйте замість цього модуль.

Якщо ви хочете дізнатися, як викрасти внутрішні імпортні машини, ось де ви б хотіли дізнатися, як це зробити:

Іноді є вагомі причини, щоб потрапити в цю небезпеку. Причину, яку ви даєте, немає серед них. Перейменуйте свій модуль.

Якщо ви скористаєтеся небезпечним шляхом, вам доведеться зіткнутися з однією проблемою, що коли ви завантажуєте модуль, він закінчується "офіційним ім'ям", щоб Python міг уникнути необхідності аналізувати вміст цього модуля ще раз. Зображення "офіційного імені" модуля до самого об'єкта модуля можна знайти в sys.modules.

Це означає, що якщо ви import calendarв одному місці, будь-який модуль імпортований буде вважатися модулем з офіційною назвою calendarі всі інші спроби в import calendarбудь-якому іншому місці, в тому числі в іншому коді, який є частиною основної бібліотеки Python, отримають цей календар.

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

Ви можете зробити надзвичайно потворну та жахливу річ, яка не передбачає підключення механізму імпорту. Це те, що ви, мабуть, не повинні робити, але це, ймовірно, спрацює. Він перетворює ваш calendarмодуль в гібрид модуля системного календаря і вашого модуля календаря. Дякую Боазу Яніву за скелет функції, яку я використовую . Помістіть це на початку вашого 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. І не такий волохатий у використанні взагалі. Але ви завжди повинні знати, що код, який покладається на шляхи обробки пітоном так чи інакше, шукаючи модулі в такому порядку, може рано чи пізно зламатися.
Боаз Янів

1
Правильно, але в такому ізольованому випадку (зіткнення назви модуля) підключення механізму імпорту є надмірним. А оскільки він волохатий і несумісний, краще залишити його в спокої.
Боаз Янів

1
@jspacek nope, поки що добре, але зіткнення відбулося б лише при використанні дебюгера PyDev, а не в регулярному використанні. І переконайтеся, що ви перевіряєте останній код (URL-адреса в github), оскільки він трохи змінився з наведеної вище відповіді
MestreLion

1
@jspacek: Це гра, а не бібліотека, тому в моєму випадку відстала сумісність зовсім не є проблемою. А зіткнення простору імен відбувається лише при використанні запуску через PyDev IDE (який використовує codestd модуль Python ), тобто лише частина розробників могла б мати якісь проблеми з цим "злиттям хак". На користувачів це взагалі не вплине.
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)

-2

Змінення шляху імпорту:

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

Це не спрацює, оскільки після цього не вдасться імпортувати локальний модуль, не переходячи та не поводячись із імпортною технікою.
всезначний

@Omnifarious: це інша проблема, яку можна подолати за допомогою третього модуля, який виконує імпорт з календаря *.
linuts

Ні, це, ймовірно, не буде працювати, тому що python кешує ім'я модуля sys.modules, і він знову не імпортує модуль з тим самим іменем.
Боаз Янів
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.