Способы извлечения информации об установленных модулях Python

Раздел: Разработка на Python -> Управление пакетами Python

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

Основные подходы к чтению данных о пакете

Как получить версию и метаданные пакета самым простым и надёжным способом?

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


import importlib.metadata as meta

# Получение версии пакета requests
try:
    version = meta.version('requests')
    print(f'Версия requests: {version}')
except meta.PackageNotFoundError:
    print('Пакет requests не установлен')

не работает import python (не работает импорт в python)

Функция version() возвращает строку с номером версии. Для получения полного набора метаданных используется metadata(), которая возвращает объект PackageMetadata с доступом к полям через атрибуты или как к словарю.


import importlib.metadata as meta

md = meta.metadata('requests')
print(f'Автор: {md["Author"]}')
print(f'Лицензия: {md["License"]}')
print(f'Требуемые зависимости: {md.get_all("Requires-Dist")}')

Python core package (базовые пакеты python)

Типичные ошибки:

  • PackageNotFoundError - возникает, если пакет не установлен или имя указано неверно. Решение: проверить установку командой pip list или использовать блок try-except.
  • KeyError - если поле отсутствует в метаданных (например, 'License' не указано). Решение: использовать метод get() с значением по умолчанию.
  • Проблемы с кодировкой - в редких случаях файлы METADATA могут быть в нестандартной кодировке. importlib.metadata обрабатывает это автоматически.

Этот подход подходит для большинства сценариев: получение версии, зависимостей, точек входа, лицензии. Он не требует внешних библиотек и работает быстро.

Как получить метаданные с помощью устаревшего pkg_resources?

До появления importlib.metadata стандартным способом был модуль pkg_resources из пакета setuptools. Он все еще используется в старых проектах.


import pkg_resources

try:
    pkg = pkg_resources.get_distribution('requests')
    print(f'Версия: {pkg.version}')
    print(f'Автор: {pkg.metadata["Author"]}')
except pkg_resources.DistributionNotFound:
    print('Пакет не найден')

Python package version (версия пакета python)

Недостатки: модуль тяжеловесный, считается устаревшим, может не работать в некоторых окружениях (например, при использовании pip install --no-deps). Рекомендуется переходить на importlib.metadata.

Ошибка DistributionNotFound - аналогична PackageNotFoundError. Решение - проверять установку.

Как прочитать информацию о пакете через команду pip show?

Иногда удобно вызвать внешнюю команду pip show и обработать её вывод. Это полезно в скриптах, которые уже используют subprocess, или для совместимости с очень старыми версиями Python.


import subprocess
import sys

def get_pip_show(package_name):
    result = subprocess.run([sys.executable, '-m', 'pip', 'show', package_name],
                           capture_output=True, text=True)
    if result.returncode != 0:
        return None  # пакет не найден
    data = {}
    for line in result.stdout.strip().splitlines():
        if ':' in line:
            key, value = line.split(':', 1)
            data[key.strip()] = value.strip()
    return data

info = get_pip_show('requests')
if info:
    print(f'Версия: {info.get("Version")}')
    print(f'Требует: {info.get("Requires")}')
else:
    print('Пакет не установлен')

Python package dependencies (зависимости пакетов python)

Данный метод медленнее, так как запускает новый процесс. Также вывод pip show локализован, что может привести к неожиданным ключам. Рекомендуется для одноразовых скриптов, а не для production.

Проблема: если pip не установлен (в некоторых минимальных образах), команда завершится ошибкой. Решение - проверять наличие pip.

Как напрямую прочитать файл METADATA установленного пакета?

Метаданные пакета хранятся в файле METADATA внутри директории *.dist-info в site-packages. Можно найти этот путь через importlib.util.find_spec или pkgutil и прочитать файл вручную.


import importlib.util
import os
import email.parser

def read_metadata_direct(package_name):
    spec = importlib.util.find_spec(package_name)
    if spec is None:
        return None
    # Ищем dist-info рядом с модулем
    path = os.path.dirname(spec.origin)
    for entry in os.listdir(path):
        if entry.endswith('.dist-info'):
            meta_path = os.path.join(path, entry, 'METADATA')
            if os.path.isfile(meta_path):
                with open(meta_path, 'r', encoding='utf-8') as f:
                    return email.parser.HeaderParser().parsestr(f.read())
    return None

meta_data = read_metadata_direct('requests')
if meta_data:
    print(f'Version: {meta_data["Version"]}')
    print(f'Author: {meta_data.get("Author")}')

Run python package (запуск python пакета)

Этот способ низкоуровневый и менее надёжный, так как формат dist-info может меняться. Используйте, если нужен доступ к сырым данным.

Ошибка: если пакет не имеет origin (встроенный модуль) или dist-info отсутствует. Решение - проверять наличие spec и файлов.

Как просмотреть исходный код функции или класса из пакета?

Модуль inspect позволяет получить исходный код объектов, если он доступен (написан на Python).


import inspect
import requests

# Получить исходный код функции requests.get
source = inspect.getsource(requests.get)
print(source[:300])  # первые 300 символов

Python package index (индекс пакетов python (pypi))

Если модуль написан на C (например, модули из стандартной библиотеки, реализованные на C), inspect.getsource вызовет OSError. В таких случаях можно использовать inspect.getfile для получения пути к файлу.

Ошибка OSError - при попытке получить исходный код встроенного модуля. Решение: проверять с помощью inspect.isfunction() и обрабатывать исключение.

Как получить список модулей внутри пакета?

Для перечисления всех модулей, входящих в пакет, можно использовать pkgutil.iter_modules.


import pkgutil
import requests

# Предположим, что requests - пакет
for importer, modname, ispkg in pkgutil.walk_packages(path=requests.__path__):
    print(f'{modname} (пакет: {ispkg})')

Это позволяет узнать структуру пакета. Полезно для анализа внутренних зависимостей.

Проблема: если пакет не имеет __path__ (namespace package), walk_packages может не сработать. Решение: использовать pkgutil.iter_modules с корневым путем.

- Python 3 packages (пакеты в python 3)
- Python packages (пакеты python)
- Read package python (чтение пакета в python)

Расширенные примеры чтения пакетов

Ниже представлены неочевидные и углубленные сценарии использования описанных методов.

1. Получение полной информации о пакете с помощью importlib.metadata

Пример демонстрирует извлечение всех метаданных пакета 'requests', включая точки входа (entry points), классификаторы (classifiers) и список файлов.

Пример

import importlib.metadata as meta

pkg_name = 'requests'
try:
    md = meta.metadata(pkg_name)
    print('=== Метаданные requests ===')
    print(f'Версия: {md["Version"]}')
    print(f'Имя: {md["Name"]}')
    print(f'Автор: {md.get("Author", "не указан")}')
    print(f'Лицензия: {md.get("License", "не указана")}')
    print(f'URL: {md.get("Home-page", "нет")}')
    print('Зависимости:')
    for req in md.get_all('Requires-Dist', []):
        print(f'  {req}')
    print('Классификаторы:')
    for cls in md.get_all('Classifier', []):
        print(f'  {cls}')
    # Точки входа
    eps = meta.entry_points(group='console_scripts')
    print('Консольные скрипты:')
    for ep in eps:
        if ep.dist.name == pkg_name:
            print(f'  {ep.name} -> {ep.value}')
except meta.PackageNotFoundError:
    print(f'Пакет {pkg_name} не установлен')
=== Метаданные requests ===
Версия: 2.31.0
Имя: requests
Автор: Kenneth Reitz
Лицензия: Apache 2.0
URL: https://requests.readthedocs.io
Зависимости:
  certifi >=2017.4.17
  charset-normalizer >=2,<4
  idna >=2.5,<4
  urllib3 >=1.21.1,<3
Классификаторы:
  Development Status :: 5 - Production/Stable
  Intended Audience :: Developers
  ...
Консольные скрипты:
  (нет)

2. Обработка ошибок при отсутствии пакета

При массовой проверке пакетов важно корректно обрабатывать случаи, когда пакет не найден.

Пример

import importlib.metadata as meta

packages_to_check = ['requests', 'nonexistent_package', 'flask']
for pkg in packages_to_check:
    try:
        ver = meta.version(pkg)
        print(f'{pkg}: версия {ver}')
    except meta.PackageNotFoundError:
        print(f'{pkg}: не установлен')
    except Exception as e:
        print(f'{pkg}: ошибка {e}')
requests: версия 2.31.0
nonexistent_package: не установлен
flask: версия 3.0.0

3. Рекурсивный обход файлов пакета с использованием pathlib и importlib.resources

Модуль importlib.resources предоставляет доступ к файлам, входящим в состав пакета (например, данные конфигурации).

Пример

import importlib.resources as res
import pathlib

# Пакет 'email' содержит шаблоны
try:
    # Получить путь к файлу 'charsets.py' внутри пакета 'email.mime'
    path = res.files('email.mime').joinpath('charsets.py')
    print(f'Файл существует: {path.is_file()}')
    # Прочитать содержимое
    content = path.read_text(encoding='utf-8')
    print(f'Первые 100 символов: {content[:100]}')
except ModuleNotFoundError:
    print('Пакет email.mime не найден')
except Exception as e:
    print(f'Ошибка: {e}')
Файл существует: True
Первые 100 символов: """Base64 and quoted-printable encodings

This module contains functions for...

4. Сравнение версий пакетов с помощью packaging

Иногда нужно проверить, удовлетворяет ли установленная версия некоторому диапазону.

Пример

import importlib.metadata as meta
from packaging.version import Version, VersionSet

def check_version_in_range(pkg_name, min_ver, max_ver):
    try:
        ver = Version(meta.version(pkg_name))
        vs = VersionSet([Version(min_ver), Version(max_ver)])
        return ver in vs
    except meta.PackageNotFoundError:
        return False

print('requests в диапазоне [2.0, 3.0):', check_version_in_range('requests', '2.0', '3.0'))
print('numpy в диапазоне [1.0, 2.0):', check_version_in_range('numpy', '1.0', '2.0'))
requests в диапазоне [2.0, 3.0): True
numpy в диапазоне [1.0, 2.0): True

5. Чтение метаданных из wheel-файла

Можно прочитать метаданные напрямую из .whl файла без установки пакета.

Пример

import zipfile
import email.parser

def read_metadata_from_wheel(wheel_path):
    with zipfile.ZipFile(wheel_path, 'r') as zf:
        # Ищем файл *.dist-info/METADATA
        for name in zf.namelist():
            if name.endswith('.dist-info/METADATA'):
                with zf.open(name) as f:
                    data = f.read().decode('utf-8')
                    return email.parser.HeaderParser().parsestr(data)
    return None

# Пример с файлом (путь нужно заменить на существующий)
# metadata = read_metadata_from_wheel('/path/to/some_package-1.0-py3-none-any.whl')
# print(metadata['Name'])

Этот метод полезен для предварительного анализа перед установкой.

6. Использование importlib.metadata для проверки зависимостей всего окружения

Напишем функцию, которая собирает все пакеты и их зависимости.

Пример

import importlib.metadata as meta

def get_all_requirements():
    requirements = {}
    for dist in meta.distributions():
        pkg_name = dist.metadata['Name']
        reqs = dist.requires or []
        requirements[pkg_name] = [str(r) for r in reqs]
    return requirements

all_reqs = get_all_requirements()
for pkg, reqs in list(all_reqs.items())[:3]:
    print(f'{pkg}: {reqs}')
requests: ['certifi>=2017.4.17', 'charset-normalizer>=2,<4', 'idna>=2.5,<4', 'urllib3>=1.21.1,<3']
pip: ['setuptools>=40.8.0', 'wheel']
setuptools: []

Чтение пакета в Python - comments

En
Read package python (python)