Что такое метод класса в Python и зачем нужен.
Альтернативный конструктор (несколько конструкторов) класса в Python.
Сразу начнем с примера простого класса, который содержит обычный метод и метод класса:
class MyClass: def method(self): return 'instance method called', self @classmethod def classmethod(cls): return 'class method called', cls
Как работают методы класса в Python?
В Python, методы класса отмечаются декоратором @classmethod
, следовательно в приведенном выше примере, метод класса будет определен в функции classmethod()
.
Как можно заметить, метод класса вместо того, чтобы принимать аргумент self
, принимает аргумент cls
. При вызове метода этот аргумент указывает на сам класс, а не на экземпляр класса.
Поскольку метод класса имеет доступ только к аргументу cls
, он не может изменять состояние экземпляра объекта. Для этого потребуется доступ к аргументу self
. НО все же методы класса могут изменять состояние класса, которое применяется ко всем экземплярам класса.
MyClass()
настроен таким образом, что реализация каждого метода возвращает кортеж для отслеживания, что происходит, и к каким частям класса или объекта метод может получить доступ.
Вот что происходит, когда мы вызываем метод экземпляра:
>>> obj = MyClass() >>> obj.method() # ('instance method called', <__main__.MyClass object at 0x7f9748403f40>) # Можно передать объект `obj` экземпляра вручную, # для получения того же результата >>> MyClass.method(obj) # ('instance method called', <__main__.MyClass object at 0x7f9748403f40>)
Этот кусок кода подтвердил, что метод экземпляра класса имеет доступ к экземпляру объекта, напечатанному как <__main__.MyClass object>
через аргумент self
.
Кстати, методы экземпляра также могут получить доступ к самому классу через атрибут self. __class__
. Это делает методы экземпляра мощными с точки зрения ограничений доступа — они могут изменять состояние как экземпляра объекта, так и самого класса.
Попробуем вызвать метод класса:
>>> obj.classmethod() # ('class method called', <class '__main__.MyClass'>)
Вызов метода класса obj.classmethod()
показал, что он не имеет доступа к объекту <__main__.MyClass object>
, а только к объекту <class '__main__.MyClass'>
, представляющему сам класс (в Python все является объектом, даже сами классы).
Обратите внимание, как Python автоматически передает класс в качестве первого аргумента функции, когда вызывается MyClass.classmethod()
. Вызов метода в Python через точечную нотацию запускает это поведение. Параметр self
в методах экземпляра работает точно так же.
Теперь посмотрим, что происходит, когда попытаться вызвать эти методы в самом классе — без предварительного создания экземпляра объекта:
>>> MyClass. classmethod() # ('class method called', <class '__main__.MyClass'>) >>> MyClass.method() # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # TypeError: method() missing 1 required positional argument: 'self'
Из примера видно, что можно нормально вызвать метод класса MyClass.classmethod()
, но попытка вызвать метод экземпляра MyClass.method()
завершилась ошибкой TypeError
. Этого следовало ожидать, ведь экземпляр объекта не создан, что означает, что Python не может заполнить аргумент self
и следовательно, вызов не выполняется.
Для чего нужны методы класса в Python?
Следующие примеры кода должны сделать понимание метода класса более ясным. Далее рассмотрим пример класса, имеющего дело с информацией о дате (это будет шаблон):
class Date: def __init__(self, day=0, month=0, year=0): self.day = day self.month = month self.year = year def string_to_db(self): return f'{self. year}-{self.month}-{self.day}'
Этот класс, очевидно, можно использовать для хранения информации об определенных датах, без информации о часовом поясе (предположим, что все даты представлены в формате UTC).
Здесь есть конструктор __init__
, типичный инициализатор экземпляров классов Python, который получает аргументы как типичный метод экземпляра, имея первый необязательный аргумент self
, который содержит ссылку на вновь созданный экземпляр.
Например есть несколько задач, которые можно решить при помощи будущих методов этого класса, не только определенного для примера метода, банального перевода числовых значений в формат строки с датой для баз данных.
Предположим, что мы хотим создать много экземпляров класса Date()
с информацией о дате, поступающей из внешнего источника, в виде строки с форматом dd.mm.yyyy
. Предположим, нужно сделать это в разных местах исходного кода проекта.
Итак, что для этого необходимо сделать:
- Выполнить синтаксический анализ строки, получить день, месяц и год как три целочисленные переменные или кортеж из трех элементов, включающий эти переменные.
- Создать экземпляр
Date
, передав эти значения в конструктор класса при его создании.
Это будет выглядеть так:
>>> string_date = '10.10.2020' >>> day, month, year = map(int, string_date.split('.')) >>> date = Date(day, month, year) >>> date.string_to_db() # '2020-10-10'
Для выполнения этой задачи, например C++ или Java может реализовать такую возможность с перегрузкой метода __init__
, но в Python перегрузка методов отсутствует. Вместо нее необходимо использовать метод класса (декоратор @classmethod).
Создадим еще один «конструктор».
class Date(object): def __init__(self, day=0, month=0, year=0): self.day = day self.month = month self.year = year @classmethod def from_string(cls, date_as_string): day, month, year = map(int, date_as_string.split('.')) date1 = cls(day, month, year) return date1 def string_to_db(self): return f'{self. year}-{self.month}-{self.day}'
Более подробно о перегрузке смотрите в материале «Перегрузка методов в Python».
Обратите внимание, как используется аргумент cls
в методе класса from_string()
вместо прямого вызова конструктора класса Date()
.
Теперь создать новый экземпляр Date
, так же, можно следующим образом:
>>> date1 = Date.from_string('30.12.2020') >>> date1.string_to_db() # '2020-12-30' >>> date2 = Date.from_string('01.01.2021') >>> date2.string_to_db() # '2021-1-1'
Рассмотрим приведенную выше реализацию чтобы понять, какие преимущества здесь есть:
- Реализован синтаксический анализ строки даты в одном месте, и теперь его можно использовать повторно.
- Инкапсуляция здесь отлично работает. Если вы думаете, что можете реализовать синтаксический анализ строк как единственную функцию в другом месте, это решение намного лучше соответствует парадигме ООП.
cls
— это объект, который содержит сам класс, а не его экземпляр. Это довольно круто, потому что, если мы наследуем классDate
, для всех дочерних элементов также будет определен метод классаfrom_string()
.
Используя методы класса, можно добавить столько альтернативных конструкторов, сколько необходимо. Такое поведение может сделать интерфейс создаваемых классов самодокументированным (до определенной степени конечно) и упростить их использование.
Python. Урок 14. Классы и объекты
Данный урок посвящен объектно-ориентированному программированию в 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 "<stdin>", line 1, in <module> 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 "<stdin>", line 1, in <module> 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 "<stdin>", line 1, in <module> 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 "<stdin>", line 1, in <module> 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().__init__(color)
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. Урок 13. Модули и пакеты Python. Урок 15. Итераторы и генераторы>>>
Метод класса Python, объясненный с примерами — PYnative
В объектно-ориентированном программировании мы используем методы экземпляра и методы класса. Внутри класса мы можем определить следующие три типа методов.
- Метод экземпляра : Используется для доступа или изменения состояния объекта. Если мы используем переменные экземпляра внутри метода, такие методы называются методами экземпляра. Он должен иметь параметр
self
для ссылки на текущий объект. - Метод класса : Используется для доступа или изменения состояния класса. В реализации метода, если мы используем только переменные класса, то такой тип методов мы должны объявить как метод класса. Метод класса имеет параметр
cls
, который относится к классу. - Статический метод : это общий служебный метод, который выполняет задачу изолированно. Внутри этого метода мы не используем переменную экземпляра или класса, потому что этот статический метод не принимает никаких параметров, таких как
, self,
и 9.0008 класс .
Также прочитайте Метод класса Python против статического метода против метода экземпляра.
Прочитав эту статью, вы узнаете :
- Как создавать и использовать методы класса в Python
- Создание метода класса с помощью декоратора
@classmethod
и функцииclassmethod()
динамически добавлять или удалять методы класса
Содержание
- Что такое метод класса в Python
- Определение метода класса
- Пример 1. Создание метода класса с помощью декоратора @classmethod
- Пример 2. Создание метода класса с помощью функции classmethod()
- Пример 3. Доступ к переменным класса в методах класса
- Метод класса в наследовании
- Динамическое добавление метода класса в класс
- Динамическое удаление методов класса
Что такое метод класса в Python
Методы класса — это методы, которые вызываются в самом классе, а не в конкретном экземпляре объекта. Следовательно, он принадлежит уровню класса, и все экземпляры класса совместно используют метод класса.
- Метод класса привязан к классу , а не к объекту класса. Он может получить доступ только к переменным класса.
- Он может изменить состояние класса, изменив значение переменной класса, которая будет применяться ко всем объектам класса.
При реализации метода, если мы используем только переменные класса, мы должны объявить такие методы как методы класса. Метод класса имеет cls
в качестве первого параметра, который относится к классу.
Методы класса используются, когда нам 9 лет0006 фабричные методы . Фабричные методы — это те методы, которые возвращают объект класса для различных вариантов использования . Таким образом, фабричные методы создают конкретные реализации общего интерфейса.
Метод класса можно вызвать с помощью ClassName.method_name()
, а также с помощью объекта класса.
Определить метод класса
Любой метод, который мы создадим в классе, будет автоматически создан как метод экземпляра. Мы должны явно указать Python, что это метод класса, использующий @classmethod
декоратор или функция classmethod()
.
Методы класса определяются внутри класса, и это очень похоже на определение обычной функции.
Например, внутри метода экземпляра мы используем ключевое слово self
для доступа или изменения переменных экземпляра. То же самое внутри метода класса, мы используем ключевое слово cls
в качестве первого параметра для доступа к переменным класса. Поэтому метод класса дает нам контроль над изменением состояния класса.
- Вы можете использовать переменную с другим именем для
cls
, но это не рекомендуется, поскольку self является рекомендуемым соглашением в Python. - Метод класса может обращаться только к атрибутам класса, но не к атрибутам экземпляра
Пример 1. Создание метода класса с помощью декоратора @classmethod
Чтобы сделать метод методом класса, добавьте декоратор @classmethod
перед определением метода, и добавьте cls
в качестве первого параметра метода.
Декоратор @classmethod
— это встроенный декоратор функций. В Python мы используем декоратор @classmethod
для объявления метода как метода класса. Декоратор @classmethod
— это выражение, которое вычисляется после определения нашей функции.
Давайте посмотрим, как создать фабричный метод, используя метод класса. В этом примере мы создадим объект класса Student, используя метод класса.
с даты импорта даты и времени Ученик класса: def __init__(я, имя, возраст): self.name = имя возраст = возраст @классметод def calculate_age (cls, имя, год рождения): # рассчитать возраст и установить его как возраст # вернуть новый объект вернуть cls(имя, дата.сегодня().год - год_рождения) деф-шоу (я): print(self.name + " возраст: " + str(self.age)) Джесса = Студент('Джесса', 20) Джесса.шоу() # создать новый объект, используя фабричный метод радость = Студент.calculate_age("Радость", 1995) радость.шоу()
Вывод
Возраст Джессы: 20
Возраст Джона: 26
- В приведенном выше примере мы создали два объекта, один с помощью конструктора, а второй с помощью метода
calculate_age()
. - Конструктор принимает два аргумента: имя и возраст. С другой стороны, метод класса принимает
cls
,имя
ирождение_год
и возвращает экземпляр класса, который представляет собой не что иное, как новый объект. -
Декоратор @classmethod
используется для преобразования методаcalculate_age()
в метод класса. - Метод
calculate_age()
принимает класс Student (cls
) в качестве первого параметра и возвращает конструктор, вызываяStudent(name, date.today().year -birthYear)
, что эквивалентноStudent( имя, возраст)
.
Пример 2. Создание метода класса с помощью функции classmethod()
Помимо декоратора, встроенная функция classmethod()
используется для преобразования обычного метода в метод класса. classmethod()
– это встроенная в Python функция, которая возвращает метод класса для заданной функции.
Синтаксис :
classmethod(function)
-
function
: Это имя метода, который вы хотите преобразовать в метод класса. - Возвращает преобразованный метод класса.
Примечание : метод, который вы хотите преобразовать в метод класса, должен принимать класс ( cls
) в качестве первого аргумента, точно так же, как метод экземпляра получает экземпляр ( self
).
Как мы знаем, метод класса привязан к классу, а не к объекту. Таким образом, мы можем вызвать метод класса как путем вызова класса, так и объекта.
Функция classmethod()
— это старый способ создания метода класса в Python. В более новой версии Python мы должны использовать декоратор @classmethod
для создания метода класса.
Пример : Создать метод класса с помощью classmethod()
функция
class School: # переменная класса имя = 'Школа ABC' определение имя_школы (cls): print('Название школы:', cls.name) # создать метод класса School.school_name = classmethod(School.school_name) # вызов метода класса Школа. school_name()
Результат
Название школы: ABC School
Пример 3: Доступ к переменным класса в методах класса
Используя метод класса, мы можем получить доступ или изменить только переменные класса. Давайте посмотрим, как получить доступ и изменить переменные класса в методе класса.
Переменные класса являются общими для всех экземпляров класса . Используя метод класса, мы можем изменить состояние класса, изменив значение переменной класса, которое будет применяться ко всем объектам класса.
класс Студент: school_name = 'Азбука школы' def __init__(я, имя, возраст): self.name = имя возраст = возраст @классметод def change_school (cls, school_name): # class_name.class_variable cls.school_name = имя_школы # метод экземпляра деф-шоу (я): print(self.name, self.age, 'Школа:', Student.school_name) Джесса = Студент('Джесса', 20) Джесса.шоу() # изменить название_школы Student. change_school('Школа XYZ') Джесса.шоу()
Выход :
Школа Jessa 20: Школа ABC
Школа Jessa 20: Школа XYZ
Метод класса в наследовании
При наследовании метод класса родительского класса доступен для дочернего класса.
Давайте создадим класс Vehicle, который содержит метод класса фабрики from_price(), который будет возвращать экземпляр Vehicle из цены. Когда мы вызываем тот же метод, используя имя дочернего класса, он возвращает объект дочернего класса.
Всякий раз, когда мы получаем класс из родительского класса, у которого есть метод класса, он создает правильный экземпляр производного класса. В следующем примере показано, как метод класса работает при наследовании.
Пример
класс Транспортное средство: brand_name = 'БМВ' def __init__(я, имя, цена): self.name = имя собственная цена = цена @классметод def from_price(cls, имя, цена): # ind_price = доллар * 76 # создать новый объект Транспортное средство вернуть cls(имя, (цена * 75)) деф-шоу (я): print(self. name, self.price) Класс Автомобиль(Автомобиль): среднее значение (я, расстояние, топливо_использовано): пробег = расстояние / израсходованное топливо print(self.name, 'Пробег', пробег) bmw_us = Автомобиль('BMW X5', 65000) bmw_us.show() # метод класса родительского класса доступен дочернему классу # это вернет объект вызывающего класса bmw_ind = Car.from_price('BMW X5', 65000) bmw_ind.show() # тип проверки печать (тип (bmw_ind))
Выход
BMW X5 65000 БМВ Х5 4875000 class '__main__.Car'
Динамическое добавление метода класса в класс
Обычно мы добавляем методы класса в тело класса при определении класса. Однако Python — это динамический язык, который позволяет нам добавлять или удалять методы во время выполнения. Поэтому это полезно, когда вы хотите расширить функциональность класса без изменения его базовой структуры, поскольку многие системы используют одну и ту же структуру.
Нам нужно использовать classmethod()
функция для добавления нового метода класса в класс.
Пример:
Давайте посмотрим, как добавить новый метод класса в класс Student во время выполнения.
класс Студент: school_name = 'Азбука школы' def __init__(я, имя, возраст): self.name = имя возраст = возраст деф-шоу (я): print(self.name, self.age) # урок закончился # метод вне класса упражнения на деф (cls): # доступ к переменным класса print("Упражнения ниже для", cls.school_name) # Добавление метода класса во время выполнения в класс Student.exercises = classmethod(упражнения) Джесса = Студент("Джесса", 14) Джесса.шоу() # вызов нового метода Студент.упражнения()
Выход
Джесса 14 Ниже приведены упражнения для ABC School
Динамическое удаление методов класса
Мы можем динамически удалять методы класса из класса. В Python есть два способа сделать это:
- , используя
Del
Operator - с использованием
Delattr ()
Метод
с использованием оператора DEL
Del
Оперион Del
Del
удаляет метод экземпляра, добавленный классом. Используйте del class_name.class_method
синтаксис для удаления метода класса.Пример :
В этом примере мы удалим метод класса с именем change_school()
из класса Student. Если вы попытаетесь получить к нему доступ после его удаления, вы получите ошибку атрибута.
класс Студент: school_name = 'Азбука школы' def __init__(я, имя, возраст): self.name = имя возраст = возраст @классметод def change_school (cls, school_name): cls.school_name = имя_школы Джесса = Студент('Джесса', 20) print(Student.change_school('Школа XYZ')) печать (Студент.school_name) # удалить метод класса del Student.change_school # вызов метода класса # выдаст ошибку print(Student.change_school('Школа PQR'))
Выход
Школа XYZ AttributeError: объект типа «Студент» не имеет атрибута «change_school»
При использовании метода delatt()
Метод delattr()
используется для удаления именованного атрибута и метода из класса. Аргументом delattr
является объект и строка. Строка должна быть именем атрибута или именем метода.
Пример
Джесса = Студент('Джесса', 20) print(Student.change_school('Школа XYZ')) печать (Студент.school_name) # удалить метод класса delattr(Студент, 'change_school') # вызов метода класса # выдаст ошибку print(Student.change_school('Школа PQR'))
Выход
Школа XYZ AttributeError: тип объекта «Студент» не имеет атрибута «change_school»
Экземпляр Python, класс и статические методы демистифицированы — настоящий Python
Смотреть сейчас Этот учебник содержит связанный видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным учебным пособием, чтобы углубить свое понимание: Типы методов ООП в Python: @classmethod vs @staticmethod vs Instance Methods
В этом руководстве я помогу демистифицировать то, что стоит за методы класса , статические методы и обычные методы экземпляра .
Если вы разовьете интуитивное понимание их различий, вы сможете написать объектно-ориентированный Python, который будет более четко выражать свои намерения и будет легче поддерживаться в долгосрочной перспективе.
Экземпляр, класс и статические методы — обзор
Давайте начнем с написания класса (Python 3), который содержит простые примеры для всех трех типов методов:
класс MyClass: Метод защиты (сам): вернуть 'вызванный метод экземпляра', self @классметод Метод определения класса (cls): вернуть 'вызванный метод класса', cls @статический метод определение статического метода(): вернуть 'вызван статический метод'
ПРИМЕЧАНИЕ: Для пользователей Python 2: Декораторы
@staticmethod
и@classmethod
доступны начиная с Python 2.4, и этот пример будет работать как есть. Вместо использования простого классаMyClass:
вы можете объявить класс нового стиля, наследующий от объекта, с синтаксисом
class MyClass(object):
. В остальном вы готовы идти.
Удаление рекламы
Методы экземпляра
Первый метод MyClass
, называемый методом
, является обычным методом экземпляра . Это основной тип метода без излишеств, который вы будете использовать большую часть времени. Вы можете видеть, что метод принимает один параметр, self
, который указывает на экземпляр MyClass
при вызове метода (но, конечно, методы экземпляра могут принимать более одного параметра).
Через параметр self
методы экземпляра могут свободно обращаться к атрибутам и другим методам того же объекта. Это дает им большие возможности, когда дело доходит до изменения состояния объекта.
Мало того, что они могут изменять состояние объекта, методы экземпляра также могут получить доступ к самому классу через self.__class__
атрибут. Это означает, что методы экземпляра также могут изменять состояние класса.
Методы класса
Давайте сравним это со вторым методом, MyClass.classmethod
. Я пометил этот метод декоратором @classmethod
, чтобы пометить его как метод класса .
Вместо того, чтобы принимать параметр self
, методы класса принимают параметр cls
, который указывает на класс, а не на экземпляр объекта, при вызове метода.
Поскольку метод класса имеет доступ только к этому аргументу cls
, он не может изменять состояние экземпляра объекта. Для этого потребуется доступ к self
. Однако методы класса по-прежнему могут изменять состояние класса, которое применяется ко всем экземплярам класса.
Статические методы
Третий метод, MyClass.staticmethod
, был помечен декоратором @staticmethod
, чтобы пометить его как статический метод .
Этот тип метода не принимает ни self
, ни параметр cls
(но, конечно, он может принимать произвольное количество других параметров).
Следовательно, статический метод не может изменять ни состояние объекта, ни состояние класса. Статические методы ограничены в том, к каким данным они могут получить доступ, и в первую очередь они представляют собой способ пространства имен ваших методов.
Давайте посмотрим на них в действии!
Я знаю, что до сих пор это обсуждение было довольно теоретическим. И я считаю важным, чтобы вы развили интуитивное понимание того, как эти типы методов различаются на практике. Сейчас мы рассмотрим несколько конкретных примеров.
Давайте посмотрим, как эти методы ведут себя в действии, когда мы их вызываем. Мы начнем с создания экземпляра класса, а затем вызовем для него три разных метода.
MyClass
был настроен таким образом, что реализация каждого метода возвращает кортеж, содержащий информацию, чтобы мы могли отслеживать, что происходит — и к каким частям класса или объекта может получить доступ метод.
Вот что происходит, когда мы вызываем метод экземпляра :
>>>
>>> объект = МойКласс() >>> объект. метод() ('вызван метод экземпляра', <экземпляр MyClass по адресу 0x10205d190>)
Это подтвердило, что метод
(метод экземпляра) имеет доступ к экземпляру объекта (напечатанному как <экземпляр MyClass>
) через аргумент self
.
При вызове метода Python заменяет аргумент self
экземпляром объекта obj
. Мы могли бы игнорировать синтаксический сахар синтаксиса точечного вызова ( obj.method()
) и передать экземпляр объекта вручную , чтобы получить тот же результат:
>>>
>>> MyClass.method(obj) ('вызван метод экземпляра', <экземпляр MyClass по адресу 0x10205d190>)
Можете ли вы угадать, что произойдет, если вы попытаетесь вызвать метод без предварительного создания экземпляра?
Кстати, методы экземпляра также могут обращаться к самому классу через атрибут self.__class__
. Это делает методы экземпляра мощными с точки зрения ограничений доступа — они могут изменять состояние экземпляра объекта 9. 0360 и на самом классе.
Давайте попробуем метод класса далее:
>>>
>>> obj.classmethod() ('вызван метод класса', <класс MyClass по адресу 0x101a2f4c8>)
Вызов classmethod()
показал нам, что он не имеет доступа к объекту
, а только к объекту
, представляющему сам класс (все в Python является объектом, даже сами классы).
Обратите внимание, как Python автоматически передает класс в качестве первого аргумента функции, когда мы вызываем MyClass.classmethod()
. Вызов метода в Python через синтаксис точек вызывает это поведение. Аналогично работает параметр self
в методах экземпляра.
Обратите внимание, что имена этих параметров self
и cls
— это просто условность. Вы могли бы так же легко назвать их the_object
и the_class
и получите тот же результат. Все, что имеет значение, это то, что они расположены первыми в списке параметров метода.
Время вызвать статический метод сейчас:
>>>
>>> obj.staticmethod() 'вызван статический метод'
Вы видели, как мы вызвали staticmethod()
для объекта и смогли сделать это успешно? Некоторые разработчики удивляются, когда узнают, что можно вызвать статический метод для экземпляра объекта.
За кулисами Python просто применяет ограничения доступа, не передавая аргумент self
или cls
при вызове статического метода с использованием точечного синтаксиса.
Это подтверждает, что статические методы не могут получить доступ ни к состоянию экземпляра объекта, ни к состоянию класса. Они работают как обычные функции, но принадлежат пространству имен класса (и каждого экземпляра).
Теперь давайте посмотрим, что происходит, когда мы пытаемся вызвать эти методы в самом классе — без предварительного создания экземпляра объекта:
>>>
>>> MyClass. classmethod() ('вызван метод класса', <класс MyClass по адресу 0x101a2f4c8>) >>> MyClass.staticmethod() 'вызван статический метод' >>> МойКласс.метод() TypeError: несвязанный метод method() должен вызываться с экземпляром MyClass в качестве первого аргумент (вместо этого ничего не получил)
Мы смогли вызвать classmethod()
и staticmethod()
просто отлично, но попытались вызвать метод экземпляра method() 9Ошибка 0009 с ошибкой
TypeError
.
И этого следовало ожидать — на этот раз мы не создавали экземпляр объекта и попытались вызвать функцию экземпляра непосредственно на самом чертеже класса. Это означает, что Python не может заполнить аргумент self
, и поэтому вызов завершается ошибкой.
Это должно сделать различие между этими тремя типами методов более ясным. Но я не собираюсь на этом останавливаться. В следующих двух разделах я рассмотрю два более реалистичных примера того, когда следует использовать эти специальные типы методов.
Я буду основывать свои примеры на этом простом Pizza
class:
класс Пицца: def __init__(я, ингредиенты): self.ingredients = ингредиенты защита __repr__(сам): return f'Пицца({self.ingredients!r})'
>>>
>>> Пицца(['сыр', 'помидоры']) Пицца(['сыр', 'помидоры'])
Примечание: В этом примере кода и в других примерах руководства используются f-строки Python 3.6 для создания строки, возвращаемой
.__repr__
. В Python 2 и версиях Python 3 до 3.6 вы должны использовать другое выражение форматирования строки, например:деф __repr__(сам): вернуть 'Пицца (%r)' % self.ingredients
Удалить рекламу
Фабрики вкусной пиццы с
@classmethod
Если вы когда-нибудь пробовали пиццу в реальном мире, вы знаете, что существует множество вкусных вариантов:
Пицца(['моцарелла', 'помидоры']) Пицца(['моцарелла', 'помидоры', 'ветчина', 'грибы']) Пицца(['моцарелла'] * 4)
Итальянцы придумали таксономию пиццы несколько столетий назад, поэтому все эти восхитительные виды пиццы имеют свои собственные названия. Мы бы хорошо воспользовались этим и предоставили пользователям нашего класса Pizza
лучший интерфейс для создания объектов пиццы, о которых они мечтают.
Хороший и чистый способ сделать это — использовать методы класса в качестве фабричных функций для различных видов пиццы, которые мы можем создать:
класс Пицца: def __init__(я, ингредиенты): self.ingredients = ингредиенты защита __repr__(сам): return f'Пицца({self.ingredients!r})' @классметод защита Маргариты (cls): return cls(['моцарелла', 'помидоры']) @классметод деф прошутто (cls): return cls(['моцарелла', 'помидоры', 'ветчина'])
Обратите внимание, что я использую аргумент cls
в фабричных методах margherita
и prosciutto
вместо прямого вызова конструктора Pizza
.
Это трюк, который вы можете использовать, чтобы следовать принципу «Не повторяйся» (DRY). Если в какой-то момент мы решим переименовать этот класс, нам не придется помнить об обновлении имени конструктора во всех фабричных функциях метода класса.
Теперь, что мы можем сделать с этими фабричными методами? Давайте попробуем их:
>>>
>>> Пицца.Маргарита() Пицца(['моцарелла', 'помидоры']) >>> Пицца.прошутто() Пицца(['моцарелла', 'помидоры', 'ветчина'])
Как видите, мы можем использовать фабричные функции для создания новых объектов Pizza
, настроенных так, как мы хотим. Все они используют внутри один и тот же конструктор __init__
и просто предоставляют ярлык для запоминания всех различных ингредиентов.
Еще один взгляд на использование методов класса заключается в том, что они позволяют вам определять альтернативные конструкторы для ваших классов.
Python допускает только один метод __init__
для каждого класса. Используя методы класса, можно добавить столько альтернативных конструкторов, сколько необходимо. Это может сделать интерфейс ваших классов самодокументируемым (до определенной степени) и упростить их использование.
Когда использовать статические методы
Здесь немного сложнее привести хороший пример. Но вот что, я буду растягивать аналогию с пиццей все тоньше и тоньше… (ням!)
Вот что у меня получилось:
импорт математики класс Пицца: def __init__(я, радиус, ингредиенты): self.radius = радиус self.ingredients = ингредиенты защита __repr__(сам): return (f'Пицца({self.radius!r}, ' f'{сам.ингредиенты!r})') область защиты (я): вернуть self.circle_area (self.radius) @статический метод определение круг_область (г): вернуть г ** 2 * math.pi
Что я здесь изменил? Сначала я модифицировал конструктор и __repr__
чтобы принять дополнительный аргумент радиуса
.
Я также добавил метод экземпляра area()
, который вычисляет и возвращает площадь пиццы (это также было бы хорошим кандидатом на @property
— но эй, это просто игрушечный пример).
Вместо того, чтобы вычислять площадь непосредственно в area()
, используя известную формулу площади круга, я выделил ее в отдельный статический метод circle_area()
.
Давайте попробуем!
>>>
>>> p = Пицца(4, ['моцарелла', 'помидоры']) >>> р Пицца(4, ['моцарелла', 'помидоры']) >>> р.площадь() 50.26548245743669 >>> Pizza.circle_area(4) 50.26548245743669
Конечно, это несколько упрощенный пример, но он поможет объяснить некоторые преимущества статических методов.
Как мы узнали, статические методы не могут получить доступ к классу или состоянию экземпляра, потому что они не принимают cls
или собственный аргумент
. Это большое ограничение, но это также отличный сигнал, чтобы показать, что конкретный метод независим от всего остального вокруг него.
В приведенном выше примере ясно, что circle_area()
не может каким-либо образом изменить класс или экземпляр класса. (Конечно, вы всегда можете обойти это с помощью глобальной переменной, но здесь речь не об этом.)
Теперь, почему это полезно?
Пометка метода как статического метода — это не просто намек на то, что метод не будет изменять состояние класса или экземпляра — это ограничение также применяется средой выполнения Python.
Методы, подобные этому, позволяют вам четко сообщать о частях архитектуры вашего класса, так что новая работа по разработке естественным образом направляется в рамках этих установленных границ. Конечно, было бы достаточно легко бросить вызов этим ограничениям. Но на практике они часто помогают избежать случайных модификаций, идущих вразрез с исходным замыслом.
Иными словами, использование статических методов и методов класса — это способы сообщить о намерении разработчика, при этом достаточно усилив это намерение, чтобы избежать большинства ошибочных ошибок и ошибок, которые могут нарушить проект.
При умеренном применении и, когда это имеет смысл, написание некоторых ваших методов таким образом может обеспечить преимущества обслуживания и снизить вероятность неправильного использования ваших классов другими разработчиками.
Статические методы также имеют преимущества при написании тестового кода.
Поскольку метод circle_area()
полностью независим от остальной части класса, его гораздо проще тестировать.
Нам не нужно беспокоиться о настройке полного экземпляра класса, прежде чем мы сможем протестировать метод в модульном тесте. Мы можем просто выстрелить, как если бы тестировали обычную функцию. Опять же, это облегчает дальнейшее обслуживание.
Удалить рекламу
Ключевые выводы
- Методам экземпляра требуется экземпляр класса, и они могут получить доступ к экземпляру через
self
. - Методам класса не нужен экземпляр класса. Они не могут получить доступ к экземпляру (
self
), но имеют доступ к самому классу черезcls
. - Статические методы не имеют доступа к
cls
илиself
. Они работают как обычные функции, но принадлежат пространству имен класса.