4.3. Inheritance Simple

4.3.1. Diagram

../../_images/inheritance-usecase-single.png

4.3.2. Code

class Account:
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname


class User(Account):
    pass


class Staff(Account):
    def astuple(self):
        data = vars(self).values()
        return tuple(data)


class Admin(Account):
    def astuple(self):
        data = vars(self).values()
        return tuple(data)

    def asdict(self):
        data = vars(self)
        return dict(data)

4.3.3. Usage

>>> mark = User('Mark', 'Watney')
>>>
>>> mark.astuple()
Traceback (most recent call last):
AttributeError: 'User' object has no attribute 'astuple'
>>>
>>> mark.asdict()
Traceback (most recent call last):
AttributeError: 'User' object has no attribute 'asdict'
>>> rick = Staff('Rick', 'Martinez')
>>>
>>> rick.astuple()
('Rick', 'Martinez')
>>>
>>> rick.asdict()
Traceback (most recent call last):
AttributeError: 'Staff' object has no attribute 'asdict'
>>> melissa = Admin('Melissa', 'Lewis')
>>>
>>> melissa.astuple()
('Melissa', 'Lewis')
>>>
>>> melissa.asdict()
{'firstname': 'Melissa', 'lastname': 'Lewis'}

4.3.4. Assignments

Code 4.53. Solution
"""
* Assignment: OOP InheritancePatterns SingleInheritance
* Type: class assignment
* Complexity: easy
* Lines of code: 2 lines
* Time: 2 min

English:
    1. Create class `Account`
    2. Create class `User` which inherits from `Account`
    3. Create class `Admin` which inherits from `Account`
    4. Run doctests - all must succeed

Polish:
    1. Stwórz klasę `Account`
    2. Stwórz klasę `User`, która dziedziczy po `Account`
    3. Stwórz klasę `Admin`, która dziedziczy po `Account`
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass

    >>> assert isclass(Account)
    >>> assert isclass(User)
    >>> assert isclass(Admin)
    >>> assert issubclass(User, Account)
    >>> assert issubclass(Admin, Account)
    >>> assert not issubclass(User, Admin)
    >>> assert not issubclass(Admin, User)
"""

# Create class `Account`
# type: type[Account]
class Account:
    pass

# Create class `User` which inherits from `Account`
# type: type[Account]
...

# Create class `Admin` which inherits from `Account`
# type: type[Account]
...


Code 4.54. Solution
"""
* Assignment: Inheritance Problems Decompose
* Complexity: easy
* Lines of code: 30 lines
* Time: 8 min

English:
    1. Refactor class `Hero` to use multiple inheritance
    2. Name mixin classes: `HasHealth` and `HasPosition`
    3. Note, that order of inheritance is important
        a. Try to inherit from `HasPosition`, `HasHealth`
        b. Then `HasHealth`, `HasPosition`
        c. What changed and why?
    4. Run doctests - all must succeed

Polish:
    1. Zrefaktoruj klasę `Hero` aby użyć wielodziedziczenia
    2. Nazwij klasy domieszkowe: `HasHealth` i `HasPosition`
    3. Zwróć uwagę, że kolejność dziedziczenia ma znaczenie
        a. Spróbuj dziedziczyć po `HasPosition`, `HasHealth`
        b. A później `HasHealth`, `HasPosition`
        c. Co się zmieniło i dlaczego?
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from random import seed; seed(0)
    >>> from inspect import isclass

    >>> assert isclass(Hero)
    >>> assert isclass(HasHealth)
    >>> assert isclass(HasPosition)
    >>> assert issubclass(Hero, HasHealth)
    >>> assert issubclass(Hero, HasPosition)
    >>> assert hasattr(HasHealth, 'health')
    >>> assert hasattr(HasHealth, 'is_alive')
    >>> assert hasattr(HasHealth, 'is_dead')
    >>> assert hasattr(HasPosition, 'position_x')
    >>> assert hasattr(HasPosition, 'position_y')
    >>> assert hasattr(HasPosition, 'position_set')
    >>> assert hasattr(HasPosition, 'position_change')
    >>> assert hasattr(HasPosition, 'position_get')
    >>> assert hasattr(Hero, 'HEALTH_MIN')
    >>> assert hasattr(Hero, 'HEALTH_MAX')
    >>> assert hasattr(Hero, 'health')
    >>> assert hasattr(Hero, 'position_x')
    >>> assert hasattr(Hero, 'position_y')
    >>> assert hasattr(Hero, 'is_alive')
    >>> assert hasattr(Hero, 'is_dead')
    >>> assert hasattr(Hero, 'position_set')
    >>> assert hasattr(Hero, 'position_change')
    >>> assert hasattr(Hero, 'position_get')
    >>> watney = Hero()
    >>> watney.is_alive()
    True
    >>> watney.position_set(x=1, y=2)
    >>> watney.position_change(left=1, up=2)
    >>> watney.position_get()
    (0, 0)
    >>> watney.position_change(right=1, down=2)
    >>> watney.position_get()
    (1, 2)
"""

from dataclasses import dataclass
from random import randint
from typing import ClassVar


@dataclass
class Hero:
    HEALTH_MIN: ClassVar[int] = 10
    HEALTH_MAX: ClassVar[int] = 20
    health: int = 0
    position_x: int = 0
    position_y: int = 0

    def position_set(self, x: int, y: int) -> None:
        self.position_x = x
        self.position_y = y

    def position_change(self, right=0, left=0, down=0, up=0):
        x = self.position_x + right - left
        y = self.position_y + down - up
        self.position_set(x, y)

    def position_get(self) -> tuple:
        return self.position_x, self.position_y

    def __post_init__(self) -> None:
        self.health = randint(self.HEALTH_MIN, self.HEALTH_MAX)

    def is_alive(self) -> bool:
        return self.health > 0

    def is_dead(self) -> bool:
        return self.health <= 0