Search code examples
pythonpython-3.xclasssubclassing

Setting parent attributes in child class


It has just dawned on me that I can set an attribute of a child class that only it's parent uses

In [34]: class A:
    ...:     def __init__(self):
    ...:         pass
    ...:
    ...:     def a(self):
    ...:         print(f'b = {self.b}')
    ...:
    ...: class B(A):
    ...:     def __init__(self):
    ...:         super(B, self).__init__()
    ...:         self.b = 1
    ...:

In [35]: b = B()
    ...: b.a()
b = 1

This implementation seems counterintuitive and something feels wrong about it but I'm not quite sure what it is.

I would think that the following makes more sense

In [38]: class A:
    ...:     def __init__(self, b):
    ...:         self.b = b
    ...:
    ...:     def a(self):
    ...:         print(f'b = {self.b}')
    ...:
    ...: class B(A):
    ...:     def __init__(self):
    ...:         super(B, self).__init__(1)
    ...:

In [39]: b = B()
    ...: b.a()
b = 1

Are there use cases where the former would be a more recommended implementation than the latter?


Solution

  • Conceptually, you are doing two different things. In the first case, you have something like an abstract class; in other words, a base class which is not meant to be instantiated alone, because the definitions of certain attributes are "missing"; it is understood that subclasses will implement those attributes.

    The more idiomatic way to do something like this would be to mark A as an abstract base class using the abc module, for example:

    from abc import ABCMeta, abstractmethod
    
    class A(metaclass=ABCMeta):
    
        @property
        @abstractmethod
        def x(self):
            pass
    
        def print_me(self):
            print(f'x = {self.x}')
    
    class B(A):
    
        @property
        def x(self):
            return 1
    
    A().print_me()
    

    The output will then be:

    TypeError: Can't instantiate abstract class A with abstract methods x
    

    On the other hand, this works:

    B().print_me()  # prints x = 1
    

    By doing this, you signal clearly that a subclass must override the x property, otherwise the print_me function won't work.

    Moving to the second case, you have a concrete base class and subclass, with the subclass acting something like a constraint on the nature of instances that can be created. In this case, a standalone instance of A is perfectly valid; it is just that instances of B provide the additional guarantee that a specific attribute will always be a certain value (or, at least, be initialised to a certain value, if you intend your class to be mutable).