Are Python pure virtual functions possible and/or worth it?
Asked Answered
E

2

23

I may be coming from a different mindset, being primarily a C++ programmer. This question has to do with OOP in Python and more specifically pure virtual methods. So taking code I adapted from this question I am looking at this basic sample.

class Animal():
    def speak(self):
        print("...")

class Cat(Animal):
    def speak(self):
        print("meow")

class Dog(Animal):
    def speak(self):
        print("woof")

my_pets = [Dog(), Cat(), Dog()]

for _pet in my_pets:
     _pet.speak()

So you see it calls the speak function for different derived classes. Now my problem is that duck typing is all good and I think I have grasped it. However, is it wrong to pursue more strict OOP in Python? So I looked at the Abstract Base Classes and specifically abstractmethod. To me all this seems to do is allow me to call the base class method with super. Is there any way/reason(in Python) to make speak() pure such that implementing a derived animal without speak would throw an error?

My argument for such a pursuit would be when writing modules and frameworks that you intend people to subclass, this would self document for them the fact that they need implement the function. A probably very bad idea is something like this, having the base class "pure" function throw an exception. Problem is that this error is found at runtime!

class VirtualException(BaseException):
    def __init__(self, _type, _func):
        BaseException(self)

class Animal():
    def speak(self):
        raise VirtualException()

class Cat(Animal):
    def speak(self):
        print("meow")

class Dog(Animal):
    def speak(self):
        print("woof")

class Wildebeest(Animal):
    def function2(self):
        print("What!")

my_pets = [Dog(), Cat(), Dog(), Wildebeest()]

for _pet in my_pets:
    _pet.speak()
Ejecta answered 20/10, 2014 at 4:32 Comment(4)
As far as I can see, what you describe is exactly what abstractmethod does. Can you clarify what you want that abstractmethod doesn't provide? You're not going to get a "compile-time" solution for anything, because Python doesn't really have a compile time in the C++ sense. But abstractmethod lets you get the error when the class is defined (before it is instantiated).Gissing
@Gissing Yes sorry for the clumsy statement. I know I won't get compile time errors. It is just nice to know I can get an error when the abstract base class object or an invalid derived class object is instantiated. My mistake was using a Python 3 interpreter which made abstractmethod appear to do nothing because I wasn't defining the class in the correct Python3 way. See the accepted answer.Ejecta
Related: #4714636Dipteran
I don't see "being found at runtime" as being a problem, especially when working with dynamic languages such as Python. All errors in Python are found at runtime. There is no compiler, so when else would an error be found?Trauma
T
39

Abstract base classes already do what you want. abstractmethod has nothing to do with letting you call the method with super; you can do that anyway. Instead, any methods decorated with abstractmethod must be overridden for a subclass to be instantiable:

Python 3:

>>> class Foo(metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(self):
...         pass
...
>>> class Bar(Foo):
...     pass
...
>>> class Baz(Bar):
...     def foo(self):
...         return super(Baz, self).foo()
...
>>> Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Foo with abstract methods foo
>>> Bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Bar with abstract methods foo
>>> Baz()
<__main__.Baz object at 0x00000210D702E2B0>

Python 2:

>>> class Foo(object):
...     __metaclass__ = abc.ABCMeta
...     @abc.abstractmethod
...     def foo(self): pass
...
>>> class Bar(Foo): pass
...
>>> class Baz(Bar):
...     def foo(self): return super(Baz, self).foo()
...
>>> Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Foo with abstract methods foo
>>> Bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Bar with abstract methods foo
>>> Baz()
<__main__.Baz object at 0x0000000001EC10B8>
Threescore answered 20/10, 2014 at 4:39 Comment(2)
Okay this is what I was looking for. My mistake was thinking that Python2 and 3 weren't extremely different. I probably mislabelled this question but Python 3 just lets this run without raising anything. Unless you call class Foo(metaclass=ABCMeta):. I then misunderstood what abstractmethod was for because the Python2 docs commented on super and I couldn't see if it was doing anything else. Thanks.Ejecta
If anybody can format the traceback lines so they aren't colored as Python code but still in the same code block I'd be grateful to learn how to do that.Rebel
O
11

Problem is that this error is found at runtime!

Well, it is Python... most errors are going to show up at runtime.

As far as I know, the most common pattern to deal with is in Python is basically what you describe: just have the base class's speak method throw an exception:

class Animal():
    def speak(self):
        raise NotImplementedError('You need to define a speak method!')
Oldfangled answered 20/10, 2014 at 4:36 Comment(3)
+1 for "most errors are going to show up at runtime"!Lingerie
Okay...sorry that was clumsy. I never thought otherwise. I was just hoping for second best, so when an object is instantiated as opposed to later when making a call to speak.Ejecta
@Lingerie This is basically my main pet peeve with Python. Except for this, it's a great language.Stroboscope

© 2022 - 2024 — McMap. All rights reserved.