Мы продолжаем
изучение важной темы в ООП – наследование. Вначале нашего курса по ООП я
говорил, что пользовательский класс по умолчанию автоматически наследуется от
базового класса object языка Python. Например, если
записать некий класс Geom без какой-либо реализации:
то, смотрите, в
среде PyCharm записав ниже
«Geom.» мы увидим список различных методов и свойств. Например, можно вывести
значение свойства __class__:
Откуда все это
взялось? Как вы уже догадались, от базового класса object, который неявно
добавляется, начиная с версии языка Python 3. Это
эквивалентно такой записи:
И все эти
атрибуты находятся в классе object.
Зачем это было
сделано? Очевидно, чтобы обеспечить стандартный базовый функционал работы с
классами. В частности, когда мы создаем экземпляр класса:
а, затем,
выводим его с помощью функции print:
то автоматически
отрабатывает магический метод __str__, который определен в базовом классе object. И так со всеми
атрибутами, ничего лишнего там нет. Как вы понимаете, иметь такой базовый
функционал очень удобно, поэтому в Python происходит
такое наследование по умолчанию от object. В результате,
мы имеем иерархию наследования:
Однако, если
добавить дочерний класс от Geom, например, класс Line для
представления линии:
class Geom(object):
pass
class Line(Geom):
pass
то иерархия
наследования уже будет такой:
То есть, указывая
в качестве базового любой другой класс, непосредственное наследование от object уже не
происходит, только косвенное – через базовые классы. Разумеется, здесь объекты Line также имеют
полный доступ ко всем открытым атрибутам класса object:
l = Line()
print(l.__class__)
Мало того, мы
можем определять, является ли тот или иной класс подклассом другого класса. Это
делает функция issubclass(), например:
print(issubclass(Line, Geom))
Она возвращает
значение True, если класс Line является
подклассом класса Geom. А вот если указать их в другом порядке:
print(issubclass(Geom, Line))
то получим False, так как класс Geom не является
дочерним от класса Line.
А вот с
объектами классов эта функция не работает. Если записать:
print(issubclass(l, Geom))
то получим
ошибку, что аргумент должен быть классом, а не его экземпляром. Если нам все же
нужно проверить принадлежность объекта тому или иному классу, в том числе и
базовому, то следует использовать уже знакомую нам функцию isinstance():
print(isinstance(l, Geom))
print(isinstance(l, Line))
В обоих случаях
получим значение True. Также истину вернет и проверка на базовый класс object:
print(isinstance(l, object))
Это еще раз
показывает, что все классы неявно наследуются от object.
Наследование от встроенных типов данных
Интересный факт
языка Python, что все
стандартные типы данных являются классами:
Мы в этом легко
можем убедиться, если выполним для них функцию issubclass():
issubclass(int, object)
issubclass(list, object)
Всюду увидим True. А мы знаем,
что эта функция работает исключительно с классами, а не объектами, поэтому
данный факт подтверждает, что эти типы являются классами языка Python.
Раз это так, то
что нам мешает наследоваться от них и расширять функционал по мере необходимости?
Ничего, поэтому можно сделать, например, такую реализацию:
class Vector(list):
def __str__(self):
return " ".join(map(str, self))
v = Vector([1, 2, 3])
print(v)
Мы здесь
переопределили магический метод __str__ для вывода списка в виде
набора данных через пробел. Мало того, теперь тип данных нашего списка стал не list, а Vector:
увидим:
<class '__main__.Vector'>
Конечно,
стандартные типы данных редко расширяют с помощью пользовательских классов, но
понимать, что они представляют собой классы и что такая возможность в принципе
существует, важно.
Итак, из этого
занятия вы должны были узнать, что все классы по умолчанию наследуются от
базового класса object, как работает функция issubclass() и что из себя
представляют встроенные типы данных языка Python.