Объектно-ориентированное программирование. Специальные методы.

Статические и классовые методы

Существуют 2 особенных декоратора, которые можно повесить на функции внутри класса: - @staticmethod - @classmethod

Декоратор @staticmethod определяет обычную функцию (статический метод) в пространстве имён класса. У него нет обязательного параметра-ссылки self. Может быть полезно для вспомогательных функций, чтобы не мусорить пространство имён модуля. Доступ к таким методам можно получить как из экземпляра класса, так и из самого класса:

class SomeClass(object):
  @staticmethod
  def hello():
    print("Hello, world")

SomeClass.hello()  # Hello, world
obj = SomeClass()
obj.hello()        # Hello, world
Hello, world
Hello, world

Декоратор @classmethod создаёт метод класса и требует обязательную ссылку на класс (cls). Поэтому объект класса явно передаётся через первый параметр как это с параметром self происходит для обычных методов. Также как и для self, переданный cls может отличаться от класса, в котором определён класс-метод (может быть потомок). Часто используется для создания альтернативных конструкторов.

class SomeClass(object):
  @classmethod
  def hello(cls):
    print('Hello, класс {}'.format(cls.__name__))

SomeClass.hello() # Hello, класс SomeClass
Hello, класс SomeClass

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

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # classmethod чтобы создать объект по году рождения,
    # "альтернативный" конструктор
    @classmethod
    def fromBirthYear(cls, name, year):
        return cls(name, 2019 - year)

    # статический метод,чтобы проверить совершеннолетие
    @staticmethod
    def isAdult(age):
        return age > 18

person1 = Person('Петя', 21)
person2 = Person.fromBirthYear('Петя', 1996)

print(person1.age)
print(person2.age)

# print the result
print(Person.isAdult(22))
21
23
True

Важно понимать, что ни classmethod ни staticmethod НЕ являются функциями от конкретного объекта класса и соответственно не принимают self. Подчеркнем еще раз их различия: - classmethod принимает cls как первый параметр, тогда как staticmethod в специальных аргументах не нуждается - classmethod может получать доступ или менять состояние класса, в то время как staticmethod нет - staticmethod в целом вообще ничего не знают про класс. Это просто функция над аргументами, объявленная внутри класса.

Специальные методы (магические) вида _ < param > _

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

Эти методы могут эмулировать поведение встроенных классов, но при этом они необязательно существуют у самих встроенных классов. Например, у объектов int при сложении не вызывается метод add. Таким образом, их нельзя переопределить.

Давайте для примера переопределим стандартную операцию сложения. Рассмотрим класс Vector, используемый для представления радиус-векторов на координатной плоскости, и определим в нем поля-координаты: x и y. Также очень хотелось бы определить для векторов операцию +, чтобы их можно было складывать столь же удобно, как и числа или строки.

Для этого необходимо перегрузить операцию +: определить функцию, которая будет использоваться, если операция + будет вызвана для объекта класса Vector. Для этого нужно определить метод add класса Vector, у которого два параметра: неявная ссылка self на экземпляр класса, для которого она будет вызвана (это левый операнд операции +) и явная ссылка other на правый операнд:

class Vector():
    def __init__(self, x = 0, y = 0):
        self.x = x
        self.y = y
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

A = Vector(1, 2)
B = Vector(3, 4)
C = A + B
print(C.x, C.y)
4 6

Теперь при вызове оператора A + B Питон вызовет метод A.add(B), то есть вызовет указанный метод, где self = A, other = B.

Аналогично можно определить и оставшиеся операции. Полезной для переопределения является операция <. Она должна возвращать логическое значение True, если левый операнд меньше правого или False в противном случае (также в том случае, если объекты равны). Для переопределения этого операнда нужно определить метод lt (less than):

class Vector:
    def __lt__(self, other):
        return self.x < other.x or self.x == other.x and self.y < other.y

В этом примере оператор вернет True, если у левого операнда поле x меньше, чем у правого операнда, а также если поля x у них равны, а поле y меньше у левого операнда.

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

Список основных перегружаемых операторов

Метод Использование
Операторы сравнения  
__lt__(self, other) x < y
__le__(self, other) x <= y
__eq__(self, other) x == y
__ne__(self, other) x != y
__gt__(self, other) x > y
__ge__(self, other) x >= y
Арифметические операторы
Сложение
__add__(self, other) x + y
__radd__(self, other) y + x
__iadd__(self, other) x += y
Вычитание
__sub__(self, other) x - y
__rsub__(self, other) y - x
__isub__(self, other) x -= y
Умножение
__mul__(self, other) x * y
__rmul__(self, other) y * x
__imul__(self, other) x *= y
Математическое умножение (например векторное)
__matmul__(self, other) x @ y
__rmatmul__(self, other) y @ x
__imatmul__(self, other) x @= y
Деление
__truediv__(self, other) x / y
__rtruediv__(self, other) y / x
__itruediv__(self, other) x /= y
Целочисленное деление
__floordiv__(self, other) x // y
__rfloordiv__(self, other) y // x
__ifloordiv__(self, other) x //= y
__divmod__(self, other) divmod(x, y)
Остаток
__mod__(self, other) x % y
__rmod__(self, other) y % x
__imod__(self, other) x %= y
Возведение в степень
__pow__(self, other) x ** y
__rpow__(self, other) y ** x
__ipow__(self, other) x **= y
Отрицание, модуль
__pos__(self) +x
__neg__(self) -x
__abs__(self) abs(x)
__len__(self) len(x)
Преобразование к стандартным типам
__int__(self) int(x)
__float__(self) float(x)
__complex__(self) complex(x)
__str__(self) str(x)
__round__(self, digits = 0) round(x, digits)
Блок with
__enter__(self)  
__exit__(self)  

Задачи:

Задача 1:

Реализуйте свой класс Complex для комплексных чисел, аналогично встроенной реализации complex:

  1. Добавьте инициализатор класса
  2. Реализуйте основные математические операции
  3. Реализуйте операцию модуля (abs, вызываемую как |c|)
  4. Оба класса должны давать осмысленный вывод как при print, так и просто при вызове в ячейке

Задача 2:

  1. Создайте класс Vector с полями x, y, z определите для него конструктор, метод __str__, необходимые арифметические операции. Реализуйте конструктор, который принимает строку в формате "x,y".
  2. Программа получает на вход число N, далее координаты N точек. Доопределите в классе Vector недостающие операторы, найдите и выведите координаты точки, наиболее удаленной от начала координат.
  3. Используя класс Vector выведите координаты центра масс данного множества точек.
  4. Даны два вектора. Выведите площадь параллелограмма, построенного на заданных векторах.
  5. Даны три вектора. Выведите объём параллелепипеда, построенного на заданных векторах.
  6. Среди данных точек найдите три точки, образующие треугольник с наибольшим периметром. Выведите данный периметр.
  7. Среди данных точек найдите три точки, образующие треугольник с наибольшей площадью. Выведите данную площадь.