В предыдущем уроке мы разобрались, зачем вообще нужен объектно-ориентированный подход и какие идеи лежат в его основе. Теперь пришло время перейти от концепций к практике и начать работать с классами и объектами непосредственно в Python.
На этом уроке мы:
- научимся определять собственные классы;
- разберёмся, как создаются объекты (экземпляры классов);
- поймём разницу между атрибутами класса и атрибутами объектов;
- научимся добавлять, изменять и удалять атрибуты;
- подробно рассмотрим, как Python ищет атрибуты и почему иногда возникают неожиданные эффекты.
Этот урок — фундаментальный. Если здесь что-то не понять, дальше в ООП будет значительно сложнее.
Начнём с самого простого — определения класса.
class Point:
passРазберём этот код построчно:
- ключевое слово
classговорит Python, что мы объявляем новый класс; Point— имя класса (по стандарту PEP8 имена классов пишутся с заглавной буквы);- двоеточие
:завершает заголовок класса; - оператор
passозначает «ничего не делать».
На данном этапе класс Point существует, но не содержит ни данных, ни поведения.
Тем не менее, это уже полноценный тип данных.
Добавим в класс первые атрибуты:
class Point:
color = 'red'
circle = 2Здесь важно сразу понять ключевую идею:
Класс в Python — это отдельное пространство имён.
Фактически Python создаёт объект класса Point, внутри которого есть переменные:
colorcircle
Мы можем работать с ними напрямую через имя класса:
Point.color # 'red'
Point.circle # 2И даже изменять их:
Point.color = 'black'У любого объекта в Python есть специальный атрибут __dict__, в котором хранится его пространство имён.
Point.__dict__В выводе ты увидишь:
- множество служебных атрибутов Python;
- наши атрибуты
colorиcircle.
Это полезный инструмент для понимания того, где именно хранятся данные.
Теперь создадим объект этого класса:
a = Point()Что произошло:
- был создан новый объект;
- переменная
aтеперь ссылается на него; - объект был создан по шаблону класса
Point.
Создадим ещё один объект:
b = Point()Теперь у нас есть:
- один класс
Point; - два независимых объекта
aиb.
Проверим их тип:
type(a) # <class '__main__.Point'>
type(a) == Point # True
isinstance(a, Point) # TrueИмя класса здесь выступает в роли типа данных.
Это один из самых важных моментов урока.
Посмотрим на атрибуты объектов:
a.__dict__
b.__dict__Результат — пустые словари.
Почему?
Потому что:
aиbне имеют собственных атрибутов;- они лишь имеют доступ к атрибутам класса
Point.
Схематично это выглядит так:
Класс Point
├─ color = 'red'
├─ circle = 2
|
Объект a Объект b
├─ (пусто) ├─ (пусто)
Но при этом:
a.color # 'red'
b.circle # 2Python сначала ищет атрибут в объекте, а если не находит — в классе.
Изменим атрибут класса:
Point.circle = 1Теперь:
a.circle # 1
b.circle # 1Это логично: атрибуты класса существуют в одном экземпляре и разделяются всеми объектами.
Теперь выполним следующее:
a.color = 'green'И сразу проверим:
a.color # 'green'
b.color # 'red'Почему так произошло?
Потому что:
- при присваивании Python создаёт атрибут в текущем пространстве имён;
- в данном случае — в объекте
a.
Проверим:
a.__dict__ # {'color': 'green'}
b.__dict__ # {}Теперь схема выглядит так:
Класс Point
├─ color = 'red'
├─ circle = 1
|
Объект a
├─ color = 'green'
|
Объект b
├─ (пусто)
Это ключевой принцип:
Присваивание через объект создаёт или изменяет атрибут объекта, а не класса.
Мы можем динамически добавлять атрибуты в класс.
Обычный способ:
Point.type_pt = 'disc'Или с помощью встроенной функции:
setattr(Point, 'prop', 1)Если атрибут уже существует, он будет перезаписан:
setattr(Point, 'type_pt', 'square')Если обратиться к несуществующему атрибуту:
Point.aвозникнет ошибка AttributeError.
Чтобы избежать этого, используется getattr:
getattr(Point, 'a', False) # False
getattr(Point, 'color') # 'red'На практике getattr используют:
- когда имя атрибута формируется динамически;
- когда есть риск, что атрибут отсутствует.
Удалить атрибут можно несколькими способами.
Через del:
del Point.propИли через функцию:
delattr(Point, 'type_pt')Перед удалением полезно проверить существование:
hasattr(Point, 'circle') # TrueПопробуем удалить атрибут у объекта b:
del b.colorМы получим ошибку, потому что:
- у
bнет собственного атрибутаcolor.
А вот у объекта a он есть:
del a.colorПосле этого:
a.color # снова 'red' (берётся из класса)Почему?
Потому что:
- Python сначала ищет атрибут в объекте;
- если не находит — ищет в классе.
Вернёмся к задаче с точками на плоскости.
Цвет и размер — общие. А координаты — индивидуальные.
a.x = 1
a.y = 2
b.x = 10
b.y = 20Теперь:
a.__dict__ # {'x': 1, 'y': 2}
b.__dict__ # {'x': 10, 'y': 20}Схема:
Класс Point
├─ color
├─ circle
|
Объект a Объект b
├─ x = 1 ├─ x = 10
├─ y = 2 ├─ y = 20
Так и строится модель:
- общее — в классе;
- индивидуальное — в объектах.
В Python можно добавлять описание класса:
class Point:
"""Класс для представления координат точек на плоскости"""
color = 'red'
circle = 2И получить его:
Point.__doc__Это особенно важно:
- в больших проектах;
- при командной разработке;
- при возвращении к коду через время.
Теперь ты должен чётко понимать:
- как объявляются классы;
- как создаются объекты;
- чем отличаются атрибуты класса и объекта;
- как Python ищет атрибуты;
- как добавлять, изменять и удалять свойства.
Это база, без которой невозможно двигаться дальше в ООП.
- Почему класс называют шаблоном для объектов?
- Что такое пространство имён класса?
- Почему
a.__dict__может быть пустым, ноa.colorработать? - Чем отличаются атрибуты класса и атрибуты объекта?
- Что произойдёт при присваивании
a.attr = value? - Почему изменение атрибута класса влияет на все объекты?
- В каком порядке Python ищет атрибут?
- Когда стоит использовать
getattr? - Почему удаление атрибута объекта может «открыть» атрибут класса?
-
Объявите класс с именем
DataBase, который бы хранил в себе следующую информацию:pk: 1title:"Классы и объекты"author:"Bob Bobov"views: 14356comments: 12
Имена атрибутов используйте точно такие же.
-
Объявите класс
Goodsсо следующими атрибутами:title:"Мороженое"weight: 154tp:"Еда"price: 1024
После объявления класса:
- измените значение атрибута
priceна2048; - добавьте атрибут
inflationсо значением100.
-
Объявите класс
TravelBlogс атрибутом класса:total_blogs = 0
Создайте объект
tb1и добавьте ему локальные свойства:name:'Франция'days: 6
После этого увеличьте
total_blogsна 1.Создайте объект
tb2и добавьте ему локальные свойства:name:'Италия'days: 5
Снова увеличьте
total_blogsна 1.
-
Объявите класс
Personс атрибутами:name:'Боб Бобов'job:'Программист'city:'Нальчик'
Создайте объект
p1этого класса.Проверьте, существует ли у объекта
p1локальный атрибутjob. ВыведитеTrue, если он существует именно в объектеp1, иFalse— если нет.
-
Объявите класс
Deviceс атрибутами:brand = "Generic"power = 100
Создайте объект
d1.Затем:
- измените
powerу объектаd1на300; - удалите атрибут
powerу объектаd1; - выведите значение
d1.power.
Дополнительно: Объясните, почему получилось именно такое значение.
-
Объявите класс
Studentс атрибутом класса:school = "Just School"
Создайте список из трёх объектов этого класса.
Каждому объекту добавьте локальный атрибут
name(прим.["Bob", "Alex", "Tom"]).Затем:
- измените
schoolв классе на"IT Top School"; - выведите для каждого объекта его
nameиschool.