что такое class в python
Примеры работы с классами в Python
Python — объектно-ориентированный язык с начала его существования. Поэтому, создание и использование классов и объектов в Python просто и легко. Эта статья поможет разобраться на примерах в области поддержки объектно-ориентированного программирования Python. Если у вас нет опыта работы с объектно-ориентированным программированием (OOП), ознакомьтесь с вводным курсом или учебным пособием, чтобы понять основные понятия.
Создание классов
Пример создания класса на Python:
Создание экземпляров класса
Доступ к атрибутам
Теперь, систематизируем все.
При выполнении этого кода, мы получаем следующий результат:
Вы можете добавлять, удалять или изменять атрибуты классов и объектов в любой момент.
Вместо использования привычных операторов для доступа к атрибутам вы можете использовать эти функции:
Встроенные атрибуты класса
Для вышеуказанного класса давайте попробуем получить доступ ко всем этим атрибутам:
Когда этот код выполняется, он возвращает такой результат:
Удаление объектов (сбор мусора)
Python автоматически удаляет ненужные объекты (встроенные типы или экземпляры классов), чтобы освободить пространство памяти. С помощью процесса ‘Garbage Collection’ Python периодически восстанавливает блоки памяти, которые больше не используются.
Сборщик мусора Python запускается во время выполнения программы и тогда, когда количество ссылок на объект достигает нуля. С изменением количества обращений к нему, меняется количество ссылок.
Пример работы __del__()
Деструктор __del__() выводит имя класса того экземпляра, который должен быть уничтожен:
Когда вышеуказанный код выполняется и выводит следующее:
Наследование класса в python
Наследование — это процесс, когда один класс наследует атрибуты и методы другого. Класс, чьи свойства и методы наследуются, называют Родителем или Суперклассом. А класс, свойства которого наследуются — класс-потомок или Подкласс.
Вместо того, чтобы начинать с нуля, вы можете создать класс, на основе уже существующего. Укажите родительский класс в круглых скобках после имени нового класса.
Класс наследник наследует атрибуты своего родительского класса. Вы можете использовать эти атрибуты так, как будто они определены в классе наследнике. Он может переопределять элементы данных и методы родителя.
Синтаксис наследования класса
Классы наследники объявляются так, как и родительские классы. Только, список наследуемых классов, указан после имени класса.
Класс и объект в Python
Объектно-ориентированное программирование в Python
Python — это процедурно-ориентированный и одновременно объектно-ориентированный язык программирования.
Процедурно-ориентированный
«Процедурно-ориентированный» подразумевает наличие функций. Программист может создавать функции, которые затем используются в сторонних скриптах.
Объектно-ориентированный
«Объектно-ориентированный» подразумевает наличие классов. Есть возможность создавать классы, представляющие собой прототипы для будущих объектов.
Создание класса в Python
Синтаксис для написания нового класса:
Атрибут:
Атрибут — это элемент класса. Например, у прямоугольника таких 2: ширина ( width ) и высота ( height ).
Метод:
Конструктор:
Создание объекта с помощью класса Rectangle:
Что происходит при создании объекта с помощью класса?
При создании объекта класса Rectangle запускается конструктор выбранного класса, и атрибутам нового объекта передаются значения аргументов. Как на этом изображении:
Конструктор с аргументами по умолчанию
В других языках программирования конструкторов может быть несколько. В Python — только один. Но этот язык разрешает задавать значение по умолчанию.
Все требуемые аргументы нужно указывать до аргументов со значениями по умолчанию.
Сравнение объектов
В Python объект, созданный с помощью конструктора, занимает реальное место в памяти. Это значит, что у него есть точный адрес.
Атрибуты
В Python есть два похожих понятия, которые на самом деле отличаются:
Стоит разобрать на практике:
Атрибут
Объекты, созданные одним и тем же классом, будут занимать разные места в памяти, а их атрибуты с «одинаковыми именами» — ссылаться на разные адреса. Например:
Атрибуты функции
Обычно получать доступ к атрибутам объекта можно с помощью оператора «точка» (например, player1.name ). Но Python умеет делать это и с помощью функции.
Функция | Описание |
---|---|
getattr (obj, name[,default]) | Возвращает значение атрибута или значение по умолчанию, если первое не было указано |
hasattr (obj, name) | Проверяет атрибут объекта — был ли он передан аргументом «name» |
setattr (obj, name, value) | Задает значение атрибута. Если атрибута не существует, создает его |
delattr (obj, name) | Удаляет атрибут |
Встроенные атрибуты класса
Объекты класса — дочерние элементы по отношению к атрибутам самого языка Python. Таким образом они заимствуют некоторые атрибуты:
Переменные класса
Переменные класса в Python — это то же самое, что Field в других языках, таких как Java или С#. Получить к ним доступ можно только с помощью имени класса или объекта.
Для получения доступа к переменной класса лучше все-таки использовать имя класса, а не объект. Это поможет не путать «переменную класса» и атрибуты.
У каждой переменной класса есть свой адрес в памяти. И он доступен всем объектам класса.
Составляющие класса или объекта
Основы ООП в Python — классы, объекты, методы
О ОП — самая используемая парадигма программирования. Это одновременно и особый способ мышления, и отдельная методика. Её концепцию проще всего понимать на примерах из реальной жизни. И это неспроста. Объектно-ориентированное программирование помогает представлять содержимое программы наиболее естественным для нашего мира способом.
Главным понятием ООП является понятие программного объекта. Вообще говоря, большинство сущностей на планете Земля — это некие объекты. И с частью из них мы взаимодействуем при помощи программирования. Банковский счёт, персонаж компьютерной игры или анимированный виджет сайта — всё это легко представить в виде объектов. Можно сказать, что объектно-ориентированное программирование позволяет смоделировать реальный объект в виде программного.
Множество объектов со схожими свойствами формируются в классы. Идея класса также является одной из основополагающих концепций ООП. Со стороны программы, класс — это всего лишь тип данных, но для программиста это куда более глубокая абстрактная структура. Но перейдём уже к конкретике.
💁♂️ Итак, мы — разработчики игр. Наша студия трудится над новым автосимулятором. В игре будут представлены разные виды транспорта: легковые автомобили, гоночные, грузовые и пассажирские. Все их можно описать одним словом — автотранспорт. Сделав это, мы абстрагировались от деталей и, таким образом, определили класс. Объектом этого класса может быть, как Бьюик 1968-го года, так и грузовой Freightliner Columbia желтого цвета.
У класса есть свойства и функции (в ООП их называют методами).
Свойствами класса «автотранспорт» могут быть, например: год выпуска, вид и цвет. На уровне объектов это будет выглядеть так: Бьюик Электра — это объект класса «Автотранспорт» со следующими свойствами:
Можно сказать, что объект — это вполне конкретный экземпляр класса
Помимо физических атрибутов, которые описывают внешний вид и характеристики транспортного средства, автомобили обладают между собой и другими фундаментальными сходствами. Например, все они могут ехать, тормозить, переключать скорости, поворачивать и сигналить. В нашем случае, всё это — методы класса «Автотранспорт». То есть действия, которые любые объекты данного класса могут выполнять.
Мы разрабатываем игру, поэтому предполагается, что машины в ней будут исправными. Значит, вполне естественно, что каждая из них может ехать и тормозить.
В Питоне класс «Автотранспорт» может выглядеть так:
# класс автотранспорт class MotorTransport(object): def __init__(self, color, year, auto_type): self.color = color self.year = year self.auto_type = auto_type # тормозить def stop(self): print(«Pressing the brake pedal») # ехать def drive(self): print(‘WRRRRRUM!’)
Теперь никто не помешает нам получить собственную красную феррари. Пусть и в симуляторе.
# создадим объект класса Автотранспорт ferrari_testarossa = MotorTransport(‘Red’, 1987, ‘passenger car’) # жмём на газ и вперёд! ferrari_testarossa.drive() > WRRRRRUM!
Принципы ООП
Абстракция
Абстракция — это выделение основных, наиболее значимых характеристик объекта и игнорирование второстепенных.
Любой составной объект реального мира — это абстракция. Говоря «ноутбук», вам не требуется дальнейших пояснений, вроде того, что это организованный набор пластика, металла, жидкокристаллического дисплея и микросхем. Абстракция позволяет игнорировать нерелевантные детали, поэтому для нашего сознания это один из главных способов справляться со сложностью реального мира. Если б, подходя к холодильнику, вы должны были иметь дело с отдельно металлом корпуса, пластиковыми фрагментами, лакокрасочным слоем и мотором, вы вряд ли смогли бы достать из морозилки замороженную клубнику.
Полиморфизм
Наследование
Это способность одного класса расширять понятие другого, и главный механизм повторного использования кода в ООП. Вернёмся к нашему автосимулятору. На уровне абстракции «Автотранспорт» мы не учитываем особенности каждого конкретного вида транспортного средства, а рассматриваем их «в целом». Если же более детализировано приглядеться, например, к грузовикам, то окажется, что у них есть такие свойства и возможности, которых нет ни у легковых, ни у пассажирских машин. Но, при этом, они всё ещё обладают всеми другими характеристиками, присущими автотранспорту.
Мы могли бы сделать отдельный класс «Грузовик», который является наследником «Автотранспорта». Объекты этого класса могли бы определять все прошлые атрибуты (цвет, год выпуска), но и получить новые. Для грузовиков это могли быть грузоподъёмность, снаряженная масса и наличие жилого отсека в кабине. А методом, который есть только у грузовиков, могла быть функция сцепления и отцепления прицепа.
Инкапсуляция
Инкапсуляция — это ещё один принцип, который нужен для безопасности и управления сложностью кода. Инкапсуляция блокирует доступ к деталям сложной концепции. Абстракция подразумевает возможность рассмотреть объект с общей точки зрения, а инкапсуляция не позволяет рассматривать этот объект с какой-либо другой.
Вы разработали для муниципальных служб класс «Квартира». У неё есть свойства вроде адреса, метража и высоты потолков. И методы, такие как получение информации о каждом из этих свойств и, главное, метод, реализующий постановку на учёт в Росреестре. Это готовая концепция, и вам не нужно чтобы кто-то мог добавлять методы «открыть дверь» и «получить место хранения денег». Это А) Небезопасно и Б) Избыточно, а также, в рамках выбранной реализации, не нужно. Работникам Росреестра не требуется заходить к вам домой, чтобы узнать высоту потолков — они пользуются только теми документами, которые вы сами им предоставили.
Класс
Классы, в некотором смысле, подобны чертежам: это не объекты сами по себе, а их схемы. Класс «банковских счетов» имеет строго определенные и одинаковые для всех атрибуты, но объекты в нём — сами счета — уникальны.
Как в Python создать класс
class SimpleClass: pass
Для именования классов в Python обычно используют стиль «camel case», где первая буква — заглавная.
Конструктор
Метод, который вызывается при создании объектов, в ООП зовётся конструктором. Он нужен для объектов, которые изначально должны иметь какие-то значение. Например, пустые экземпляры класса «Студент» бессмысленны, и желательно иметь хотя бы минимальный обозначенный набор вроде имени, фамилии и группы.
В качестве Питоновского конструктора выступает метод __init__() :
class Student: def __init__(self, name, surname, group): self.name = name self.surname = surname self.group = group alex = Student(«Alex», «Ivanov», «admin»)
Атрибуты класса
Поля могут быть статическими и динамическими:
☝️ Обратите внимание — статический и динамический атрибут может иметь одно и то же имя:
class MightiestWeapon: # статический атрибут name = «Default name» def __init__(self, name): # динамический атрибут self.name = name weapon = MightiestWeapon(«sword») print(MightiestWeapon.name) print(weapon.name)
Методы класса
Метод — это функция класса.
class SpaceShip: def atack(self): print(‘Пиу!’) star_destroyer = SpaceShip() star_destroyer.atack() > Пиу!
Что такое self?
🐈 Отличный пример с котофеями:
Уровни доступа атрибутов и методов
В Питоне не существует квалификаторов доступа к полям класса. Отсутствие аналогов связки public/private/protected можно рассматривать как упущение со стороны принципа инкапсуляции.
Декораторы
Декоратор — это функция-обёртка. В неё можно завернуть другой метод, и, тем самым, изменить его функциональность, не меняя код.
Объекты или экземпляры класса
Чем объекты отличаются от классов
Как уже было сказано, объект — это конкретный экземпляр класса. Все мы относимся к классу людей, но каждый из нас — уникальный объект этого класса.
Как создать объект класса в Python
Если у нас есть реализация класса, то его экземпляр создать очень просто:
class AirConditioner: def __init__(self, model, capacity): self.model = model self.capacity = capacity def turn_on(self): print(‘Now in the room will be cool’) # создадим объект класса Кондиционер ballu = AirConditioner(‘BPAC-07’, 785) ballu.turn_on() > Now in the room will be cool
Атрибуты объекта
Атрибуты класса могут быть динамическими и статическими. На уровне объекта они инициализируются так:
class MightiestWeapon: name = «Default name» def __init__(self, weapon_type): self.weapon_type = weapon_type # атрибут name можно переопределить и не создавая объекта MightiestWeapon.name = ‘Steel Sword’ print(MightiestWeapon.name) > Steal Sword # создаём объект и сразу же инициализируем динамический атрибут с помощью конструктора hero_sword = MightiestWeapon(‘sword’) # и теперь, уже для конкретного объекта, можно задать имя hero_sword.name = ‘Excalibur’ # новое статическое имя по умолчанию для всего класса не изменится print(MightiestWeapon.name) > Steal Sword print(hero_sword.name) > Excalibur
Наследование
Нередко в процессе написания кода выясняется, что некоторые объекты аналогичны другим за исключением нескольких различий. Определение сходств и различий между такими объектами называется «наследованием».
# класс «Животное». Это достаточно абстрактный класс всего с одним методом «Издать звук». class Animal: def make_a_sound(self): print(«Издаёт животный звук»)
Мы все прекрасно знаем, что котики, к примеру, любят всё ронять, а собакены — рыть землю. Создадим два соответствующих класса-наследника:
# факт наследования в Python указывается при объявлении класса-наследника. # в скобках, после имени класса, указывается класс-родитель class Cat(Animal): def drop_everything(self): print(‘Вставай скорее, я всё уронил!’) class Dog(Animal): def dig_the_ground(self): print(‘Однажды я докопаюсь до ядра планеты!’)
Теперь объекты этих двух классов могут не только издавать животные звуки, но и выполнять собственные уникальные действия:
Tom = Cat() Tom.make_a_sound() > Издаёт животный звук Tom.drop_everything() > Вставай скорее, я всё уронил!
Переопределение
Сейчас у нас и кошка, и собака просто «издают животные звуки», а хотелось бы, конечно, слышать звуки, свойственные именно этим животным. Для этого существует механика переопределения. Достаточно объявить в классе-наследнике метод с тем же названием, что и в базовом классе:
class Dog(Animal): def dig_the_ground(self): print(‘Однажды я докопаюсь до ядра планеты!’) # отныне для объектов класса «Собака» будет выполняться именно эта реализация метода def make_a_sound(self): print(‘Гав-гав!’) Balto = Dog() Balto.make_a_sound() > Гав-гав!
Документирование классов
Весь код нужно комментировать и документировать. Классы — не исключение. Стоит помнить, что код вы пишите не для себя, и вполне вероятно, что написанное вами придётся поддерживать другим людям. Комментарии повышают читаемость и увеличивают легкость восприятие кода в разы, тем самым экономя время и деньги.
ООП ещё долгое время будет оставаться передовой парадигмой программирования. Но учить её полезно и по другой причине. Прямая связь объектно-ориентированного программирования с реальным миром помогает глубже понимать устройство и принципы работы, как самого языка, так и написания кода в целом.
Заметки об объектной системе языка Python ч.2
Вторая часть заметок об объектной системе python’a (первая часть тут). В этой статье рассказывается, что такое классы, метаклассы, type, object и как происходит поиск атрибутов в классе.
Классы
Классы (типы) — это объектные фабрики. Их главная задача — создавать объекты, обладающие определенным поведением.
Классы определяют поведение объектов с помощью своих атрибутов (которые хранятся в __dict__ класса): методов, свойств, классовых переменные, дескрипторов, а также с помощью атрибутов, унаследованных от родительских классов.
Инстанцирование обычного объекта происходит в 2 этапа: сначала его создание, потом инициализация. Соответственно, сначала запускается метод класса __new__, который возвращает объект данного класса, потом выполняется метод класса __init__, который инициализирует уже созданный объект.
Например, объявим класс:
>>> class A ( object ):
. pass
.
>>>
Для класса A не определены ни __new__, ни __init__. В соответствии с алгоритмом поиска атрибутов для класса (типа), который не стоит путать с алгоритмом поиска атрибутов для обычных объектов, когда класс не найдет их в своем__dict__, он будет искать эти методы в __dict__ своих базовых (родительских) классах.
Класс А имеет в качестве родителя встроенный класс object. Таким образом он будет их искать в object.__dict__
Раз есть такие методы, значит, получается, что a = A() аналогичен последовательности вызовов:
a = object.__new__(A)
object.__init__(a)
В общем виде, используя super, который как раз и реализует алгоритм поиска атрибутов по родительским классам [1]:
a = super(A, A).__new__(A)
super(A, A).__init__(a)
Singleton v.1
Понимая, как происходит создание объекта, можно написать реализацию паттерна одиночка.
Мы должны гарантировать, что у класса есть только один экземпляр. Т.е. при вызове конструктора класса, всегда возвращаем один и тот же экземпляр класса.
А это значит, что при вызов метода __new__ должен возвращать каждый раз один и тот же объект. Хранить сам объект можно, например, в классовой переменной instance.
В результате получаем:
Классы и метаклассы.
Для класса (типа), так же как и для обычного объекта, существует класс (тип), который создает классы и определяет поведение класса. Этот класс называется метаклассом.
Создание класса, как и обычного объекта происходит с помощью вызова конструктора, но т.к. в классе есть несколько дополнительных специальных атрибутов, которые должны быть инициализированы, в конструктор передаются и соответствующие обязательные параметры.
XClass = XMetaClass(name, bases, attrs)
Тогда, сразу после создания
XClass.__name__ равно name,
XClass.__bases__ равен bases,
XClass.__dict__ равен attrs, а
XClass.__class__ равен XMetaClass
По умолчанию для всех определяемых классов метаклассом является type.
>>> class A ( object ):
. pass
.
Эквивалентно, по аналогии с обычными объектами:
>>> class B (A):
. def foo ( self ):
. 42
.
При определении класса, можно задать свой метакласс с помощью
классовой переменной __metaclass__:
>>> class A ( object ):
. __metaclass__ = Meta
.
>>>
Что равносильно: A = Meta(‘A’, (object,), <>)
О type и object
Прежде всего type и object — это объекты. И, как у всех порядочных объектов, у них есть специальные атрибуты __class__ и __dict__:
Более того object, и type — это объекты типа (классы), и у них тоже есть специальные атрибуты __name__, __bases___:
Экземпляры типа или класса object — это объекты (любые). Т.е. любой объект — экземпляр класса object:
Даже функция является объектом:
>>> def bar ():
. pass
.
>>> isinstance (bar, object )
True
Кроме того, класс object сам является своим экземпляром:
type тоже является его экземпляром:
Инстанцирование — object() возвращает самый простой и общий объект:
>>> o = object ()
У которого даже __dict__ нет, есть только __class__.
Экземпляры класса или типа type — это только другие классы или другие типы:
Число — это не класс
Встроенная функция setattr тоже не класс.
>>> isinstance (A, type )
True
Тип строки — это класс.
Т.к. object и type — тоже классы, то они являются экземплярами класса type:
Т.к. множество классов (типов) являются подмножеством множества объектов, то логично предположить, что type является подклассом object, т.е.
type — это просто класс, экземплярами которого являются другие классы. (т.е. метакласс). А сами классы можно считать расширением простых, обычных объектов.
Таким образом, когда мы наследуем класс от object, этот класс автоматически наследует поведение класса object, т.е. при инстанцировании он будет возвращать обычный объект. А когда мы наследуем от класса type, мы также автоматически наследуем поведение класса type, т.е. при инстацированни будет создаваться класс. А класс, который создает класс, называется метаклассом.
Значит, чтобы определить просто класс, нужно наследовать его от object, чтобы определить метакласс — наследуем его от type.
И еще: не нужно путать type(a) и type(name, bases, attrs).
type(a) — вызов с одним аргументом, возвращает тип объекта,
a type(name, bases, attrs) — вызов с тремя аргументами — это вызов конструктора класса.
О поиске атрибутов в классе
Как уже было отмечено, алгоритм поиска атрибутов в обычном объекте, но есть некоторые тонкости, т.к. у типов (классов) есть __bases__ — родительские классы (типы).
Если атрибут есть в __dict__ возвращается он, затем идет поиск по базовым классам из __bases__, а потом идет обращение к __dict__ __class__’а (т.е. фактически метакласса) и его (метакласса) родительских классов (метаклассов).
Все что определяется в метаклассе доступно для класса, но не доступно для экзмепляров класса — обычных объектов, т.к. поиск атрибутов в обычном объекте ведется только по __dict__ словарям класса.
В A.__dict__ ‘foo’ нет:
Зато он есть в метаклассе, поэтому:
Экземпляр класса C также вызовет метод foo из класса B.
А экземпляр D не найдет:
Метаклассы
Метаклассы являются фабриками классов (или типов). Инстанцирование класса тоже проходит в 2 этапа — создание объекта типа (класса) и его инициализация. Это также делается с помощью двух методов метакласса. Сначала вызывается метод __new__ метакласса с параметрами, необходимыми для создания класса — name, bases, attrs, а потом __init__ с теми же параметрами и уже созданным классом.
В начале метакласс Meta ищет метод __new__ у себя в словаре __dict__, не находит его там и начинает искать в __dict__ своих родительских классах (т.е. метаклассах, в данном случае type), т.е. происходит обычный поиск атрибута в классе. В результате исполнения __new__ с соответствующими параметрами получает новый класс, который потом инициализируется вызовом __init__ метода метакласса.
В совсем развернутом виде получается:
cls = type.__dict__[‘__new__’](Meta, ‘A’, (object,), <>)
type.__dict__[‘__init__’](cls, ‘A’, (object,), <>)
Или с помощью super
cls = super(Meta, Meta).__new__(Meta, ‘A’, (object,), <>)
super(Meta, Meta).__init__(cls, ‘A’, (object,), <>)
Стоит отметить, что в отличие от инстанцирования обычных объектов, используется не object.__new__ и object.__init__, а type.__new__ и type.__init__. У object.__new__ и type.__new__ разные сигнатуры, и object.__new__ возвращает обычный объект (regular object), а type.__new__ — объект типа (typeobject), т.е. класс.
Посмотрим, как это все работает на примере.
Во время инстанцирования просто объекта, никаких надписей не выводится.
>>> a = A()
>>>
Кроме того, соответственно, во методах __new__ и __init__ метакласса можно менять все: имя, список суперклассов, атрибуты.