Выборочный запуск нескольких функций, в зависимости от внешней конфигурации
Коллеги! Даны несколько функций, которые принимают один и тот же аргумент.
def func1(a):
print('Первая функция')
return a
def func2(a):
print('Вторая функция')
return a
Откуда-то (например, из файла, хранящего конфигурацию или из стандартного ввода, неважно) скрипт получает настройку, какие функции запускать, какие нет.
dic_flag = {'fun1': True, 'fun2': False}
Можно написать кучу условных операторов, чтобы запускать эти функции.
if dic_flag['fun1']:
print(func1(a))
if dic_flag['fun2']:
print(func2(a))
Но хочется процесс автоматизировать, например, вот так:
def func1(a):
print('Первая функция')
return a
def func2(a):
print('Вторая функция')
return a
dic_flag = {'fun1': True, 'fun2': False}
dic_func = {'fun1': func1, 'fun2': func2}
a = 10
for flag in dic_flag:
if dic_flag[flag]:
print(dic_func[flag](a))
Вопросы
Что можно придумать красивого для данной задачи? Может в ООП что-то? Или какие-то стандартные библиотеки есть? В реальности функций несколько десятков.
Опционально:
А если основной аргумент один, а какие-то дополнительные параметры разные?
Ответы (3 шт):
Можно создать класс, который будет управлять вызовом функций в зависимости от настроек:
class FunctionManager:
def __init__(self, functions, flags):
self.functions = functions
self.flags = flags
def run_functions(self, argument):
for func_name, flag in self.flags.items():
if flag and func_name in self.functions:
print(self.functions[func_name](argument))
def func1(a):
print('Первая функция')
return a
def func2(a):
print('Вторая функция')
return a
dic_flag = {'func1': True, 'func2': False}
dic_func = {key: globals()[key] for key, value in dic_flag.items() if value and key in globals() and callable(globals()[key])}
manager = FunctionManager(dic_func, dic_flag)
a = 10
manager.run_functions(a)
Бонусом добавил что словарь dic_func собирается сам по заданным в dic_flag ключам. Ключи должны совпадать с названиями функций. Выключенные функции в словарь не попадают.
Так же можно поизврощаться, если в словаре fun1, а функция должна иметь другое название:
def func1(a):
print('Первая функция')
return a
def func2(a):
print('Вторая функция')
return a
dic_flag = {'fun1': True, 'fun2': True}
a = 10
for flag, value in dic_flag.items():
if value:
func_name = globals().get(f'func{flag[3:]}') # Извлечение функции по имени
if func_name:
func_name(a)
Еще немного подумав, могу предложить такое направление мысли:
def func1(a, **kwargs):
print('Первая функция')
for k, v in kwargs.items(): print(f'Key={k} and Value={v}')
return a
def func2(a, **kwargs):
print('Вторая функция')
for k, v in kwargs.items(): print(f'Key={k} and Value={v}')
return a
def func3(a, **kwargs):
print('Третья функция')
for k, v in kwargs.items(): print(f'Key={k} and Value={v}')
return a
dic_flag = {'fun1': [True, {'name': 'Pankaj', 'age': 34}], 'fun2': [False, {'name': 'J', 'age': 14}], 'fun3': [True, {'name': 'Pank', 'age': 54}]}
dic_func = {'fun1': func1, 'fun2': func2, 'fun3': func3}
a = 10
# Фильтруем функции по флагам
funcs_to_run = filter(lambda f: dic_flag[f][0], dic_func)
for value in funcs_to_run:
print(dic_func[value](a,**dic_flag[value][1]))
Позвольте еще немного извращения, теперь уже с дампом самих функций:
import dill # pip install dill ,можно обойтись и стандартным import pickle
def save_locals_to_pkl(file_path):
def func1(a, **kwargs):
print('Первая функция')
for k, v in kwargs.items(): print(f'Key={k} and Value={v}')
return a
def func2(a, **kwargs):
print('Вторая функция')
for k, v in kwargs.items(): print(f'Key={k} and Value={v}')
return a
def func3(a, **kwargs):
print('Третья функция')
for k, v in kwargs.items(): print(f'Key={k} and Value={v}')
return a
local_funcs = {'func1': func1, 'func2': func2, 'func3': func3}
# Сохраняем словарь функций в файл с использованием dill
with open(file_path, 'wb') as file:
dill.dump(local_funcs, file)
def load_locals_from_pkl(file_path):
with open(file_path, 'rb') as file:
local_funcs = dill.load(file)
# Так как мы возвращаем словарь, можем добавить функции в глобальную область видимости
globals().update(local_funcs)
# Пример вывода информации о функциях
for func in local_funcs:
print(f"Загруженная функция: {func}")
# Пример сохранения функций в дамп
save_locals_to_pkl('local_funcs.pkl')
# Загружаем и восстанавливаем локальные функции из дампа
load_locals_from_pkl('local_funcs.pkl')
a = 10
# Теперь функции доступны в глобальной области видимости
dic_flag = {'fun1': [True, {'name': 'Pankaj', 'age': 34}], 'fun2': [False, {'name': 'J', 'age': 14}], 'fun3': [True, {'name': 'Pank', 'age': 54}]}
func1(a,**dic_flag['fun1'][1])
func2(a,**dic_flag['fun2'][1])
func3(a,**dic_flag['fun3'][1])
или так:
def load_locals_from_pkl(file_path):
with open(file_path, 'rb') as file:
local_funcs = dill.load(file)
# Пример вывода информации о функциях
for func_name, func in local_funcs.items():
print(f"Загруженная функция {func_name}: {func}")
return local_funcs
# Пример сохранения
save_locals_to_pkl('local_funcs.pkl')
# Загружаем и восстанавливаем локальные функции
local_funcs = load_locals_from_pkl('local_funcs.pkl')
# Теперь функции доступны в глобальной области видимости
a = 10
dic_flag = {'fun1': [True, {'name': 'Pankaj', 'age': 34}], 'fun2': [False, {'name': 'J', 'age': 14}], 'fun3': [True, {'name': 'Pank', 'age': 54}]}
local_funcs['func1'](a, **dic_flag['fun1'][1])
local_funcs['func2'](a, **dic_flag['fun2'][1])
local_funcs['func3'](a, **dic_flag['fun3'][1])
А еще можно сохранять не словарь функций, а список функций:
# local_funcs = {'func1': func1, 'func2': func2, 'func3': func3}
# Создаем список с локальными функциями
local_funcs = [func1, func2, func3]
И вызывать нужные функции уже по индексу:
def load_locals_from_pkl(file_path):
with open(file_path, 'rb') as file:
local_funcs = dill.load(file)
return local_funcs
local_funcs = load_locals_from_pkl('local_funcs.pkl')
local_funcs[0](a, **dic_flag['fun1'][1])
local_funcs[1](a, **dic_flag['fun2'][1])
local_funcs[2](a, **dic_flag['fun3'][1])
По итогу можно разбить функции на отдельные дампы и грузить только нужные из них.
import glob
import os
def load_locals_from_folder(folder_path):
# Получаем список файлов в заданной папке с расширением .pkl
pkl_files = glob.glob(os.path.join(folder_path, '*.pkl'))
for pkl_file in pkl_files:
with open(pkl_file, 'rb') as file:
local_funcs = dill.load(file)
globals().update(local_funcs)
Можно вообще уйти в дебри и использовать строковое представление для создания функций. Для этого можно использовать exec() или eval():
def add_exec_function(func_name):
# Создаем строковое представление функции
function_code = f'''
def {func_name}(a, *args):
print('{func_name.capitalize()}')
for v in args: print(f'Value={{v}}')
return a
'''
# Выполняем строковое представление кода
exec(function_code, globals(), locals())
# Возвращаем созданную функцию
return locals()[func_name]
# Пример добавления новой функции
func4 = add_exec_function('func4')
# Теперь можно вызывать новую функцию
result = func4(42, 'value3', 56, 72)
print(result)
Так что можно буквально из файл(а\ов) грузить нужные функции в виде строк и их исполнять.
def add_evil_function(func_name):
# Создаем строковое представление функции
function_code = f'''
def {func_name}(a, **kwargs):
print('{func_name.capitalize()}')
for k, v in kwargs.items(): print(f'Key={{k}} and Value={{v}}')
return a
'''
# Выполняем строковое представление кода с использованием eval()
eval(compile(function_code, '<string>', 'exec'))
# Возвращаем созданную функцию
return locals()[func_name]
# Пример добавления новой функции
func4 = add_evil_function('func4')
# Теперь можно вызывать новую функцию
result = func4(42, param1='value1', param2='value2')
print(result)
Запускать функции можно вообще как угодно:
def func1(a):
print('Первая функция')
return a
def func2(a):
print('Вторая функция')
return a
# Cловарь функций
functions = {'fun1': func1, 'fun2': func2}
# Настройки из словаря
dic_flag = {'fun1':[True, {'name': 'Pankaj', 'age': 34}], 'fun2': [False, {'name': 'kaj', 'age': 24}]}
# Запускаем функции хехе
results = [func(dic_flag[func_name][1]) if dic_flag[func_name][0] else None for func_name, func in functions.items()]
print(results)
У тебя и так все работает, тебе лень проверить.
def get_function_name(func):
return str(func.__name__)
def func1(a):
print('Первая функция')
return a
def func2(a):
print('Вторая функция')
return a
s=[func1, func2]
dic_flag = {'func1': True, 'func2': False}
x=10
for f in s:
if dic_flag[get_function_name(f)]:
f(x)
Первая функция
Как python-программисты мы хотим сделать код более лаконичным и красивым. Но самое главное правило - чтобы он был понятным и рабочим.
Я сделал более универсальный способ, несмотря на то, что код может показаться сложным на первый взгляд. В целом, чтобы разобраться в этом коде - важно понимать основы ООП. Также чуть-чуть переработал вашу конфигурацию - у вас есть возможность нормально добавлять разные аргументы в функции, либо вообще их не добавлять. Код предусматривает также работу с методами в описанных классах в вашем файле, если они там конечно есть, то они также будут корректно распознаны, хоть это обёрнутый @staticmethod, хоть @classmethod, либо вообще ничего - в таком случае в качестве self я передаю класс, то есть самого себя, в моем контексте это будет func(class_ref).
Сам код:
from itertools import chain
import inspect
class FunctionRunner:
def __init__(self, module, configuration: dict):
self.module = module
self.configuration = configuration
module_name = self.module.__name__
dev_functions = list(
chain(
inspect.getmembers(self.module, inspect.isfunction),
inspect.getmembers(self.module, inspect.ismethod),
inspect.getmembers(self.module, inspect.isclass),
)
)
functions = {}
for func in dev_functions:
if func[1].__module__ == module_name:
if inspect.isclass(func[1]):
dev_methods = list(
chain(
inspect.getmembers(func[1], inspect.isfunction),
inspect.getmembers(func[1], inspect.ismethod)
)
)
methods = {
method[0]: {'class': func[1], 'method': method[1]} for method in dev_methods
if func[1].__module__ == module_name
}
functions.update(**methods)
else:
functions.update({func[0]: func[1]})
self.func_for_run = functions
def run(self):
filtered_dict = {
name: func for name, func in self.func_for_run.items()
if name in self.configuration and self.configuration[name]['run']
}
for name, func in filtered_dict.items():
class_ref = func
if isinstance(func, dict):
class_ref = func['class']
func = func['method']
args = self.configuration[name].get('args')
func_args = inspect.getfullargspec(func).args
if args is None:
if 'self' in func_args:
func(class_ref)
else:
func()
elif isinstance(args, (list, tuple)):
if 'self' in func_args:
args.insert(0, class_ref)
func(*args)
else:
if 'self' in func_args:
args = [class_ref, args]
func(*args)
else:
func(args)
Ваш файл с функциями function_module.py
def func1():
print('Первая функция')
return ''
def func2(a):
print('Вторая функция')
return a
def func3(a, b):
print('Третья функция')
return a, b
class Test:
@staticmethod
def method1(a):
print('Первый метод')
return a
def method2(self, a, b):
print('Второй метод')
return a, b
@classmethod
def method3(cls):
print('Третий метод')
return ''
Ваш файл вызова логики app.py
import function_module # ваше имя файла, где находятся функции
from func_runner import FunctionRunner
dict_flag = {
'func1': {'run': True, 'args': None},
'func2': {'run': False, 'args': 10},
'func3': {'run': True, 'args': [10, '']},
'method1': {'run': True, 'args': [1]},
'method2': {'run': False, 'args': [1, 2]},
'method3': {'run': True, 'args': None},
}
fr = FunctionRunner(function_module, configuration=dict_flag)
fr.run()
Вывод:
Первая функция
Третья функция
Первый метод
Третий метод
Преимущества:
Подключить мой класс для вас будет очень просто, так как все что вы делаете, так это в конструктор передаете сам импорт вашего модуля, и конфигурацию.
Вам не нужно заморачиваться с логикой, которая написана внутри моего класса в отличие от других приведенных ответов где логику придется применять каждый раз для разных вызовов. Плюс там много ненужных вариантов, которые может быть написал вообще
chatgpt(извините, если кого-то обидел :) ).Мой класс вызывает только те функции, которые вы сами явно прописали в модуле, исключая импорты. Это означает, что вам не нужно руками прописывать все функции и методы, получать ссылки на них в словарь - класс сделает все сам.
Важно понимать, что параметры нужно внимательно передать в функции/методы, если все же требуется такая логика, иначе будет ошибка (как и при обычном запуске).
В целом я вообще не рекомендую использовать такой подход в программировании, так как логика выглядит достаточно запутанной и другим программистам будет сложно разобраться, где и какая функция вызывается. Не рекомендую и вашу идею с получением с внешнего источника и запуск таким образом функций. Лучше всего сделать какой-нибудь главный метод, который принимает аргументы, и уже на основе них запускать ваши функции. В качестве альтернативы посмотрите на match-case, который добавили в python 3.10 он может показаться вам полезным.
UPDATE:
Подумал, что вдруг вам не нужна такая навороченная логика, а нужно что-то максимально приближено к той задаче, которую вы привели, то упростил логику и подогнал под ваш вариант с функциями.
class FunctionRunner:
def __init__(self, module, configuration: dict):
self.module = module
self.configuration = configuration
module_name = self.module.__name__
dev_functions = list(
chain(
inspect.getmembers(self.module, inspect.isfunction),
inspect.getmembers(self.module, inspect.ismethod),
)
)
functions = {}
for func in dev_functions:
if func[1].__module__ == module_name:
functions.update({func[0]: func[1]})
self.func_for_run = functions
def run(self, arguments: dict):
filtered_dict = {
name: func for name, func in self.func_for_run.items()
if name in self.configuration and self.configuration[name]
}
for name, func in filtered_dict.items():
func_args = arguments.get(name)
if func_args is None:
func()
elif isinstance(func_args, (list, tuple)):
func(*func_args)
else:
func(func_args)
dict_flag = {'func1': True, 'func2': True, 'func3': True}
func_arguments = {'func1': None, 'func2': 10, 'func3': [5, 10]}
# если аргументов нет для функций, просто отправьте пустой словарь
fr = FunctionRunner(function_module, configuration=dict_flag)
fr.run(arguments=func_arguments)
В целом задача оказалось интересной, был рад решить ее и помочь вам. Желаю успехов