2

I am trying to have a child class inherit a method from some parent class that points to parent's own private variable. I want the inherited method of the child to use the child's own private variable. Is this possible, without redefining the whole method in the child class?

The code works fine if using public or protected (single underscore) variables. But our instructor insists on using private (double underscore) variables. We are only a couple of days into object-oriented programming so I do not really know what is the term I am looking for (sorry for that!).

I suspect I should only be using protected ones, but I want to comply with the instructions as much as possible.

class ClassA:
    def __init__(self): self.__var = 10

    def method1(self):
        self.__var += 1     #method1 is intended to work on ClassA's __var
        return self.__var   #The method will also return the processed value of ClassA's __var

class ClassB(ClassA):
    def __init__(self): self.__var = 20

    #ClassA's method1 is inherited by ClassB. 
    #But I want this method1 to use ClassB's __var


ObjectA = ClassA()
ObjectB = ClassB()

print(ObjectA.method1()) #Will output 11
print(ObjectB.method1()) #Will raise an AttributeError, still looks for __var of ClassA

Above is just a demonstration of what I intend to achieve.

When using public or protected variables, I will have an output of 11 and 21.
When using private variables however, I will have an 11 and an error.

I want ClassB's method1 to use ClassB's __var (_ClassB__var).

I could get ClassB to have a variable named _ClassA__var and set it to 20, and it does work, but that is kind of messy IMO.


Edit: I think I've found an answer.

class ClassA:
    def __init__(self): self.__var = 10

    def method1(self): return self.get_var() + 1

    def get_var(self): return self.__var
    def set_var(self, val): self.__var = val


class ClassB(ClassA):
    def __init__(self): self.__var = 20

    def get_var(self): return self.__var
    def set_var(self, val): self.__var = val


ObjectA = ClassA()
ObjectB = ClassB()

print(ObjectA.method1()) 
print(ObjectB.method1())

Instead of referencing the class attributes directly, I had the method point to a getter. This idea came from a comment under this post. As of this editing, it is not included in an answer, so I'm putting this here for clarity.

Thanks, @Allan!

  • 2
    The purpose of `__`-prefixed names is to prevent the type of shadowing you want. – chepner Apr 21 '19 at 02:46
  • If your instructor insists on double-underscore attributes, they're probably expecting you to use getters and setters for those attributes. (That's not what you should do in real Python programming, but for the sake of this course, it's probably what you'll have to do.) – user2357112 Apr 21 '19 at 03:07
  • @user2357112 Thank you for your comment. Yep, our instructor wanted us to include getters and setters. However, I wanted to use the method from the parent class, not its attribute. And I want this inherited method to use the child's own attribute. – Mark Johnson Apr 21 '19 at 03:16
  • If your instructor wants getters and setters, you'd still need to duplicate those to make them work for both `ClassA` and `ClassB`. Then `method1` would become a single implementation that would work in both classes, because it'd call these getters and setters. – Allan Apr 21 '19 at 03:19
  • @Allan Ooh! I think I like that idea, it's quite spot on. I never thought of using the getters and setters like that. If you could add that to your answer below, I could mark it as Accepted. :) – Mark Johnson Apr 21 '19 at 03:31

1 Answers1

3

It's impossible to have ClassB use ClassA's __var without invading its namespace. As you noticed, when you use __var, the Python interpreter actually renames the variable to something like _classname__variable. That includes the reference in method1, so now method1 will always refer to __var in ClassA. If you really want some crazy solution, it's probably possible, but I think your instructor won't like it because you'd be very much violating encapsulation.

In other languages like Java, you actually have three levels of access: private, protected and public. Private means only objects of that same class can see and manipulate it. Protected is what you are after, and Python doesn't have that in the language. The closest you can get is using _var, which is a Python convention, not a rule. In fact, for most real-world, production Python code, a single underscore is enough, as it indicates to other programmers "don't mess with this attribute". As for public attributes, just name the variable without preceding underscores.

Now, there are really no private attributes/variables in Python. See this thread for more information on the differences between self._var and self.__var: What is the meaning of a single and a double underscore before an object name?


If you wanted to call method1 in a ClassB object with ClassA's __var, you'd need to first initialize ClassA's constructor in ClassB's constructor (assuming Python 3 here):

class ClassB(ClassA):
    def __init__(self):
        super().__init__()
        self.__var = 20

When you call that constructor through super().__init__(), you are effectively initializing ClassA's attributes in memory. If you don't do that, it won't initialize anything, and thus you get that AttributeError.


This is one dirty way to make it work. Please don't do this in your assignment. In fact, don't do this in any code unless you have a very good reason to do so:

class ClassA:
    def __init__(self):
        self.__var = 10

    def method1(self):
        varname = "_{}__var".format(self.__class__.__name__)
        v = getattr(self, varname)
        setattr(self, varname, v + 1)
        return getattr(self, varname)

class ClassB(ClassA):
    def __init__(self):
        super().__init__()
        self.__var = 20
Allan
  • 525
  • 2
  • 7
  • This gets rid of the name error (by ensuring `_classA__var` exists), but doesn't change the fact that `ClassA.method1` will still operate on that attribute, not `_classB__var` (which is what the OP wants). – chepner Apr 21 '19 at 02:44
  • @Allan Thank you for your answer! However, I wanted to call `method1` in a `ClassB` object with `ClassB`'s own `__var`, as what @chepner pointed out. Trying out your code, I had an output of `11` and `11`, but I expected `11` and `12`. Cheers. – Mark Johnson Apr 21 '19 at 03:18
  • Did you mean `11` and `21`? Unfortunately, that's not possible, unless you do something weird like I did in the last part of the answer. Did you try that last one? It gives `11` and `21`. – Allan Apr 21 '19 at 03:20
  • @Allan Oops. I was actually waiting for your edits since I assumed you have edited your answer after seeing the first comment. I guess I only saw the first bit, sorry about that. – Mark Johnson Apr 21 '19 at 03:33