# Polymorphism ````{admonition} Overview :class: overview Questions: - What is Polymorphism? Objectives: - Understand the concepts behind Polymorphism. ```` ## Polymorphism Polymorphism the concept of using different classes in place of one another. Specifically, an object is polymorphic if it can be used in place of one or more classes or interfaces. The intended use of a polymorphic object is to allow objects that are children of a parent class to be used as their parent class or for multiple objects that inherit from an interface to be used as the interface. This is very similar to python's concept of `Duck Typing`, i.e. if it looks like a duck and quacks like a duck, then it must be a duck. Duck typing is more of a passive concept, we assume objects of certain types can be used in certain ways and simply try to use them. For example, if we believe a variable is a number, we assume we can perform mathematical operations with it. Polymorphism is the practice of making sure duck typing will work. We will use the two classes, `Molecule` and `AtomMolecule` to create an example of polymorphism. First we will restate the two classes here. ````{tab-set-code} ```{code-block} python class Molecule: def __init__(self, name, charge, symbols, coordinates): self.name = name self.charge = charge self.symbols = symbols self.coordinates = coordinates @property def symbols(self): return self._symbols @symbols.setter def symbols(self, symbols): self._symbols = symbols self._update_num_atoms() def _update_num_atoms(self): self.num_atoms = len(self.symbols) def __str__(self): return f'name: {self.name}\ncharge: {self.charge}\nsymbols: {self.symbols}\ncoordinates: {self.coordinates}\nnumber of atoms: {self.num_atoms}' ``` ```` ````{tab-set-code} ```{code-block} python class AtomMolecule(Molecule): def __init__(self, name, charge, atoms): self.atoms = atoms super().__init__(name, charge, self.symbols, self.coordinates) @property def atoms(self): return self._atoms @atoms.setter def atoms(self, atoms): self._atoms = atoms self._update_symbols() self._update_coordinates() def _update_symbols(self): list_symbols = [] for atom in self.atoms: list_symbols.append(atom.symbol) self._symbols = list_symbols def _update_coordinates(self): list_coordinates = [] for atom in self.atoms: list_coordinates.append(atom.coordinates) self.coordinates = list_coordinates ``` ```` Since `AtomMolecule` is a child of the `Molecule` class, take note that they both share a many variable names.. To ensure that `AtomMolecule` is polymorphic, we want to ensure that any method that operates on a `Molecule` will correctly operate on an instance of `AtomMolecule` as well. Here we will build a simple method that utilizes variables of a `Molecule` to provide a formatted output. ````{tab-set-code} ```{code-block} python def formatted_print(molecule): return f'{molecule.name} is made of {molecule.symbols} and has an atomic charge of {molecule.charge}' ``` ```` We will create a `Molecule` to provide to the method. ````{tab-set-code} ```{code-block} python mol1 = Molecule(name='water molecule', charge=0.0, symbols=["O", "H", "H"], coordinates=[[0,0,0],[0,1,0],[0,0,1]]) formatted_print(mol1) ``` ```` ````{tab-set-code} ```{code-block} output "water molecule is made of ['O', 'H', 'H'] and has an atomic charge of 0.0" ``` ```` For `AtomMolecule` to be polymorphic, it should also work with the method. ````{tab-set-code} ```{code-block} python oxygen = Atom("oxygen", "O", 8, 15.999, [0,0,0]) hydrogen1 = Atom("hydrogen", "H", 1, 1.00784, [0,1,0]) hydrogen2 = Atom("hydrogen", "H", 1, 1.00784, [0,0,1]) mol2 = AtomMolecule(name='water molecule', charge=0.0, atoms=[oxygen, hydrogen1, hydrogen2]) formatted_print(mol2) ``` ```` ````{tab-set-code} ```{code-block} output "water molecule is made of ['O', 'H', 'H'] and has an atomic charge of 0.0" ``` ```` We have properly made `AtomMolecule` polymorphic. Any method that utilizes `Molecule` objects should be able to use `AtomMolecule` objects. This allows us to extend the behaviour of a `Molecule` without breaking any code that relies on it. ````{admonition} Key Points :class: key - Encapsulation - Inheritance ````