【问题标题】:Not sure why I'm stuck in a Python stuck recursion loop不知道为什么我被困在 Python 的递归循环中
【发布时间】:2022-01-26 03:32:01
【问题描述】:

这里的 add 和 mul 定义是无意义的,因为它们依赖于返回 self,导致无限循环。如果他们使用 lambdas 创建一个新的发行版,那么它可以正常工作,如下面我自己的回答。

我只是在玩类,并试图构建一个小型统计工具。但是,当我运行此代码时,我陷入了__mul__ 调用中的递归循环,该调用正在n1.pdf 调用中运行,我无法弄清楚原因。我认为这与 Python 懒惰地执行 __mul__ 而不是做我“想要”的事情(让我们用 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)

附言我知道乘以0.5后不再是pdf,这不是问题。

【问题讨论】:

  • 旁注:函数__mul__ 不会在每个流路径中返回值。
  • 旁注 2:通过返回 self,函数 __mul__ 的行为不像二进制函数(例如,与函数 __add__ 相比)。
  • 据我了解,这些函数都不应该改变self,而只是返回一个新对象。
  • 欢迎回到 Stack Overflow。作为复习,请阅读How to Ask 并注意这不是论坛。请do not tag questions as "solved"; instead, accept an answer.
  • @TomKarzes 的目标是通过指定在给定点评估 PDF 和 CDF 所需的规则,将两个 Distributions 相乘创建一个代表产品的 Distribution。被建模的概念是probability distribution,它非常有意义 - 只是代码没有正确表达这个想法。

标签: python recursion infinite-recursion


【解决方案1】:
class Distribution:
    ...
    def pdf(self, x):
        return self.pdf(x)

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

cdf() 相同。

【讨论】:

  • self.pdfself.cdf 的分配有同样的问题。例如,self.pdf = lambda x : self.pdf(x) + other.pdf(x) 分配给self.pdf 一个无限递归的函数。
  • 非常正确,这是一个愚蠢的定义,哎呀。如果以这种方式完成,顶级分发本身中的 PDF 和 CDF 应该是被传递以创建分发的函数。我首先尝试了抽象,但我需要一个循环定义。
【解决方案2】:
def pdf(self, x):
    return self.pdf(x)
    
def cdf(self, x):
    return self.cdf(x)

我假设您的意图是委托给属性。由于它们总是被分配的,它们将被找到(假设您在一个实例上进行查找)而不是类方法(这将直接是没有这些属性的无限递归);但这反过来又意味着这些类方法毫无用处。 x.cdf(y),其中cdf 是一个可调用的实例属性,可以正常工作;也不需要提供方法。

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

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

这里有一个单独的问题,因为您正在编写 __mul____add__ 实现 - 即 *+ 运算符,它们应该 返回一个新值 em>,并且 改变任一操作数。 (如果你写了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

【讨论】:

  • 当然,您可以使用嵌套函数,就像您自己解决问题时所做的那样,而不是命名lambda。或者您可以将lambdas 直接传递给构造函数。
  • 非常感谢您提供详细的答案,并解决了这两个问题(第一个问题让我非常愚蠢地错过了,而且是由于对遗留实现的思考不够清楚)。我以前从未听说过 Python 有后期绑定闭包。顺便说一句,你完全读懂了我的意图。非常感谢!
  • 参考链接(也编辑到问题中):stackoverflow.com/questions/2295290/…
【解决方案3】:

感谢@John、@Tom 和@bbbbbb 的帮助...问题是试图返回 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

那么这就解决了这个问题

【讨论】:

    猜你喜欢
    • 2018-06-05
    • 1970-01-01
    • 2015-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-04
    • 2019-11-03
    相关资源
    最近更新 更多