Ромбическое наследование

Проблема: представьте себе, что классы наследуются так написано на схеме.

1.1.jpg

class D(B,C):
    ...
d = D()
d.v - ?

В классах А и С содержится поле v. Если мы рассмотрим любой из методов поиска полей, допустим, обход графа вглубину, то увидим, что у первого ближайшего предка (а именно у класса B) нет поля v. Поле v унаследуется из корня, а надо бы из C. Очевидно, что такой обход не годится.

2.3.png

Тут снова получится, что при обходе вширину мы раньше доберёмся до A.v, чам до B.v.

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

  1. Монотонность. Это означает, что порядок обхода родительских классов данного должен сохраняться и у всех его производных классов
  2. Соблюдения порядка объявления (т.е. расставление приоритетов).

В завершении этого напишем небольшую программу.

class A: pass
class B(A): pass
class C(A): pass
class D(B,C): pass
Вывод:
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
>>>

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

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

class Int(int):
    pass

    def __add__(self,other):
        return type(self)(int.__add__(self,other))

Сложение работает так, что возвращает тип Int, а остальное возвращает как обычно.

Допишем в исходной программе класс INt и INt2

class Int(int):
    def __add__(self,other):
        return type(self)(int.__add__(self,other))
class INt:
    def __init__(self,*val):
        self.val = int(*val)

    def __add__(self,other):
        return type(self)(self.val + other.val)
class Int2(Int):
    def __mul__(self,other):
        return type(self)(???.__mul__(self,other))

Что писать вместо ??? ? И как узнать, в каком родительском классе определен этот метод? Для этого есть функция super(). У функции super() есть два параметра: класс и объект. Без параметров — класс и объект, в контексте которых была вызвана функция. Эта функция возвращает не какой-то родительский класс, а специальный объект, в пространстве имён которого есть поля всех родительских классов согласно правилам их видимости.

class Int(int):
    def __add__(self,other):
        return type(self)(int.__add__(self,other))
class INt:
    def __init__(self,*val):
        self.val = int(*val)

    def __add__(self,other):
        return type(self)(self.val + other.val)
class Int2(Int):
    def __mul__(self,other):
        return type(self)(super().__mul__(other))

Вывод:

>>> i,j=Int2(9),Int2(80)
>>> type(i*j)
<class '__main__.Int2'>
>>> type(i+j)
<class '__main__.Int2'>

Посмотрим откуда метод

>>> super(Int2,i)
<super: <class 'Int2'>, <Int2 object>>
>>> super(Int2,i).__add__
<bound method Int.__add__ of 9>
>>> super(Int2,i).__sub__
<method-wrapper '__sub__' of Int2 object at 0x034645A8>

Замечание: на каком основании у функции super() пропущены параметры type(self) и self? Ведь при объявлении методов мы этот self всегда пишем? Синтаксис не позволяет опускать (как в Си++ или Java) объект при обращении к его полю, потому что возникает неоднозначность: Var — это self.Var или глобальное имя? Знать заранее (до использования класса) нельзя. А вот подставить тип объекта и сам объект при вызове super() можно вполне однозначно -- FrBrGeorge 2017-12-03 21:59:52.

Исключения

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

Синтаксическая ошибка (исключение, которое трудно обработать):

>>> bin
<built-in function bin>
>>> ....
SyntaxError: invalid syntax
>>> /+/
SyntaxError: invalid syntax

Такие исключения как: деление на ноль, неопределенный идентификатор, сложить число и строку – это все объекты Python. Кто-нибудь должен обработать исключение и если никто не хочет это делать, то обработать исключение можем мы.

try:
    a = 2
    b = "QWE"
    c = a + b
except:
    print("Oops!")

Сначала все выполняется, а затем случается исключение.

Замечание: синтаксические ошибки не перехватываются.

try:
    a = 2
    b = "QWE"
    print("1")
    d = 1/6
    print("2")
    c = a + b
except Exception:
    print("Oops!")

Вывод:

1
2
Oops!

Программа

class A(Exception): pass
class B(A): pass
class C(B): pass

for E in A,B,C:
    try:
        raise E
    except C:
        print("C")
    except B:
        print("B")
    except A:
        print("A")

Вывод:

A
B
C
>>>

Переставим местами C и B.

class A(Exception): pass
class B(A): pass
class C(B): pass

for E in A,B,C:
    try:
        raise E
    except B:
        print("B")
    except C:
        print("C")
    except A:
        print("A")

Обратим внимание на то, что вывод другой.

A
B
B
>>>

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

 class A(Exception): pass
class B(A): pass
class C(B): pass

for E in A,B,C:
    try:
        raise E("QQ","QQ","QQ","QKRQ")
    except B as EX:
        print(type(EX))
        print(EX.args)
        print(EX)
        print("B")
    except C:
        print("C")
    except A:
        print("A")
    else:
        print("Wow, no exceptions!")

Вывод:

A
<class '__main__.B'>
('QQ', 'QQ', 'QQ', 'QKRQ')
('QQ', 'QQ', 'QQ', 'QKRQ')
B
<class '__main__.C'>
('QQ', 'QQ', 'QQ', 'QKRQ')
('QQ', 'QQ', 'QQ', 'QKRQ')
B
>>>

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

 try:
    print("1")
    a = 1/0
finally:
    print("Done")

Вывод:

1
Done
Traceback (most recent call last):
  File "E:\Документы (не удалять)!!!\Alya\Рабочий стол\1.py", line 4, in <module>
    a = 1/0
ZeroDivisionError: division by zero

Исключение обрабатывается и выполняется код.

try:
    print("1")
    #a = 1/0
    a = "WER" + 4
except TypeError:
    print("TE")
finally:
    print("Done")

Вывод:

1
TE
Done
>>>

Теперь берем необработанное исключение.

try:
    print("1")
    a = 1/0
    #a = "WER" + 4
    print("2")

except TypeError:
    print("TE")
else:
    print("OK")
finally:
    print("Done")
Вывод:
1
Done
Traceback (most recent call last):
  File "E:\Документы (не удалять)!!!\Alya\Рабочий стол\1.py", line 4, in <module>
    a = 1/0
ZeroDivisionError: division by zero
>>>

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

LecturesCMC/PythonIntro2017/11_Exceptions/Conspect (последним исправлял пользователь FrBrGeorge 2017-12-04 18:21:24)