语境
假设我有以下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语句以及与此功能相关的所有变量,这既繁琐又出错-易于。
如果我想在函数执行期间始终有可能打印或不打印,情况会变得更糟,这导致我要么声明两个非常相似的函数(一个带有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。
题
是否有Python方式将打印功能与的原始功能“分离” example_function?
更一般而言,是否存在一种将可选功能与功能主要目的分离的Python方法?
到目前为止我尝试过的是:
我目前发现的解决方案是使用回调进行解耦。例如,可以这样重写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模块