0

I want to create the class 'Human' with attributes "name" and "gender". I want to restrict assignment of the "gender" attribute to only "male" or "female". For this purpose, we have overridden __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)

But I am getting below exception:

  [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

But If I pass wrong gender(h.gender = 'f') it gives me proper Error(AttributeError: Gender can only be "male" or "female")

I am not able to figure out that what went wrong when I passed the proper gender.

Girish Gupta
  • 1,241
  • 13
  • 27

2 Answers2

3

The problem is that your __setattr__ function contains the line self.gender =... which calls __setattr__ in an infinite loop. You need to store the attribute without recursively calling __setattr__ by using the superclass method:

super().__setattr__(name, value)

Note also in your example code, if you tried to print h.name, you'd get an AttributeError, as your __setattr__ function never set the attribute. So what you want is something like:

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)
Girish Gupta
  • 1,241
  • 13
  • 27
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
0

Chris Dodd's answer answers the question.

I would like to share an alternative implementation which avoids this trap:

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)

Please note that the setter function name should be the same like the getter function name. Otherwise you will end up with two properties, one readonly and one read- and writable.

For more information see:

jakun
  • 624
  • 5
  • 13