Как создать экземпляр класса python
Перейти к содержимому

Как создать экземпляр класса python

  • автор:

Создание экземпляра класса в Python

Процесс создания экземпляра класса запускается всякий раз, когда происходит вызов объекта класса Python. Процесс создания экземпляра состоит из двух отдельных шагов, которые можно описать следующим образом:

  • Создание нового экземпляра целевого класса;
  • Инициализация нового экземпляра с соответствующим начальным состоянием.

Для выполнения первого шага в классах Python есть специальный метод .__new__() , который отвечает за создание и возврат нового пустого объекта. Затем другой специальный метод .__init__() , принимает результирующий объект вместе с аргументами конструктора класса.

Метод .__init__() принимает новый объект в качестве первого аргумента self . Затем он устанавливает любой требуемый атрибут экземпляра в допустимое состояние, используя аргументы, переданные ему конструктором класса.

Короче говоря, процесс создания экземпляра Python начинается с запуска создателя экземпляра .__new__() для создания нового пустого объекта. Процесс продолжается инициализатором экземпляра .__init__() , который принимает аргументы конструктора для инициализации вновь созданного объекта.

Чтобы изучить внутреннюю работу процесса создания экземпляров Python, рассмотрите следующий пример класса Point , который (в демонстрационных целях) реализует пользовательскую версию обоих методов, .__new__() и .__init__() :

# point.py class Point: def __new__(cls, *args, **kwargs): print("1. Создается новый экземпляр Point.") return super().__new__(cls) def __init__(self, x, y): print("2. Инициализируется новый экземпляр Point.") self.x = x self.y = y def __repr__(self) -> str: return f"type(self).__name__>(x=self.x>, y=self.y>)" 

Описание того, что делает этот код:

  • Строка def __new__(cls, *args, **kwargs) определяет метод, который принимает класс в качестве первого аргумента. Обратите внимание, что использование cls в качестве имени этого аргумента является строгим соглашением в Python, точно так же, как использование self для имени текущего экземпляра. Метод также принимает *args и **kwargs , что позволяет передавать неопределенное количество аргументов инициализации базовому экземпляру.
  • Строка return super().__new__(cls) создает и возвращает новый экземпляр Point, вызывая метод родительского класса .__new__() с cls в качестве аргумента. Этот экземпляр будет первым аргументом для .__init__() . В этом примере объект является родительским классом, и вызов super() дает доступ к нему.
  • Строка def __init__(self, x, y) определяет метод конструктора, который отвечает за этап инициализации. Этот метод принимает первый аргумент с именем self , который содержит ссылку на текущий экземпляр и два дополнительных аргумента, x и y .
  • Внутри метода конструктора .__init__() инициализируются начальные значения атрибутов экземпляра Point.x и Point.y соответственно. Для этого он использует входные аргументы x и y .

Сохраним код, представленный выше в файл с именем point.py и запустим интерпретатор Python:

>>> from point import Point >>> point = Point(21, 42) # 1. Создается новый экземпляр Point. # 2. Инициализируется новый экземпляр Point. >>> point # Point(x=21, y=42) 

Операции, доступные экземплярам класса.

Единственные операции, понятные объектам-экземплярам класса, являются ссылки на атрибуты класса. Существует два вида допустимых имен атрибутов класса, это атрибуты данных и методы класса.

Атрибуты данных соответствуют «переменным экземпляра» в языке Smalltalk или «членам данных» в языке C++. Атрибуты данных класса в Python можно не объявлять, как например это делается с локальным переменным, они появляться динамически, когда им впервые присваивается значение. При этом, динамически созданные атрибуты хранятся в специальном словаре объекта-экземпляра x.__dict__ . Например, если x это экземпляр MyClass , то следующий фрагмент кода напечатает значение 16 .

Создайте файл test.py с определением класса MyClass и запустите его в интерактивном режиме командой: python3 -i test.py .

# файл `test.py` class MyClass: """Простой пример класса""" i = 12345 def f(self): return 'hello world' # запускаем: $ python3 -i test.py >>> x = MyClass() # обратите внимание, что атрибут # `counter` в классе не определен >>> x.counter = 1 # динамически созданные атрибуты экземпляра класса # хранятся в специальных словарях этих экземпляров >>> x.__dict__ # >>> while x.counter  10: . x.counter = x.counter * 2 . >>> x.counter # 16 # удаляем динамически созданный атрибут >>> del x.counter # смотрим специальный словарь экземпляра >>> x.__dict__ # <> # пытаемся получить значение x.counter # Traceback (most recent call last): # File "", line 1, in # AttributeError: 'MyClass' object has no attribute 'counter' 

Другой вид ссылки на атрибут объекта-экземпляра — это метод. Метод — это функция, которая принадлежит объекту класса. В языке Python термин «метод«, для экземпляров классов, не уникален: другие типы объектов также могут иметь свои методы. Например, объекты списка list() имеют методы list.append , list.insert , list.remove , list.sort и т. д. Дальше будем использовать термин «метод» исключительно для обозначения «методов объектов экземпляра класса«, если явно не указано иное.

Допустимые имена методов объекта экземпляра класса зависят от его класса. По определению, все атрибуты класса, являющиеся объектами функций, определяют соответствующие методы его экземпляров. Таким образом, в нашем примере x.f это допустимая ссылка на связанный метод, так как MyClass.f это функция. Тогда как x.i это НЕ метод, а ссылка на атрибут класса MyClass.i . При этом выражение x.f это объект связанного метода, т.е. не то же самое, что MyClass.f . Так как MyClass.f — это объект функции.

Смотрим пример, который это показывает:

>>> MyClass.f # >>> x.f # > >>> MyClass.i # 12345 >>> x.i # 12345 
  • ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
  • Пространство имен и область видимости в классах
  • Определение классов
  • Объект класса и конструктор класса
  • Создание экземпляра класса
  • Метод экземпляра класса
  • Что такое метод класса и зачем нужен
  • Что такое статический метод в классах Python и зачем нужен
  • Атрибуты класса и переменные экземпляра класса
  • Кэширование методов экземпляра декоратором lru_cache
  • Закрытые/приватные методы и переменные класса Python
  • Наследование классов
  • Множественное наследование классов
  • Абстрактные классы
  • Перегрузка методов в классе Python
  • Что такое миксины и как их использовать
  • Класс Python как структура данных, подобная языку C
  • Создание пользовательских типов данных
  • Специальные (магические) методы класса Python
  • Базовая настройка классов Python магическими методами
  • Настройка доступа к атрибутам класса Python
  • Дескриптор класса для чайников
  • Протокол дескриптора класса
  • Практический пример дескриптора
  • Использование метода .__new__() в классах Python
  • Специальный атрибут __slots__ класса Python
  • Специальный метод __init_subclass__ класса Python
  • Определение метаклассов metaclass
  • Эмуляция контейнерных типов в классах Python
  • Другие специальные методы класса
  • Как Python ищет специальные методы в классах
  • Шаблон проектирования Фабрика и его реализация

Создание классов и объектов

В языке программирования Python классы создаются с помощью инструкции class , за которой следует произвольное имя класса, после которого ставится двоеточие, далее с новой строки и с отступом реализуется тело класса:

class ИмяКласса: код_тела_класса

Если класс является дочерним, то родительские классы перечисляются в круглых скобках после имени класса.

Объект создается путем вызова класса по его имени. При этом после имени класса обязательно ставятся скобки:

ИмяКласса()

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

имя_переменной = ИмяКласса()

В последствии к объекту обращаются через связанную с ним переменную.

Пример «пустого» класса и двух созданных на его основе объектов:

>>> class A: . pass . >>> a = A() >>> b = A()

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

С точки зрения пространства имен класс можно представить подобным модулю. Также как в модуле в классе могут быть свои переменные со значениями и функции. Также как в модуле у класса есть собственное пространство имен, доступ к которому возможен через имя класса:

>>> class B: . n = 5 . def adder(v): . return v + B.n . >>> B.n 5 >>> B.adder(4) 9

Однако в случае классов используется особая терминология. Пусть имена, определенные в классе, называются атрибутами этого класса. В примере имена n и adder – это атрибуты класса B . Атрибуты-переменные часто называют полями или свойствами (в других языках понятия «поле» и «свойство» не совсем одно и то же). Полем является n . Атрибуты-функции называются методами. Методом в классе B является adder . Количество свойств и методов в классе может быть любым.

Класс как шаблон для создания объектов

На самом деле классы – не модули. Они своего рода шаблоны, от которых создаются объекты-экземпляры. Такие объекты наследуют от класса его атрибуты. Вернемся к нашему классу B и создадим на его основе два объекта:

>>> class B: . n = 5 . def adder(v): . return v + B.n . >>> a = B() >>> b = B()

У объектов, связанных с переменными a и b , нет собственного поля n . Однако они наследуют его от своего класса:

>>> a.n 5 >>> a.n is B.n True

То есть поля a.n и B.n – это одно и то же поле, к которому можно обращаться и через имя a , и через имя b , и через имя класса. Поле одно, ссылок на него три.

Однако что произойдет в момент присваивания этому полю значения через какой-нибудь объект-экземпляр?

>>> a.n = 10 >>> a.n 10 >>> b.n 5 >>> B.n 5

В этот момент у экземпляра появляется собственный атрибут n , который перекроет (переопределит) родительский, то есть тот, который достался от класса.

>>> a.n is B.n False >>> b.n is B.n True

При этом присвоение через B.n отразится только на b и B , но не на a :

>>> B.n = 100 >>> B.n, b.n, a.n (100, 100, 10)

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

>>> B.adder is b.adder False >>> type(B.adder) >>> type(b.adder)

Через имя класса мы вызываем функцию adder :

>>> B.adder(33) 133

Через имя объекта вызываем метод adder :

>>> b.adder(33) Traceback (most recent call last): File "", line 1, in TypeError: adder() takes 1 positional argument but 2 were given

В сообщении об ошибке говорится, что adder принимает только один аргумент, а было передано два. Откуда появился второй, если в скобках было указано только одно число?

Дело в том, что в отличии от функции в метод первым аргументом всегда передается объект, к которому применяется этот метод. То есть выражение b.adder(33) как бы преобразовывается в adder(b, 33) . Сам же b.adder как объект типа method хранит сведения, с каким классом он связан и какому объекту-экземпляру принадлежит:

>>> b.adder >

В нашем случае, чтобы вызывать adder через объекты-экземпляры, класс можно переписать так:

>>> class B: . n = 5 . def adder(obj, v): . return v + obj.n . >>> b = B() >>> b.adder(33) 38

В коде выше при вызове метода adder переменной-параметру obj присваивается объект, связанный с переменной, к которой применяется данный метод. В данном случае это объект, связанный с b . Если adder будет вызван на другой объект, то уже он будет присвоен obj :

>>> a = B() >>> a.n = 9 >>> a.adder(3) 12

В Python переменную-параметр метода, которая связывается с экземпляром своего класса, принято называть именем self. Таким образом, более корректный код будет таким:

>>> class B: . n = 5 . def adder(self, v): . return v + self.n

Можем ли мы все также вызывать adder как функцию, через имя класса? Вполне. Только теперь в функцию надо передавать два аргумента:

>>> B.adder(B, 200) 205 >>> B.adder(a, 200) 209

Здесь первым аргументом в функцию передается объект, у которого есть поле n лишь только потому, что далее к этому полю обращаются через выражение self.n .

Однако если атрибут определен так, что предполагается его работа в качестве метода, а не функции, то через класс его уже не вызывают (нет смысла, логика программы этого не подразумевает).

С другой стороны, в ООП есть понятие «статический метод». По сути это функция, которая может вызываться и через класс, и через объект, и которой первым аргументом не подставляется объект, на который она вызывается. В Python статический метод можно создать посредством использования специального декоратора.

Атрибут __dict__

В Python у объектов есть встроенные специальные атрибуты. Мы их не определяем, но они есть. Одним из таких атрибутов объекта является свойство __dict__ . Его значением является словарь, в котором ключи – это имена свойств экземпляра, а значения – текущие значения свойств.

>>> class B: . n = 5 . def adder(self, v): . return v + self.n . >>> w = B() >>> w.__dict__ <> >>> w.n = 8 >>> w.__dict__

В примере у экземпляра класса B сначала нет собственных атрибутов. Свойство n и метод adder – это атрибуты объекта-класса, а не объекта-экземпляра, созданного от этого класса. Лишь когда мы выполняем присваивание новому полю n экземпляра, у него появляется собственное свойство, что мы наблюдаем через словарь __dict__ .

В следующем уроке мы увидим, что свойства экземпляра обычно не назначаются за пределами класса. Это происходит в методах классах путем присваивание через self . Например, self.n = 10 .

Атрибут __dict__ используется не только для просмотра свойств объекта. С его помощью можно удалять, добавлять свойства, а также изменять их значения.

>>> w.__dict__['m'] = 100 >>> w.__dict__ >>> w.m 100

Практическая работа

Напишите программу по следующему описанию. Есть класс «Воин». От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.

Курс с примерами решений практических работ:
pdf-версия

X Скрыть Наверх

Объектно-ориентированное программирование на Python

Примеры работы с классами в Python

Python — объектно-ориентированный язык с начала его существования. Поэтому, создание и использование классов и объектов в Python просто и легко. Эта статья поможет разобраться на примерах в области поддержки объектно-ориентированного программирования Python. Если у вас нет опыта работы с объектно-ориентированным программированием (OOП), ознакомьтесь с вводным курсом или учебным пособием, чтобы понять основные понятия.

Создание классов

Оператор class создает новое определение класса. Имя класса сразу следует за ключевым словом class , после которого ставиться двоеточие:

class ClassName: """Необязательная строка документации класса""" class_suite
  • У класса есть строка документации, к которой можно получить доступ через ClassName.__doc__ .
  • class_suite состоит из частей класса, атрибутов данных и функции.

Пример создания класса на Python:

 
class Employee: """Базовый класс для всех сотрудников""" emp_count = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.emp_count += 1 def display_count(self): print('Всего сотрудников: %d' % Employee.empCount) def display_employee(self): print('Имя: <>. Зарплата: <>'.format(self.name, self.salary))
  • Переменная emp_count — переменная класса, значение которой разделяется между экземплярами этого класса. Получить доступ к этой переменной можно через Employee.emp_count из класса или за его пределами.
  • Первый метод __init__() — специальный метод, который называют конструктором класса или методом инициализации. Его вызывает Python при создании нового экземпляра этого класса.
  • Объявляйте другие методы класса, как обычные функции, за исключением того, что первый аргумент для каждого метода self . Python добавляет аргумент self в список для вас; и тогда вам не нужно включать его при вызове этих методов.

Создание экземпляров класса

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

 
# Это создаст первый объект класса Employee emp1 = Employee("Андрей", 2000) # Это создаст второй объект класса Employee emp2 = Employee("Мария", 5000)

Доступ к атрибутам

Получите доступ к атрибутам класса, используя оператор . после объекта класса. Доступ к классу можно получить используя имя переменой класса:

 
emp1.display_employee() emp2.display_employee() print("Всего сотрудников: %d" % Employee.emp_count)

Теперь, систематизируем все.

 
class Employee: """Базовый класс для всех сотрудников""" emp_count = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.emp_count += 1 def display_count(self): print('Всего сотрудников: %d' % Employee.emp_count) def display_employee(self): print('Имя: <>. Зарплата: <>'.format(self.name, self.salary)) # Это создаст первый объект класса Employee emp1 = Employee("Андрей", 2000) # Это создаст второй объект класса Employee emp2 = Employee("Мария", 5000) emp1.display_employee() emp2.display_employee() print("Всего сотрудников: %d" % Employee.emp_count)

При выполнении этого кода, мы получаем следующий результат:

Имя: Андрей. Зарплата: 2000 Имя: Мария. Зарплата: 5000 Всего сотрудников: 2 

Вы можете добавлять, удалять или изменять атрибуты классов и объектов в любой момент.

 
emp1.age = 7 # Добавит атрибут 'age' emp1.age = 8 # Изменит атрибут 'age' del emp1.age # Удалит атрибут 'age'

Вместо использования привычных операторов для доступа к атрибутам вы можете использовать эти функции:

  • getattr(obj, name [, default]) — для доступа к атрибуту объекта.
  • hasattr(obj, name) — проверить, есть ли в obj атрибут name .
  • setattr(obj, name, value) — задать атрибут. Если атрибут не существует, он будет создан.
  • delattr(obj, name) — удалить атрибут.
 
hasattr(emp1, 'age') # возвращает true если атрибут 'age' существует getattr(emp1, 'age') # возвращает значение атрибута 'age' setattr(emp1, 'age', 8) #устанавливает атрибут 'age' на 8 delattr(empl, 'age') # удаляет атрибут 'age'

Встроенные атрибуты класса

Каждый класс Python хранит встроенные атрибуты, и предоставляет к ним доступ через оператор . , как и любой другой атрибут:

  • __dict__ — словарь, содержащий пространство имен класса.
  • __doc__ — строка документации класса. None если, документация отсутствует.
  • __name__ — имя класса.
  • __module__ — имя модуля, в котором определяется класс. Этот атрибут __main__ в интерактивном режиме.
  • __bases__ — могут быть пустые tuple, содержащие базовые классы, в порядке их появления в списке базового класса.

Для вышеуказанного класса давайте попробуем получить доступ ко всем этим атрибутам:

 
class Employee: """Базовый класс для всех сотрудников""" emp_count = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.empCount += 1 def display_count(self): print('Всего сотрудников: %d' % Employee.empCount) def display_employee(self): print('Имя: <>. Зарплата: <>'.format(self.name, self.salary)) print("Employee.__doc__:", Employee.__doc__) print("Employee.__name__:", Employee.__name__) print("Employee.__module__:", Employee.__module__) print("Employee.__bases__:", Employee.__bases__) print("Employee.__dict__:", Employee.__dict__)

Когда этот код выполняется, он возвращает такой результат:

Employee.__doc__: Базовый класс для всех сотрудников Employee.__name__: Employee Employee.__module__: __main__ Employee.__bases__: (,) Employee.__dict__: , 'display_count': , 'display_employee': , '__dict__': , '__weakref__': > 

Удаление объектов (сбор мусора)

Python автоматически удаляет ненужные объекты (встроенные типы или экземпляры классов), чтобы освободить пространство памяти. С помощью процесса ‘Garbage Collection’ Python периодически восстанавливает блоки памяти, которые больше не используются.

Сборщик мусора Python запускается во время выполнения программы и тогда, когда количество ссылок на объект достигает нуля. С изменением количества обращений к нему, меняется количество ссылок.

Когда объект присваивают новой переменной или добавляют в контейнер (список, кортеж, словарь), количество ссылок объекта увеличивается. Количество ссылок на объект уменьшается, когда он удаляется с помощью del , или его ссылка выходит за пределы видимости. Когда количество ссылок достигает нуля, Python автоматически собирает его.

a = 40 # создали объект b = a # увеличивает количество ссылок c = [b] # увеличивает количество ссылок del a # уменьшает количество ссылок b = 100 # уменьшает количество ссылок c[0] = -1 # уменьшает количество ссылок

Обычно вы не заметите, когда сборщик мусора уничтожает экземпляр и очищает свое пространство. Но классом можно реализовать специальный метод __del__() , называемый деструктором. Он вызывается, перед уничтожением экземпляра. Этот метод может использоваться для очистки любых ресурсов памяти.

Пример работы __del__()
Деструктор __del__() выводит имя класса того экземпляра, который должен быть уничтожен:

 
class Point: def __init__(self, x=0, y=0): self.x = x self.y = y def __del__(self): class_name = self.__class__.__name__ print('<> уничтожен'.format(class_name)) pt1 = Point() pt2 = pt1 pt3 = pt1 print(id(pt1), id(pt2), id(pt3)) # выведите id объектов del pt1 del pt2 del pt3

Когда вышеуказанный код выполняется и выводит следующее:

17692784 17692784 17692784 Point уничтожен

В идеале вы должны создавать свои классы в отдельном модуле. Затем импортировать их в основной модуль программы с помощью import SomeClass .

Наследование класса в python

Наследование — это процесс, когда один класс наследует атрибуты и методы другого. Класс, чьи свойства и методы наследуются, называют Родителем или Суперклассом. А класс, свойства которого наследуются — класс-потомок или Подкласс.

Вместо того, чтобы начинать с нуля, вы можете создать класс, на основе уже существующего. Укажите родительский класс в круглых скобках после имени нового класса.

Класс наследник наследует атрибуты своего родительского класса. Вы можете использовать эти атрибуты так, как будто они определены в классе наследнике. Он может переопределять элементы данных и методы родителя.

Синтаксис наследования класса

Классы наследники объявляются так, как и родительские классы. Только, список наследуемых классов, указан после имени класса.

class SubClassName(ParentClass1[, ParentClass2, . ]): """Необязательная строка документации класса""" class_suite

Пример наследования класса в Python

 
class Parent: # объявляем родительский класс parent_attr = 100 def __init__(self): print('Вызов родительского конструктора') def parent_method(self): print('Вызов родительского метода') def set_attr(self, attr): Parent.parent_attr = attr def get_attr(self): print('Атрибут родителя: <>'.format(Parent.parent_attr)) class Child(Parent): # объявляем класс наследник def __init__(self): print('Вызов конструктора класса наследника') def child_method(self): print('Вызов метода класса наследника') c = Child() # экземпляр класса Child c.child_method() # вызов метода child_method c.parent_method() # вызов родительского метода parent_method c.set_attr(200) # еще раз вызов родительского метода c.get_attr() # снова вызов родительского метода

Когда этот код выполняется, он выводит следующий результат:

Вызов конструктора класса наследника Вызов метода класса наследника Вызов родительского метода Атрибут родителя: 200

Аналогичным образом вы можете управлять классом с помощью нескольких родительских классов:

class A: # объявите класс A . class B: # объявите класс B . class C(A, B): # C наследуется от A и B . 

Вы можете использовать функции issubclass() или isinstance() для проверки отношений двух классов и экземпляров.

  • Логическая функция issubclass(sub, sup) возвращает значение True , если данный подкласс sub действительно является подклассом sup .
  • Логическая функция isinstance(obj, Class) возвращает True , если obj является экземпляром класса Class или является экземпляром подкласса класса.

Переопределение методов

Вы всегда можете переопределить методы родительского класса. В вашем подклассе могут понадобиться специальные функции. Это одна из причин переопределения родительских методов.

Пример переопределения методов:

 
class Parent: # объявите родительский класс def my_method(self): print('Вызов родительского метода') class Child(Parent): # объявите класс наследник def my_method(self): print('Вызов метода наследника') c = Child() # экземпляр класса Child c.my_method() # метод переопределен классом наследником

Когда этот код выполняется, он производит следующий результат:

Вызов метода наследника

Популярные базовые методы

В данной таблице перечислены некоторые общие функции. Вы можете переопределить их в своих собственных классах.

Метод, описание и пример вызова
1 __init__(self [, args. ]) — конструктор (с любыми необязательными аргументами)
obj = className(args)
2 __del__(self) — деструктор, удаляет объект
del obj
3 __repr__(self) — программное представление объекта
repr(obj)
4 __str__(self) — строковое представление объекта
str(obj)

Пример использования __add__

Предположим, вы создали класс Vector для представления двумерных векторов. Что происходит, когда вы используете дополнительный оператор для их добавления? Скорее всего, Python будет против.

Однако вы можете определить метод __add__ в своем классе для добавления векторов и оператор + будет вести себя так как нужно.

 
class Vector: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return 'Vector (<>, <>)'.format(self.a, self.b) def __add__(self, other): return Vector(self.a + other.a, self.b + other.b) v1 = Vector(2, 10) v2 = Vector(5, -2) print(v1 + v2)

При выполнении этого кода, мы получим:

Vector(7, 8)

Приватные методы и атрибуты

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

Пример приватного атрибута:

 
class JustCounter: __secret_count = 0 def count(self): self.__secret_count += 1 print(self.__secret_count) counter = JustCounter() counter.count() counter.count() print(counter.__secret_count)

При выполнении данного кода, имеем следующий результат:

1 2 Traceback (most recent call last): File "test.py", line 12, in print(counter.__secret_count) AttributeError: 'JustCounter' object has no attribute '__secret_count'

Вы можете получить доступ к таким атрибутам, так object._className__attrName . Если вы замените свою последнюю строку следующим образом, то она будет работать.

Python. Урок 14. Классы и объекты

Follow us on Google Plus Follow us on rss

Данный урок посвящен объектно-ориентированному программированию в Python. Разобраны такие темы как создание объектов и классов, работа с конструктором, наследование и полиморфизм в Python.

Основные понятия объектно-ориентированного программирования

Объектно-ориентированное программирование (ООП) является методологией разработки программного обеспечения, в основе которой лежит понятие класса и объекта, при этом сама программа создается как некоторая совокупность объектов, которые взаимодействую друг с другом и с внешним миром. Каждый объект является экземпляром некоторого класса. Классы образуют иерархии. Более подробно о понятии ООП можно прочитать на википедии.

Выделяют три основных “столпа” ООП- это инкапсуляция, наследование и полиморфизм.

Инкапсуляция

Под инкапсуляцией понимается сокрытие деталей реализации, данных и т.п. от внешней стороны . Например, можно определить класс “холодильник”, который будет содержать следующие данные: производитель, объем, количество камер хранения, потребляемая мощность и т.п., и методы: открыть/закрыть холодильник, включить/выключить, но при этом реализация того, как происходит непосредственно включение и выключение пользователю вашего класса не доступна, что позволяет ее менять без опасения, что это может отразиться на использующе й класс «холодильник» программе. При этом класс становится новым типом данных в рамках разрабатываемой программы. Можно создавать переменные этого нового типа, такие переменные называются объекты.

Наследование

Под наследованием понимается возможность создания нового класса на базе существующего. Наследование предполагает наличие отношения “является” между классом наследником и классом родителем. При этом класс потомок будет содержать те же атрибуты и методы, что и базовый класс, но при этом его можно (и нужно) расширять через добавление новых методов и атрибутов.

Примером базового класса, демонстрирующего наследование, можно определить класс “автомобиль”, имеющий атрибуты: масса, мощность двигателя, объем топливного бака и методы: завести и заглушить. У такого класса может быть потомок – “грузовой автомобиль”, он будет содержать те же атрибуты и методы, что и класс “автомобиль”, и дополнительные свойства: количество осей, мощность компрессора и т.п..

Полиморфизм

Полиморфизм позволяет одинаково обращаться с объектами, имеющими однотипный интерфейс, независимо от внутренней реализации объекта. Например, с объектом класса “грузовой автомобиль” можно производить те же операции, что и с объектом класса “автомобиль”, т.к. первый является наследником второго, при этом обратное утверждение неверно (во всяком случае не всегда). Другими словами полиморфизм предполагает разную реализацию методов с одинаковыми именами. Это очень полезно при наследовании, когда в классе наследнике можно переопределить методы класса родителя.

Классы в Python

Создание классов и объектов

Создание класса в Python начинается с инструкции class. Вот так будет выглядеть минимальный класс.

class C: pass

Класс состоит из объявления (инструкция class), имени класса (нашем случае это имя C) и тела класса, которое содержит атрибуты и методы (в нашем минимальном классе есть только одна инструкция pass).

Для того чтобы создать объект класса необходимо воспользоваться следующим синтаксисом:

имя_объекта = имя_класса()

Статические и динамические атрибуты класса

Как уже было сказано выше, класс может содержать атрибуты и методы. Атрибут может быть статическим и динамическим (уровня объекта класса). Суть в том, что для работы со статическим атрибутом, вам не нужно создавать экземпляр класса, а для работы с динамическим – нужно. Пример:

class Rectangle: default_color = "green" def __init__(self, width, height): self.width = width self.height = height

В представленном выше классе, атрибут default_color – это статический атрибут, и доступ к нему, как было сказано выше, можно получить не создавая объект класса Rectangle.

>>> Rectangle.default_color 'green'

width и height – это динамические атрибуты, при их создании было использовано ключевое слово self. Пока просто примите это как должное, более подробно про self будет рассказано ниже. Для доступа к width и height предварительно нужно создать объект класса Rectangle:

>>> rect = Rectangle(10, 20) >>> rect.width 10 >>> rect.height 20

Если обратиться через класс, то получим ошибку:

>>> Rectangle.width Traceback (most recent call last): File "", line 1, in AttributeError: type object 'Rectangle' has no attribute 'width'

При этом, если вы обратитесь к статическому атрибуту через экземпляр класса, то все будет ОК, до тех пор, пока вы не попытаетесь его поменять.

Проверим ещё раз значение атрибута default_color:

>>> Rectangle.default_color 'green'

Присвоим ему новое значение:

>>> Rectangle.default_color = "red" >>> Rectangle.default_color 'red'

Создадим два объекта класса Rectangle и проверим, что default_color у них совпадает:

>>> r1 = Rectangle(1,2) >>> r2 = Rectangle(10, 20) >>> r1.default_color 'red' >>> r2.default_color 'red'

Если поменять значение default_color через имя класса Rectangle, то все будет ожидаемо: у объектов r1 и r2 это значение изменится, но если поменять его через экземпляр класса, то у экземпляра будет создан атрибут с таким же именем как статический, а доступ к последнему будет потерян:

Меняем default_color через r1:

>>> r1.default_color = "blue" >>> r1.default_color 'blue'

При этом у r2 остается значение статического атрибута:

>>> r2.default_color 'red' >>> Rectangle.default_color 'red'

Вообще напрямую работать с атрибутами – не очень хорошая идея, лучше для этого использовать свойства.

Методы класса

Добавим к нашему классу метод. Метод – это функция, находящаяся внутри класса и выполняющая определенную работу.

Методы бывают статическими, классовыми (среднее между статическими и обычными) и уровня класса (будем их называть просто словом метод). Статический метод создается с декоратором @staticmethod, классовый – с декоратором @classmethod, первым аргументом в него передается cls, обычный метод создается без специального декоратора, ему первым аргументом передается self:

class MyClass: @staticmethod def ex_static_method(): print("static method") @classmethod def ex_class_method(cls): print("class method") def ex_method(self): print("method")

Статический и классовый метод можно вызвать, не создавая экземпляр класса, для вызова ex_method() нужен объект:

>>> MyClass.ex_static_method() static method >>> MyClass.ex_class_method() class method >>> MyClass.ex_method() Traceback (most recent call last): File "", line 1, in TypeError: ex_method() missing 1 required positional argument: 'self' >>> m = MyClass() >>> m.ex_method() method

Конструктор класса и инициализация экземпляра класса

В Python разделяют конструктор класса и метод для инициализации экземпляра класса. Конструктор класса это метод __new__(cls, *args, **kwargs) для инициализации экземпляра класса используется метод __init__(self). При этом, как вы могли заметить __new__ – это классовый метод, а __init__ таким не является. Метод __new__ редко переопределяется, чаще используется реализация от базового класса object (см. раздел Наследование), __init__ же наоборот является очень удобным способом задать параметры объекта при его создании.

Создадим реализацию класса Rectangle с измененным конструктором и инициализатором, через который задается ширина и высота прямоугольника:

class Rectangle: def __new__(cls, *args, **kwargs): print("Hello from __new__") return super().__new__(cls) def __init__(self, width, height): print("Hello from __init__") self.width = width self.height = height >>> rect = Rectangle(10, 20) Hello from __new__ Hello from __init__ >>> rect.width 10 >>> rect.height 20

Что такое self?

До этого момента вы уже успели познакомиться с ключевым словом self. self – это ссылка на текущий экземпляр класса, в таких языках как Java, C# аналогом является ключевое слово this. Через self вы получаете доступ к атрибутам и методам класса внутри него:

class Rectangle: def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height

В приведенной реализации метод area получает доступ к атрибутам width и height для расчета площади. Если бы в качестве первого параметра не было указано self, то при попытке вызвать area программа была бы остановлена с ошибкой.

Уровни доступа атрибута и метода

Если вы знакомы с языками программирования Java, C#, C++ то, наверное, уже задались вопросом: “а как управлять уровнем доступа?”. В перечисленных языка вы можете явно указать для переменной, что доступ к ней снаружи класса запрещен, это делается с помощью ключевых слов (private, protected и т.д.). В Python таких возможностей нет, и любой может обратиться к атрибутам и методам вашего класса, если возникнет такая необходимость. Это существенный недостаток этого языка, т.к. нарушается один из ключевых принципов ООП – инкапсуляция. Хорошим тоном считается, что для чтения/изменения какого-то атрибута должны использоваться специальные методы, которые называются getter/setter, их можно реализовать, но ничего не помешает изменить атрибут напрямую. При этом есть соглашение, что метод или атрибут, который начинается с нижнего подчеркивания, является скрытым, и снаружи класса трогать его не нужно (хотя сделать это можно).

Внесем соответствующие изменения в класс Rectangle:

class Rectangle: def __init__(self, width, height): self._width = width self._height = height def get_width(self): return self._width def set_width(self, w): self._width = w def get_height(self): return self._height def set_height(self, h): self._height = h def area(self): return self._width * self._height

В приведенном примере для доступа к _width и _height используются специальные методы, но ничего не мешает вам обратиться к ним (атрибутам) напрямую.

>>> rect = Rectangle(10, 20) >>> rect.get_width() 10 >>> rect._width 10

Если же атрибут или метод начинается с двух подчеркиваний, то тут напрямую вы к нему уже не обратитесь (простым образом). Модифицируем наш класс Rectangle:

class Rectangle: def __init__(self, width, height): self.__width = width self.__height = height def get_width(self): return self.__width def set_width(self, w): self.__width = w def get_height(self): return self.__height def set_height(self, h): self.__height = h def area(self): return self.__width * self.__height

Попытка обратиться к __width напрямую вызовет ошибку, нужно работать только через get_width():

>>> rect = Rectangle(10, 20) >>> rect.__width Traceback (most recent call last): File "", line 1, in AttributeError: 'Rectangle' object has no attribute '__width' >>> rect.get_width() 10

Но на самом деле это сделать можно, просто этот атрибут теперь для внешнего использования носит название: _Rectangle__width:

>>> rect._Rectangle__width 10 >>> rect._Rectangle__width = 20 >>> rect.get_width() 20

Свойства

Свойством называется такой метод класса, работа с которым подобна работе с атрибутом. Для объявления метода свойством необходимо использовать декоратор @property.

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

Сделаем реализацию класса Rectangle с использованием свойств:

class Rectangle: def __init__(self, width, height): self.__width = width self.__height = height @property def width(self): return self.__width @width.setter def width(self, w): if w > 0: self.__width = w else: raise ValueError @property def height(self): return self.__height @height.setter def height(self, h): if h > 0: self.__height = h else: raise ValueError def area(self): return self.__width * self.__height

Теперь работать с width и height можно так, как будто они являются атрибутами:

>>> rect = Rectangle(10, 20) >>> rect.width 10 >>> rect.height 20

Можно не только читать, но и задавать новые значения свойствам:

>>> rect.width = 50 >>> rect.width 50 >>> rect.height = 70 >>> rect.height 70

Если вы обратили внимание: в setter’ах этих свойств осуществляется проверка входных значений, если значение меньше нуля, то будет выброшено исключение ValueError:

>>> rect.width = -10 Traceback (most recent call last): File "", line 1, in File "test.py", line 28, in width raise ValueError ValueError

Наследование

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

Синтаксически создание класса с указанием его родителя выглядит так:

class имя_класса(имя_родителя1, [имя_родителя2,…, имя_родителя_n])

Переработаем наш пример так, чтобы в нем присутствовало наследование:

class Figure: def __init__(self, color): self.__color = color @property def color(self): return self.__color @color.setter def color(self, c): self.__color = c class Rectangle(Figure): def __init__(self, width, height, color): super().__init__(color) self.__width = width self.__height = height @property def width(self): return self.__width @width.setter def width(self, w): if w > 0: self.__width = w else: raise ValueError @property def height(self): return self.__height @height.setter def height(self, h): if h > 0: self.__height = h else: raise ValueError def area(self): return self.__width * self.__height

Родительским классом является Figure, который при инициализации принимает цвет фигуры и предоставляет его через свойства. Rectangle – класс наследник от Figure. Обратите внимание на его метод __init__: в нем первым делом вызывается конструктор (хотя это не совсем верно, но будем говорить так) его родительского класса:

super – это ключевое слово, которое используется для обращения к родительскому классу.

Теперь у объекта класса Rectangle помимо уже знакомых свойств width и height появилось свойство color:

>>> rect = Rectangle(10, 20, "green") >>> rect.width 10 >>> rect.height 20 >>> rect.color 'green' >>> rect.color = "red" >>> rect.color 'red'

Полиморфизм

Как уже было сказано во введении в рамках ООП полиморфизм, как правило, используется с позиции переопределения методов базового класса в классе наследнике. Проще всего это рассмотреть на примере. Добавим в наш базовый класс метод info(), который печатает сводную информацию по объекту класса Figure и переопределим этот метод в классе Rectangle, добавим в него дополнительные данные:

class Figure: def __init__(self, color): self.__color = color @property def color(self): return self.__color @color.setter def color(self, c): self.__color = c def info(self): print("Figure") print("Color: " + self.__color) class Rectangle(Figure): def __init__(self, width, height, color): super().__init__(color) self.__width = width self.__height = height @property def width(self): return self.__width @width.setter def width(self, w): if w > 0: self.__width = w else: raise ValueError @property def height(self): return self.__height @height.setter def height(self, h): if h > 0: self.__height = h else: raise ValueError def info(self): print("Rectangle") print("Color: " + self.color) print("Width: " + str(self.width)) print("Height: " + str(self.height)) print("Area: " + str(self.area())) def area(self): return self.__width * self.__height

Посмотрим, как это работает

>>> fig = Figure("orange") >>> fig.info() Figure Color: orange >>> rect = Rectangle(10, 20, "green") >>> rect.info() Rectangle Color: green Width: 10 Height: 20 Area: 200

Таким образом, класс наследник может расширять функционал класса родителя.

P.S.

Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. На нашем сайте вы можете найти вводные уроки по этой теме. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.

Раздел: Python Уроки по Python Метки: Python, Уроки Python

Python. Урок 14. Классы и объекты : 20 комментариев

  1. Алексей 19.07.2018 А вот если Вы добавите вот это
    .entry-title a:last-child float:right;
    >
    в свой css будет намного удобнее, нежели вы будите использовать 2-ную табуляцию в HTML. Спасибо.
  1. writer 04.12.2018 Спасибо за замечание! Добавим!
  1. Мария04.02.2021 круто.
  1. writer 13.08.2019 Да, это действительно ссылка на текущий объект. Нужно будет вообще этот урок переработать, в нем плохо раскрыты многие вопросы! Спасибо за комментарий!
  1. writer 02.09.2019 ОК, спасибо! Добавим!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *