Наследование и дескрипторы
Идея наследования состоит в том, что бы использовать описание предыдущего объекта для написания нового.
class C:
var = 10
def __init__(self,val):
self.var = val
def __str__(self):
return "<{}>".format(self.var)
class D(C):
pass
c = C(123)
d = D(456)
print(c,type(c),issubclass(C,D))
print(d,type(d),issubclass(D,C))Написали два разных класса. Один является подклассом другого класса. Issubclass - возвращает флаг, указывающий на то, является ли указанный класс подклассом указанного класса (классов).
<123> <class '__main__.C'> False <456> <class '__main__.D'> True >>>
В процессе наследования Python отслеживает, кто от кого порождается. Изменим немного нашу программу. Добавим класс S.
class C:
var = 10
def __init__(self,val):
self.var = val
def __str__(self):
return "<{}>".format(self.var)
class S(C):
def __str__(self):
s = C.__str__(self)
return "{}:{}".format(s,type(self))
class D(S):
pass
c = C(123)
s = S(100500)
print(s)
d = D(456)
print(c,type(c),issubclass(C,D))
print(d,type(d),issubclass(D,C))Получилось, что у элемента типа s перебит str, у элемента типа c такого еще нет, а у элемента d наследовано от s.
<100500>:<class '__main__.S'> <123> <class '__main__.C'> False <456>:<class '__main__.D'> <class '__main__.D'> True
Класс D принял все поля, которые были у S. Очевидно, что в класс можно добавить свои методы и поля.
class C:
var = 10
def __init__(self,val):
self.var = val
def __str__(self):
return "<{}>".format(self.var)
class S(C):
def __str__(self):
s = C.__str__(self)
return "{}:{}".format(s,type(self))
class D(S):
def newm(self):
return self.var%2
c = C(123)
s = S(100500)
print(s)
d = D(456)
print(c,type(c),issubclass(C,D))
print(d,type(d),issubclass(D,C))В наследовании есть хитрости. Напишем в предыдущей программе метод __add__(self).
class C:
var = 10
def __init__(self,val):
self.var = val
def __str__(self):
return "<{}>".format(self.var)
def __add__(self,other):
return C(self.var+other.var)
__repr__=__str__
class S(C):
def __str__(self):
s = C.__str__(self)
return "{}:{}".format(s,type(self))
class D(S):
def newm(self):
return self.var%2
c = C(123)
s = S(100500)
print(s)
d = D(456)
print(c,type(c),issubclass(C,D))
print(d,type(d),issubclass(D,C))
Этот объект типа C и так не должно быть.
<100500>:<class '__main__.S'>
<123> <class '__main__.C'> False
<456>:<class '__main__.D'> <class '__main__.D'> True
>>> c+s
<100623>
>>> type(c+s)
<class '__main__.C'>
>>> type(s+c)
<class '__main__.C'>
>>> s+d
<100956>
>>>Применим следующую конструкцию
def __add__(self,other):
return type(self)(self.var+other.var)Получим
<100500>:<class '__main__.S'> <123> <class '__main__.C'> False <456>:<class '__main__.D'> <class '__main__.D'> True >>> c+s <__main__.C object at 0x02F3FA50> >>> s+c <__main__.S object at 0x031A6F10> >>>
Помимо issubclass есть isinstance(). Эта функция поддерживает наследование. Для isinstance() экземпляр производного класса есть экземпляр его базового класса.
Замечание: объект D является экземпляром класса С.
>>> isinstance(c,C) True >>> isinstance(d,D) True >>> isinstance(d,C) True >>>
Поговорим о защите полей. В Python нет такого, что поля доступны только внутри методов объекта, а снаружи доступ закрыт. Перебить или не перебить поле становится элементом договоренности до определённого момента. Проблема: если я унаследовал класс, не читая, что есть в этом классе, а потом перебил метод, который не начинается на “__” – значит, я так хотел. А если он начинается на “__” – то внутри “кишки” родителя начинают ходить к другому полю.
Если мы взяли и случайно перебили поле, то на помощь приходит костыль. В этом месте все будет нормально
class C:
__var = 10
def __str__(self):
return "<{}>".format(self.__var)Но если мы наследуем один класс от другого, то появляются легкие извращения.
class C:
__var = 10
def __str__(self):
return "<{}>".format(self.__var)
class D(C):
pass
c = C()
print(c)
d = D()
print(d)
Вывод
<10>
<10>
>>>Определим метод следующим образом.
class C:
__var = 10
def __str__(self):
return "<{}>".format(self.__var)
class D(C):
def add(self):
self.__var += 1
c = C()
print(c)
d = D()
print(d)
Получим, что
<10>
<10>
>>> d.add()
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
d.add()
File "E:/Документы (не удалять)!!!/Alya/Рабочий стол/1.py", line 7, in add
self.__var += 1
AttributeError: 'D' object has no attribute '_D__var'
>>>
>>> c.__var
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
c.__var
AttributeError: 'C' object has no attribute '__var'
>>> c._C__var
10
>>>Т.е. при определении класса С ко всем поля, которые начинаются на “__” , дописывается имя класса.
Замечание: имена не должны заканчиваться на “__”.
Множественное наследование
Это ситуация, когда класс наследует свойства нескольких других классов.
class A:
var = 1
class B:
def fun(self):
return 100500
class C(A,B):
Field = None
Вывод:
>>> b = B()
>>> b.fun()
100500
>>> dir(b)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fun']
>>> c = C()
>>> dir(c)
['Field', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fun', 'var']
>>>Класс С является подклассом A и B. Плохая ситуация возникает, когда у родителей есть одинаковые поля(еще хуже – одинаковые методы). Идея, в которой выживает то поле, которое вызывается последним, не работает в замкнутом наследовании. Тогда используем метод обхода графа. Зададим объект, у которого будут методы get, set, del.
class Desc:
var = 0
def __set__(self,obj,val):
print("Set {} of {}".format(val,obj))
self.var = val
def __get__(self,obj,cls):
print("Get from {} (class {})".format(obj,cls))
def __delete__(self,obj):
print("Del from",obj)Объект типа дескриптор должен быть полем нашего класса.
class Desc:
var = 0
def __set__(self,obj,val):
print("Set {} of {}".format(val,obj))
self.var = val
def __get__(self,obj,cls):
print("Get from {} (class {})".format(obj,cls))
def __delete__(self,obj):
print("Del from",obj)
class C:
fld = Desc()
def __init__(self,name):
self.name = name
def __str__(self):
return self.nameПроверяем
>>> c = C("BEBE")
>>> print(c)
BEBE
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fld', 'name']
>>> c.__dict__
{'name': 'BEBE'}
>>> c.var = 100500
>>> c = C("BEBE")
>>> c.fld = 100500
Set 100500 of BEBE
>>>В этот момент возникает альтернативный способ работы с дескрипторами. Доступ к соответствующим полям, которые мы определяем как дескриптор, обеспечивается с помощью методов get, set и del.
Бонусы:
- Присваивание и отдачу значения поля можно контролировать.
- Не плодятся namespace внутри объекта.
Если вставить исключение
class Desc:
var = 0
def __set__(self,obj,val):
#print("Set {} of {}".format(val,obj))
#self.var = val
raise ValueError
def __get__(self,obj,cls):
print("Get from {} (class {})".format(obj,cls))
return self.var
def __delete__(self,obj):
print("Del from",obj)
class C:
fld = Desc()
def __init__(self,name):
self.name = name
def __str__(self):
return self.nameСмотрим
>>> c=C()
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
c=C()
TypeError: __init__() missing 1 required positional argument: 'name'
>>> c=C("RO")
>>> c.var
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
c.var
AttributeError: 'C' object has no attribute 'var'
>>> c.fld
Get from RO (class <class '__main__.C'>)
0
>>> c.fld=0
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
c.fld=0
File "E:/Документы (не удалять)!!!/Alya/Рабочий стол/1.py", line 6, in __set__
raise ValueError
ValueError
>>>Получается объект, у которого есть fld поля.
Исключения
Исключения (exceptions) - тип данных в python. Исключения необходимы для того, чтобы сообщать программисту об ошибках, так как компилятор не способен выловить все ошибки.
Продолжение следует
