Search code examples
pythonoopencapsulation

Why is a class variable accessable from outside


Learing Python I just encountered something I do not really understand. Let us take this example:

class CV_Test:
    classVar = 'First'

cv = CV_Test()
print(cv.classVar)
CV_Test.classVar = 'Second'
cv2 = CV_Test()
print(cv2.classVar)
print(CV_Test.classVar)

Output:

First
Second
Second

Can anyone tell me why this is possible and what it is good for? Isn't this contradictory to defining a class as a blueprint if I can change maybe crucial values within a class from outside and is this not a conflict of the OOP paradigam of encapsulation. Coming from .NET I actually just know accessing variables via a getter and setter but not just like this. So I am curious what important purpose there can be that this is allowed.


Solution

  • Why is it possible? Python does not follow a restrictive programming paradigm, meaning that if something can make sense in some scenario, the interpreter should not stand in the way of the programmer willing to do that.

    That being said, this approach requires a higher level of discipline and responsibility on the programmer's side, but also allows for a greater degree of flexibility in its meta-programming capabilities.

    So, in the end this is a design choice. The advantage of it is that you do not need to explicitly have to use getters/setters.

    For protected/private members/methods it is customary to prepend a _ or __, respectively. Additionally, one would be able to fake a getter/setter protected behavior (which would also allow the execution of additional code) via the method decorators @property and @.setter, e.g.:

    class MyClass():
        _an_attribute = False
    
        @property
        def an_attribute(self):
            return self._an_attribute
    
        @an_attribute.setter
        def an_attribute(self, value):
            self._an_attribute = value
    

    This can be used like this:

    x = MyClass()
    
    x.an_attribute
    # False
    
    x.an_attribute = 1
    # sets the internal `_an_attribute` to 1.
    
    x.an_attribute
    # 1
    

    and you can leave out the @an_attribute.setter part, if you want a read-only (sort of) property, so that the following code:

    x = MyClass()
    
    x.an_attribute
    # False
    

    but, attempting to change its value would result in:

    x.an_attribute = 1
    

    AttributeError: can't set attribute

    Of course you can still do:

    x._an_attribute = 2
    
    x.an_attribute
    # 2
    

    (EDIT: added some more code to better show the usage)

    EDIT: On monkey patching

    Additionally, in your code, you are also modifying the class after its definition, and the changes have retrospective (sort of) effects. This is typically called monkey patching and can again be useful in some scenarios where you want to trigger a certain behavior in some portion of code while keeping most of its logic, e.g.:

    class Number():
        value = '0'
    
        def numerify(self):
            return float(self.value)
    
    x = Number()
    x.numerify()
    # 0.0
    
    Number.numerify = lambda self: int(self.value)
    x.numerify()
    # 0
    

    But this is certainly not a encouraged programming style if cleaner options are available.