Понимание переменных класса и экземпляра в Python 3
При изучении объектно-ориентированного программирования на Python может возникнуть несколько сложностей, когда дело доходит до разграничения переменных класса и экземпляра. В этом руководстве я объясню разницу между переменными класса и экземпляра и приведу примеры, демонстрирующие различные варианты использования.
Переменные класса и экземпляра
Во-первых, быстрый обзор, если вы новичок в объектно-ориентированном программировании. Класс - это шаблон для создания объектов, а экземпляр - это сам объект. Классы часто представляют что-то в реальном мире, так что представьте, хотите ли вы создать класс, списка студентов. Вы можете создать класс с именем Student, который представляет собой шаблон, который определяет различные атрибуты студента. Таким образом, каждый студент является экземпляром класса Student.
При работе с данными любого типа некоторые атрибуты будут уникальными, а некоторые будут общими. Рассмотрим пример с учениками: у каждого учащегося в этом классе один и тот же номер и один учитель, но у каждого из них есть уникальное имя, возраст и любимый предмет.
Переменные класса
Переменные класса обычно являются переменными, которые являются общими для всех экземпляров. И они определены так:
class Student: teacher = 'Mrs. Jones' # переменная класса
Каждый экземпляр класса будет иметь одинаковое значение для этих переменных:
tom = Student() susan = Student() print(tom.teacher) >> "Mrs. Jones" print(susan.teacher) >> "Mrs. Jones"
Переменные экземпляра
Переменные экземпляра (также называемые атрибутами данных) уникальны для каждого экземпляра класса и определяются в методе класса, например:
class Student: teacher = 'Mrs. Jones' # переменная класса def __init__(self, name): self.name = name # переменная экземпляра
Посмотрите, как каждый экземпляр теперь содержит уникальное значение name:
tom = Student('Tom') susan = Student('Susan') print(tom.name) >> "Tom" print(susan.name) >> "Susan"
Резюме
Это был только базовый обзор переменных класса и экземпляра. Мы углубимся в следующие шаги, но наиболее важный вывод заключается в том, что переменные класса обычно используются для значений, которые являются общими для всех экземпляров класса, в то время как переменные экземпляра используются для значений, уникальных для каждого экземпляра.
Чего ожидать от переменных класса
Переменные класса являются общими для всех экземпляров класса. Напоминаем, что они определены так:
class Student: teacher = 'Mrs. Jones'
Иными словами, переменные класса ссылаются на одно и то же место в памяти. Смотрите следующее:
tom = Student() susan = Student() id(tom.teacher) == id(susan.teacher) >> True
Функция id возвращает адрес объекта в памяти для реализации CPython.
Таким образом, с помощью функции id мы можем подтвердить, что атрибут teacher ссылается на то же место в памяти.
Изменение переменной класса
Что произойдет, если мы изменим переменную класса даже после создания экземпляров?
tom = Student() tom.teacher >> Mrs. Jones Student.teacher = 'Mr. Smith' tom.teacher >> Mr. Smith
Как и следовало ожидать, поскольку переменная teacher ссылается на общее местоположение в памяти, она также обновляется в экземпляре.
Изменение переменной экземпляра
Это, вероятно, наиболее очевидное и ожидаемое поведение, поэтому не стесняйтесь пропустить этот шаг. Но я все же покажу несколько примеров для полноты.
Рассмотрим наш класс Student с переменными класса и экземпляра:
class Student: teacher = 'Mrs. Jones' def __init__(self, name): self.name = name
Мы видим, что каждый экземпляр класса имеет уникальный адрес памяти для имени:
tom = Student('Tom') susan = Student('Susan') id(tom.name) == id(susan.name) >> False
Как и следовало ожидать, обновление атрибута name в одном экземпляре не влияет на другой:
tom.name >> Tom susan.name >> Susan tom.name = 'Thomas' tom.name >> Thomas susan.name >> Susan
Переменные экземпляра переопределяют переменные класса (и методы)
Важно отметить, что переменные экземпляра (или атрибуты данных) переопределяют переменные класса.
Помните следующее?
tom = Student() susan = Student() id(tom.teacher) == id(susan.teacher) >> True
Что произойдет, если мы изменим атрибут teacher прямо в одном из случаев:
tom.teacher = 'Mr. Clark'
Это важно отметить: переменные экземпляров не нужно объявлять, они создаются всякий раз, когда им присваиваются, а переменные экземпляра переопределяют переменные класса. Это означает, что в экземпляре Tom teacher больше не ссылается на переменную класса, а на вновь созданную переменную экземпляра.
И, естественно, экземпляр Сьюзен не затронут:
tom.teacher >> Mr. Clark susan.teacher >> Mrs. Jones
Надеюсь, вы видите, как такое поведение может привести к путанице. По этой причине важно сохранять организованные имена переменных. Если переменная объявлена как переменная класса, она (обычно) не должна быть переопределена. Переменные экземпляра могут быть определены в очевидных местах, например, метод __init__. Часто хорошо придумать соглашение об именовании переменных. Например, методы класса должны быть глаголами, существительными переменных класса и существительными переменных экземпляра с префиксом «_».
Использование изменяемых объектов в качестве переменных класса
В связи с предыдущим шагом, будьте осторожны при использовании изменяемых объектов в качестве переменных класса. Вы можете быть удивлены поведением.
Представьте, что мы хотим получить список результатов тестов студента. Мы могли бы составить такой класс:
class Student: teacher = 'Mrs. Jones' test_scores = [] def __init__(self, name): self.name = name def add_score(self, score): self.test_scores.append(score)
У нас есть переменная класса для хранения баллов, и у нас есть метод класса для добавления баллов. Теперь давайте добавим несколько баллов.
tom = Student('Tom') susan = Student('Susan') tom.add_score(90) susan.add_score(100)
Можете ли вы угадать, какую ошибку мы только что сделали?
tom.test_scores >> [90, 100]
Да, test_scores - это переменная класса, а не переменная экземпляра. Каждый экземпляр просто добавляет значения в переменную класса. Мы действительно хотим, чтобы каждый экземпляр содержал свой собственный список test_scores.
Так что лучший класс может выглядеть так:
class Student: teacher = 'Mrs. Jones' def __init__(self, name): self.name = name self.test_scores = [] def add_score(self, score): self.test_scores.append(score)
И теперь наша проблема решена!
Заключение
Надеюсь, вы узнали разницу между переменными класса и экземпляра и что ожидать от каждой из них. Если я что-то упустил в этом руководстве или у вас есть отличные примеры путаницы переменных класса и экземпляра, пожалуйста, прокомментируйте ниже. Я был бы рад добавить их в это руководство!