Классы и объекты
Переменные (повторение)
Переменные не нужно объявлять заранее. У переменных нет типа. Тип есть только у данных, на которые указывают переменные. Для опредения типа используют функцию type()
x = 5
print(x) # 5
print(type(x)) # <class 'int'>
x = 3.14
print(x) # 3.14
print(type(x)) # <class 'float'>
x = 'Hello'
print(x) # Hello
print(type(x)) # <class 'str'>
int, float, str, bool, complex - встроенные типы данных языка. Они unmutable (неизменяемые). (Только неизменяемые данные могут быть ключами в словаре).
Ссылки на объекты
Все переменные содержат только ссылки на объекты.
Оператор = связывает переменную с объектом в памяти через ссылку.
Если переменная уже существует, то справа от = напишем ссылку на объект, которая будет храниться в переменной.
Если переменной еще нет, то оператор = создает переменную и записывает в нее ссылку (которая указана справа от =).
Для упрощения рисунков дальше вместо ссылок на неизменяемые объекты будем рисовать переменные со значением.
>>> a = 3
- Создается объект, представляющий число 3 (так как объект неизменяемый, то он создается только если его еще нет, но с логической точки зрения можете считать, что каждый раз создается новый объект).
- Создается переменная a, если ее еще нет.
- В переменную а записывается ссылка на объект, представляющий число 3.
Термины:
- Переменная - запись в системной таблице, где предусмотрено место для хранения ссылок на объекты.
- Объект - это область памяти с объемом, достаточным для представления значения этого объекта.
- Ссылка – это автоматически разыменовываемый указатель на объект (связь переменная - объект).
id - идентификатор объекта
Чтобы быстро отличать один объект от другого, у каждого объекта есть свой номер. У разных объектов номера разные (уникальные).
Чтобы узнать идентификационный номер объекта (идентификатор), используют встроенную функцию id(объект)
В примере создаем 2 разных списка с одинаковым содержимым.
Переменная a ссылается на объект с идентификатором 4292354496.
Переменная b - на другой список с идентификатором 4292357856.
>>> a = [1, 2, 3]
>>> id(a)
4292354496
>>> b = [1, 2, 3]
>>> id(b)
4292357856
is - ссылаются на один объект
Ключевое слово is проверяет, ведут ли ссылки на один и тот же объект.
То есть a is b
это id(a) == id(b)
>>> a = [1, 2, 3]
>>> id(a)
4292354496
>>> b = [1, 2, 3]
>>> id(b)
4292357856
>>> a is b
False
>>> a == b
True
>>> a = b # теперь a и b ссылаются на один и тот же объект (одинаковые id)
>>> id(a)
4292357856
>>> id(b)
4292357856
>>> a is b
True
Классы
Мы создавали и работали с объектами разных типов. Научимся создавать свои новые типы объектов.
Каждый объект - это данные и что можно делать с этими данными (функции).
В строке 'Hello' данные - это буквы H, e, l, l, o.
Что можно делать со строкой? Найти на каком месте в строке стоит буква 'e' вызвав 'Hello'.index('e'). Сделать все буквы большими 'Hello'.toupper()
Данные называют атрибутами (переменными) объекта, а функции - методами объекта.
Описание данных (переменных) и что с ними можно делать (функций) для разных, но похожих объектов, образует класс.
Строки 'Hello' и 'Good bye' - два разных объекта класса str.
Класс Segment1 - отрезок на оси Х
Опишем класс - отрезок на оси Х.
У каждого отрезка есть начало (start) и конец (finish) - это его атрибуты (переменные).
Что можно сделать с отрезком? Его можно напечатать, вычислить длину, сдвинуть по оси Х вправо или влево.
Создадим новый тип и назовем его Segment1.
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 __repr__(self):
return '[{}, {}] = {}'.format(self.start, self.finish, self.length())
def length(self):
return abs(self.start - self.finish)
def move(self, dx):
self.start += dx
self.finish += dx
# Закончились отступы - закончилось описание класса.
# Классом можно пользоваться:
s1 = Segment1(-3.5, 7) # вызывается __init__(-3.5, 7)
print(s1.start, s1.finish) # объект.переменная - к полям можно обратиться по имени через точку
x = s1.length() # объект.метод
print('Длина равна', x)
print(s1) # 1 7 вызывается str(s1), которая вызывает s1.__str__()
print(repr(s1)) # [1, 7] = 6 - вызывается repr(s1), которая вызывает s1.repr()
s1.start = 1 # объект.переменная - изменили значение поля start объекта s1
print(s1) # 1 7
s1.move(2) # передвинули отрезок на +2 по оси Х
print(s1) # 3 9
s2 = Segment1(0, 2.5) # другой объект класса Segment1, на него ссылается s2
print(s2) # 0 2.5
s2 = s1
print(s2) # 3 9 теперь s2 тоже ссылается на отрезок [3, 9], на отрезок [0, 2.5] нет ссылок.
Разберем код примера.
Создаем класс так:
class ИмяКласса(object):
описание класса
ИмяКласса придумаем сами. Мы придумали Segment1.
class - ключевое слово. Менять нельзя.
object - на основе какого класса делаем свой новый класс. Пока будем брать только object. Подробнее расскажем в наследовании классов.
"""Класс Segment1 описывает отрезки на оси Х"""
Это строка документации. Она описывает что делает класс. Можно автоматически из строк документации сделать help по классу, его полям и методам.
def __init__(self, start=0, finish=0):
# Эта функция вызывается, когда мы создаем новый объект класса.
# self - это название переменной, которая указвает на сам объект.
self.start = start # переменная объекта
self.finish = finish
Функция с именем __init__ вызывается, когда мы делаем объект класса.
Чтобы сделать объект класса, пишут имя класса и в скобках указывают параметры функции __init__.
s1 = Segment1(-3.5, 7) # вызывается __init__(-3.5, 7)
Первый аргумент любой функции объекта класса - это ссылка на сам объект. Принято называть ее self.
Для обращение к переменной объекта (поля start и finish) из любого его метода нужно писать self.start и self.finish.
Для обращения к полям по имени объекта (ссылки s1 и s2) нужно писать s1.start, s1.finish, s2.start, s2.finish.
Для обращения к методу объекта изнутри объекта, нужно использовать self.
Внутри метода __repr__
мы вызвали метод length
того же объекта и обращаемся к нему self.length()
- Обратите внимание, первым аргументом функции length идет self.
- def length(self):
- При вызове мы его НЕ указываем:
- s1.length() снаружи объекта, по ссылке s1
- self.length() изнутри объекта, по ссылке self
- s1.move(2) снаружи объекта, по ссылке s1
Что есть в классе и объекте?
Все в питоне сделано на основе словарей. Их можно смотреть через __dict__
Объект - видим поля объекта.
>>> print(s1.__dict__) # напечатать поля (переменные) объекта
{'start': 3, 'finish': 9}
Класс - видим методы класса.
>>> print(Segment1.__dict__) # напечатать все методы класса
{'__module__': '__main__', '__doc__': 'Класс Segment1 описывает отрезки на оси Х', '__init__': <function Segment1.__init__ at 0xffd75d68>, '__str__': <function Segment1.__str__ at 0xffd75d20>, 'length': <function Segment1.length at 0xffd75cd8>, 'move': <function Segment1.move at 0xffd75c90>, '__dict__': <attribute '__dict__' of 'Segment1' objects>, '__weakref__': <attribute '__weakref__' of 'Segment1' objects>}
dir() - какие переменные и функции доступны для объекта
>>> a = [1, 2, 3]
>>> dir(a)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Заключение
- Термины:
- Класс - описание похожих объектов. Тип в питоне.
- Объект - 1 экземпляр класса.
- атрибуты объекта - переменные объекта.
- методы объекта - функции объекта.
- Синтаксис
class Имя(object):
- создаем классclass Segment1(object):
s = Segment1(1, 7)
- создаем объект класса, вызывается функция __init__ этого класса.- self - ссылка на себя, нужно ставить первым аргументом любой функции объекта.
def length(self)
def move(self, dx)
- __init__ - конструктор класса, в этой функции определяем переменные объекта.
- Он вызывается
- __str__ - этот метод вызывается при печати объекта (преобразовании объекта к строке)
- __repr__ - этот метод вызывается при вызове функции repr(объект)
- Обращение к полям и методам объекта
- self.start - изнутри этого же объекта к полю start
- s1.start - по ссылке s1 на объект к полю start
- self.length() - изнутри этого же объекта к функции объекта length
- s1.length() - по ссылке s1 на объект к функции объекта length
- Встроенные переменные и функции
type(s1)
- тип объекта, на который ссылается переменная s1id(s1)
- идентификатор этого объектаa is b
- переменные a и b ссылаются на один и тот же объект?id(a) == id(b)
dir(s1)
- какие переменные и функции доступны для объектаs1.__dict__
- какие переменные содержит объектSegment1.__dict__
- какие методы содержит класс
Задачи
Segment1 normalize
Допишите в класс Segment1 функцию normalize
, которая проверяет, что start НЕ больше finish. Если start и finish перепутаны, то поменяйте их местами.
Проверьте функцию.
Segment1 flip
Допишите в класс Segment1 функцию flip
, которая отображает сам отрезок относительно точки 0.
Проверьте функцию.
Segment1 is_crossed
Допишите в класс Segment1 функцию is_crossed(self, other)
, которая проверяет, пересекается сам отрезок (self) с другим отрезком (other). Возвращает True (пересекаются хотя бы в 1 точке) или False (не имеют общих точек).
Проверьте функцию.
Segment1 - выровнять отрезки по левому краю
Если нужно, допишите функции в класс Segment1. Даны отрезки - по 1 на строку, целые числа через пробел. Выровнять отрезки по левому краю (по самому левому) и напечатать в том же порядке.
Input:
0 9
-1 3
-10 -7
5 11
Output:
-10 -1
-10 -6
-10 -7
-10 6
Segment1 - найти длину самого большого отрезка
Даны отрезки по 1 отрезку на строку. Напечатать длину самого большого (длинного) отрезка.
Segment1 - найти самый длинный отрезок
Даны отрезки по 1 отрезку на строку. Напечатать отрезок самой большой длины. Если таких отрезков несколько - напечатать их все.
Задачи с графическим выводом (черепаха)
Segment1 - все отрезки сдвинуть началом в точку 0
В примере создаются два отрезка (100, 200) и (-50, 30), рисуются синим, передвигаются началами в точку х=0 и рисуются красным.
Даны отрезки по одному отрезку на строку.
Нарисовать синим отрезок, нарисовать отрезок такой же длины с началом в точке 0 (сдвинуть отрезок в точку 0).
Input:
100 200
-50 150
-200 -50
10 70
Segment1 - отрезок максимальной длины
Сначала решить задачу: даны отрезки по одному отрезку на строку. Напечатать длину самого длинного отрезка.
Даны отрезки по одному отрезку на строку.
Для каждого отрезка нарисовать отрезок синим и нарисовать самый длинный отрезок из прочитанных красным.
Segment1 - отрезок минимальной длины
Сначала решить задачу: даны отрезки по одному отрезку на строку. Напечатать длину самого короткого отрезка.
Даны отрезки по одному отрезку на строку.
Для каждого отрезка нарисовать отрезок синим и нарисовать самый короткий отрезок из прочитанных красным.
Segment1 - включает все отрезки
Даны отрезки по одному отрезку на строку.
Для каждого отрезка нарисовать отрезок синим и нарисовать самый красным отрезок, который включает в себя все прочитанные отрезки.