5.4. Abstract Polymorphism
5.4.1. SetUp
>>> from abc import ABC, abstractmethod
>>> from dataclasses import dataclass
5.4.2. Elif
It all starts with single if
statement
>>> language = 'English'
>>>
>>> if language == 'English':
... result = 'Hello'
>>>
>>> print(result)
Hello
>>> language = 'English'
>>>
>>> if language == 'English':
... result = 'Hello'
... elif language == 'Polish':
... result = 'Witaj'
>>>
>>> print(result)
Hello
It quickly grows into multiple elif
:
>>> language = 'English'
>>>
>>> if language == 'English':
... result = 'Hello'
... elif language == 'Polish':
... result = 'Witaj'
... elif language == 'Spanish':
... result = 'Hola'
... else:
... result = 'Unknown language'
>>>
>>> print(result)
Hello
5.4.3. Match: Switch Pattern
Since Python 3.10: PEP 636 -- Structural Pattern Matching: Tutorial
More information in Match About [1]
In other languages you may find switch
statement. Since Python 3.10
there is a match
statement which can do the similar thing:
>>> language = 'English'
>>>
>>> match language:
... case 'English': result = 'Hello'
... case 'Polish': result = 'Witaj'
... case 'Spanish': result = 'Hola'
... case _: result = 'Unknown language'
5.4.4. Procedural Polymorphism
UNIX
getchar()
function used function lookup table with pointers
>>> keyboard = {
... 'open': lambda: ...,
... 'close': lambda: ...,
... 'read': lambda bytes: ...,
... 'write': lambda content: ...,
... 'seek': lambda position: ...,
... }
>>>
>>> file = {
... 'open': lambda: ...,
... 'close': lambda: ...,
... 'read': lambda bytes: ...,
... 'write': lambda content: ...,
... 'seek': lambda position: ...,
... }
>>>
>>> socket = {
... 'open': lambda: ...,
... 'close': lambda: ...,
... 'read': lambda bytes: ...,
... 'write': lambda content: ...,
... 'seek': lambda position: ...,
... }
>>>
>>>
>>> def getchar(obj):
... obj['open']()
... obj['seek'](0)
... obj['read'](1)
... obj['close']()
>>>
>>>
>>> getchar(file)
>>> getchar(keyboard)
>>> getchar(socket)
5.4.5. Explicit Polymorphism
>>> @dataclass
... class Element(ABC):
... name: str
...
... @abstractmethod
... def render(self):
... pass
>>>
>>>
>>> @dataclass
... class TextInput(Element):
... def render(self):
... print(f'Rendering {self.name} TextInput')
>>>
>>>
>>> @dataclass
... class Button(Element):
... def render(self):
... print(f'Rendering {self.name} Button')
>>> login_window = [
... TextInput(name='Username'),
... TextInput(name='Password'),
... Button(name='Submit'),
... ]
>>> def render(component: list[Element]):
... for element in component:
... element.render()
>>>
>>> render(login_window)
Rendering Username TextInput
Rendering Password TextInput
Rendering Submit Button
5.4.6. Structural Polymorphism
Duck typing
>>> @dataclass
... class TextInput:
... name: str
...
... def render(self):
... print(f'Rendering {self.name} TextInput')
>>>
>>>
>>> @dataclass
... class Button:
... name: str
...
... def render(self):
... print(f'Rendering {self.name} Button')
>>> login_window = [
... TextInput(name='Username'),
... TextInput(name='Password'),
... Button(name='Submit'),
... ]
>>> def render(component):
... for element in component:
... element.render()
>>>
>>> render(login_window)
Rendering Username TextInput
Rendering Password TextInput
Rendering Submit Button
5.4.7. Use Case - 1
>>> from abc import ABC, abstractmethod
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Account(ABC):
... username: str
...
... @abstractmethod
... def login(self):
... pass
>>>
>>>
>>> class User(Account):
... def login(self):
... return f'User login: {self.username}'
>>>
>>> class Admin(Account):
... def login(self):
... return f'Admin login: {self.username}'
>>>
>>>
>>> def login(accounts: list[Account]) -> None:
... for account in accounts:
... print(account.login())
>>>
>>>
>>> group = [
... User('mwatney'),
... Admin('mlewis'),
... User('rmartinez'),
... User('avogel'),
... ]
>>>
>>> login(group)
User login: mwatney
Admin login: mlewis
User login: rmartinez
User login: avogel
In Python, due to the duck typing and dynamic nature of the language, the Interface or abstract class is not needed to do polymorphism:
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class User:
... username: str
...
... def login(self):
... return f'User login: {self.username}'
>>>
>>> @dataclass
... class Admin:
... username: str
...
... def login(self):
... return f'Admin login: {self.username}'
>>>
>>>
>>> group = [
... User('mwatney'),
... Admin('mlewis'),
... User('rmartinez'),
... User('avogel'),
... ]
>>>
>>> for account in group:
... print(account.login())
User login: mwatney
Admin login: mlewis
User login: rmartinez
User login: avogel
5.4.8. Use Case - 2
Login Window
>>> import re
>>>
>>>
>>> class Element:
... def __init__(self, name):
... self.name = name
...
... def on_mouse_hover(self):
... raise NotImplementedError
...
... def on_mouse_out(self):
... raise NotImplementedError
...
... def on_mouse_click(self):
... raise NotImplementedError
...
... def on_key_press(self):
... raise NotImplementedError
...
... def render(self):
... raise NotImplementedError
>>>
>>>
>>> class Button(Element):
... action: str
...
... def __init__(self, *args, action: str | None = None, **kwargs):
... self.action = action
... super().__init__(*args, **kwargs)
...
... def on_key_press(self):
... pass
...
... def on_mouse_hover(self):
... pass
...
... def on_mouse_out(self):
... pass
...
... def on_mouse_click(self):
... pass
...
... def render(self):
... action = self.action
... print(f'Rendering Button with {action}')
>>>
>>>
>>> class Input(Element):
... regex: re.Pattern
...
... def __init__(self, *args, regex: str | None = None, **kwargs):
... self.regex = re.compile(regex)
... super().__init__(*args, **kwargs)
...
... def on_key_press(self):
... pass
...
... def on_mouse_hover(self):
... pass
...
... def on_mouse_out(self):
... pass
...
... def on_mouse_click(self):
... pass
...
... def render(self):
... regex = self.regex
... print(f'Rendering Input with {regex}')
>>>
>>>
>>> def render(components: list[Element]):
... for obj in components:
... obj.render()
>>>
>>>
>>> login_window = [
... Input('Username', regex='[a-zA-Z0-9]'),
... Input('Password', regex='[a-zA-Z0-9!@#$%^&*()]'),
... Button('Submit', action='/login.html'),
... ]
>>>
>>> render(login_window)
Rendering Input with re.compile('[a-zA-Z0-9]')
Rendering Input with re.compile('[a-zA-Z0-9!@#$%^&*()]')
Rendering Button with /login.html