5 методов извлечения функций из Python-модулей: полное руководство
Для кого эта статья:
- Разработчики Python, желающие улучшить свои навыки анализа кода
- Ученики и студенты, обучающиеся программированию на Python
Профессионалы, работающие с большими модулями и стремящиеся оптимизировать свой рабочий процесс
Давайте будем честны: разбираться в чужом Python-коде — тот ещё квест, особенно когда модуль большой, а документации нет. Извлечение списка функций из модуля — это не просто техническая необходимость, а настоящий способ быстро понять, "что здесь вообще происходит". Хороший разработчик всегда вооружен инструментами для инспекции кода. В этой статье я разберу пять проверенных методов, которые превратят вас из детектива, разбирающего код строчка за строчкой, в аналитика, мгновенно видящего структуру модуля. 🕵️♂️
Если вы хотите не только узнавать о функциях в модуле, но и создавать собственные мощные модули, которые другие будут с удовольствием исследовать, обратите внимание на курс Обучение Python-разработке от Skypro. На нём вы научитесь не только писать чистый, хорошо структурированный код, но и эффективно применять все инструменты для анализа и оптимизации существующих решений — от основ до продвинутых техник Python-разработки.
Основные способы получения списка функций в модуле Python
Python предоставляет несколько встроенных инструментов для инспекции модулей и извлечения из них функций. Каждый метод имеет свои сильные стороны и ограничения, которые важно учитывать в зависимости от конкретной задачи.
Прежде чем погрузиться в детали, давайте определим, что именно мы хотим получить при анализе модуля:
- Все публичные функции (не начинающиеся с подчеркивания)
- Приватные и защищенные функции (начинающиеся с одного или двух подчеркиваний)
- Только пользовательские функции (без встроенных и импортированных)
- Метаданные о функциях (сигнатуры, документация, исходный код)
В зависимости от этих критериев, мы можем выбрать наиболее подходящий метод из следующих пяти подходов.
| Метод | Сложность | Точность | Дополнительные возможности |
|---|---|---|---|
| dir() | Низкая | Средняя | Показывает все атрибуты |
| inspect.getmembers() | Средняя | Высокая | Фильтрация по типу |
| Ручная фильтрация | Средняя | Высокая | Гибкая настройка |
| inspect.getmembers() с фильтрацией | Средняя | Очень высокая | Метаданные о функциях |
| AST анализ | Высокая | Очень высокая | Анализ исходного кода |

Метод №1: Использование функции dir() для анализа модуля
Начнем с самого простого и универсального способа — использования встроенной функции dir(). Она возвращает список атрибутов указанного объекта, включая функции и методы.
Вот базовый пример использования dir() для получения всех атрибутов модуля:
import math
# Получаем все атрибуты модуля math
all_attributes = dir(math)
# Выводим список атрибутов
for attr in all_attributes:
print(attr)
Однако в выводе dir() содержатся не только функции, но и переменные, классы и другие типы атрибутов. Для фильтрации только функций можно использовать следующий подход:
import math
# Получаем атрибуты модуля
all_attributes = dir(math)
# Отфильтровываем только функции
functions = []
for attr in all_attributes:
# Получаем объект атрибута
obj = getattr(math, attr)
# Проверяем, является ли объект вызываемым (функцией)
if callable(obj) and not attr.startswith('__'):
functions.append(attr)
print(f"Найдено {len(functions)} функций в модуле math:")
for func in functions:
print(f"- {func}")
Алексей, Python-архитектор Когда я начал работать над проектом по автоматической генерации документации, первым делом попробовал использовать
dir()для анализа модулей. Это казалось логичным — простой инструмент для простой задачи. Но быстро выяснил, чтоdir()даёт слишком много "шума". В одном из модулей было около 200 атрибутов, и только 30 из них были настоящими функциями, которые нам требовалось документировать. После нескольких часов ручной фильтрации я понял, что нужен более точный метод. Я добавил проверку наcallable()и дополнительные условия для исключения импортированных функций. Это сработало, но позже я всё-таки перешёл наinspect.getmembers()— он оказался гораздо элегантнее для сложных случаев. Но для быстрой разведки новых модулейdir() + callable()до сих пор остаётся моим первым выбором.
Преимущества метода dir():
- Простота использования — встроенная функция без дополнительных импортов
- Универсальность — работает с любыми объектами Python
- Быстродействие — минимальные накладные расходы
Недостатки метода dir():
- Возвращает все атрибуты без различия их типов
- Не предоставляет дополнительной информации о функциях
- Требует дополнительной фильтрации для получения только функций
Для простых случаев dir() вполне подходит, особенно если нужно быстро изучить содержимое незнакомого модуля. Для более глубокого анализа стоит обратиться к следующим методам. 🔍
Метод №2: Модуль inspect и его мощные возможности
Модуль inspect — это настоящая швейцарская армейская бритва для анализа объектов Python в реальном времени. В отличие от простой функции dir(), он предоставляет множество специализированных инструментов для глубокого изучения кода.
Основной метод модуля inspect для нашей задачи — функция isfunction(), которая точно определяет, является ли объект функцией:
import inspect
import math
# Получаем только функции из модуля math
functions = []
for name in dir(math):
obj = getattr(math, name)
if inspect.isfunction(obj):
functions.append(name)
print(f"Функции в модуле math: {functions}")
Обратите внимание, что в стандартной библиотеке многие методы реализованы как встроенные функции (built-in), которые не определяются как функции Python. Для них нужно использовать функцию isbuiltin():
import inspect
import math
# Получаем как обычные функции, так и встроенные
functions = []
for name in dir(math):
obj = getattr(math, name)
if inspect.isfunction(obj) or inspect.isbuiltin(obj):
functions.append(name)
print(f"Всего функций (включая встроенные): {len(functions)}")
print(functions)
Но настоящая мощь inspect раскрывается, когда мы хотим получить детальную информацию о функциях:
import inspect
import math
for name in dir(math):
obj = getattr(math, name)
if inspect.isfunction(obj) or inspect.isbuiltin(obj):
# Получаем сигнатуру функции
try:
signature = inspect.signature(obj)
doc = obj.__doc__.split("\n")[0] if obj.__doc__ else "Нет документации"
print(f"{name}{signature} – {doc}")
except (ValueError, TypeError):
print(f"{name}() – Невозможно получить сигнатуру")
Модуль inspect также позволяет получить исходный код функции, что особенно полезно для понимания как она работает:
import inspect
import requests # Используем библиотеку с Python-функциями
# Выберем функцию для анализа
function = requests.get
# Получим исходный код (если доступен)
try:
source_code = inspect.getsource(function)
print(f"Исходный код функции {function.__name__}:")
print(source_code)
except TypeError:
print(f"Невозможно получить исходный код для {function.__name__} (возможно, это C-функция)")
| Функция inspect | Назначение | Пример использования |
|---|---|---|
| isfunction() | Проверяет, является ли объект функцией Python | inspect.isfunction(math.sin) |
| isbuiltin() | Проверяет, является ли объект встроенной функцией | inspect.isbuiltin(len) |
| ismethod() | Проверяет, является ли объект методом класса | inspect.ismethod(str.split) |
| signature() | Возвращает сигнатуру функции | inspect.signature(print) |
| getsource() | Возвращает исходный код функции | inspect.getsource(sorted) |
| getdoc() | Возвращает документацию функции | inspect.getdoc(open) |
Метод №3: Фильтрация атрибутов модуля по типам объектов
Иногда нам требуется более тонкий контроль над процессом фильтрации функций из модуля. Например, мы можем захотеть получить только пользовательские функции, игнорируя импортированные или встроенные. В таких случаях ручная фильтрация атрибутов по типам объектов может быть наиболее гибким решением.
Рассмотрим пример, где мы создаем собственный модуль и затем анализируем его содержимое:
# Создаем тестовый модуль для анализа
# Сохраним его в файл test_module.py
def public_function():
"""Это публичная функция."""
return "Публичная функция"
def _private_function():
"""Это приватная функция."""
return "Приватная функция"
class TestClass:
def method(self):
return "Метод класса"
# Импортируем стороннюю функцию
from math import sin
# Добавляем константу
PI = 3.14159
Теперь напишем код для анализа этого модуля:
import types
import test_module
import inspect
# Получим все атрибуты модуля
all_attributes = dir(test_module)
# Создадим словарь для категоризации атрибутов
module_contents = {
'user_functions': [],
'imported_functions': [],
'private_functions': [],
'methods': [],
'classes': [],
'constants': []
}
# Проанализируем каждый атрибут
for attr_name in all_attributes:
# Пропустим специальные атрибуты Python
if attr_name.startswith('__') and attr_name.endswith('__'):
continue
# Получим объект атрибута
attr = getattr(test_module, attr_name)
# Категоризируем атрибут на основе его типа и имени
if inspect.isfunction(attr):
# Проверим, определена ли функция в этом модуле
if inspect.getmodule(attr) == test_module:
if attr_name.startswith('_'):
module_contents['private_functions'].append(attr_name)
else:
module_contents['user_functions'].append(attr_name)
else:
module_contents['imported_functions'].append(attr_name)
elif inspect.isclass(attr):
module_contents['classes'].append(attr_name)
elif inspect.ismethod(attr):
module_contents['methods'].append(attr_name)
elif not callable(attr) and not attr_name.startswith('_'):
# Вероятно, это константа
module_contents['constants'].append(attr_name)
# Выведем результаты
for category, items in module_contents.items():
if items:
print(f"\n{category.replace('_', ' ').title()}:")
for item in items:
print(f"- {item}")
Этот подход даёт нам чёткую картину состава модуля, разделяя его содержимое на логические категории. Особенно полезно это при изучении больших модулей или при документировании своего кода. 📊
Для дальнейшего улучшения можно добавить получение дополнительной информации о каждой функции:
def analyze_function(func):
"""Анализирует функцию и возвращает информацию о ней."""
try:
signature = str(inspect.signature(func))
except (ValueError, TypeError):
signature = "(неизвестная сигнатура)"
doc = func.__doc__
if doc:
doc = doc.strip().split('\n')[0] # Берем только первую строку документации
else:
doc = "Нет документации"
return {
'name': func.__name__,
'signature': signature,
'doc': doc,
'module': inspect.getmodule(func).__name__
}
# Теперь можно использовать эту функцию для вывода подробной информации:
for func_name in module_contents['user_functions']:
func = getattr(test_module, func_name)
info = analyze_function(func)
print(f"{info['name']}{info['signature']} – {info['doc']}")
Метод №4: Извлечение функций с помощью getmembers()
Метод getmembers() из модуля inspect предоставляет элегантный способ извлечения членов объекта определенного типа. Это позволяет нам в одну строку получить все функции модуля без необходимости ручной фильтрации.
Марина, Lead Python Developer Однажды наша команда столкнулась с интересной проблемой. Мы разрабатывали фреймворк, который должен был автоматически регистрировать все обработчики API, объявленные в определённых модулях. Изначально мы использовали простой подход с
dir()и фильтрацией по именам, но это приводило к хрупкому коду и требовало от разработчиков соблюдения строгих соглашений об именовании. Я предложила использоватьinspect.getmembers()с предикатомisfunction, и это полностью изменило архитектуру нашего решения. Мы смогли автоматически находить все подходящие функции и проверять их аннотации типов для определения, какие из них являются обработчиками API. Это не только упростило код, но и сделало его более надежным — теперь разработчики могли называть свои функции как угодно, главное было правильно их аннотировать. С тех порgetmembers()стал нашим стандартным инструментом для рефлексии кода. Мы даже создали на его основе декораторы, которые автоматически регистрировали функции в нужных местах при их определении.
Вот простой пример использования getmembers() для получения всех функций модуля:
import inspect
import math
# Получаем все функции из модуля math
functions = inspect.getmembers(math, inspect.isfunction)
print(f"Функции в модуле math:")
for name, func in functions:
print(f"- {name}")
Главное преимущество getmembers() — возможность указать предикат для фильтрации объектов определенного типа. Мы можем легко комбинировать несколько предикатов для более сложной фильтрации:
import inspect
import math
# Определим предикатную функцию для более сложной фильтрации
def is_math_function(obj):
return (inspect.isfunction(obj) or inspect.isbuiltin(obj)) and not obj.__name__.startswith('_')
# Получаем публичные функции из модуля
functions = inspect.getmembers(math, is_math_function)
print(f"Публичные функции в модуле math:")
for name, func in functions:
print(f"- {name}")
Комбинируя getmembers() с другими функциями из модуля inspect, мы можем создать мощный анализатор функций:
import inspect
import json
import requests
# Анализируем модуль requests
functions = inspect.getmembers(requests, inspect.isfunction)
# Создадим структурированную информацию о каждой функции
function_info = {}
for name, func in functions:
try:
signature = str(inspect.signature(func))
except (ValueError, TypeError):
signature = "(неизвестная сигнатура)"
# Получим документацию и исходный модуль
doc = inspect.getdoc(func)
doc_summary = doc.split('\n')[0] if doc else "Нет документации"
module_name = inspect.getmodule(func).__name__
# Добавим в словарь
function_info[name] = {
"signature": signature,
"doc_summary": doc_summary,
"module": module_name
}
# Выведем информацию в JSON-формате для наглядности
print(json.dumps(function_info, indent=2))
Преимущества использования getmembers():
- Лаконичный и читаемый код для получения членов определенного типа
- Возможность использования предикатных функций для сложной фильтрации
- Возвращает не только имена, но и сами объекты функций
- Хорошо интегрируется с другими функциями модуля
inspect
Этот метод является наиболее универсальным и гибким для большинства задач по анализу функций модуля. 🛠️
Метод №5: Автоматизация анализа кода через AST
Если предыдущие методы работают с загруженными модулями в памяти, то анализ через Abstract Syntax Tree (AST) позволяет изучать исходный код Python-файлов без их фактического импорта и выполнения. Это особенно полезно при анализе потенциально небезопасного кода или когда импорт модуля может иметь побочные эффекты.
Python предоставляет модуль ast для работы с абстрактным синтаксическим деревом. С его помощью мы можем парсить исходный код и находить в нем определения функций:
import ast
import os
def get_functions_from_file(file_path):
"""Извлекает все определения функций из Python-файла."""
with open(file_path, 'r', encoding='utf-8') as file:
code = file.read()
# Парсим код в AST
tree = ast.parse(code)
# Извлекаем определения функций
functions = []
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
functions.append({
'name': node.name,
'lineno': node.lineno,
'args': [arg.arg for arg in node.args.args],
'defaults': len(node.args.defaults)
})
return functions
# Пример использования для анализа текущего файла
current_file = os.path.abspath(__file__)
functions = get_functions_from_file(current_file)
print(f"Функции в файле {os.path.basename(current_file)}:")
for func in functions:
args_info = f"{len(func['args'])} аргументов"
if func['defaults'] > 0:
args_info += f" ({func['defaults']} со значениями по умолчанию)"
print(f"- {func['name']} (строка {func['lineno']}): {args_info}")
Для более глубокого анализа мы можем написать специальный визитор AST, который собирает дополнительную информацию о функциях:
import ast
import os
class FunctionVisitor(ast.NodeVisitor):
def __init__(self):
self.functions = []
def visit_FunctionDef(self, node):
# Собираем декораторы
decorators = []
for decorator in node.decorator_list:
if isinstance(decorator, ast.Name):
decorators.append(decorator.id)
elif isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Name):
decorators.append(f"{decorator.func.id}()")
# Собираем аннотации типов
return_annotation = None
if node.returns:
if isinstance(node.returns, ast.Name):
return_annotation = node.returns.id
# Проверяем, есть ли документация
docstring = ast.get_docstring(node)
self.functions.append({
'name': node.name,
'lineno': node.lineno,
'args': [arg.arg for arg in node.args.args],
'decorators': decorators,
'return_annotation': return_annotation,
'has_docstring': docstring is not None
})
# Продолжаем обход по дереву
self.generic_visit(node)
def analyze_python_file(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
code = file.read()
tree = ast.parse(code)
visitor = FunctionVisitor()
visitor.visit(tree)
return visitor.functions
# Анализируем любой Python-файл
sample_file = 'sample.py' # Замените на путь к реальному файлу
try:
functions = analyze_python_file(sample_file)
print(f"Найдено {len(functions)} функций в {sample_file}:")
for func in functions:
decorator_info = f" [декораторы: {', '.join(func['decorators'])}]" if func['decorators'] else ""
doc_info = " (есть документация)" if func['has_docstring'] else " (нет документации)"
print(f"- {func['name']} (строка {func['lineno']}){decorator_info}{doc_info}")
except FileNotFoundError:
print(f"Файл {sample_file} не найден.")
except SyntaxError as e:
print(f"Ошибка синтаксиса в файле: {e}")
Использование AST-анализа имеет несколько ключевых преимуществ:
- Безопасность — код не выполняется, а только анализируется
- Глубокий анализ — можно получить детальную информацию о структуре функций
- Независимость от импорта — работает даже с кодом, который не может быть импортирован
- Анализ исходного кода — можно получить информацию об аргументах, аннотациях типов и декораторах
AST-анализ особенно полезен при создании инструментов статического анализа кода, генераторов документации или для автоматического рефакторинга. Это самый мощный из рассмотренных методов, но и наиболее сложный в использовании. 🔬
Изучив все пять методов получения списка функций из модуля Python, вы теперь вооружены инструментами для любого сценария. От быстрого просмотра с помощью
dir()до глубокого статического анализа с AST — выбор зависит только от ваших потребностей. Используйте эти методы не только для изучения чужого кода, но и для автоматизации собственных процессов разработки. Помните: хороший программист не просто пишет код, но и мастерски его анализирует.