Как вызвать функцию модуля по имени в Python (getattr)
Динамический вызов функции модуля по строковому имени в Python с помощью getattr и importlib. Примеры кода, проверки безопасности, обработка async-функций и полный шаблон реализации call(module, func_name). Избегайте eval для безопасности.
Как динамически вызвать функцию модуля, используя её имя в виде строки в Python? Например, если у меня есть:
import foo
func_name = "bar"
call(foo, func_name) # Это должно вызвать foo.bar()
Как правильно реализовать эту функциональность?
Чтобы динамически вызвать python функцию модуля по её строковому имени, используйте встроенную функцию getattr: func = getattr(foo, func_name) и затем func(*args, **kwargs). Для динамического импорта модуля применяйте importlib.import_module, избегайте eval и всегда проверяйте, что найденный атрибут — callable, обрабатывая AttributeError/TypeError; для coroutine-функций учитывайте inspect.iscoroutinefunction и вызывайте их через await или asyncio.run.
Содержание
- Как вызвать функцию модуля по имени (getattr)
- Динамический импорт модуля и путь вида “package.module.func”
- Проверки и безопасность при динамическом вызове функции в Python
- Асинхронные функции и методы класса
- Полный пример: надёжная реализация call(module, func_name)
- Источники
- Заключение
Как вызвать функцию модуля по имени (getattr)
Самый простой и обычно правильный способ — использовать getattr. Это стандартный механизм для динамического доступа к атрибутам (включая функции) модуля:
# пример простой обёртки
def call(module, func_name, *args, **kwargs):
func = getattr(module, func_name) # если нет — AttributeError
if not callable(func):
raise TypeError(f"{func_name!r} is not callable")
return func(*args, **kwargs)
# использование
import foo
result = call(foo, "bar", 1, 2) # эквивалент foo.bar(1, 2)
Если хотите избежать исключения при отсутствии атрибута, используйте значение по умолчанию у getattr и проверку:
func = getattr(module, func_name, None)
if func is None or not callable(func):
raise AttributeError(f"Функция {func_name!r} не найдена или не является вызываемой")
return func(*args, **kwargs)
Подробности по getattr в официальной документации: https://docs.python.org/3/library/functions.html#getattr
Динамический импорт модуля и путь вида “package.module.func”
Если у вас исходно есть строка с именем модуля (например, "foo" или путь "pkg.sub.foo"), используйте importlib.import_module для надёжного импорта:
import importlib
def call_from_module_name(module_name, func_name, *args, **kwargs):
module = importlib.import_module(module_name)
func = getattr(module, func_name, None)
if not callable(func):
raise AttributeError(...)
return func(*args, **kwargs)
Если передаётся полная строка вида "foo.bar" (модуль + функция), можно разобрать так:
def call_from_path(path, *args, **kwargs):
module_name, func_name = path.rsplit(".", 1)
module = importlib.import_module(module_name)
return call(module, func_name, *args, **kwargs)
importlib.import_module предпочтительнее __import__ — это обсуждается в руководствах и примерах по динамическому импорту и вызову функций (см. пример и объяснения на GeeksforGeeks): https://www.geeksforgeeks.org/python-call-a-function-by-a-string-name-python/
Проверки и безопасность при динамическом вызове функции в Python
Что делать, если имя функции приходит от пользователя или из внешнего источника? Нельзя просто вызывать всё подряд — это риск.
Рекомендации:
- Не используйте
eval. Это небезопасно для любых данных из внешних источников. - Ограничьте список разрешённых функций (whitelist) или явно передавайте маппинг допустимых функций.
- Проверяйте, что атрибут существует и что он
callable. - При необходимости проверяйте сигнатуру через
inspect.signatureперед вызовом.
Пример whitelist-подхода:
# явный список разрешённых функций
ALLOWED = {
"bar": foo.bar,
"baz": foo.baz,
}
def safe_call(func_name, *args, **kwargs):
func = ALLOWED.get(func_name)
if func is None:
raise ValueError("Недопустимое имя функции")
return func(*args, **kwargs)
Проверка аргументов:
import inspect
sig = inspect.signature(func)
try:
sig.bind(*args, **kwargs) # проверка соответствия аргументов
except TypeError as e:
raise TypeError("Неправильные аргументы для функции") from e
Ещё одна тонкость: hasattr(obj, name) внутренно вызывает getattr и может “поглотить” AttributeError, возникающий внутри свойства — учитывайте это при валидации. Практические обсуждения и примеры использования getattr vs eval можно посмотреть здесь: https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string
Асинхронные функции и методы класса
А что если вызываемая функция — coroutine (async def)? Или это метод класса/экземпляра?
- Coroutine:
- В синхронном коде можно запускать coroutine через
asyncio.run. - Внутри async-контекста —
await.
Примеры:
import inspect, asyncio
def call_sync(module, func_name, *args, **kwargs):
func = getattr(module, func_name)
if inspect.iscoroutinefunction(func):
return asyncio.run(func(*args, **kwargs)) # синхронный запуск coroutine
return func(*args, **kwargs)
async def call_async(module, func_name, *args, **kwargs):
func = getattr(module, func_name)
result = func(*args, **kwargs)
if inspect.isawaitable(result):
return await result
return result
- Методы класса/экземпляра:
- Если у вас экземпляр
obj, тоgetattr(obj, "method")()вернёт уже привязанный метод —selfне нужно передавать. - Если берёте метод с класса
getattr(C, "method"), то его нужно вызвать передав экземпляр явно:getattr(C, "method")(obj)— это редкий кейс, но важно понимать разницу.
Полный пример: надёжная реализация call(module, func_name)
Ниже — компактный, практичный и достаточно безопасный шаблон, который покрывает динамический импорт, проверку и async-поддержку в простом варианте.
import importlib
import inspect
import asyncio
from types import ModuleType
from typing import Any, Union
def _import_module(module_or_name: Union[ModuleType, str]) -> ModuleType:
if isinstance(module_or_name, str):
return importlib.import_module(module_or_name)
return module_or_name
def call(module_or_name: Union[ModuleType, str], func_name: str, *args, **kwargs) -> Any:
module = _import_module(module_or_name)
func = getattr(module, func_name, None)
if func is None or not callable(func):
raise AttributeError(f"Модуль {getattr(module, '__name__', str(module))!s} не содержит вызываемой {func_name!r}")
# Необязательная проверка аргументов (можно убрать для производительности)
try:
inspect.signature(func).bind_partial(*args, **kwargs)
except TypeError:
# сигнатура не совместима — дадим ошибку на этапе вызова
pass
if inspect.iscoroutinefunction(func):
# Запуск coroutine в синхронном коде
return asyncio.run(func(*args, **kwargs))
return func(*args, **kwargs)
# Примеры использования:
# import foo
# call(foo, "bar", 1, 2)
# call("foo", "bar", 1, 2) # строка с именем модуля — тоже работает
Если вы работаете внутри async-функции, используйте call_async-версию, которая await-ит результат вместо asyncio.run.
Источники
- Calling a function of a module by using its name (a string) - Stack Overflow
- Call a function by a String name - Python - GeeksforGeeks
- Built-in Functions — getattr — Python 3 documentation
- Вызов функции по её строковому имени - Stack Overflow на русском
- Вызов метода класса по имени в python (shumiloff)
- Как вызвать функцию в python? — 3 варианта, 5 примеров кода (pythonru)
Заключение
Коротко: для динамического вызова функции модуля по имени используйте getattr вместе с importlib.import_module при необходимости динамического импорта. Это стандартный, понятный и относительно безопасный способ вызова функции в Python — при условии валидации входных данных, проверки callable и аккуратной работы с async-функциями.