覆盖 __setattr__ 时无限递归

infinite recursion when overriding __setattr__

我想创建具有属性 "name" 和 "gender" 的 class 'Human'。我想将 "gender" 属性的分配限制为仅 "male" 或 "female"。为此,我们重写了 __setattr__(self, name, value)。

class Human(object):
    def __setattr__(self, name, value):
        if name == 'gender':
            if value in ('male', 'female'):
                self.gender = value
            else:
                raise AttributeError('Gender can only be "male" or "female"')


h = Human()
h.name = 'Sweety'
h.gender = 'female'
print(h.gender)

但我遇到以下异常:

  [Previous line repeated 328 more times]
  File "/Users/admin/algorithms/betright_test.py", line 143, in **__setattr__**
    if name == 'gender':
RecursionError: maximum recursion depth exceeded in comparison

但是如果我传递了错误的性别(h.gender = 'f'),它会给我正确的错误(AttributeError: Gender can only be "male" or "female")

我无法弄清楚当我通过正确的性别时出了什么问题。

问题是您的 __setattr__ 函数包含行 self.gender =...,它在无限循环中调用 __setattr__。您需要存储属性 而无需 使用超类方法递归调用 __setattr__

super().__setattr__(name, value)

另请注意,在您的示例代码中,如果您尝试打印 h.name,您将得到一个 AttributeError,因为您的 __setattr__ 函数从未设置该属性。所以你想要的是这样的:

def __setattr__(self, name, value):
    if name == 'gender':
        if value not in ('male', 'female'):
            raise AttributeError('Gender can only be "male" or "female"')
    super().__setattr__(name, value)

回答了问题。

我想分享一个避免这个陷阱的替代实现:

class Human(object):

    ALLOWED_GENDERS = ['male', 'female']

    def __init__(self, name=None, gender=None):
        self.name = name
        self._gender = gender

    @property
    def gender(self):
        return self._gender

    @gender.setter
    def gender(self, newvalue):
        if newvalue not in self.ALLOWED_GENDERS:
            raise ValueError('Invalid value for gender: %s. Should be one of %s.' % (newvalue, ", ".join(self.ALLOWED_GENDERS)))

        self._gender = newvalue


h = Human()
h.name = 'Sweety'
h.gender = 'female'

print(h.gender)

请注意 setter 函数名应该与 getter 函数名相同。 否则你将得到两个属性,一个是只读的,一个是可读写的。

有关详细信息,请参阅: