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

2024-09-22 10:21:51 发布

您现在位置:Python中文网/ 问答频道 /正文

请原谅我关于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

。。。另一个:

^{pr2}$

这两个功能本身都是成功的。显然,这些函数要求重构。不同的代码单位是更新规则。下面是我的重构尝试:

@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?尤其让我困惑的是参数/参数,例如,没有默认值的vs,在decorator和“wrapper”中访问它们等等。例如,更新规则本身不需要K,但是初始化代码需要,所以我想知道我的函数签名是否正确。在

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

  1. 包装器(例如,inner)只有在传递参数时才是必需的吗?因为我看到没有包装器的decorator示例,也没有传递任何参数,它们工作得很好。在
  2. 再阅读Python文档,functools似乎很有用;它的主要目的是保留原始函数的元数据(例如,algo1.__name__和{})?在
  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)仍然有效。从句法上讲,我不知道为什么会这样。为了便于学习,你能澄清(或引用参考文献)?谢谢!在

Tags: 函数代码算法none参数ifis规则
1条回答
网友
1楼 · 发布于 2024-09-22 10:21:51

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

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改为

^{pr2}$

同样地,简化iterator修饰中的调用不需要两个参数,也不需要默认值。然而,避免这个简化部分实际上可以使你的生活更简单,如果修饰函数和装饰它的结果,保持彼此完全相同的签名,通常会更简单,除非你真的有相反的特殊需要。在

编辑:操作员不断地向这个问题提出问题……:

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

Is it true that a wrapper (e.g., inner) is only necessary when parameters are being passed? Because I see decorator examples without wrappers, and no parameters are passed, and they work just fine.

不带参数使用的decorator@decorname中使用时,被修饰的函数被调用,并且必须返回一个函数;一个使用参数的装饰器(如@decorname(23))必须返回一个(“高阶”)函数,该函数又被修饰的函数调用,并且必须返回一个函数。无论被修饰的函数是否带参数,都不会更改这组规则。从技术上讲,在没有内部函数的情况下实现这一点是有可能的(我想这就是你所说的“包装器”是什么意思?)但很少有人这样做。在

From reading the Python docs some more, functools appears useful; is its main purpose to preserve the metadata of the original function (e.g., algo1.name and algo1.doc)?

是的,functools.wraps正是为此目的而使用的(functools还包含{},它有着完全不同的用途)。在

With the signatures def algo1(X, A, S, c) and def inner(X, A=None, S=None, K=2, maxiter=10, c=0.1), the call algo1(X, maxiter=20) still works. Syntactically, I'm not sure why that is. For learning purposes, could you clarify (or cite a reference)? Thanks!

这是因为inner是用这些参数实际调用的函数(在algo1被修饰之后)并且只传递(到“真正的底层algo1)参数X, A, S, c(在被包装的algo1被赋予简化签名的版本中)。正如我前面提到的,问题在于,这使得被修饰的函数和最终修饰的函数之间的元数据(特别是签名)不同;这使得读取和维护非常混乱,因此通常在两个级别上都保持相同的签名,除特殊情况外。在

相关问题 更多 >