Методы класса и статические методы
Статические методы
Когда мы реализовали класс Drob, нам нужна была функция, которая считает НОД для 2 чисел, чтобы сокращать дробь.
Это полезная функция. Хочется хранить ее внутри класса.
Но это не метод 1 экземпляра класса (одной дроби), он общий для всех дробей.
Этот метод не зависит ни от одной переменной ни в дроби, ни во всем классе дробей.
Такие методы называют статическими методами класса и пишут с @staticmethod
перед определением метода.
Если статических методов в классе несколько, то у каждого нужно написать @staticmethod
Добавим в класс Drob статический метод nod.
Статический метод не относится к конкретной дроби. Он для всех дробей. У него нет self.
Статический метод не может написать self.переменная или self.функция, потому что у него нет self
Обращение к статической функции:
- изнутри объекта класса: self или название класса
- снаружи: название класса
class Drob(object):
""" Дробь вида a/b"""
def __init__(self, a=0, b=1):
self.a = a
self.b = b
self.normalize()
@staticmethod
def nod(x, y): # нет self
res = x % y
while res > 1:
x, y = y, res
res = x % y
return y
def normalize(self):
""" Приводит дробь вида 4/6 к 2/3"""
k = Drob.nod(self.a, self.b) # можно self.nod
self.a //= k
self.b //= k
def __str__(self):
return '{}/{}'.format(self.a, self.b)
# реализация функций __eq__, __lt__, __add__, __sub__, __mul__,
# конец класса
d1 = Drob(4, 6)
print(d1) # 2/3
z = Drob.nod(123, 21) # класс.статический_метод
print(z) # 3
Еще один пример. В классе Segment1 отрезков по оси Х можно написать функцию пересечения отрезка с другим отрезком как функцию экземпляра класса crossed_with или как функцию не относящуюся ни к одному экземпляру (общую для всех экземпляров) is_crossed.
class Segment1(object):
"""Класс Segment1 описывает отрезки на оси Х"""
def __init__(self, start=0, finish=0):
# Эта функция вызывается, когда мы создаем новый объект класса.
# self - это название переменной, которая указвает на сам объект.
self.start = start # переменная объекта
self.finish = finish
def __str__(self):
return '{} {}'.format(self.start, self.finish)
def crossed_with(self, other): # метод экземпляра класса
""" Пересекается этот отрезок self с другим отрезком other? """
if self.finish < other.start or other.finish < self.start:
return False
return True
@staticmethod
def is_crossed(seg1, seg2): # статический метод класса (нет self)
""" Пересекаются ли отрезки seg1 и seg2? """
if seg1.finish < seg2.start or seg2.finish < seg1.start:
return False
return True
# вызываем метод экземпляра класса и статический метод класса:
s1 = Segment1(2, 5)
s2 = Segment1(3, 7)
print(s1.crossed_with(s2)) # метод экземпляра класса
print(Segment1.is_crossed(s1, s2)) # статический метод класса
Переменные класса
Пусть мы делаем класс окружность. В каждой окружности есть радиус, (x, y) - координаты центра окружности. У окружности можно посчитать радиус и периметр. Для их вычсления нужно число пи (3.14...)
Вопрос - к какой именно окружности принадлежит число пи? Ни к какой. Оно характеризует все окружности сразу.
Значит, число пи должно храниться не в переменных экземпляра класса (1 окружность), а в переменных всего класса (объект класса).
Переменные класса пишутся или вне методов в классе, или внутри метода как класс.переменная
Посчитаем, сколько экземпляров класса Drob создали за время работы программы. Можно создать глобальную переменну counter. Но лучше сделать ее переменной класса.
class Segment1(object):
"""Класс Segment1 описывает отрезки на оси Х"""
counter = 0 # еще не было создано ни одной окружности
def __init__(self, start=0, finish=0):
# Эта функция вызывается, когда мы создаем новый объект класса.
# self - это название переменной, которая указвает на сам объект.
self.start = start # переменная объекта
self.finish = finish
Segment1.counter += 1
@classmethod
def how_many(cls):
return cls.counter
# конец класса
s1 = Segment1(2, 5)
s2 = Segment1(3, 7)
print(Segment1.how_many()) # вызов метода класса по имени класса
print(s1.how_many()) # вызов метода класса по имени класса
Переменная counter
- одна на весь класс. Общая. Каждый экземпляр класса (1 дробь) может в нее писать по ссылке Segment1.counter
Чем отличается @staticmethod
от @classmethod
?
В методе класса how_many:
- есть аргумент cls (класс Drob). Если self означает 1 дробь (объект с полями и методами), то cls - объект класса (один на класс Drob, описывает этот класс).
- Обращаться к полям и методам класса можно или по имени класса Drob.counter или по переменной cls.counter;
- Вызывается этот метод по имени класса или ссылки на экземпляр класса. Лучше всегда обращайтесь к переменной по имени класса.
Делаем экземпляр класса
Напишем функцию read(line), которая будет из строки делать отрезок.
Хотим, чтобы Segment1.read('2, 5')
создал и вернул Segment1(2, 5)
Функция read всего класса, а не 1 отрезка. Значит, это или staticmethod, или classmethod.
Если функция всего класса возвращает экземпляр класса, делайте ее classmethod (Позже объясним зачем, когда будем изучать наследование классов).
class Segment1(object):
...
@classmethod
def read(cls, line):
start, finish = map(int, line.split(','))
t = Segment1(start, finish)
return t
# или сразу return Segment1(start, finish) - можно обойтись без переменной t
Если есть ссылка на объект всего класса, можно заменить вызов Segment1(..)
на cls(..)
class Segment1(object):
...
@classmethod
def read(cls, line):
start, finish = map(int, line.split(','))
return cls(start, finish) # cls - ссылка на весь класс
Итого
class A(object):
class_var = 2
def __init__(self, x):
self.x = x
@classmethod
def foo(cls):
...
@staticmethod
def bzz():
...
a = A()
A.class_var = 33
A.foo()
A.bzz()
- Класс - тоже объект. У класса могут быть переменные и методы. Класс А, переменная class_var, метод foo.
- переменная класса пишется вне всех функций внутри класса; class_var
- обращаются к переменной класса по класс.переменная; A.class_var
- метод класса не работает с 1 экземпляром класса, у него нет self.
def foo(cls):
- метод класса работает с объектом класса, на него ссылаются через cls ;
- конструктор можно вызвать через _cls(аргументы)
cls(7)
- к методу класса можно обратиться через имя класса
A.foo()
- если метод не для 1 дроби, а для всех и ему не нужны переменные класса, то делаем
@staticmethod
Задачи
Drob
Drob.nod, Drob.read
В класс Drob дописать статический метод nod и метод класса read.
Drob - калькулятор 2 дробей
Реализовать калькуляр дробей, который вычисляет сумму и разность дробей, заданных по формату:
1/3
+
1/6
=
Нужно напечатать ответ: 1/2
Drob - сумматор многих дробей
Как в предыдущей задаче. Только дробей может быть не 2, а больше. Только + и -.
Drob - калькулятор без скобок
Напишите калькулятор, который считает дроби.
Нужно 2 стека. Стек дробей stack_drob и стек операций (+, -, *) stack_op.
Пусть у каждой операции будет приоритет.
prior = {'+': 1, '-': 1, '*':2, '=':0} # символ операции : приоритет операции
Полжить в стек можно функцию append
работы с list, а взять функцией pop()
.
Сначала положить в стек операций =. (Кладем кортеж ('=', 0) - символ и приоритет. Такие пары хранятся в стеке операций. Как считать выражение:
- если дробь, положить в стек дробей.
- если операция:
- если в стеке операция с меньшим приоритетом, положить нашу операцию в стек операций;
- иначе пока подходящие операции не закончатся,
- достать операцию из стека операций;
- достать 2 дроби из стека дробей;
- посчитать результат "дробь операция дробь" и положить его в стек дробей.
В конце должен быть пустой стек операций и 1 дробь в стеке дробоей - результат работы калькулятора.
Кнаты, сикли, галеоны
У волшебников свои деньги. Это галеоны (galeon), сикли (sicle), кнаты (knut).
- 1 галеон = 17 сиклей
- 1 сикль = 29 кнатов
Написать класс WMoney, который хранит сумму в кнатах, а печатает по формату 1 galeon 5 sicle 10 knut.
В классе написать метод read(str), который из строки '1 galeon 5 sicle 10 knut' делает WMoney(galeon=1, sicle=5, knut=10)
Timer - дописать
Кнаты, сикли, галеоны
Чек в магическом магазине со сдачей.
Автомат по продаже билетов
Переменные класса - список допустимых монет.
Электричка на Марсе
Модифицировать задачу дополнения расписания, при условии, что планета не Земля, и там другое количество минут в 1 часе и часов в сутках (может дробное???)