Home > Software design >  Virtual function being implemented without any initialization in python
Virtual function being implemented without any initialization in python

Time:03-11

Consider the following code :

class parent_print():
    
    def print_word(self):
        self.print_hello()
        
class child_print(parent_print):
    
    def print_hello(self):
        print('Hello')
        
        
basic_print = child_print()
basic_print.print_word()

Here I am assuming that print_hello() is a virtual function in the parent class. And all the children of parent (parent_print) will have a method implemented named print_hello. So that all children can call a single function print_word and appropriate binding to the print_hello function is done based on the child's method implementation.

But in languages like C we need to specifically say that a function is virtual in the parent class with the virtual keyword and also use the function destructor symbol ~.

But how is this possible in python with no mention in python parent class to the function print_hello() being a virtual function.

I am assuming that the concept that python uses is that of virtual functions , if I am wrong please correct me and explain the concept.

CodePudding user response:

Rather than thinking of this as being a matter of "virtual functions", it might be more useful to think of this as an example of "duck typing". In this expression:

self.print_hello()

we simply access the print_hello attribute of self (whatever it might be), and then call it. If it doesn't have such an attribute, an AttributeError is raised at runtime. If the attribute isn't callable, a TypeError is raised. That's all there is to it. No assumptions are made about the type of self -- we simply ask it to "quack like a duck" and see if it can.

The danger of duck typing is that it's very easy to accidentally ask something to quack that does not in fact know how to quack -- if you instantiate a parent_print and call print_word on it, it will fail:

abstract_print = parent_print()
abstract_print.print_word()
# raises AttributeError: 'parent_print' object has no attribute 'print_hello'

Python does have support for static type declarations and the concept of abstract classes, which can help you avoid mistakes. For example, if we run a static type checker (mypy) on your code as-is, we'll get an error:

test.py:4: error: "parent_print" has no attribute "print_hello"

which is exactly correct -- from a static typing perspective, it's not valid to call print_hello since we haven't established that all parent_print instances have such an attribute.

To fix this, we can declare print_hello as an abstract method (I'll also clean up the names to match standard Python conventions, in the interest of building good habits):

from abc import ABC, abstractmethod

class ParentPrint(ABC):
    
    @abstractmethod
    def print_hello(self) -> None: ...

    def print_word(self) -> None:
        self.print_hello()
        
class ChildPrint(ParentPrint):
    
    def print_hello(self) -> None:
        print('Hello')
        
        
basic_print = ChildPrint()
basic_print.print_word()

Now the code typechecks with no issues.

The @abstractmethod decorator also indicates that the class is abstract ("pure virtual") and can't be instantiated. If we attempt to create a ParentPrint, or any subclass of it that doesn't provide an implementation of the abstract method, we get an error, both statically from mypy and at runtime in the form of a TypeError that is raised as soon as you try to instantiate the object (before you even try to call the abstract method):

abstract_print = ParentPrint()
# raises error: Cannot instantiate abstract class "ParentPrint" with abstract attribute "print_hello"
  • Related