контекст
Предположим, у меня есть следующий код Python:
def example_function(numbers, n_iters):
sum_all = 0
for number in numbers:
for _ in range(n_iters):
number = halve(number)
sum_all += number
return sum_all
ns = [1, 3, 12]
print(example_function(ns, 3))
example_functionЗдесь мы просто просматриваем каждый из элементов в nsсписке и делим их пополам по 3 раза, одновременно накапливая результаты. Результат выполнения этого сценария просто:
2.0
Так как 1 / (2 ^ 3) * (1 + 3 + 12) = 2.
Теперь предположим, что (по любой причине, возможно, отладке или ведению журнала) я хотел бы отобразить некоторую информацию о промежуточных шагах, которые example_functionвыполняет. Может быть, я бы тогда переписал эту функцию примерно так:
def example_function(numbers, n_iters):
sum_all = 0
for number in numbers:
print('Processing number', number)
for i_iter in range(n_iters):
number = number/2
print(number)
sum_all += number
print('sum_all:', sum_all)
return sum_all
который теперь при вызове с теми же аргументами, что и раньше, выдает следующее:
Processing number 1
0.5
0.25
0.125
sum_all: 0.125
Processing number 3
1.5
0.75
0.375
sum_all: 0.5
Processing number 12
6.0
3.0
1.5
sum_all: 2.0
Это достигает именно того, что я намеревался. Однако это немного противоречит принципу, согласно которому функция должна выполнять только одно действие, и теперь код для example_functionнее несколько длиннее и сложнее. Для такой простой функции это не проблема, но в моем контексте у меня есть довольно сложные функции, вызывающие друг друга, и операторы печати часто включают более сложные шаги, чем показано здесь, что приводит к существенному увеличению сложности моего кода (например, из моих функций было больше строк кода, связанных с ведением журнала, чем было строк, связанных с его фактическим назначением!).
Кроме того, если позже я решу, что больше не хочу печатать операторы в своей функции, мне придется пройти example_functionи удалить все printоператоры вручную вместе с любыми переменными, связанными с этой функцией, процесс, который является утомительным и ошибочным. -prone.
Ситуация становится еще хуже, если я хотел бы всегда иметь возможность печатать или не печатать во время выполнения функции, что приводит меня к объявлению двух чрезвычайно похожих функций (одна с printоператорами, одна без), что ужасно для поддержки, или определить что-то вроде:
def example_function(numbers, n_iters, debug_mode=False):
sum_all = 0
for number in numbers:
if debug_mode:
print('Processing number', number)
for i_iter in range(n_iters):
number = number/2
if debug_mode:
print(number)
sum_all += number
if debug_mode:
print('sum_all:', sum_all)
return sum_all
что приводит к раздутой и (мы надеемся) излишне сложной функции, даже в простом случае нашего example_function.
Вопрос
Есть ли питонный способ «отделить» функциональность печати от оригинальной функциональности example_function?
В более общем смысле, есть ли питонный способ отделить необязательную функциональность от основного назначения функции?
Что я уже пробовал:
Решение, которое я нашел на данный момент, использует обратные вызовы для развязки. Например, можно переписать example_functionтак:
def example_function(numbers, n_iters, callback=None):
sum_all = 0
for number in numbers:
for i_iter in range(n_iters):
number = number/2
if callback is not None:
callback(locals())
sum_all += number
return sum_all
и затем определение функции обратного вызова, которая выполняет любую функцию печати, которую я хочу:
def print_callback(locals):
print(locals['number'])
и звонит example_functionтак:
ns = [1, 3, 12]
example_function(ns, 3, callback=print_callback)
который затем выводит:
0.5
0.25
0.125
1.5
0.75
0.375
6.0
3.0
1.5
2.0
Это успешно отделяет функциональность печати от базовой функциональности example_function. Однако основная проблема этого подхода заключается в том, что функция обратного вызова может быть запущена только в определенной части example_function(в данном случае сразу после деления текущего числа на два), и вся печать должна выполняться именно там. Это иногда вынуждает проект функции обратного вызова быть довольно сложным (и делает невозможным выполнение некоторых действий).
Например, если бы вы хотели добиться точно такого же типа печати, как я делал в предыдущей части вопроса (показывая, какое число обрабатывается, вместе с соответствующими ему половинками), результирующий обратный вызов будет выглядеть так:
def complicated_callback(locals):
i_iter = locals['i_iter']
number = locals['number']
if i_iter == 0:
print('Processing number', number*2)
print(number)
if i_iter == locals['n_iters']-1:
print('sum_all:', locals['sum_all']+number)
что приводит к тому же результату, что и раньше:
Processing number 1.0
0.5
0.25
0.125
sum_all: 0.125
Processing number 3.0
1.5
0.75
0.375
sum_all: 0.5
Processing number 12.0
6.0
3.0
1.5
sum_all: 2.0
но это боль писать, читать и отлаживать.
loggingмодуль поможет здесь. Хотя мой вопрос использует printоператоры при настройке контекста, я на самом деле ищу решение о том, как отделить любой тип необязательной функциональности от основной цели функции. Например, может быть, я хочу, чтобы функция отображала вещи во время работы. В этом случае я считаю, что loggingмодуль даже не будет применим.
loggingдемонстрируют предложения по использованию ), но не на то, как отделить произвольный код.
loggingмодуль Python