Наследование классов: неправильный вывод

Столкнулся с проблемой неправильного вывода результата наследуемого метода.

Задача:

Создайте базовый класс Specialist, сохраняющий атрибут экземпляра name имеющий методы drink_coffee, возвращающий строку "{имя} пьет кофе", и work, возвращающий строку вида "{имя} выполняет работу". Создайте дочерний класс Coder, наследующий методы и атрибуты класса Specialist, и переопределяющий метод work таким образом, чтобы он возвращал строку вида "{имя} пишет код". Создайте дочерний класс ProjectManager, наследующий методы и атрибуты класса Specialist, и переопределяющий метод work таким образом, чтобы он возвращал строку вида "{имя} управляет проектом". Создайте класс TeamLeader, наследующий атрибуты и методы классов Coder и ProectManager таким образом, чтобы при вызове метода work возвращалась строка "{имя} управляет проектом". Проверка этого критерия будет производиться при помощи инструмента MRO

Мой код:

class Specialist:
    def __init__(self, name):
        self.name = name

    # Метод drink_coffee
    def drink_coffee(self):
        return f"{self.name} пьет кофе"

    # Базовый метод work
    def work(self):
        return f"{self.name} выполняет работу"


# Дочерний класс Coder, наследующий от Specialist
class Coder(Specialist):
    # Переопределяем метод work
    def work(self):
        return f"{self.name} пишет код"


# Дочерний класс ProjectManager, наследующий от Specialist
class ProjectManager(Specialist):
    # Переопределяем метод work
    def work(self):
        return f"{self.name} управляет проектом"


# Класс TeamLeader, наследующий от Coder и ProjectManager
class TeamLeader(Coder, ProjectManager):
    # Переопределять метод work не нужно, он будет взят по MRO из ProjectManager

    pass

Тест который должен проходить:

t = TeamLeader('Василий') 
print(t.work())

Правильный вывод:
Василий управляет проектом

Мой неправильный вывод:
Василий пишет код


Ответы (2 шт):

Автор решения: Amgarak

Для решения вашей проблемы, нужно сперва разобраться с порядком разрешения методов (method resolution order). MRO - позволяет Python выяснить, из какого класса-предка нужно вызывать метод, если он не обнаружен непосредственно в классе-потомке.

Если распечатаем MRO -> print(TeamLeader.__mro__):

Василий пишет код
(<class '__main__.TeamLeader'>, <class '__main__.Coder'>, <class '__main__.ProjectManager'>, <class '__main__.Specialist'>, <class 'object'>)

TeamLeader -> Coder -> ProjectManager -> Specialist -> object

Что выходит? Сначала ищем метод в классе TeamLeader, затем в Coder, затем в ProjectManager и только потом в Specialist. В данном случае метод work найден в классе Coder, отсюда и результат "Василий пишет код".

Следовательно, что бы получить "Василий управляет проектом", необходимо изменить порядок наследования:

class TeamLeader(ProjectManager, Coder):
    pass

Теперь MRO будет:

TeamLeader -> ProjectManager -> Coder -> Specialist -> object

Таким образом, метод work будет найден в классе ProjectManager, и результат - "Василий управляет проектом".

Василий управляет проектом
(<class '__main__.TeamLeader'>, <class '__main__.ProjectManager'>, <class '__main__.Coder'>, <class '__main__.Specialist'>, <class 'object'>)
→ Ссылка
Автор решения: Vladimir Bogdanov

В качестве академического интереса. Раз TeamLeader наследует и от Coder, и от ProjectManager, то делает работу того и другого.

class Specialist:
    def __init__(self, name):
        self.name = name

    def drink_coffee(self):
        return f"{self.name} пьет кофе"

    def work(self):
        return f"{self.name} выполняет работу"


class Coder(Specialist):
    def work(self):
        return f"{self.name} пишет код"


class ProjectManager(Specialist):
    def work(self):
        return f"{self.name} управляет проектом"


class TeamLeader(Coder, ProjectManager):
    def __getattribute__(self, attr):
        match attr:
            case 'work':
                _str = "\n".join(
                    getattr(_cls, attr, lambda _: "")(self)
                    for _cls in self.__class__.__bases__
                )
                return lambda: _str
            case _:
                return super().__getattribute__(attr)


t = TeamLeader("Василий")
print(t.work())
→ Ссылка