Home > other >  Is an attribute protected if initialized as public but setters & getters treat it as protected?
Is an attribute protected if initialized as public but setters & getters treat it as protected?

Time:12-14

While learning the basics of object oriented programming in Python, I came across something I cannot search effectively to find matching keywords:

I wrote validation checks for a basic class Course, which has a protected attribute _level. The getters and setters treat this as a protected attribute. The validation makes sure self._level are between 0 and 100.

  • But the validation checks in the setter are evaded:
class Course:
    
    def __init__(self, level=None):        
        self._level = level if level else 0
        
    @property
    def level(self):
        return self._level
    
    @level.setter
    def level(self, value):        
        if not isinstance(value, int):
            raise TypeError('The value of level must be of type int.')        
        if value < 0:
            self._level = 0
        elif value > 100:
            self._level = 100
        else:
            self._level = value

###
courses = [Course(), Course(10), Course(-10), Course(150)]
for c in courses:
    print(c.level)
>>> 0
>>> 10
>>> -10
>>> 150
  • I then change the initialization from a protected self._level to a public attribute self.level. My setters and getters still refer to a protected attribute self._level.
class Course:
    
    def __init__(self, level=None):        
        self.level = level if level else 0
        
    @property
    def level(self):
        return self._level
    
    @level.setter
    def level(self, value):        
        if not isinstance(value, int):
            raise TypeError('The value of level must be of type int.')        
        if value < 0:
            self._level = 0
        elif value > 100:
            self._level = 100
        else:
            self._level = value
        
courses = [Course(), Course(10), Course(-10), Course(150)]
for c in courses:
    print(c.level)
>>> 0
>>> 10
>>> 0
>>> 100

I was surprised that not only does the class realize it is the same attribute, but now my validations are implemented!

How does Python reconcile self.level with self._level, and why must I initialize it as a public attribute in order for my validations to kick in?

CodePudding user response:

The issue is you aren't using your property in the __init__, so of course, the property setter is never called!

self._level = level if level else 0

Should be:

self.level = level if level else 0

Some advice, stop thinking in terms of "protected" vs "public". The Python runtime recognizes no such thing, there are no access modifiers in Python. These are merely naming conventions. I think this naming convention is confusing you. There is no reason why if you have

@property
def level(self):
    ...

Should modify a variable called _level, again, this is merely a convention. You could call it whatever you want. So, consider the following:

class Course:

    def __init__(self, level=None):        
        self.foo = level if level else 0
        
    @property
    def level(self):
        return self.foo
    
    @level.setter
    def level(self, value):        
        if not isinstance(value, int):
            raise TypeError('The value of level must be of type int.')        
        if value < 0:
            self.foo = 0
        elif value > 100:
            self.foo = 100
        else:
            self.foo = value

This should hopefully make it more obvious why your __init__ never actually calls the setter -- because you don't use the property! The property is level, not foo. Of course, you would probably never use a naming scheme like the above because it would be confusing, but it should illuminate what is happening with your code.

Now, it should become obvious why if we change the __init__ to:

    def __init__(self, level=None):        
        self.level = level if level else 0

The property is called. Because self.level is the property, not self.foo. The property setters/getters will only be called if you use the property, which is self.level, not self.foo

  • Related