不确定为什么我会陷入 Python 卡住的递归循环

Not sure why I'm stuck in a Python stuck recursion loop

这里的add和mul定义是无意义的,因为它们依赖于返回self,导致死循环。如果他们使用 lambda 表达式创建一个新的发行版,那么它就可以正常工作,正如我在下面的回答中所述。

我只是在玩弄 类 并试图构建一个小型统计工具。但是,当我 运行 这段代码时,我陷入了 __mul__ 调用中的递归循环中,该循环在 n1.pdf 调用中被 运行 调用,我无法弄清楚原因。我认为这与 Python 懒惰地执行 __mul__ 而不是做我想做的 'wanted' (让我们用 CS 的语言来说)是创建一个新指针有关旧函数调用由指向 pdf 的新指针拥有的 pdf,然后将旧指针(主 .pdf 指针)设置为新函数。

我认为这句话的措辞很糟糕,所以如果您理解我的问题,非常欢迎进行编辑。

import math
import random

class Distribution:
    def __init__(self, pdf, cdf):
        self.pdf = pdf
        self.cdf = cdf

    def pdf(self, x):
        return self.pdf(x)
        
    def cdf(self, x):
        return self.cdf(x)

    def __mul__(self, other):
        if isinstance(other, float) or isinstance(other, int):
            newpdf = lambda x : self.pdf(x) * other
            self.pdf = newpdf
            newcdf = lambda x : self.cdf(x) * other
            self.cdf = newcdf
            return self
        else:
            return NotImplemented

    def __add__(self, other):
        self.pdf = lambda x : self.pdf(x) + other.pdf(x)
        self.cdf = lambda x : self.cdf(x) + other.cdf(x)
        return Distribution(self.pdf, self.cdf)
    
class Normal(Distribution):
    def __init__(self, mean, stdev):
        self.mean = mean
        self.stdev = stdev

    def pdf(self, x):
        return (1.0 / math.sqrt(2 * math.pi * self.stdev ** 2)) * math.exp(-0.5 * (x - self.mean) ** 2 / self.stdev ** 2)

    def cdf(self, x):
        return (1 + math.erf((x - self.mean) / math.sqrt(2) / self.stdev)) / 2

    def sample(self):
        return self.mean + self.stdev * math.sqrt(2) * math.cos(2 * math.pi * random.random())

if __name__ == "__main__":
    n1 = Normal(1,2)
    n1half = n1 * 0.5
    x = n1.pdf(1)
    print(x)

p.s。我知道它乘以0.5后不再是pdf,这不是问题。

class Distribution:
    ...
    def pdf(self, x):
        return self.pdf(x)

pdf()调用自己,调用自己,调用自己...无限。

cdf()相同。

感谢@John 和@Tom 以及@bbbbbb 的帮助...问题是尝试return self 而不是创建新的发行版。如果我将 mul 的 def'n 更改为

def __mul__(self, other):
        if isinstance(other, float) or isinstance(other, int):
            def newpdf(x):
                return self.pdf(x) * other
            def newcdf(x):
                return self.cdf(x) * other
            return Distribution(newpdf, newcdf)
        else:
            return NotImplemented

那么这个问题就解决了

def pdf(self, x):
    return self.pdf(x)
    
def cdf(self, x):
    return self.cdf(x)

我假设您的意图是委托给属性。因为它们总是被赋值,所以它们会被发现(假设你在一个实例上进行查找)而不是 class 方法(如果没有这些属性,这将是无限递归);但这又意味着这些 class 方法毫无用处。 x.cdf(y),其中 cdf 是一个可调用的实例属性,可以正常工作;也不需要提供方法。

newpdf = lambda x : self.pdf(x) * other
self.pdf = newpdf

我假设您的意图是创建一个依赖于 self.pdf 的现有值的新函数。不幸的是,它不是那样工作的。问题是 lambda 是 late binding。当它执行时,那个就是它会查找self.pdf...并找到自己的时间。

这里有一个单独的问题,因为您正在编写 __mul____add__ 实现 - 即 *+ 运算符,它们应该是return 一个新值 ,并且 not 改变任一操作数。 (如果你写 a = 3b = 4 然后 c = a * b,如果 ab 的值发生变化,你会非常惊讶,是吗?)

我们可以一次解决这两个问题,只需使用计算的 pdfcdf 创建一个新实例(无论如何我们都需要):

def __mul__(self, other):
    if isinstance(other, float) or isinstance(other, int):
        newpdf = lambda x : self.pdf(x) * other
        newcdf = lambda x : self.cdf(x) * other
        return Distribution(newpdf, newcdf)
    else:
        return NotImplemented

同样,__add__应该使用局部变量,而不是修改self:

def __add__(self, other):
    newpdf = lambda x : self.pdf(x) + other.pdf(x)
    newcdf = lambda x : self.cdf(x) + other.cdf(x)
    return Distribution(newpdf, newcdf)

请注意,实现这些方法还可以为您提供增强的赋值运算符 *=+=(尽管通过创建新对象并重新绑定名称)。

我们来测试一下:

if __name__ == "__main__":
    n1 = Normal(1,2)
    n1half = n1 * 0.5
    print(n1.pdf(1))
    print(n1half.pdf(1))
    n1 += n1 
    print(n1.pdf(1))

我得到:

>py test.py
0.19947114020071635
0.09973557010035818
0.3989422804014327