Source: Python super guide

← Rendered

markdown source:

---
description: Использование super() в python
tags: python-standart-library python
title: Python super guide
---
Базовый вариант использования

```python
class LoggingDict(dict):
    def __setitem__(self, key, value):
        logging.info('Setting %r to %r' % (key, value))
        super().__setitem__(key, value)
```

Этот класс имеет все те же возможности, что и его родитель, dict , но он расширяет метод `__setitem__`, чтобы создавать записи в журнале при каждом обновлении ключа. После внесения записи в журнал метод использует `super()` для делегирования работы по фактическому обновлению словаря с помощью пары ключ/значение.

Класс, которому делегируется работа указаывать по имени не нужно

```python
class LoggingDict(SomeOtherMapping):            # new base class
    def __setitem__(self, key, value):
        logging.info('Setting %r to %r' % (key, value))
        super().__setitem__(key, value)         # no change needed
```

Какой именно класс будет вызван зависит от того, как образовано дерево родительских классов. В примере ниже оно выглядит так: LoggingOD, LoggingDict, OrderedDict, dict, object - работа будет делегирована нужному нам методу в `OrderedDict`

```python
class LoggingOD(LoggingDict, collections.OrderedDict):
    pass
```

Мы не изменяли исходный код LoggingDict. Вместо этого мы создали подкласс, единственная логика которого состоит в том, чтобы объединить два существующих класса и управлять порядком поиска в них. Порядок поиска реализован через модель MRO

```python
>>> pprint(LoggingOD.__mro__)
(<class '__main__.LoggingOD'>,
 <class '__main__.LoggingDict'>,
 <class 'collections.OrderedDict'>,
 <class 'dict'>,
 <class 'object'>)
 ```

`super()` занимается делегированием вызовов методов некоторому классу в дереве предков экземпляра. Чтобы переупорядочиваемые вызовы методов работали, классы должны разрабатываться совместно. При этом возникают три легко решаемых правила:

- метод, вызываемый `super()`, должен существовать
- вызывающий и вызываемый методы должны иметь совпадающую сигнатуру аргумента
- каждое вхождение метода должно использовать `super()`

Рассмотрим стратегии получения аргументов вызывающего объекта, чтобы они соответствовали сигнатуре вызываемого метода. Это немного сложнее, чем традиционные вызовы методов, когда вызываемый объект известен заранее. При использовании `super()` вызываемый объект неизвестен во время написания класса (поскольку подкласс, написанный позже, может ввести новые классы в MRO).

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

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

Каждый уровень убирает ключевые аргументы, которые ему нужны, чтобы окончательный пустой словарь мог быть отправлен методу, который вообще не ожидает аргументов (например, `object.__init__` в примере ниже не ожидает аргументов)

```python
class Shape:
    def __init__(self, shapename, **kwds):
        self.shapename = shapename
        super().__init__(**kwds)

class ColoredShape(Shape):
    def __init__(self, color, **kwds):
        self.color = color
        super().__init__(**kwds)

cs = ColoredShape(color='red', shapename='circle')
```

Следующая проблема - как убедиться, что целевой метод существует. В примере выше мы знаем, что у `object` всегда есть `__init__()` и `object` - всегда последний в цепочке МРО, поэтому любая последовательность из `super().__init__(**kwds)` гарантированно завершается вызовом метода `object.__init__()`. Но так бывает далеко не всегда и `object` может не иметь необходимого нам метода. Для таких случаев требуется написать корневой класс, который гарантированно будет вызываться перед `object` и будет иметь нужный нам метод. Задача такого корневого класса - стать последним в цепочке вызовов, заменив собойс `object`.

```python
class Root:
    def draw(self):
        # the delegation chain stops here
        assert not hasattr(super(), 'draw')

class Shape(Root):
    def __init__(self, shapename, **kwds):
        self.shapename = shapename
        super().__init__(**kwds)
    def draw(self):
        print('Drawing.  Setting shape to:', self.shapename)
        super().draw()

class ColoredShape(Shape):
    def __init__(self, color, **kwds):
        self.color = color
        super().__init__(**kwds)
    def draw(self):
        print('Drawing.  Setting color to:', self.color)
        super().draw()

cs = ColoredShape(color='blue', shapename='square')
cs.draw()
```

В данном примере, если мы захотим внедрить дополнительные классы, нам придется так-же отнаследовать их от корневого, чтобы ни один из путей в дереве не мог достичь `object` минуя `Root.draw()`. Необходимо написать об этом в документации.

Наконец, для гарантирантий выполнения всей цепочки, каждый делегирующий метод каждого класса должен реализовывать `super()`.

Иногда подкласс может захотеть использовать совместные методы, доступные через множественне наследование со сторонним классом, который не был предназначен для этого (к примеру, в нем не реализован `super()` или не реализован корневой класс). Для этого реализуется класс-адаптер.

```python
class Moveable:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def draw(self):
        print('Drawing at position:', self.x, self.y)
```

Адаптером для этого класса будет:

```python
class MoveableAdapter(Root):
    def __init__(self, x, y, **kwds):
        self.movable = Moveable(x, y)
        super().__init__(**kwds)
    def draw(self):
        self.movable.draw()
        super().draw()
```

И далее:

```python
class MovableColoredShape(ColoredShape, MoveableAdapter):
    pass

MovableColoredShape(color='red', shapename='triangle',
                    x=10, y=20).draw()
```

## Нюансы

- При создании подклассов встроенной функции, такой как `dict()`, часто необходимо переопределить или расширить несколько методов одновременно. В приведенных выше примерах расширение `__setitem__` не используется другими методами, такими как `dict.update`, поэтому может потребоваться их расширение. Это требование не уникально для `super()`; скорее, оно возникает всякий раз, когда встроенные функции являются подклассами.
- Если класс опирается на один родительский класс, предшествующий другому (например, `LoggingOD` зависит от  `LoggingDict` , следующего перед `OrderedDict`, который стоит перед `dict`), легко добавить утверждения для проверки и документирования предполагаемого порядка разрешения методов:

    ```python
        position = LoggingOD.__mro__.index
        assert position(LoggingDict) < position(OrderedDict)
        assert position(OrderedDict) < position(dict)
    ```

Полный пример кода [тут](https://code.activestate.com/recipes/577720-how-to-use-super-effectively/)

Смотри еще:

- [сорс](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/)
- [[python-datamodel]]
- [[python-buildin-functions]]
- [[python-standart-library]]


[python-datamodel]: ../lists/python-datamodel "Python datamodel"
[python-buildin-functions]: python-buildin-functions "Python build-in functions"
[python-standart-library]: ../lists/python-standart-library "Стандартная библиотека python и полезные ресурсы"