重复代码被认为是软件开发中的一种不良实践,原因有很多,其中之一是它需要更多的工作来维护。如果你有相同的算法作用于不同的数据集,你可以将算法放入一个函数中,并传入数据,避免代码的重复。然而,有时你会发现,代码本身发生了变化,但两个或多个地方仍然有大量重复的模板代码。

一个典型的例子可能是日志记录:

def multiply(a, b):
    result = a * b
    log("multiply has been called")
    return result

def add(a, b):
    result = a + b
    log("add has been called")
    return result

在这种情况下,不容易找出如何提取出重复的代码。我们可以遵循之前的模式,将公共代码移到一个函数中,但仅仅用不同的数据调用这个函数,并不足以产生我们想要的不同行为(加法或乘法)。相反,我们需要传递一个函数给公共函数。这就涉及到一个操作函数函数,称为高阶函数

在 Python 中,装饰器是一种用于高级函数的语法糖。

属性装饰器的最小示例:

class Foo(object):
    @property
    def bar(self):
        return 'baz'

F = Foo()
print(F.bar)  # 输出 'baz'

上面的例子其实就是以下代码的语法糖:

class Foo(object):
    def bar(self):
        return 'baz'
    bar = property(bar)

F = Foo()
print(F.bar)  # 输出 'baz'

通用装饰器的最小示例:

def decorator(f):
    def called(*args, **kargs):
        print('A function is called somewhere')
        return f(*args, **kargs)
    return called

class Foo(object):
    @decorator
    def bar(self):
        return 'baz'

F = Foo()
print(F.bar())  # 输出 'A function is called somewhere' 和 'baz'

装饰器的一个好用场景是让你重构代码,将公共特性移动到装饰器中。例如,假设你想追踪所有函数调用,并打印每次调用的所有参数的值。那么你可以像下面这样在装饰器中实现:

# 定义一个 Trace ,用于装饰器
class Trace(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *args, **kwargs):
        print("entering function " + self.f.__name__)
        i = 0
        for arg in args:
            print("arg {0}: {1}".format(i, arg))
            i += 1
            
        return self.f(*args, **kwargs)

然后你可以在任何你定义的函数上使用这个装饰器:

@Trace
def sum(a, b):
    print("inside sum")
    return a + b

运行这段代码时,你会看到似如下的输出:

sum(3, 2)
# 输出:
# entering function sum
# arg 0: 3
# arg 1: 2
# inside sum

另外,你也可以用函数代替来创建装饰器:

def Trace(f):
    def my_f(*args, **kwargs):
        print("entering " + f.__name__)
        result = f(*args, **kwargs)
        print("exiting " + f.__name__)
        return result
    my_f.__name = f.__name__
    my_f.__doc__ = f.__doc__
    return my_f

# 使用 Trace 装饰器的示例
@Trace
def sum(a, b):
    print("inside sum")
    return a + b

# 运行时应该看到:
sum(3, 2)
# 输出:
# entering sum
# inside sum
# exiting sum
# 5

记住,良好的实践是返回函数函数的合理装饰替代品,以便可以链式使用装饰器。

Last modified: Friday, 31 January 2025, 1:02 AM