Skip to content

Latest commit

 

History

History
530 lines (343 loc) · 14 KB

File metadata and controls

530 lines (343 loc) · 14 KB

Урок 2. Классы и объекты. Атрибуты классов и объектов

В предыдущем уроке мы разобрались, зачем вообще нужен объектно-ориентированный подход и какие идеи лежат в его основе. Теперь пришло время перейти от концепций к практике и начать работать с классами и объектами непосредственно в Python.

На этом уроке мы:

  • научимся определять собственные классы;
  • разберёмся, как создаются объекты (экземпляры классов);
  • поймём разницу между атрибутами класса и атрибутами объектов;
  • научимся добавлять, изменять и удалять атрибуты;
  • подробно рассмотрим, как Python ищет атрибуты и почему иногда возникают неожиданные эффекты.

Этот урок — фундаментальный. Если здесь что-то не понять, дальше в ООП будет значительно сложнее.


Определение класса в Python

Начнём с самого простого — определения класса.

class Point:
    pass

Разберём этот код построчно:

  • ключевое слово class говорит Python, что мы объявляем новый класс;
  • Point — имя класса (по стандарту PEP8 имена классов пишутся с заглавной буквы);
  • двоеточие : завершает заголовок класса;
  • оператор pass означает «ничего не делать».

На данном этапе класс Point существует, но не содержит ни данных, ни поведения. Тем не менее, это уже полноценный тип данных.


Класс как пространство имён

Добавим в класс первые атрибуты:

class Point:
    color = 'red'
    circle = 2

Здесь важно сразу понять ключевую идею:

Класс в Python — это отдельное пространство имён.

Фактически Python создаёт объект класса Point, внутри которого есть переменные:

  • color
  • circle

Мы можем работать с ними напрямую через имя класса:

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    # 2

Python сначала ищет атрибут в объекте, а если не находит — в классе.


Атрибуты класса — общие для всех объектов

Изменим атрибут класса:

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 ищет атрибуты;
  • как добавлять, изменять и удалять свойства.

Это база, без которой невозможно двигаться дальше в ООП.


Вопросы

  1. Почему класс называют шаблоном для объектов?
  2. Что такое пространство имён класса?
  3. Почему a.__dict__ может быть пустым, но a.color работать?
  4. Чем отличаются атрибуты класса и атрибуты объекта?
  5. Что произойдёт при присваивании a.attr = value?
  6. Почему изменение атрибута класса влияет на все объекты?
  7. В каком порядке Python ищет атрибут?
  8. Когда стоит использовать getattr?
  9. Почему удаление атрибута объекта может «открыть» атрибут класса?

Задачи

  1. Объявите класс с именем DataBase, который бы хранил в себе следующую информацию:

    • pk: 1
    • title: "Классы и объекты"
    • author: "Bob Bobov"
    • views: 14356
    • comments: 12

    Имена атрибутов используйте точно такие же.


  1. Объявите класс Goods со следующими атрибутами:

    • title: "Мороженое"
    • weight: 154
    • tp: "Еда"
    • price: 1024

    После объявления класса:

    • измените значение атрибута price на 2048;
    • добавьте атрибут inflation со значением 100.

  1. Объявите класс TravelBlog с атрибутом класса:

    • total_blogs = 0

    Создайте объект tb1 и добавьте ему локальные свойства:

    • name: 'Франция'
    • days: 6

    После этого увеличьте total_blogs на 1.

    Создайте объект tb2 и добавьте ему локальные свойства:

    • name: 'Италия'
    • days: 5

    Снова увеличьте total_blogs на 1.


  1. Объявите класс Person с атрибутами:

    • name: 'Боб Бобов'
    • job: 'Программист'
    • city: 'Нальчик'

    Создайте объект p1 этого класса.

    Проверьте, существует ли у объекта p1 локальный атрибут job. Выведите True, если он существует именно в объекте p1, и False — если нет.


  1. Объявите класс Device с атрибутами:

    • brand = "Generic"
    • power = 100

    Создайте объект d1.

    Затем:

    • измените power у объекта d1 на 300;
    • удалите атрибут power у объекта d1;
    • выведите значение d1.power.

    Дополнительно: Объясните, почему получилось именно такое значение.


  1. Объявите класс Student с атрибутом класса:

    • school = "Just School"

    Создайте список из трёх объектов этого класса.

    Каждому объекту добавьте локальный атрибут name (прим. ["Bob", "Alex", "Tom"]).

    Затем:

    • измените school в классе на "IT Top School";
    • выведите для каждого объекта его name и school.

Предыдущий урок | Следующий урок