Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Python Encapsulation
#1
Hi,

Was reading some articles and got this code in the multi-choice option, wanted to know what would be the put of this code?

class BaseClass:
    def __init__(self):
        self._x = 10

class DerivedClass(BaseClass):
    def __init__(self):
        BaseClass.__init__(self)
        print(self._x)

obj1 = DerivedClass()
obj2 = BaseClass()
print(obj2.x))
Thank You!
buran write Apr-27-2023, 08:04 AM:
Spam link removed
Reply
#2
I don't think anyone understands what you mean by "what would be the put of this code?"

I have no idea what is the purpose of this code. I thiink it is supposed to demonstrate inheritance and __init__(), but it does a poor job at that. It might be better as this:
class BaseClass:
    def __init__(self):
        self._x = 10

class DerivedClass(BaseClass):
    def __init__(self):
        super().__init__()

obj1 = DerivedClass()
obj2 = BaseClass()
print("obj1", obj1._x, "obj2", obj2._x)
But why have a DerivedClass.__init__()? If there is no DerivedClass.__init__(), BaseClass.__init__() is called when you create an instance of balse class. So maybe this is how the code should look.
class BaseClass:
    def __init__(self):
        self._x = 10

class DerivedClass(BaseClass):
    pass

obj1 = DerivedClass()
obj2 = BaseClass()
print("obj1", obj1._x, "obj2", obj2._x)
At least now it is obvious that DerivedClass does nothing. Maybe we should give DerivedClass a purpose?
class BaseClass:
    def __init__(self, value=10):
        self._x = value

class DerivedClass(BaseClass):
    def __init__(self, *args, **kwargs):
        super().__init__()
        self._y = self._x**2

obj1 = DerivedClass(3.14)
obj2 = BaseClass(42)
print("obj1._x", obj1._x, "obj2._x", obj2._x)
print("obj1._y", obj1._y)
print("obj2._y", obj2._y)
Output:
obj1._x 10 obj2._x 42 obj1._y 100 Traceback (most recent call last): File "...", line 14, in <module> print("obj2._y", obj2._y) AttributeError: 'BaseClass' object has no attribute '_y'
Now we see that both obj1 and obj2 have an attribute "_x", but only obj1 has an attribute "_y". But I don't think this fits in with the article.

So let's talk about the article. There are no access modifiers in Python. In Python all object attributes are public. There are no private attributes or protected attributes. There is a convention in Python that variable names starting with an underscore should be "considered" private and not used external to the class. Your liked article uses the term "protected" when describing variables starting with an underscore. Python does nothing special with the underscore. "_x" is just a variable name that starts with an underscore. There is no protection provided by Python, and that is why I can print(obj1._x).

variable names starting with two underscores (__name) are modified by Python to include the class name. This is easier to demonstrate than it is to explain.
class BaseClass:
    def __init__(self, value=10):
        self.__x = value

class DerivedClass(BaseClass):
    def __init__(self, value):
        super().__init__(value)
        self.__x = value**2

obj = DerivedClass(5)
print(obj._DerivedClass__x, obj._BaseClass__x)
Output:
25 5
Both DerivedClass and BaseClass have an instance attribute named __x. Even though it appears to have the same name, these are two separate variables. Python secretly adds a prefix to the variable so the real variables are actually "_BaseClass__x" and "_DerivedClass__x". You can see from the print statement that this again does not enforce any kind of access control.

So why have the special treatment of names starting with double underscores? This is an inheritance tool, not an access modifier tool. The purpose of the __name convention is to avoid name conflicts with subclasses. The idea is if you have a class that is designed to be subclassed you can use __name convention and not worry about a subclass stomping all over those attributes. The need for something like this is exceedingly rare, and using the __name convention can cause a lot of very confusing problems such as the one demonstrated here:
class BaseClass:
    def __init__(self, value=10):
        self.__x = value

    def print(self, prefix):
        print(__class__.__name__, prefix, self.__x)

class DerivedClass(BaseClass):
    def __init__(self, value):
        super().__init__(value)
        self.__x = value**2

    def print2(self, prefix):
        print(__class__.__name__, prefix, self.__x)

obj1 = BaseClass(2)
obj2 = DerivedClass(5)
obj1.print("obj1")
obj2.print("obj2")
obj2.print2("obj2")
Output:
BaseClass obj1 2 BaseClass obj2 5 DerivedClass obj2 25
The casual observer might expect obj2.print() to print the value of obj2.__x. However print() is implemented in BaseClass, and BaseClass prints the value of _BaseClass__x, not _DerivedClass__x.
Larz60+ likes this post
Reply
#3
Explanation:

Base Class:
  • BaseClass defines a constructor (__init__) that initializes a private attribute _x with the value 10. The underscore (_) in front of _x indicates it's intended for internal use within the class and shouldn't be directly access from outside.
Derived Class:
  • DerivedClass inherits from BaseClass.

  • Its constructor (__init__) explicitly calls the base class constructor using super().__init__(). This ensures that the base class's initialization logic is executed first.

  • It then prints the value of self._x. However, due to name mangling (Python's way of protecting private attributes in subclasses), directly accessing _x would result in an AttributeError. To access the private attribute from the base class, it's recommended to use protected methods or properties defined in the base class itself.

Output:

Since _x is private, attempting to print obj2.x in the original code would cause an AttributeError. Here's the corrected output:
10
This output is produced because the print(self._x) line in DerivedClass's constructor is able to access the private attribute within the derived class instance.
buran write Apr-02-2024, 06:02 PM:
Spam link removed
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Function encapsulation Oldman45 4 2,316 Jan-22-2021, 11:38 AM
Last Post: Oldman45
  Preserve Encapsulation while Displaying Information QueenSvetlana 13 7,030 Dec-07-2017, 06:13 PM
Last Post: snippsat
  Encapsulation issue iFunKtion 4 3,971 Mar-07-2017, 10:13 PM
Last Post: ichabod801

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020