Объекты и классы в Python

В этом руководстве вы познакомитесь с базовым функционалом объектов и классов Python. Что такое класс, как его создать и грамотно использовать в программе.

Python — объектно-ориентированный язык программирования. В отличие от процедурно-ориентированного программирования, ООП опирается на объекты. 

Объект — это набор данных (переменных) и методов (функций), которые с этими данными взаимодействуют. 

Представьте чертеж дома. В нем содержится вся информация: сколько этажей, какого размера двери, окна и т. д. На основе это чертежа мы можем построить дом. Дом — это объект. 

По одному чертежу можно построить сразу несколько домов. Так же и с классом — по нему можно создать много объектов. Объект также можно назвать экземпляром класса, а процесс его создания — инстанцированием.

Как объявить класс

По аналогии с функциями, которые начинаются с def, объявление класса сопровождается ключевым словом class.

Первая строка внутри класса называется строкой документации, в ней содержится краткое описание класса. Писать ее рекомендуется, но не обязательно. 

Объявим класс.

class MyNewClass:
    '''Это строка документации. Мы создали новый класс'''
    pass

Класс создает новое локальное пространство имен, где определяются все атрибуты. Атрибутами могут быть и переменные, и функции.

Есть и специальные атрибуты, которые начинаются с двойного нижнего подчеркивания __. Например, __doc__ — строка документации класса. 

После объявления класса создается объект этого класса с тем же именем. Этот объект класса позволяет нам как получить доступ к различным его атрибутам, так и инстанцировать новые объекты этого класса.

class Person:
    "Это класс, описывающий человека"
    age = 10

    def greet(self):
        print('Привет')

# Вывод: 10
print(Person.age)

# Вывод: <function Person.greet at 0x7f32dbdbeee0>
print(Person.greet)

# Вывод: 'Это мой второй класс'
print(Person.__doc__)

Вывод

10
<function Person.greet at 0x7f32dbdbeee0>
Это класс, описывающий человека

Как создать объект

Мы уже знаем, что объект класса можно использовать для доступа к различным его атрибутам. 

Использовать его можно и для создания новых экземпляров этого класса. Создание объекта похоже на вызов функции. 

>>> harry = Person()

Так мы создадим новый экземпляр класса — harry. Доступ к атрибутам объекта осуществляется при помощи префикса имени объекта. 

Атрибутами могут быть и переменные, и методы. Методы объекта — функции этого класса. 

Это значит следующее: Person.greet — объект функции (атрибут класса), а harry.greet — объект метода

class Person:
    "Это класс, описывающий человека"
    age = 10

    def greet(self):
        print('Привет')


# создаем новый объект класса Person
harry = Person()

# Вывод: <function Person.greet>
print(Person.greet)

# Вывод: <bound method Person.greet of <__main__.Person object>>
print(harry.greet)

# Вызов метода greet() объекта
# Вывод: Привет
harry.greet()

Вывод:

<function Person.greet at 0x7fd288e4e160>
<bound method Person.greet of <__main__.Person object at 0x7fd288e9fa30>>
Привет

Возможно, вы заметили параметр self в функции класса. Но вызывали метод мы с помощью harry.greet(). И почему-то это сработало. 

Так происходит потому, что когда объект вызывает свой метод, сам объект является первым аргументом. То есть harry.greet() это то же самое, что и Person.greet(harry).

Обычно вызов метода со списком аргументов длины n равносилен вызову соответствующей функции со списком аргументов, который создается путем вставки объекта метода перед первым аргументов. 

По этим причинам первый аргумент функции в классе должен быть сам объект. Это и есть self — так договорились программисты на Python. Но в теории можно использовать и другое обозначение.

Теперь вы имеете представление о классах, экземплярах класса, функциях, методах. Главное — понимать их отличия.

Конструкторы

Функции класса, начинающиеся с двойного нижнего подчеркивания, называются специальными функциями

Наибольший интерес вызывает специальная функция __init__(). Она вызывается каждый раз, когда вы создаете новый объект класса. 

В ООП этот вид функций называют конструкторами. Обычно они используются для инициализации всех переменных класса.

class ComplexNumber:
    def __init__(self, r=0, i=0):
        self.real = r
        self.imag = i

    def get_data(self):
        print(f'{self.real}+{self.imag}j')


# Создаем новый объект ComplexNumber 
num1 = ComplexNumber(2, 3)

# Вызываем метод get_data() 
# Вывод: 2+3j
num1.get_data()

# Создаем еще один объект ComplexNumber 
# и новый атрибут 'attr'
num2 = ComplexNumber(5)
num2.attr = 10

# Вывод: (5, 0, 10)
print((num2.real, num2.imag, num2.attr))

# У объекта c1 нет атрибута 'attr', поэтому
# вызывается ошибка
# AttributeError: 'ComplexNumber' object has no attribute 'attr'
print(num1.attr)

Вывод:

2+3j
(5, 0, 10)
Traceback (most recent call last):
 File "<string>", line 27, in <module>
   print(num1.attr)
AttributeError: 'ComplexNumber' object has no attribute 'attr'

В этом примере мы объявили класс, представляющий комплексные числа. В нем две функции. Первая, __init__(), инициализирует переменные (по умолчанию это нули). Вторая, get_data(), позволяет правильно отображать числа в консоли.

Стоит отметить, что атрибуты объекта могут создаваться «на лету». Мы и создали, и считали атрибут attr объекта num2. Но это не значит, что этот атрибут будет доступен num1.

Как удалить атрибуты и объекты

Любой атрибут объекта можно в любой момент удалить. Сделать это можно с помощью оператора del. Попробуйте запустить следующую программу и проверьте, что она выводит.

>>> num1 = ComplexNumber(2,3)
>>> del num1.imag
>>> num1.get_data()
Traceback (most recent call last):
...
AttributeError: 'ComplexNumber' object has no attribute 'imag'
>>> del ComplexNumber.get_data
>>> num1.get_data()
Traceback (most recent call last):
...
AttributeError: 'ComplexNumber' object has no attribute 'get_data'

С помощью del можно удалить даже объект:

>>> c1 = ComplexNumber(1,3)
>>> del c1
>>> c1
Traceback (most recent call last):
...
NameError: name 'c1' is not defined

На самом деле всё намного сложнее. Когда мы выполняем строку c1 = ComplexNumber(1,3), создается новый экземпляр класса. Переменная с1 является ссылкой на него. 

После выполнения команды del c1 ссылка и имя c1 удаляются из соответствующего пространства имен. Однако объект все так же будет существовать. Так что если не связать новую переменную с этим объектом, он позже будет автоматически уничтожен. 

Удаление объектов, на которые нет ссылок, называется сборкой мусора

 

Проверьте себя
1. Что вернёт вызов Person.greet при обращении напрямую через класс (не через экземпляр)?
AОбъект метода (bound method)
BОшибку TypeError
CNone
DОбъект функции (function object)
2. Что произойдёт, если передать экземпляр в функцию str()?
AВернётся адрес объекта в памяти вида <__main__.Person object at ...>
BВсегда вернётся пустая строка
CВозникнет ошибка, если __str__ не определён
DPython вызовет метод __str__() объекта, если он определён
3. Что хранит атрибут __doc__ класса?
AИмя файла, в котором объявлен класс
BСтроку документации (docstring) класса
CСписок всех методов класса
DВерсию Python, под которой создан класс

Закрепите практикой

Задачи с автоматической проверкой — решайте прямо здесь, не уходя из учебника.