4

I have a class that extends a base class. Upon instantiation, I want to check if the subclass has one of the classes implemented from its base, but I'm not sure the best way. hasattr(self, '[method]') returns the method from super if not implemented by the child, so I'm trying to tell the difference.

Here is an example:

class Base :
   def __init__ ( self,) :
       pass
   
   def fail (self,) :
       pass

# Now create the subclass w/o .fail
class Task ( Base ) :
    def __init__ ( self, ):
         print( hasattr( self, 'fail' ) ) # < returns True

When Task() is instantiated it prints True because Task inherits .fail from Base. But in this case, I want to know that Task Does Not implement .fail, so I want a False returned somehow. It seems like I'm looking for something like isimplemented( self, 'fail' ). What am I missing?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
sadmicrowave
  • 39,964
  • 34
  • 108
  • 180

3 Answers3

3

IIUC, you can check super().fail == self.fail

class Base:
    def __init__(self):
        pass
   
    def fail(self):
        pass

class Task(Base):
    def __init__(self):
        print(super().fail == self.fail)
    
class Task2(Base):
    def __init__(self):
        print(super().fail == self.fail)
    
    def fail(self):
        # Override
        pass

Output:

t1 = Task()
# True

t2 = Task2()
# False
Chris
  • 29,127
  • 3
  • 28
  • 51
  • This makes sense, except in my use case, I would want `t1 = Task()`to return `False` as well. Because I don't want to explicit have to define `fail` if it is only going to have a `pass` in it... – sadmicrowave Sep 15 '21 at 02:20
  • 1
    So you want to check if subclass has overridden a method of its superclass? perhaps you can just then do `super().fail != self.fail`? – Chris Sep 15 '21 at 03:17
  • Does not work if called not from within a constructor. For that use `print(super(type(self), self).fail == self.fail)` – mishadr Jul 05 '23 at 11:40
3

I'm not sure I understand correctly, but it sounds like you might be looking for Abstract Base Classes. (Documentation here, tutorial here.) If you specify an abstractmethod in a base class that inherits from abc.ABC, then attempting to instantiate a subclass will fail unless that subclass overrides the abstractmethod.

from abc import ABC, abstractmethod

class Base(ABC):
    @abstractmethod
    def fail(self):
        pass

class Task(Base):
   pass
    
class Task2(Base):
    def fail(self):
        pass

# this raises an exception
# `fail` method has not been overridden in the subclass.
t1 = Task()

# this succeeds
# `fail` method has been overridden in the subclass.
t2 = Task2()

If you want a check to happen at class definition time rather than instance instantiation time, another option is to write an __init_subclass__ method in your base class, which is called every time you subclass your base class or you subclass a class inheriting from your base class. (You don't have to raise an exception in __init_subclass__ — you could just add a fail_overriden boolean attribute to the class, or do anything you like really.)

class Base:
    def fail(self):
        pass

    def __init_subclass__(cls, **kwargs):
        if cls.fail == Base.fail:
            raise TypeError(
               'Subclasses of `Base` must override the `fail` method'
            )
        super().__init_subclass__(**kwargs)


# this class definition raises an exception
# because `fail` has not been overridden
class Task(Base):
    pass


# this class definition works fine.
class Task2(Base):
    def fail(self):
        pass

And if you just want each instance to tell you whether fail was overridden in their subclass, you can do this:

class Base:
    def __init__(self):
        print(type(self).fail != Base.fail)

    def fail(self):
        pass

class Task(Base):
   def __init__(self):
       super().__init__()
    
class Task2(Base):
    def __init__(self):
       super().__init__()

    def fail(self):
        pass

t1 = Task() # prints "True"
t2 = Task2() # prints "False"
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
  • 1
    Hey Alex, thanks for the excellent post. Option 1 won't work because I don't want the subclass to be required to implement `fail` in this case, I just need to know if it **was** implemented. Option 2 or 3 seems most likely - I honestly didn't know about `__init_subclass__`. I'll check it out, but the last solution seems most elegant and ultimately matches the use case the most. Thanks! – sadmicrowave Sep 17 '21 at 03:06
  • @sadmicrowave great! Glad I could help. – Alex Waygood Sep 17 '21 at 06:58
  • absolute gem, thanks. Solution #2 for me as I need to check a class that does not get instantiated (SQLAlchemy ORM Tables) – David Mendes Oct 27 '22 at 12:57
1

Not sure if I understand correctly but you could check if fail method is in the vars of the classes, but not inherited to the main class.

So you could try:

class Base:
    def __init__(self):
        print(self.__dir__())
   
    def fail(self):
        pass

class Task(Base):
    def __init__(self):
        print('fail' not in vars(Task))
    
class Task2(Base):
    def __init__(self):
        print('fail' not in vars(Task2))
    
    def fail(self):
        # Override
        pass
    
t1 = Task()
t2 = Task2()

Output:

True
False

Or use __dict__:

...
class Task(Base):
    def __init__(self):
        print('fail' not in Task.__dict__)
    
class Task2(Base):
    def __init__(self):
        print('fail' not in Task2.__dict__)
    
    def fail(self):
        # Override
        pass
...
U13-Forward
  • 69,221
  • 14
  • 89
  • 114