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"