Python装饰器或其他重构的潜在用途:迭代优化

时间:2010-02-21 14:32:34

标签: python refactoring numpy decorator

请原谅我关于Python装饰器的另一个问题。我确实阅读了很多这些,但我想知道具体的以下问题的最佳解决方案是什么。

我写了几个在numpy / scipy中做某种形式的渐变下降的函数。给定矩阵X,我尝试迭代地最小化一些距离d(X,AS),作为A和S的函数。每个算法遵循相同的基本过程,但每个算法具有不同的更新规则。例如,以下是我的两个函数(注意唯一的区别在于更新规则):

def algo1(X, A=None, S=None, K=2, maxiter=10, c=0.1):
    M, N = X.shape
    if A is None:
        A = matrix(rand(M, K))
    if S is None:
        S = matrix(rand(K, N))
    for iter in range(maxiter):
        # Begin update rule.
        A = multiply(A, (X*S.T + c)/(A*S*S.T + c))
        S = multiply(S, (A.T*X + c)/(A.T*A*S + c))
        # End update rule.
        for k in range(K):
            na = norm(A[:,k])
            A[:,k] /= na
            S[k,:] *= na
    return A, S

......和另一个:

def algo2(X, A=None, S=None, K=2, maxiter=10, c=0.1):
    M, N = X.shape
    O = matrix(ones([M, N]))
    if A is None:
        A = matrix(rand(M, K))
    if S is None:
        S = matrix(rand(K, N))
    for iter in range(maxiter):
        # Begin update rule.
        A = multiply(A, ((X/(A*S))*S.T + c)/(O*S.T + c))
        S = multiply(S, (A.T*(X/(A*S)) + c)/(A.T*O + c))
        # End update rule.
        for k in range(K):
            na = norm(A[:,k])
            A[:,k] /= na
            S[k,:] *= na
    return A, S

这两项功能都是独立的。显然,这些函数要求重构。不同的代码单元是更新规则。所以这是我重构的尝试:

@iterate
def algo1(X, A=None, S=None, K=2, maxiter=10, c=0.1):
    A = multiply(A, (X*S.T + c)/(A*S*S.T + c))
    S = multiply(S, (A.T*X + c)/(A.T*A*S + c))

@iterate
def algo2(X, A=None, S=None, K=2, maxiter=10, c=0.1):
    A = multiply(A, ((X/(A*S))*S.T + c)/(O*S.T + c))
    S = multiply(S, (A.T*(X/(A*S)) + c)/(A.T*O + c))

以下是一些潜在的函数调用:

A, S = algo1(X)
A, S = algo1(X, A0, S0, maxiter=50, c=0.2)
A, S = algo1(X, K=10, maxiter=40)

问题:

  1. 哪种技术最适合重构此代码?函数装饰器?
  2. 如果是这样,你会怎么写iterate?让我特别困惑的是参数/参数,例如,有没有默认值,在装饰器和“包装器”中访问它们等等。例如,更新规则本身不需要K,但初始化代码确实如此,所以我想知道我的函数签名是否正确。
  3. 编辑:谢谢你的帮助。更多问题:

    1. 只有在传递参数时才需要包装器(例如inner)吗?因为我看到装饰器示例没有包装器,并且没有传递参数,并且它们工作得很好。
    2. 通过阅读Python文档,functools似乎很有用;它的主要目的是保留原始函数的元数据(例如algo1.__name__algo1.__doc__)?
    3. 使用签名def algo1(X, A, S, c)def inner(X, A=None, S=None, K=2, maxiter=10, c=0.1),来电algo1(X, maxiter=20)仍然有效。从语法上讲,我不确定为什么会这样。出于学习目的,您能否澄清(或引用参考资料)?谢谢!

1 个答案:

答案 0 :(得分:5)

以下内容应该可以作为您要使用的装饰器:

import functools

def iterate(update):
    @functools.wraps(update)
    def inner(X, A=None, S=None, K=2, maxiter=10, c=0.1):
        M, N = X.shape
        O = matrix(ones([M, N]))
        if A is None:
            A = matrix(rand(M, K))
        if S is None:
            S = matrix(rand(K, N))
        for iter in range(maxiter):
            A, S = update(X, A, S, K, maxiter, c)
            for k in range(K):
                na = norm(A[:,k])
                A[:,k] /= na
                S[k,:] *= na
        return A, S
    return inner

正如您所注意到的,您可以简化algo1和algo2的签名,但它并不是真正的关键部分,并且保持签名完整可以简化您的测试和重构。如果想要简化,您可以更改def语句,例如,

def algo1(X, A, S, c):

同样简化了iterator装饰中的调用 - 不需要两个参数,也不需要默认值。然而,避免这种简化部分实际上可以使你的生活变得更简单 - 如果装饰功能和装饰功能的结果保持彼此完全相同的签名通常会更简单,除非你确实有特定的需求。

编辑:OP不断就此问题提出问题......:

编辑:谢谢你的帮助。更多问题:

  

包装器是否正确(例如,   内部)只有在必要时   参数正在传递?因为我   看没有装饰器的例子   包装器,没有参数   通过,他们工作得很好。

使用没有参数的装饰器(在@decorname使用中)被正在装饰的函数调用,并且必须返回一个函数;使用带有参数的的装饰器(如@decorname(23))必须返回一个(“高阶”)函数,该函数又被正在修饰的函数调用,并且必须返回一个函数。被装饰的函数是否带参数,不会改变这组规则。技术上可以在没有内部函数的情况下实现这一点(我认为这是“包装器”的意思吗?)但是很少这样做。

  

从阅读Python文档一些   更多,functools似乎很有用;是它的   保留元数据的主要目的   原始功能(例如,   algo1。名称和algo1。 doc )?

是的,functools.wraps完全用于此目的(functools还包含partial,其目的完全不同。)

  

使用签名def algo1(X, A, S, c)def inner(X, A=None, S=None, K=2, maxiter=10, c=0.1)进行通话   algo1(X, maxiter=20)仍有效。   从语法上讲,我不确定为什么   是。出于学习目的,你能吗?   澄清(或引用参考)?谢谢!

这是因为inner是使用这些参数实际调用的函数(在algo1被装饰之后)并且仅向下传递(到“真正的基础algo1)参数{{1 (在包装的X, A, S, c给出简化签名的版本中。)如上所述,问题是这使得元素(特别是签名)在被装饰的函数和结果之间不同装饰功能;阅读和维护非常混乱,所以通常在两个级别都保持相同的签名,除非是特殊情况。