Начнем с того, что это статья посягается на святой устой комьюнити Python разработчиков, устой звучит так "синтаксис python - идеален, стандартные библиотеки - идеальны, и полноценны, GIL - это неизбежная жертва для такого прекрасного языка как Python ... может быть в конце столетия люди придумают как его обойти, но, а пока так ????". Приносим глубокие извинения за такую статью, это чисто юмористичная статья, не стоит принимать ей в серьез.

В общем решить эту проблему можно 50 строчками, вот код для импорта модуля из любого места, без плясок с бубнами и `sys.path`

import importlib.util
from importlib.machinery import ModuleSpec
from os import sep
from os.path import splitext, join
from pathlib import Path, PosixPath
from types import ModuleType
from typing import Optional, Union


class ObjFrom:
    def __init__(self, module: ModuleType):
        self.module: ModuleType = module

    def From(self, *obj):
        """
        Выбрать определяя объекты из модуля

        :param obj:
        :return:
        """
        return tuple(v for k, v in self.module.__dict__.items() if k in obj)


def iimport(self_file: str = None,
            count_up: int = 0,
            module_name: str = None,
            *,
            absolute_path: Union[str, Path] = None) -> ObjFrom:
    """
    Импортировать файл как модуль `python`

    :param self_file: Обычно это __file__
    :param count_up: Насколько папок поднять вверх
    :param module_name: Имя импортируемого модуля
    :param absolute_path: Путь к `python` файлу
    :return: Модуль `python`
    """
    path: str = ''
    if absolute_path is not None:
        if isinstance(absolute_path, PosixPath):
            absolute_path = absolute_path.__str__()
        path = absolute_path
    else:
        path = join(sep.join(Path(self_file).parts[:(count_up + 1) * -1]), f"{module_name}.py")
    if splitext(path)[1] != ".py":
        raise ValueError(f"Файл должен иметь расширение .py")
    spec: Optional[ModuleSpec] = importlib.util.spec_from_file_location("my_module", path)
    __module: ModuleType = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(__module)
    return ObjFrom(__module)

Как пользоваться ?

А вот так. Вам нужен модулю из другой вселенной ? пожалуйста укажите к ней абсолютный путь и берите его.

ИмяМодуля = iimport(absolute_path='Путь').module

Хорошо, но писать абсолютный путь не всегда удобно. Например, наш модуль лежит на несколько уровней директорий вверх, и что тогда строчить абсолютный путь ???? ? Ну нет, можно использовать такой кейс

ИмяМодуля = iimport(__file__, СколькоДиректорийВверх, 'ИмяМодуля').module

Чувствую как юношеская радость переполняет вас от такого нового открытия. Сколько же фич открывается перед тобой теперь ☺?? Но как говорят в ресторане - десерт подают в конце.

Если нам нужно получить конкретные объекты из модуля, то можно воспользоваться таким кейсом

Объект_1, Объект_N = iimport(absolute_path='Путь').From('Объект_1', 'Объект_N')

Статья закончена, высокий поклон публики и читателям которые дошли до этой строки. С уважением, ваш - бездарный программист, который ни в зуб ногой в Python :)

Комментарии (9)


  1. funca
    23.07.2022 00:15

    Теги: юморюмор на хабре

    "В информатике есть две трудные проблемы: у нас только одна штука и это не смешно." https://martinfowler.com/bliki/TwoHardThings.html


  1. Alex-111
    23.07.2022 06:57

    count_up - неудобна вещь, лучше поддержать обычный относительный путь ../../my_module.


  1. baldr
    23.07.2022 10:14
    +3

    В принципе, работать будет. Иногда приходится делать такие хаки, согласен.

    Однако, как я понимаю, если импортируется не один файл, а файл из библиотеки, то он сам внутри не сможет сделать локальный импорт? `from ..models import MyModel` у него не пройдет поскольку его собственный путь отсутствует в sys.path ?


  1. Andy_U
    23.07.2022 11:05
    +2

    Кроме всего остального, сильно опасаюсь, что code insight в PyCharm отвалится.


  1. iig
    23.07.2022 11:12
    +1

    Правильно установленный модуль: импортируем, используем.

    Модуль из другой вселенной - подпираем костылями, собираем грабли.


    1. baldr
      23.07.2022 11:54

      Вообще говоря, использовать importlib лично мне приходится довольно часто.

      Например, приходится писать много однотипных парсеров для разных документов. Проще оформлять их как отдельные файлы в выделенной папке и автоматически импортировать уровнем выше и складывать в специальный словарик, чем писать кучу "from parsers.docparser_p0434 import ParserP0434v2", а потом получать ошибки из-за того что парсер написал, а добавить импорт забыл.

      Да, наверное PyCharm такое не любит, но тут не он главный.


      1. masai
        25.07.2022 14:03

        Это, наверное, один из немногих оправданных случаев. Да, наверняка есть задачи, когда надо импортировать модули из ZIP или по сети, но это единичные случаи. Обычно если хочется импортировать нестандартно, то это признак проблем с архитектурой.


  1. Monstrofil
    23.07.2022 18:22
    +5

    В python есть штатный механизм, который позволяет интегрировать собственный загрузчик модулей и использовать стандартный синтаксис. В PEP 302 реализация хуков описана довольно непонятно, так что лучше сразу смотреть примеры, например, urlloader:

    with github_repo('kragniz', 'json-sempai'):
        from jsonsempai import magic
        import tester
        print(tester.hello)

    В вашем случае будет что-то вроде такого:

    with absolute_import('/path/to/module/'):
        from mymodule import magic
    
    print(magic)


  1. PavelZubkov
    23.07.2022 19:45

    В одной модульной системе на js, вместо импортов используются FQN-имена, выглядит как $path_to_module.


    Имя сущности соответствует(почти) пути до нее, чтобы ей воспользоваться просто пишется имя. И конечно можно положить в переменную, чтобы 100500 раз не писать длинное имя, если необходимо, тогда выглядит как импорт в начале файла.