0

I have two classes A and B which inherit from a Parent class. As stated in Access Child class variables in Parent class, its not good practice to reference child class attributes from parent classes. My question is if this also holds for object attributes defined in the child class (e.g. in the __init__ method of A and B). As an example, is it ok to do the following:

class Parent():

    def print_c(self):
        print(self.c)


class A(Parent):

    def __init__(self):
        super().__init__()
        self.c = 4


class B(Parent):

    def __init__(self):
        super().__init__()
        self.c = 7


a_obj = A()
a_obj.print_c() # prints 4

b_obj = B()
b_obj.print_c() # prints 7

Or is this the only "correct" way to do it:

class Parent():

    def __init__(self, c):
        self.c = c

    def print_c(self):
        print(self.c)


class A(Parent):

    def __init__(self):
        super().__init__(c=4)


class B(Parent):

    def __init__(self):
        super().__init__(c=7)


a_obj = A()
a_obj.print_c() # prints 4

b_obj = B()
b_obj.print_c() # prints 7

EDIT: As suggested by @MihailFeraru

from abc import ABC, abstractmethod

class Parent(ABC):

    @abstractmethod
    def get_c(self):
        raise NotImplementedError

    def print_c(self):
        print(self.get_c())


class A(Parent):

    def __init__(self):
        super().__init__()
        self.c = 4

    def get_c(self):
        return self.c


class B(Parent):

    def __init__(self):
        super().__init__()
        self.c = 7

    def get_c(self):
        return self.c


a_obj = A()
a_obj.print_c() # prints 4

b_obj = B()
b_obj.print_c() # prints 7
JoJo
  • 11
  • 3
  • As you have found out in your first snippet, accessing `c` from the parent works in this case. Your second snippet is definitely better because `Parent` is actually expecting `c` to be set by all child classes. (btw it can still break if you have: `class C(Parent): def __init__(self): pass`. – quamrana Jul 21 '20 at 11:55
  • 2
    There are multiple "correct" ways (e. g. making an abstract parent class with a property "c" with an abstract getter). But the first code snippet is bad because a class should not assume anything about its children without at least an abstract definition (or declaration in other languages) of it in the parent. – Michael Butscher Jul 21 '20 at 11:59
  • Thank you to both. Yes, I also thought the first option is not very "safe" since it assumes the child class has the "c" attribute. @MichaelButscher do you have any resource I could look at to understand how to make the first option work and when that makes sense? (Not very familiar with abstract classes) – JoJo Jul 21 '20 at 12:03
  • In my code I have a lot of classes that inherit from another, whose job is to perform common operations on the same attributes. Therefore, it seems easier to just "enforce" the existence of the Child property instead of always "sending" the value for initialization in the Parent Class. – JoJo Jul 21 '20 at 12:09
  • To check for present attributes of an object there is the `__dict__` attribute of an object (except for some special cases). Currently I don't remember much examples where this makes sense apart from similar behavior as in the Django example linked in your question. I can't fully imagine your use case but it may be solvable e. g. with a method that is abstract or does nothing (not so good style) in parent but is overridden to initialize the attributes. The method is called from parent to do that. – Michael Butscher Jul 21 '20 at 12:28
  • Surely your latest snippet produces an error at: `a_obj = A()`? – quamrana Jul 21 '20 at 12:48
  • @quamrana Yes, you are right! I fixed it – JoJo Jul 21 '20 at 12:57

1 Answers1

2

I think it's a very bad practice to access variables from child classes in such a way. There should be no assumptions about child classes if you don't declare them in a standard way.

If you want that your child class should have an expected behavior, you could do it in a more elegant manner, using abstract methods.

from abc import ABC, abstractmethod
class Parent(ABC):
    def use_child_capability(self):
        self.child_capability()
        # do some other stuff

    @abstractmethod
    def child_capability(self):
         raise NotImplementedError

class Child(Parent):
   def child_capability(self):
       # implement the abstract method

An abstract method is a method that MUST be implemented in a child class, if the child class does not implement it, you will get a "compile-time" error, not a run-time one.

Mihail Feraru
  • 1,419
  • 9
  • 17
  • Thank you @MihailFeraru. I understood that it is problematic and your answer seems to answer the problem. The issue is that, in my case, the child_capability() method is exactly the same for all children, so I would like to avoid repeating the code in all child classes. – JoJo Jul 21 '20 at 12:15
  • 1
    Then shouldn't you implement that class in the parent? That's the idea of inheritance. If the method should be present in all children, then it belongs to the parent. – Mihail Feraru Jul 21 '20 at 12:16
  • Yes, I understand your point. But the child variable that I want to use ("c" in my example) is different for all children, even though the method is exactly the same (print_c() in my example). In that case, I need to "send" the attribute to the parent method, which is the second case I described in my question. – JoJo Jul 21 '20 at 12:20
  • 2
    Then you should implement the body of the function in the parent using an abstract method "get_c()". Each child would implement this method returning its own value. I think that's named template pattern, when you implement the overall algorithm in the parent, then you let the child implement only the critical parts. https://sourcemaking.com/design_patterns/template_method – Mihail Feraru Jul 21 '20 at 12:24
  • Thank you. @MihailFeraru, can you check my edit incorporating your suggestion so that I can mark your answer as correct? – JoJo Jul 21 '20 at 12:37
  • 1
    Ok, now I think it is correct. – JoJo Jul 21 '20 at 12:49