Контекст
Припустимо, у мене є наступний код 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списку і вдвічі зменшіть їх, накопичивши результати. Вихід із запуску цього сценарію просто:
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оператори вручну, а також будь-які змінні, пов’язані з цією функціональністю, процес, який є втомливим і помилковим. -прон.
Ситуація стає ще гіршою, якщо я хотів би завжди мати можливість друкувати або не друкувати під час виконання функції, що призводить до того, що я або оголошував дві надзвичайно схожі функції (одну із 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