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

Ste*_*joa 1 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
Run Code Online (Sandbox Code Playgroud)

... 和另外一个:

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
Run Code Online (Sandbox Code Playgroud)

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

@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))
Run Code Online (Sandbox Code Playgroud)

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

A, S = algo1(X)
A, S = algo1(X, A0, S0, maxiter=50, c=0.2)
A, S = algo1(X, K=10, maxiter=40)
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 什么技术最适合重构此代码?功能装饰器?
  2. 如果是这样,你会怎么写iterate?让我特别困惑的是参数/参数,例如,使用与没有默认值,在装饰器和"包装器"中访问它们等.例如,更新规则本身不需要K,但初始化代码确实如此. ,我想知道我的功能签名是否正确.

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

  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)仍然有效.从语法上讲,我不确定为什么会这样.出于学习目的,您能否澄清(或引用参考资料)?谢谢!

Ale*_*lli 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
Run Code Online (Sandbox Code Playgroud)

正如您所注意到的,您可以简化algo1和algo2的签名,但它并不是真正的关键部分,并且保持签名完整可以简化您的测试和重构.如果你想简化,你会改变的def语句那些,说,

def algo1(X, A, S, c):
Run Code Online (Sandbox Code Playgroud)

并且类似地简化了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参数X, A, S, c(在包装algo1被给予简化签名的版本中).问题,如上所述,这使得元素(特别是签名)在被装饰的函数和最终的装饰函数之间不同;这对于读取和维护来说非常混乱,因此通常在两个级别保持相同的签名,除了特殊情况.