Как импортировать несколько модулей?
Добрый день! Начинаю изучать python. Как импортировать несколько модулей? Второй раз когда импорт пишу, он почему-то его как будто коментирует. В чем причина? Подскажете? Скрин:
Отслеживать
11 1 1 золотой знак 2 2 серебряных знака 8 8 бронзовых знаков
задан 22 фев 2015 в 18:05
161 1 1 золотой знак 3 3 серебряных знака 14 14 бронзовых знаков
@kelevra, наведите курсор на подчеркнутый текст и подождите, чтобы PyCharm выдал вам подсказку
22 фев 2015 в 18:22
Пишет unused import statement/ Все равно не пойму.
22 фев 2015 в 18:47
@kelevra, вы не используете этот модуль, следовательно, его необязательно импортировать. Как только вы начнете использовать этот импорт, PyCharm перестанет затемнять его.
22 фев 2015 в 19:19
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
PyCharm серым показывает неиспользуемые модули (о чем и говорит сообщение » unused import statement «). Обратитесь к любой функции из random и импорт перестанет быть серым.
P.S. Кстати, randrange подчеркивает красным, потому что в вашем случае его нужно вызывать как random.randrange(4) . Как только добавите random перед randrange , его перестанет подчеркивать, и import random перестанет быть серым.
Отслеживать
ответ дан 22 фев 2015 в 19:10
47.8k 17 17 золотых знаков 56 56 серебряных знаков 100 100 бронзовых знаков
- python
- pycharm
- модульность
-
Важное на Мете
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.11.15.1019
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Как и зачем у нас появился статический анализатор типов для Python
Рассказываем, как у нас появился статический анализатор типов для Python и как мы решали возникающие в процессе проблемы.

Удобство процесса разработки напрямую влияет на скорость работы и на количество ошибок при написании кода. Что делать, если среда разработки почему-то отказывается использовать автодополнение во всю силу? Правильно, искать обходные пути и изобретать велосипеды.
Язык программирования Python в силу своей динамичности может легко создать ситуацию, когда статический анализатор не может вывести типы и, как следствие, часть проверок отключается. В статье я подробно расскажу о проблеме в разработке системы управления выделенными серверами, которую мы сами себе создали, а затем героически решили. В примерах используется интерпретатор Python 3.10, а средой разработки выступает PyCharm 2022.2.4.
Текст будет интересен тем, кто хочет узнать новые стороны Python, и кому любопытно, с какими проблемами можно столкнуться, если использовать все модные фичи языка.
С этой темой я выступил на Python MeetUp в Selectel. Переходите по ссылке, если хотите послушать мой доклад (предупреждаю, что текст подробнее), и ознакомиться с другими.
Сперва отмечу, что проект, о котором идет речь, развивается в течение нескольких лет и уже набрал тяжелую кодовую базу. Это система управления выделенными серверами Selectel, благодаря которой клиенты компании могут быстро получить арендованную машину. Поэтому нельзя просто взять и переделать все с нуля.
Снижаем цены на выделенные серверы в реальном времени
Арендуйте готовые конфигурации или предложите свой вариант.
Начнем с простого и более популярного решения, которое вызывает проблемы.
Ленивые вычисления
Для кэширования долгих вычислений в Python используется библиотека lazy, в которой есть одноименный декоратор. Вот простой и показательный пример:
from lazy import lazy class A: a: int = 0 b: str = "" class B: @lazy def lazy(self) -> A: return A() def normal(self) -> A: return A()
Везде есть аннотации типов, так что кажется, что каждый метод класса B возвращает объект класса А. Статическому анализатору на это намекает не только подсказка типов (type hinting), но и конструкция return A(), из которой однозначно выводится тип.

Но PyCharm считает иначе.
Статический анализатор «запинается» об декоратор и выдает подсказки для декоратора, а не для возвращаемого значения. В lazy 1.5 сделали подсказки типов, но PyCharm почему-то не изменил своего мнения. Проблема небольшая, особенно если вы используете Python 3.8 или выше.
class B: @cached_property def cached(self) -> A: return A()

В Python 3.8 появился декоратор cached_property, который аналогичен декоратору lazy, но корректно обрабатывается автодополнением среды разработки.
Это было просто, переходим к более сложным случаям.
Динамические свойства объекта
Здесь требуется небольшое введение, которое объяснит всю глубину проблемы. Наша система использует сценарии — классы, которые описывают решение какой-либо маленькой задачи. Сценарии имеют универсальный интерфейс, могут вызывать друг друга и находятся под контролем Книги Сценариев. Книга Сценариев — это комплексный объект со сложной логикой, поэтому в рамках статьи я приведу более простую аналогию под названием заклинания. Начнем с интерфейса.
class Spell: spell_name: str spell_description: str def cast(self, *args, **kwargs): pass
Интерфейс Spell определяет два поля мета-информации и метод cast, который выполняет некоторую бизнес-логику. Взглянем на реализацию одного заклинания.
class Confundus(Spell): spell_name = "confundus" spell_description = "Сбивает с толку" def cast(self, exitcode=0, **kwargs): exit(exitcode + 42)
Обратите внимание, что метод cast имеет другую сигнатуру, в которой явно заданы параметры, используемые в этом заклинании. Теперь когда у нас есть как минимум одно заклинание, мы переходим к книге заклинаний.
class SpellBook: _spells: Dict[str, Type[Spell]] def __init__(self, spells: Dict): self._spells = spells.copy() def __getattr__(self, name): if name in self._spells: return self._spells[name].cast raise NotImplementedError() # Инициализация книги заклинаний spells = SpellBook(< "confundus": Confundus() >)
Книга заклинаний — это обертка, которая хранит ассоциативный массив имя-объект. Основная магия, о которой хочется рассказать, заключается в магическом методе getattr. Этот метод возвращает фунцию cast из соответствующего класса заклинания. Таким образом у книги заклинаний появляются методы по имени ключей в словаре.
>>> spells.confundus() Process finished with exit code 42
В этот момент статический анализатор среды разработки скажет «у меня лапки», ведь возможные имена задаются во времени исполнения (runtime). Отказ статического анализатора приводит к возможным опечаткам в имени заклинания или в порядке аргументов. В общем, придется искать оригинальную реализацию заклинания и постоянно с ней сверяться.
Можно ли как-то научить IDE разбираться в таком специфическом и проекто-зависимом случае? Наверняка.
Поиск решений
Плагин для среды разработки
Первое решение было на поверхности: плагин для среды разработки. PyCharm внутри себя имеет представление практически о всем проекте, так что внедриться в механизм автодополнения и активироваться при некоторых условиях казалось простой идеей. Тем более, что существуют проекты, решающие аналогичные проблемы — например, плагин для библиотеки Pydantic.
Решение хорошее, но вот проблемы, которые оно приносит.
- Плагины для IDE не зависят от проекта, хотя могут быть включены только для некоторых проектов.
- PyCharm построена на базе IntelliJ IDEA, которая, в свою очередь, написана на языке программирования Java. Плагины, конечно, тоже разрабатываются на Java.
- Система автодополнения в среде разработки — это сложный механизм, в который нельзя модифицировать за пару часов.
Итого имеем, что для одного проекта нужен разработчик, который не только знает Python, но также умеет писать на Java и понимает специфику среды разработки. При этом на выходе получится решение, уникальное для PyCharm, а пользователи vim/emacs останутся без внимания.
Нужно более универсальное решение.
mypy
Я поделился с коллегами своими сомнениями и размышлениями. Мне посоветовали посмотреть на функциональность решения под названием mypy. Я не питал особых надежд, ведь, как говорилось ранее, статический анализатор не может разобрать то, что определяется во времени исполнения.
Внезапным открытием для меня стал генератор pyi-файлов, которые можно называть заглушками (stub). Файлы pyi — это файлы интерфейсов, которые содержат только сигнатуры без реализации. Для автодополнения файлы заглушек имеют более высокий приоритет, чем файлы с исходным кодом. Это значит, что можно типизировать внешние библиотеки без изменения их исходного кода. Если, конечно, в этом есть необходимость.
class A: def __init__(self): self.a = True @lazy def foo(self) -> int: self.b = True return 2 @property def bar(self) -> str: return "2" @cached_property def baz(self) -> None: return None
Напишем простой класс и отдадим его на вход генератору заглушек в mypy — stubgen. Есть мысли, что может пойти не так?
Данный тест проводится на версии 0.991, так что не спешите расстраиваться. Может быть, уже поправили.
Выходной интерфейсный файл.
class A: a: bool def __init__(self) -> None: . b: bool def foo(self) -> int: . @property def bar(self) -> str: . def baz(self) -> None: …
Как видно, stubgen умеет находить переменные, которые инициализированы в конструкторе, но вместе с тем совершенно не разбирается в декораторах.
Пройденный исследовательский путь явно говорит, что спасение утопающих — дело рук самих утопающих.
Работаем с трудностями
Идея с файлами-заглушками решает все проблемы, актуальные для плагина IDE.
- решение доступно только в одном проекте и распространяется вместе с проектом,
- не зависит от среды разработки,
- требуется знание Python.
В нашей системе есть интерактивный консольный интерфейс, в котором загружены основные компоненты системы. Это значит, что уже написанный код решит множество задач по поиску заклинаний и инициализации книги заклинаний. Вместе с тем это значит, что входные данные — это ссылки на класс или объект класса.
Обозначим список задач, которые нужно решить, чтобы интерфейсные файлы приносили пользу:
- Научиться собирать все импорты из оригинального файла. Если в интерфейсном файле нет импортов сложных типов, то автодополнение не будет работать.
- Создать генератор заглушек для классов, так как в заглушке должны быть все методы, которые есть в исходном коде.
- Конвертировать все заклинания в методы для книги заклинаний, не забывая про актуальные импорты.
Начнем последовательно решать задачу шаг за шагом.
Получение импортов
Извлечь импорты из файла просто, но у нас только ссылка на класс или на объект. Поэтому здесь поможет пакет inspect и функция getfile, благодаря которой можно найти файл, в котором описан класс.
import inspect filename = inspect.getfile(cls)
Теперь можем пройтись по файлу и забрать все конструкции для импорта через модуль ast (Abstract Syntax Tree).
import ast with open(filename, "r") as f: root = ast.parse(f.read(), filename) for node in ast.iter_child_nodes(root): if not hasattr(node, "names"): continue for n in node.names: if isinstance(node, ast.Import): import_str = f"import " elif isinstance(node, ast.ImportFrom): import_str = f"from import " else: continue if n.asname: import_str += f" as "
Представленная реализация весьма наивна, так как учитывает только импорты в глобальной области видимости. Импорты за условием if typing.TYPE_CHECKING и внутри функций учитываться не будут. Однако для нашего проекта это приемлемо.
В конце каждой итерации внешнего цикла в import_str содержится строка, которую можно положить в файл заглушек как есть. Интерпретатор Python игнорирует повторные импорты, но если хочется избавиться от дубликатов, то можно все полученные строки сохранять во множестве.
Импорты есть, теперь обрабатываем классы.
Основной цикл парсера
В нашем проекте генерация заглушек нужна лишь для нескольких классов, которые хранятся в заранее известных файлах. Поэтому для прототипа я не реализовывал механизм поиска классов в файле. Вместо этого жестко указал (захардкодил) имена файлов-заглушек, и это всех устроило.
Теперь нам нужно по объекту класса создать заглушку с сигнатурами. Рассмотрим простой класс, которой покрывает важные случаи.
class SomeClass: # В магическом словаре __annotations__ annotated: str # Член класса initialized = 1 # Член класса + в магическом словаре both: bool = True # Метод класса def method(self) -> int: # Такое игнорируем self.ignored = "=(" return 42
Методы можно поймать с помощью условия callable(method), известные декораторы — явной проверкой на тип. Сложнее всего с переменными. Переменные могут быть иметь или аннотацию, или значение по умолчанию, а могут и то, и другое. Аннотированные переменные не являются полем класса, поэтому их нужно искать в магическом словаре annotations, который существует только если есть хотя бы одна переменная с аннотацией.
Мы не планируем конкурировать с генератором заглушек от mypy, поэтому переменные, инициализированные в теле конструктора или других методов игнорируются. В нашем проекте подавляющее большинство переменных аннотированы в теле класса, а это упрощение заметно снижает время разработки. В итоге можно написать такой код.
# Обработка полей for member_name in dir(cls): member = getattr(cls, member_name) if isinstance(member, property): # Обработка декораторов, в данном случае # @property elif callable(member): # Обработка методов elif not callable(member) and member_name not in cls.__annotations__: # Обработка инициализированных переменных # без аннотаций. Автовыведение типа через # type(member).__name__ # Обработка аннотаций if hasattr(cls, "__annotations__"): for name, t in cls.__annotations__.items(): # Имя поля + тип
Обратите внимание на следующие моменты, которые могут быть критичными.
- Функция dir() перебирает все члены класса, в том числе магические методы. Возможно, вам захочется отдельно прописать черный список методов, которые вы не хотите видеть в заглушках.
- В примере есть проверка на декоратор property, но исключительно в виде демонстрации. Чтобы узнать тип, спрятанный за декоратором, необходимо исследовать исходный код декоратора.
- Все, для чего нет отдельной ветки, трактуется как переменная, и тип выводится через функцию type(). Это может стать проблемой, когда в проекте появится новый декоратор.
Отдельного внимания заслуживает обработка методов и функций, потому что это сложнее, чем кажется.
Как работать с методами
Работа с методами сложна тем, что у нас есть ссылка на метод, а нам хочется сигнатуру, как написал программист. К счастью, в пакете inspect найдется функция и на этот случай: signature(). Функция возвращает объект класса Signature, который очень красиво преобразуется к строке.
>>> from typing import List >>> def foo(a, b: str = None) -> List: . pass >>> signature = inspect.signature(foo) >>> str(signature) (a, b: str = None) -> List
Красиво. На радостях от простого решения забыли протестировать на более сложных типах? Фатальная ошибка.
>>> import enum >>> class SomeEnum(enum.Enum): . default = "default" . magic = "magic" . >>> from typing import List >>> def foo(a, b: SomeEnum = SomeEnum.magic) -> List: . pass . >>> signature = inspect.signature(foo) >>> str(signature) (a, b: __main__.SomeEnum = ) -> List >>> signature.return_annotation typing.List
Сложные типы при преобразовании сигнатуры в строку могут превратиться в синтаксически некорректную строку, как произошло со значением по умолчанию. Некоторые типы при этом могут обрасти префиксами пакетов, которые не импортировались в исходном коде, а значит, не попали в импорты файла-заглушки. Закономерный вопрос: что делать?
- «Прибить» в заглушки импорты корневых пакетов вашего приложения. Например, для кода выше сделать что-то вроде import __main__. Однако это не поможет против перечислений.
- Написать функцию разрешения типов, которая по доступным импортам и строковому представлению будет гадать, какое именно описание типа правильное.
В моем случае я наспех собрал систему разрешения типов, которая разбивала имя типа по точкам и проверяла, есть ли такое в импортах. Не очень качественное решение, но зато быстрое.
Осталось совместить полученные знания и дополнить книгу заклинаний методами непосредственно из реализаций заклинаний. Здесь алгоритм остается тем же:
- Получаем файл, в котором реализован класс заклинания.
- Забираем импорты.
- Получаем сигнатуру функции cast.
- Дописываем в класс SpellBook функцию с именем из spell_name и сигнатурой из пункта 3, а вместо pass добавляем комментарий из spell_description.
Остается лишь одна мелочь, которая не дает покоя. Методов заклинаний не существует на самом деле, поэтому нельзя перейти к исходной реализации. Может, можно сделать через docstring? Вот так, например.
class SpellBook: def confundus(self, exitcode=0): """ Сбивает с толку """
К сожалению, вот уже пять лет как открыт запрос на эту функциональность. Но, как говорится, воз и ныне там.
Заключение
В результате этого приключения на двадцать минут получился инструмент, который собран из палок и термоклея, но даже в таком виде упрощает жизнь мне и моим коллегам. Более того, я чуть больше познакомился со специфичным в интерпретаторе Python, что, определенно, расширяет кругозор.
Исходного кода, увы, не будет. Получившийся инструмент сильно завязан на особенности нашего проекта, код которого раскрыть я не могу.
Почему при импортировании pycharm подчеркивает import «название модуля» серым цветом?

Можете пожалуйста прикрепить скрин, думаю, что проблема с отступами между импортами.
Ответ написан более двух лет назад
ONEoftheFIRST @ONEoftheFIRST Автор вопроса


Alexander Pikeev прав. Пока ты не используешь этот модуль, он не будет подсвечиваться.
Как работают импорты в Python
Импорты не так просты, как кажется. Тем более если они пишутся для двух несовместимых версий языка. Разбираемся с основными проблемами и их решением.
Порой бывает трудно правильно реализовать import с первого раза, особенно если мы хотим добиться правильной работы на плохо совместимых между собой версиях Python 2 и Python 3. Попытаемся разобраться, что из себя представляют импорты в Python и как написать решение, которое подойдёт под обе версии языка.
Ключевые моменты
- Выражения import производят поиск по списку путей в sys.path .
- sys.path всегда включает в себя путь скрипта, запущенного из командной строки, и не зависит от текущей рабочей директории.
- Импортирование пакета по сути равноценно импортированию __init__.py этого пакета.
Основные определения
- Модуль: любой файл *.py . Имя модуля — имя этого файла.
- Встроенный модуль: «модуль», который был написан на Си, скомпилирован и встроен в интерпретатор Python, и потому не имеет файла *.py .
- Пакет: любая папка, которая содержит файл __init__.py . Имя пакета — имя папки.С версии Python 3.3 любая папка (даже без __init__.py) считается пакетом.
- Объект: в Python почти всё является объектом — функции, классы, переменные и т. д.
Пример структуры директорий
test/ # Корневая папка packA/ # Пакет packA subA/ # Подпакет subA __init__.py sa1.py sa2.py __init__.py a1.py a2.py packB/ # Пакет packB (неявный пакет пространства имён) b1.py b2.py math.py random.py other.py start.py
Обратите внимание, что в корневой папке test/ нет файла __init__.py .
Что делает import
При импорте модуля Python выполняет весь код в нём. При импорте пакета Python выполняет код в файле пакета __init__.py , если такой имеется. Все объекты, определённые в модуле или __init__.py , становятся доступны импортирующему.
Основы import и sys.path
Вот как оператор import производит поиск нужного модуля или пакета согласно документации Python:
-
t
- директории, содержащей исходный скрипт (или текущей директории, если файл не указан); t
- директории по умолчанию, которая зависит от дистрибутива Python; t
- PYTHONPATH (список имён директорий; имеет синтаксис, аналогичный переменной окружения PATH ).
Технически документация не совсем полна. Интерпретатор будет искать не только файл (модуль) spam.py , но и папку (пакет) spam .
Обратите внимание, что Python сначала производит поиск среди встроенных модулей — тех, которые встроены непосредственно в интерпретатор. Список встроенных модулей зависит от дистрибутива Python, а найти этот список можно в sys.builtin_module_names (Python 2 и Python 3). Обычно в дистрибутивах есть модули sys (всегда включён в дистрибутив), math , itertools , time и прочие.
В отличие от встроенных модулей, которые при поиске проверяются первыми, остальные (не встроенные) модули стандартной библиотеки проверяются после директории запущенного скрипта. Это приводит к сбивающему с толку поведению: возможно «заменить» некоторые, но не все модули стандартной библиотеки. Допустим, модуль math является встроенным модулем, а random — нет. Таким образом, import math в start.py импортирует модуль из стандартной библиотеки, а не наш файл math.py из той же директории. В то же время, import random в start.py импортирует наш файл random.py .
Кроме того, импорты в Python регистрозависимы: import Spam и import spam — разные вещи.
Функцию pkgutil.iter_modules() (Python 2 и Python 3) можно использовать, чтобы получить список всех модулей, которые можно импортировать из заданного пути:
import pkgutil search_path = ['.'] # Используйте None, чтобы увидеть все модули, импортируемые из sys.path all_modules = [x[1] for x in pkgutil.iter_modules(path=search_path)] print(all_modules)
Чуть подробнее о sys.path
Чтобы увидеть содержимое sys.path , запустите этот код:
import sys print(sys.path)
Документация Python описывает sys.path так:
Список строк, указывающих пути для поиска модулей. Инициализируется из переменной окружения PYTHONPATH и директории по умолчанию, которая зависит от дистрибутива Python.При запуске программы после инициализации первым элементом этого списка, path[0] , будет директория, содержащая скрипт, который был использован для вызова интерпретатора Python. Если директория скрипта недоступна (например, если интерпретатор был вызван в интерактивном режиме или скрипт считывается из стандартного ввода), то path[0] является пустой строкой. Из-за этого Python сначала ищет модули в текущей директории. Обратите внимание, что директория скрипта вставляется перед путями, взятыми из PYTHONPATH .Источник: Python 2 и Python 3
Документация к интерфейсу командной строки Python добавляет информацию о запуске скриптов из командной строки. В частности, при запуске python .py .
Если имя скрипта ссылается непосредственно на Python-файл, то директория, содержащая этот файл, добавляется в начало sys.path , а файл выполняется как модуль main .Источник: Python 2 и Python 3
Итак, повторим порядок, согласно которому Python ищет импортируемые модули:
- Модули стандартной библиотеки (например, math , os ).
- Модули или пакеты, указанные в sys.path :Если интерпретатор Python запущен в интерактивном режиме:sys.path[0] — пустая строка ». Это значит, что Python будет искать в текущей рабочей директории, из которой вы запустили интерпретатор. В Unix-системах эту директорию можно узнать с помощью команды pwd.Если мы запускаем скрипт командой python
