在Python类中使用对象变量作为递归方法参数

3 投票
4 回答
508 浏览
提问于 2025-04-17 09:25

我正在尝试在一个类里面写一个递归函数,但在用对象变量作为方法参数时遇到了一些麻烦:

class nonsense(object):
  def __init__(self, val):
    self.val = val
  def factorial(self, n=self.val):
    if n<=1: return 1
    return n*self.factorial(n=n-1)

上面的代码会产生以下错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in nonsense
NameError: name 'self' is not defined

但是如果我不提到 self.val,这个错误就消失了,虽然这样指定 n 是多余的:

class nonsense(object):
  def __init__(self, val):
    self.val = val
  def factorial(self, n):
    if n<=1: return 1
    return n*self.factorial(n=n-1)

那正确的做法是什么呢?

4 个回答

1

这个奇怪行为的原因是,def 语句只在函数定义的时候执行一次。所以,初始化的值只会在那时创建,而那时还没有 self 的引用。

作为一种替代方案,可以试试这个:

class nonsense(object):
  def __init__(self, val):
    self.val = val
  def factorial(self, n=None):
    return self.factorial_aux(n if n is not None else self.val)
  def factorial_aux(self, n):
    if n <= 1:
        return 1
    return n * self.factorial(n-1)

上面的解决方案只会检查一次 n 参数是否是默认值(None),然后它会返回调用 factorial_aux(实际执行工作的函数)时传入的合适参数的结果。

6

默认参数是在定义方法的时候就被计算出来的。因此,实际上在这个时候使用在 __init__ 里定义的成员值已经“太晚”了。你应该做的是把默认值设置为 None,然后在函数内部检查这个值:

class nonsense(object):
    def __init__(self, val):
        self.val = val
    def factorial(self, n=None):
        if n is None:
            n = self.val
        elif n <= 1:
            return 1

        return n*self.factorial(n-1)
3

正如你发现的那样,在类的头部不能使用 self.xxx,而是要用 None,然后在主体部分进行修正:

def factorial(self, n=None):
    if n is None: n = self.val
    if n<=1: return 1
    return n*self.factorial(n=n-1)

原因是,当类对象正在被创建时,self 还不存在;除了 globals(),在 Python 到达 factorial 时,唯一定义的名字是 __module____init__

为了证明这一点,你可以尝试做一个实验:

class TestClassCreation(object):
    print("Started creating class")
    print("names so far: %s" % vars())

    def __init__(self):
        pass
    print("now we have %s" % vars())

    def noop(self, default=None):
        print("this gets run when noop is called")
    print("and now have %s" % vars())
    print()

    print("and now we'll fail...")
    def failure(self, some_arg=self.noop):
        pass
    print("we never get here...")

撰写回答