Рубрики
Без рубрики

Многократное наследование и классы микс в Python

Эта статья была первоначально опубликована на цифровой коте. Я недавно пересмотрел три старых поста на диджеме … Теги с Python, OOP, Django.

Эта статья Первоначально был опубликован на Цифровой кот Отказ

Недавно я пересмотрел три старых поста на видах на основе Django, которые я написал для своего блога, обновляя их в Django 3.0 и еще раз заметил, что база кода использует Смесиновые классы увеличить повторное использование кода. Я также понял, что смешины не очень популярны в Python, поэтому я решил исследовать их, расчесывая свои знания теории ООП в то же время.

Чтобы полностью оценить содержание поста, убедитесь, что вы берете на себя два столба подхода OOP: делегация , в частности, как это реализовано через наследство, а полиморфизм Отказ Этот пост о делегации и Этот пост о полиморфизме Содержит все, что вам нужно понять, как Python реализует эти концепции.

Общие понятия

Для обсуждения Mixins нам нужно начать с одного из самых противоречивых субъектов во всем мире OOP: многократное наследование. Это естественное расширение концепции простого наследства, где класс автоматически передает метод и разрешение атрибута в другой класс (родительский класс).

Позвольте мне указать это снова, как это важно для остальной части обсуждения: Наследование – это просто автоматическая делегированная механизма Отказ

Делегация была введена в ООП как способ уменьшить дублирование кода. Когда объект нуждается в определенной функции, он просто делегает его другому классу (явно или неявно), поэтому код написан только один раз.

Давайте рассмотрим пример веб-сайта управления кодом, четко полностью вымышленным и не вдохновлен каким-либо существующим продуктом. Давайте предположим, что мы создали следующую иерархию

      assignable reviewable item
 (assign_to_user, ask_review_to_user)
                 ^
                 |
                 |
                 |
            pull request

что позволяет нам поставить потянуть запрос Только конкретный код требуется этим элементом. Это отличное достижение, так как именно какие библиотеки делают для кода, но на живых объектах. Вызывы и делегация методов являются не что иное, как сообщения между объектами, поэтому иерархия делегации – это просто простая сетевая система.

К сожалению, использование наследства над композицией часто приводит к системам, которые, парадоксально, увеличивают дублирование кода. Основная проблема заключается в том, что наследование может напрямую делегировать только одному другому классу (родительский класс), в отличие от состава, где объект может делегировать любому количеству других. Это ограничение наследования означает, что у нас может быть класс, который наследует от другого, потому что он нуждается в некоторых его особенностях, но это получает функции, которые он не хочет или не должен иметь.

Давайте продолжим примеру портала управления кодами и рассмотрим Выпуск Предмет, который мы хотим хранить в системе, но не может быть рассмотрено пользователем. Если мы создадим иерархию, как это

      assignable reviewable item
   (assign_to_user, ask_review_to_user)
                   ^
                   |
                   |
                   |
                   |
          +--------+--------+
          |                 |
          |                 |
          |                 |
        issue          pull request
   (not reviewable)

В конечном итоге мы поставим функции, связанные с процессом проверки в объекте, который не должен иметь их. Стандартное решение этой проблемы состоит в том, чтобы увеличить глубину иерархии наследования и получения из нового прощественного предка.

          assignable item
         (assign_to_user)
                 ^
                 |
                 |
                 |
                 |
          +------+--------------+
          |                     |
          |                     |
          |                     |
          |         reviewable assignable item
          |            (ask_review_to_user)
          |                     ^
          |                     |
          |                     |
          |                     |
        issue              pull request

Однако этот подход останавливается жизнеспособным, как только объект должен наследовать от данного класса, но не от родителя этого класса. Например, элемент, который должен быть рассмотренным, но не назначен, как Лучшая практика что мы хотим добавить на сайт. Если мы хотим продолжать использовать наследство, единственное решение на данном этапе – дублировать код, который реализует рассмотренный характер элемента (или кода, который реализует назначенную функцию) и создавать две разные классовые иерархии.

          assignable item              +-------->  reviewable item
         (assign_to_user)              |         (ask_review_to_user)
                 ^                     |                  ^
                 |                     |                  |
                 |                     |                  |
                 |             CODE DUPLICATION           |
                 |                     |                  |
          +------+--------------+      |                  |
          |                     |      |                  |
          |                     |      |                  |
          |                     |      V                  |
          |         reviewable assignable item            |
          |            (ask_review_to_user)               |
          |                     ^                         |
          |                     |                         |
          |                     |                         |
          |                     |                         |
        issue              pull request             best practice

Обратите внимание, что это даже не учитывает, что новый Ответственный товар может понадобиться атрибуты из Присвоенный элемент , что подсказывает еще один уровень глубины в иерархии, где мы изолируем эти функции в более общем классе. Итак, к сожалению, шансы состоят в том, что это только первый из многих компромиссов, которые нам придется принять, чтобы сохранить систему в устойчивом состоянии, если мы не можем изменить наш подход.

Несколько наследство было введено в OOP, так как было понятно, что объект может захотеть делегировать определенные действия в данный класс, а другие действия к другому, имитируя то, что формы жизни делают, когда они наследуют черты из нескольких предков (родители, бабушки и дедушки , и т.д.).

Вышеуказанная ситуация может быть решена иметь потянуть запрос наследует как из класса, который предоставляет функцию назначения и от того, что реализует рассмотренный характер.

          assignable item                          reviewable item
         (assign_to_user)                        (ask_review_to_user)
                 ^                                      ^  ^
                 |                                      |  |
                 |                                      |  |
                 |                                      |  |
                 |                                      |  |
          +------+-------------+ +----------------------+  |
          |                    | |                         |
          |                    | |                         |
          |                    | |                         |
          |                    | |                         |
          |                    | |                         |
          |                    | |                         |
          |                    | |                         |
          |                    | |                         |
          |                    | |                         |
        issue              pull request              best practice

Вообще говоря, тогда введен множественное наследование, чтобы дать программисту способ сохранить наследование без введения дублирования кода, сохраняя обыкновенную иерархию классов проще. В конце концов, все, что мы делаем в разработке программного обеспечения, – это попытаться отделить проблемы, то есть для выделения функций, и множественное наследование может помочь сделать это.

Это просто примеры и могут быть действительными или нет, в зависимости от конкретного случая, но они четко показывают проблемы, которые мы можем иметь даже с очень простой иерархией 4 классов. Многие из этих проблем ясно возникают из того факта, что мы хотели реализовать делегацию только путем наследства, и я смею сказать, что 80% архитектурных ошибок в проектах ООП поступают с использованием наследства вместо состава и с использованием объектов Бога, то есть классы которые имеют обязанности слишком много разных частей системы. Всегда помните, что OOP родился с идеей небольших объектов, взаимодействующих через сообщения, поэтому соображения, которые мы производим для монолитных архитектур, действительны даже здесь.

Тем не менее, как наследование и состав осуществляют два разных типа делегирования ( быть и иметь ), они как ценные, и множественное наследование – это способ удалить ограничение одного провайдера, который приходит только один родительский класс.

Почему это противоречиво?

Учитывая то, что я только что сказал, множественное наследство кажется благословением. Когда объект может наследовать из нескольких родителей, мы можем легко распространить обязанности между различными классами и использовать только те, которые нам нужны, продвигая повторное использование кода и избегать объектов Бога.

К сожалению, все не так просто. Прежде всего, мы сталкиваемся с вопросом, что каждая архитектура ориентированная на микроэнергии, то есть риск перехода от объектов Бога (крайняя монолитная архитектура) до почти пустых объектов (экстремальный распределенный подход), обременяет программист слишком хорошо Зернированный контроль, который в конечном итоге приводит к системе, когда отношения между объектами настолько сложны, что становится невозможным понять эффект изменения в коде.

Хотя существует более непосредственная проблема в нескольких наследовании. Как это происходит с естественным наследством, родители могут обеспечить ту же «генетическую черту» в двух разных ароматах, но в результате которого у них будет только один. Оставляя в сторону генетику (что невероятно сложнее, чем программирование) и возвращаясь к OOP, мы сталкиваемся с проблемой, когда объект наследуется от двух других объектов, которые обеспечивают тот же атрибут.

Итак, если ваш класс Ребенок наследовать от родителей Родитель1. и Родитель2 и оба обеспечивают __init__ Способ, который должен использовать ваше объект?

class Parent1():
    def __init__(self):
        [...]


class Parent2():
    def __init__(self):
        [...]


class Child(Parent1, Parent2):
    # This inherits from both Parent1 and Parent2, which __init__ does it use?
    pass

Вещи могут даже хуже, когда родители могут иметь разные подписи общего метода, например,

class Parent1:
    # This inherits from Ancestor but redefines __init__
    def __init__(self, status):
        [...]


class Parent2:
    # This inherits from Ancestor but redefines __init__
    def __init__(self, name):
        [...]


class Child(Parent1, Parent2):
    # This inherits from both Parent1 and Parent2, which __init__ does it use?
    pass

Проблема может быть расширена еще дальше, представляя обыкновенный предком выше Родитель1. и Родитель2 .

class Ancestor:
    # The common ancestor, defines its own __init__ method
    def __init__(self):
        [...]


class Parent1(Ancestor):
    # This inherits from Ancestor but redefines __init__
    def __init__(self, status):
        [...]


class Parent2(Ancestor):
    # This inherits from Ancestor but redefines __init__
    def __init__(self, name):
        [...]


class Child(Parent1, Parent2):
    # This inherits from both Parent1 and Parent2, which __init__ does it use?
    pass

Как видите, у нас уже есть проблема, когда мы представляем несколько родителей, а обыкновенный предком просто добавляет новый уровень сложности. Класс предка может быть четко в любой точке дерева наследования (дедушка, Grand-бабушка и т. Д.), Важная часть состоит в том, что она передана между Родитель1. и Родитель2 . Это так называемая алмазная проблема, поскольку график наследования имеет форму алмаза

      Ancestor
       ^   ^
      /     \
     /       \
Parent1     Parent2
    ^         ^
     \       /
      \     /
       Child

Итак, в то время как с одним родителем наследование правила просты, с множественным наследованием мы немедленно у нас есть более сложная ситуация, которая не имеет тривиального решения. Все ли это предотвратить внедрение нескольких наследований?

Нисколько! Существуют решения этой проблемы, так как мы увидим в ближайшее время, но этот дальнейший уровень сложности делает многократное наследование то, что не подходит легко в дизайне и должна быть тщательно реализована, чтобы избежать тонких ошибок. Помните, что наследование является механизмом автоматической делегирования, поскольку это делает то, что происходит в коде менее очевидно. По этим причинам множественное наследование часто рассматривается как страшно и запутано, и обычно учитывается некоторое пространство только в продвинутых курсах OOP, по крайней мере, в мире Python. Я верю, что каждый программист Python вместо этого должен ознакомиться с ним и научиться воспользоваться этим.

Многократное наследование: Python Way

Давайте посмотрим, как можно решить проблему алмазов. В отличие от генетики, мы программисты не могут позволить себе какой-либо уровень неопределенности или случайности в наших процессах, поэтому в присутствии возможной неоднозначности, как тот, который создается множественным наследованием, нам необходимо записать правило, которое будет строго выполнено в каждом случае Отказ В Python это правило идет по названию MRO (порядок разрешения метода), который был введен в Python 2.3 и описан в Этот документ Мишель Симионато.

Существует много, чтобы сказать о MRO и базовом алгоритме линериризации C3, но для объема этого поста достаточно, чтобы она решает проблему с алмазом. В случае многократного наследования Python следует обычным правилам наследования (автоматическая делегация для предка, если атрибут отсутствует локально), но Заказать После прохождения дерева наследования теперь включает в себя все классы, указанные в подписи класса. В приведенном выше примере Python будет искать атрибуты в следующем порядке: Ребенок , Родитель1. , Родитель2 , Предкус Отказ

Итак, как в случае стандартного наследования, это означает, что первый класс в списке, который реализует определенный атрибут, будет выбранным поставщиком для этого разрешения. Пример может прояснить дело

class Ancestor:
    def rewind(self):
        [...]


class Parent1(Ancestor):
    def open(self):
        [...]


class Parent2(Ancestor):
    def open(self):
        [...]

    def close(self):
        [...]

    def flush(self):
        [...]


class Child(Parent1, Parent2):
    def flush(self):
        [...]

В этом случае экземпляр C Ребенок будет обеспечивать перемотка , открыть , Закрыть и Flush Отказ Когда C.rewind называется код в Предкус выполняется, так как это первый класс в списке MRO, который обеспечивает этот метод. Метод открыть предоставляется Родитель1. , пока Закрыть предоставляется Родитель2 . Если метод C.Flush называется, код предоставляется Ребенок Сам класс, что переопределяет его переопределяют, что он предоставлен Родитель2 .

Как мы видим с Flush Метод, Python не изменит свое поведение, когда речь идет о способе, перекрывающемся с несколькими родителями. Первая реализация способа с этим именем выполняется, и реализация родителей не вызывается автоматически. Как и в случае стандартного наследования, то это зависит от нас для разработки классов с подписчиком метода подписи.

Под капотом

Как работает многонациональное наследование внутренне? Как Python создает список MRO?

Python имеет очень простой подход к OOP (даже если он в конечном итоге заканчивается духом ума нароборос, см. здесь Отказ Классы сами объекты, поэтому они содержат структуры данных, которые используются языком для обеспечения признаков, и делегация не имеет исключения. Когда мы запустим метод на объекте, Python Myrecy использует __getattribute__ Метод (предоставлен объект ), который использует __Class__ достичь класса из экземпляра, и __Bases__ найти родительские классы. Последний, в частности, является кортеж, поэтому он заказывается, и он содержит все классы, от которых наследуется текущий класс.

MRO создан только с использованием только __Bases__ Но в основном алгоритм не тот тривиальный и должен с монотонностью полученной линеаризации класса. Это менее страшно, чем звучит, но не то, что вы хотите прочитать во время загара, наверное. Если это так, вышеупомянутый Документ Michele Simionato содержит все детали горы по классному линеариру, что вы всегда хотели исследовать, лежа на пляже.

К приближению к смеси, нам нужно подробно обсудить наследство и, в частности, роль метода подписей.

В Python, когда вы переопределяете метод, предоставленный классом предка, вы должны решить, если и когда вызывать его первоначальную реализацию. Это дает программисту свободы решать, нужно ли им просто увеличить метод или полностью заменить его. Помните, что единственное, что Python делает, когда класс наследует от другого, заключается в автоматическом делегировании методов, которые не реализованы.

Когда класс наследуется от другого, мы идеально создаем объекты, которые поддерживают обратную совместимость с интерфейсом родительского класса, чтобы позволить их полиморфному использованию. Это означает, что когда мы наследуем из класса и переопределите способ, изменяющий его подпись, мы делаем что-то опасное и, по крайней мере, с точки зрения полиморфизма, неправильно. Посмотрите на этот пример

class GraphicalEntity:
    def __init__(self, pos_x, pos_y, size_x, size_y):
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.size_x = size_x
        self.size_y = size_y

    def move(self, pos_x, pos_y):
        self.pos_x = pos_x
        self.pos_y = pos_y

    def resize(self, size_x, size_y):
        self.size_x = size_x
        self.size_y = size_y


class Rectangle(GraphicalEntity):
    pass


class Square(GraphicalEntity):
    def __init__(self, pos_x, pos_y, size):
        super().__init__(pos_x, pos_y, size, size)

    def resize(self, size):
        super().resize(size, size)

Обратите внимание, что Квадрат меняет подпись обоих __init__ и Изменить размер Отказ Теперь, когда мы создали эти классы, нам нужно помнить о разной подписи __init__ в Квадратный

r1 = Rectangle(100, 200, 15, 30)
r2 = Rectangle(150, 280, 23, 55)
q1 = Square(300, 400, 50)

Обычно мы принимаем, что расширенная версия класса принимает больше параметров, когда она инициализируется, поскольку мы не ожидаем, что он будет полиморфной на __init__ Отказ Проблемы возникают, когда мы пытаемся использовать полиморфизм на других методах, например, изменение размера всех Графическая принадлежность Объекты в списке

for shape in [r1, r2, q1]:
    size_x = shape.size_x
    size_y = shape.size_y
    shape.resize(size_x*2, size_y*2)

С R1 , R2 и Q1 все объекты, которые наследуют от Графическая принадлежность Мы ожидаем, что они предоставят интерфейс, предоставленный этим классом, но это не удается, потому что Квадрат изменил подпись Изменить размер Отказ То же самое произошло бы, если мы создали их в цикле от списка классов, но, как я уже сказал, это общепринято, что детские классы меняют подпись __init__ метод. Это не соответствует действительности, например, в системе на основе плагинов, где все плагины должны быть инициализированы таким же образом.

Это классическая проблема в Оопе. Пока мы, как люди, как люди, воспринимаем квадрат так же, как слегка особый прямоугольник, с точки зрения интерфейса два класса разные, и, таким образом, не должны находиться в том же дереве на наследстве, когда мы имеем дело с размерами. Это важное соображение: Прямоугольник и Квадрат полиморфны на двигаться Метод, но не на __init__ и Изменить размер Отказ Итак, вопрос в том, что если бы мы могли каким-то образом отделить две природы подвижного и изменений.

Теперь обсуждаем интерфейсы, полиморфизм и причины, по которым за ними потребуется совершенно отдельный пост, поэтому в следующих разделах я собираюсь игнорировать этот вопрос и просто рассмотреть возможность необязательного интерфейса объекта. Таким образом, вы найдете примеры объектов, которые нарушают интерфейс родителя и объекты, которые сохраняют его. Просто помните: всякий раз, когда вы измените подпись метода, вы изменяете (неявный) интерфейс объекта, и, таким образом, вы останавливаете полиморфизм. Я буду обсуждать в другой раз, если я считаю это правильно или неправильно.

MRO – это хорошее решение, которое предотвращает двусмысленность, но она оставляет программистов ответственностью создание разумных наследственных деревьев. Алгоритм помогает разрешить сложные ситуации, но это не значит, что мы должны создавать их в первую очередь. Итак, как мы можем использовать многократное наследование без создания систем, которые слишком сложно понять? Более того, можно ли использовать множественное наследование, чтобы решить проблему управления двойной (или многократной) природой объекта, как в предыдущем примере подвижной и измеримой формы?

Раствор поступает из классов микс: это небольшие классы, которые предоставляют атрибуты, но не включены в стандартное дерево наследования, работают больше как «дополнения» в текущий класс, чем как правильные предки. Смешивания происходят в языке программирования Lisp, и, в частности, в том, что можно считать первой версией общей объектной системы Lisp, расширение вкусов. Современные языки OOP реализуют микс во многих различных способах: Scala, например, имеет функцию под названием Черты , которые живут в своем собственном пространстве с конкретной иерархией, которая не мешает правильному наследству класса.

Смесиновые классы в Python

Python не обеспечивает поддержку Mixins с любой выделенной языковой функцией, поэтому мы используем многократное наследование для их реализации. Это явно требует отличной дисциплины от программиста, поскольку он нарушает одно из главных предположений для смешин: их ортогональность к дереву наследства. В Python так называемые микины – это классы, которые живут в обычном дереве на наследстве, но они могут быть маленькими, чтобы избежать создания иерархий, которые слишком сложны для программиста для понимания. В частности, смешины не должны иметь общих предков, кроме объект с другими родительскими классами.

Давайте посмотрим на простой пример

class GraphicalEntity:
    def __init__(self, pos_x, pos_y, size_x, size_y):
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.size_x = size_x
        self.size_y = size_y


class ResizableMixin:
    def resize(self, size_x, size_y):
        self.size_x = size_x
        self.size_y = size_y


class ResizableGraphicalEntity(GraphicalEntity, ResizableMixin):
    pass

Здесь класс Resizablemixin не наследует от Графическая принадлежность , но прямо из объект , так Resizablegraphiancialentiality получает от этого только Изменить размер метод. Как мы сказали раньше, это упрощает дерево наследства Resizablegraphianentital и помогает снизить риск проблемы алмазов. Это оставляет нас свободно использовать Графическая принадлежность Как родитель для других классов без наследования методов, которые мы не хотим. Пожалуйста, помните, что это произойдет, потому что классы предназначены для того, чтобы избежать его, а не из-за языковых функций: MRO Algorithm просто гарантирует, что всегда будет однозначный выбор в случае нескольких предков.

Мистины обычно не могут быть слишком универсальными. В конце концов, они предназначены для добавления функций в классы, но эти новые функции часто взаимодействуют с другими ранее существующими функциями дополненного класса. В этом случае Изменить размер Метод взаимодействует с атрибутами size_x. и Size_y которые должны присутствовать в объекте. Очевидно, что существуют примеры чистый Мистины, но так как им не потребуется инициализации, их объем определенно ограничен.

Использование миксов на наследство угона

Благодаря MRO, Python программисты могут использовать множественное наследование для переопределения методов, которые объекты наследуют от своих родителей, позволяя им настроить классы без дублирования кода. Давайте посмотрим на этот пример

class GraphicalEntity:
    def __init__(self, pos_x, pos_y, size_x, size_y):
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.size_x = size_x
        self.size_y = size_y

class Button(GraphicalEntity):
    def __init__(self, pos_x, pos_y, size_x, size_y):
        super().__init__(pos_x, pos_y, size_x, size_y)
        self.status = False

    def toggle(self):
        self.status = not self.status

b = Button(10, 20, 200, 100)

Как вы можете увидеть Кнопка класс расширяет Графическая принадлежность один классический способ, используя супер позвонить родителю __init__ Метод перед добавлением нового Статус атрибут. Теперь, если я хотел создать SquareButton Класс у меня есть два варианта.

Я мог бы просто переопределить __init__ в новом классе

class GraphicalEntity:
    def __init__(self, pos_x, pos_y, size_x, size_y):
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.size_x = size_x
        self.size_y = size_y


class Button(GraphicalEntity):
    def __init__(self, pos_x, pos_y, size_x, size_y):
        super().__init__(pos_x, pos_y, size_x, size_y)
        self.status = False

    def toggle(self):
        self.status = not self.status


class SquareButton(Button):
    def __init__(self, pos_x, pos_y, size):
        super().__init__(pos_x, pos_y, size, size)

b = SquareButton(10, 20, 200)

который выполняет запрошенную работу, но решительно соединяет особенность одного измерения с Кнопка природа. Если мы хотели создать круговое изображение, мы не могли наследовать от SquareButton , поскольку изображение имеет разную природу.

Второй вариант – это то, что выделение функций, связанных с одним измерением в классе мистин, и добавьте его в качестве родителя для нового класса

class GraphicalEntity:
    def __init__(self, pos_x, pos_y, size_x, size_y):
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.size_x = size_x
        self.size_y = size_y


class Button(GraphicalEntity):
    def __init__(self, pos_x, pos_y, size_x, size_y):
        super().__init__(pos_x, pos_y, size_x, size_y)
        self.status = False

    def toggle(self):
        self.status = not self.status


class SingleDimensionMixin:
    def __init__(self, pos_x, pos_y, size):
        super().__init__(pos_x, pos_y, size, size)


class SquareButton(SingleDimensionMixin, Button):
    pass

b = SquareButton(10, 20, 200)

Второе решение дает тот же конечный результат, но способствует повторному использованию кода, Как сейчас СингломерМиксин Класс может быть применен к другим классам, полученным из Графическая принадлежность И заставить их принимать только один размер, в то время как в первом растворе эта функция была плотно связана с Кнопка класс предка.

Обратите внимание, что положение смесина важно. Как супер Следует за MRO, называемый метод отправляется в ближайший класс в линеаризации. Если вы поставите СингломерМиксин после Кнопка В определении SquareButton Python будет жаловаться. В этом случае звонок B (10, 20, 200) и метод подписи __init __ (Self, pos_x, pos_y, size_x, size_y) не будет соответствовать.

Смесины не используются только тогда, когда вы хотите изменить интерфейс объекта, хотя. Используя супер Мы можем достичь интересных дизайнов, таких как

class GraphicalEntity:
    def __init__(self, pos_x, pos_y, size_x, size_y):
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.size_x = size_x
        self.size_y = size_y


class Button(GraphicalEntity):
    def __init__(self, pos_x, pos_y, size_x, size_y):
        super().__init__(pos_x, pos_y, size_x, size_y)
        self.status = False

    def toggle(self):
        self.status = not self.status


class LimitSizeMixin:
    def __init__(self, size_x, size_y, size):
        size_x = min(size_x, 500)
        size_y = min(size_y, 400)
        super().__init__(pos_x, pos_y, size_x, size_y)


class LimitSizeButton(Button, LimitSizeMixin):
    pass

b = LimitSizeButton(10, 20, 200, 100)

Здесь Лимицайтебуттон звонки __init__ его первого родителя, который является Кнопка Отказ Это, однако, делегирует призыв к следующему классу в MRO до инициализации Self.Status Так что звонок отправляется на Премицизмиксин , что впервые управляется некоторыми изменениями и в конечном итоге отправляет его к исходному получателю, Графическая принадлежность Отказ

Помните, что в Python вы никогда не вынуждены вызывать реализацию родителей способа, поэтому микс здесь может также остановить диспетчерирующий механизм, если это требование бизнес-логики нового объекта.

Наконец, давайте добраться до первоначального источника вдохновения для этого поста: кодовая база Django. Я покажу вам здесь, как программисты Django использовали несколько классов наследования и микс, чтобы продвигать повторное использование кода, и теперь вы будете надеяться понять все причины позади них.

Пример, который я выбрал, можно найти в Код рожевых просмотров и, в частности, в двух классах: Templateresponsemixin и TemplateView Отказ

Как вы можете знать, Django Вид Класс – это предком всех видов на классовых взглядах и обеспечивает отправка Способ, который преобразует методы HTTP-запроса в вызовы функций Python ( код ). Теперь TemplateView Является мнением, которое отвечает на получение запроса на рендеринг шаблона с данными, поступающими из контекста, пройденного при вызове представления. Учитывая механизм позади видов Django, то TemplateView должен реализовать Получить Способ и вернуть содержимое отклика HTTP. Кодекс класса

class TemplateView(TemplateResponseMixin, ContextMixin, View):
    """
    Render a template. Pass keyword arguments from the URLconf to the context.
    """
    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)

Как вы можете увидеть TemplateView. это Вид , но он использует два смеси в INJECTS. Давайте посмотрим на Templateresponsemixin.

class TemplateResponseMixin:
    [...]

    def render_to_response(self, context, **response_kwargs):
        [...]

    def get_template_names(self):
        [...]

[Я удалил код класса, как не имеет решающее значение для настоящего обсуждения, вы можете увидеть полный класс здесь Несомненно

Ясно, что Templateresponsemixin просто добавляет к любому классу два метода get_template_names. и Render_to_Response. . Последнее называется в Получить метод TemplateView создать ответ. Давайте посмотрим на упрощенную схему вызовов:

GET request --> TemplateView.dispatch --> View.dispatch --> TemplateView.get --> TemplateResponseMixin.render_to_response

Это может выглядеть сложным, но попытаться следовать коду пару раз, и вся картина начнет иметь смысл. Важным, что я хочу подчеркнуть, является то, что код в Templateresponsemixin Доступен для любого класса, который хочет иметь функцию рендеринга шаблона, например DetailView ( Код ), который получает особенность отображения деталей одного объекта OmityObjectTemplaterPonsemixin , который наследует от Templateresponsemixin , переопределив свой метод get_template_names. ( Код ).

Как мы уже говорили, микс не может быть слишком универсальным, и здесь мы видим хороший пример смесина, предназначенного для работы на определенных классах. Templateresponsemixin должен быть применен к классам, которые содержат Self.Request ( Код ), и хотя это не означает исключительно классы, полученные из Вид Понятно, что он был разработан для увеличения этого конкретного типа.

  • Наследование предназначено для продвижения повторного использования кода, но может привести к противоположному результату
  • Многократное наследование позволяет нам сохранить дерево наследования
  • Многократное наследование приводит к возможным проблемам, которые решаются в Python через MRO
  • Интерфейсы (либо неявное или явное) должно быть частью вашего дизайна
  • Классы микс используются для добавления простых изменений в классы
  • Мистины реализованы в Python с использованием нескольких наследований: они имеют отличную выразительную силу, но требуют тщательного дизайна.

Я надеюсь, что этот пост помог вам понять немного больше, как работает множественное наследование, и быть менее напуганным. Я также надеюсь, что мне удалось показать вам, что классы должны быть тщательно разработаны, и что нужно учитывать, когда вы создаете классовую систему. Еще раз, пожалуйста, не забывайте композицию, это мощный и слишком часто забытый инструмент.

Эта статья Первоначально был опубликован на Цифровой кот Отказ

Оригинал: “https://dev.to/lgiordani/multiple-inheritance-and-mixin-classes-in-python-3dcf”