С точки зрения объектно-ориентированного программирования любые значения (числа, строки, списки, кортежи и т.д.), на которые ссылаются переменные, являются объектами.
*Объект* представляет собой конкретный предмет или сущность (реальную или абстрактную), имеющую четко определенное функциональное назначение в данной предметной области. Объект обладает *состоянием, поведением и идентичностью*; структура и поведение схожих объектов определяет общий для них *класс*; термины "экземпляр класса" и "объект" взаимозаменяемы.
Узнать класс объекта можно с помощью функции ``type``:
```run-python
a=5
print(type(a)) # <class 'int'>
s='abc'
if type(s) is str: print('строка') # проверка класса объекта
b=[1,2,3]
print(type(b)) # <class 'list'>
c=(1,2,3)
print(type(c)) # <class 'tuple'>
```
*Состояние* объекта характеризуется перечнем (обычно статическим) всех свойств (атрибутов, характеристик) данного объекта и текущими (обычно динамическими) значениями каждого из этих свойств.
Объекты не существуют изолированно, а подвергаются воздействию или сами воздействуют на другие объекты.
*Поведение* объекта определяется выполняемыми над ним операциями и его состоянием, причем некоторые операции имеют побочное действие: они изменяют состояние. Состояние объекта представляет суммарный результат его поведения.
Операцией называется определенное воздействие одного объекта на другой с целью вызвать соответствующую реакцию. Выделяют следующие виды операций:
* Модификатор – операция, которая изменяет состояние объекта;
* Селектор – операция, считывающая состояние объекта, но не меняющая состояния;
* Итератор – операция, позволяющая организовать доступ ко всем частям объекта в строго определенной последовательности;
* Конструктор – операция инициализации собственных атрибутов объекта;
* Деструктор – операция, освобождающая состояние объекта.
В Python операции могут быть реализованы как методы (функции, определяемые в классе) или как свободные функции (объекты передаются в списке аргументов).
*Идентичность* (уникальность) – это такое свойство объекта, которое отличает его от всех других объектов.
Обычно для различения объектов используют имя, но объект может использоваться в программе под несколькими синонимичными именами, что может привести к не прогнозируемому изменению состояния. В Python для проверки совпадения объектов используется операция ``is``: `x quad tt"is" quad y`, `x quad tt"is not" quad y`.
Класс определяется следующим образом:\
`tt"class" quad "имя_класса" tt":"`\
`quad quad "атрибуты_класса"`\
`quad quad "методы_класса"`
Перед определением метода или класса можно указать *декораторы* после ``@``.
`"атрибуты_класса"` являются общими переменными для всех объектов данного класса, к ним можно обращаться
через класс: `"имя_класса"."имя_атрибута"` или как к собственным атрибутам объекта: `"имя_объекта"."имя_атрибута"`.
`"методы_класса"` определяются как функции, но первым параметром такой функции указывается ``self`` -- объект, к которому применяется данный метод. Метод можно вызывать следующим образом:\
`"имя_объекта"."имя_метода"("аргументы для параметров, кроме self")`\
или\
`"имя_класса"."имя_метода"("имя_объекта", "аргументы для остальных параметров")`
Параметр ``self`` отсутствует у *статических методов* класса (декоратор ``@staticmethod``), которые вызываются без указания объекта и могут обращаться только к атрибутам класса. Статические методы вызываются следующим образом:\
`"имя_класса"."имя_метода"("аргументы")`
Вместо объекта можно получать в первом параметре его класс, если написать декоратор ``@classmethod``. Такие методы также могут обращаться только к атрибутам класса через `tt"self." "имя_атрибута"`, но их можно переопределять в производных классах. Вызов:\
`"имя_класса_или_объекта"."имя_метода"("аргументы")`
Конструктор определяется как метод с именем ``__init__``:
```run-python
class Person:
def __init__(self, name, age):
self.name=name
self.age=age
def hello(self): # метод класса
print('Hello,', self.name)
p=Person('Alex',25) # вызов конструктора
print(p.name,p.age)
p.hello() # вызов метода
p.age=-1 # можно изменять атрибуты
p.jobtitle='designer' # можно добавить атрибуты
print(p.jobtitle)
```
Для предотвращения добавления атрибутов используют атрибут класса ``__slots__``
```run-python
class Person:
__slots__='name','age'
def __init__(self, name, age):
self.name=name
self.age=age
p=Person('Alex',25) # вызов конструктора
p.age=-1 # OK, можно изменять атрибут в __slots__
print(p.name,p.age)
p.jobtitle='designer' # Ошибка
```
В примере атрибуту ``age`` можно присвоить отрицательное число или строку ``"adult"``. Для контроля реальные атрибуты делают "скрытыми", добавляя ``__`` перед именем и определяя специальные методы для чтения и записи атрибута:
```run-python
class Person:
__slots__='__name','__age'
def __init__(self, name, age):
self.name=name
self.age=age
@property # декоратор метода
def name(self): # селектор для свойства name
return self.__name
@name.setter
def name(self, name): # модификатор для свойства name
if type(name) is str and len(name)>0:
self.__name = name
else:
raise Exception("Недопустимое значение для имени")
@property
def age(self): # селектор для свойства age
return self.__age
@age.setter
def age(self, age): # модификатор для свойства age
if type(age) is int and 0 <= age < 150:
self.__age = age
else:
raise Exception("Недопустимое значение для возраста")
К "скрытым" атрибутам можно обращаться напрямую, это такое же соглашение, как и "константы".
Для наследования в производном классе структуры и поведения других классов имена базовых классов указываются в скобках после имени класса. Класс ``object`` является базовым для остальных классов по умолчанию.
В производном классе можно переопределить любые методы и добавить новые атрибуты.
```run-python
from abc import ABC, abstractmethod
import math
class Shape(ABC): # абстрактный класс
@abstractmethod
def area(self): pass # площадь, абстрактный метод
class Rectangle(Shape): # Прямоугольник
def __init__(self,w,h):
self.w=w
self.h=h
def area(self):
return self.w*self.h
class Square(Rectangle): # Квадрат - это прямоугольник с одинаковой высотой и шириной
def __init__(self,a):
super().__init__(a,a) # вызов метода базового класса
class Circle(Shape): # Круг
def __init__(self,r):
self.r=r
def area(self):
return math.pi*self.r**2
# функция isinstance используется для проверки, является объект экземпляром некоторого класса
print(isinstance(b,Square), isinstance(b,Rectangle), isinstance(b,Shape), isinstance(b,object))
s=Shape() # Ошибка, нельзя создать объект абстрактного класса
```
*Специальные методы* класса позволяют перегрузить операции и встроенные функции для классов, определяемых программистом.
Название|Назначение|По умолчанию
--|--|--
``__new__(cls, ...)`` | создание объекта |
``__init__(self, ...)`` | конструктор, инициализация собственных атрибутов|
``__del__(self)``| деструктор|
``__str__(self)``| ``str(x)``|``return self.__repr__()``
``__repr__(self)``| ``repr(x)``|
``__bytes__(self)``| ``bytes(x)``|
``__format__(self, spec)``| f"{x:spec}"| ``if spec=='': return self.__str__(); else: raise TypeError()``
``__eq__(self, other)``| ``x==y``| ``return True if x is y else NotImplemented``
``__ne__(self, other)``| ``x!=y``| ``return not self.__eq__(other)``
``__lt__(self, other)``\
``__le__(self, other)``\
``__gt__(self, other)``\
``__ge__(self, other)``|``x<y``\
``x<=y``\
``x>y``\
``x>=y``| ``return NotImplemented``\
могут быть автоматически определены по ``__lt__``\
декоратором класса ``@total_ordering`` из ``functools``
``__hash__(self)``|``hash(x)``| если ``__eq__`` не определяется, то хэш адреса объекта, иначе TypeError
``__bool__(self)``|``bool(x)`` или ``if x:``| если определен ``__len__``, то ``return self.__len__()>0``\
иначе ``return True``
``__getattr__(self, name)``|``x.name`` для несуществующих атрибутов|
``__getattribute__(self, name)``| для всех ``x.name``|
``__setattr__(self, name, value)``|``x.name=value``|по умолчанию значение сохраняется во внутреннем словаре ``__dict__``
``__delattr__(self, name)``|``del x.name``|
``__dir__(self)``|``dir(x)``|
``__call__(self, args...)``|``x(args...)``|
``__len__(self)``|``len(x)``|
``__getitem__(self, key)``|``x[key]``|
``__setitem__(self, key, value)``|``x[key]=value``|
``__delitem__(self, key)``|``del x[key]``|
``__missing__(self, key)``|``x[key]`` для несуществующих ключей|
``__iter__(self)``|итератор для объекта (генератор, возвращающий элементы последовательности или коллекции)|
``__reversed__(self)``|``reversed(x)``|по умолчанию реализуется через ``__len__`` и ``__getitem__``
``__contains__(self, item)``|``item in x``|по умолчанию реализуется через ``__iter__`` или ``__getitem__``
``__add__(self, other)``|``x+y``|если не определена, пытается вызвать other.__radd__(self)
``__sub__(self, other)``|``x-y``|аналогично для остальных операций
``__mul__(self, other)``|``x*y``|
``__matmul__(self, other)``|``x@y``|
``__truediv__(self, other)``|``x/y``|
``__floordiv__(self, other)``|``x//y``|
``__mod__(self, other)``|``x%y``|
``__divmod__(self, other)``|``divmod(x,y)``|
``__pow__(self, other, mod=None)``|``x**y`` и ``pow(x, y)``|
``__lshift__(self, other)``|``x<<y``|
``__rshift__(self, other)``|``x>>y``|
``__and__(self, other)``|``x&y``|
``__xor__(self, other)``|``x^y``|
``__or__(self, other)``|``x\|y``|
``__iadd__(self, other)``|``x+=y``|``return self = self.__add__(other)``
``__isub__(self, other)``|``x-=y``|аналогично для остальных операций
``__imul__(self, other)``|``x*=y``|
``__imatmul__(self, other)``|``x@=y``|
``__itruediv__(self, other)``|``x/=y``|
``__ifloordiv__(self, other)``|``x//=y``|
``__imod__(self, other)``|``x%=y``|
``__ipow__(self, other)``|``x**=y``|
``__ilshift__(self, other)``|``x<<=y``|
``__irshift__(self, other)``|``x>>=y``|
``__iand__(self, other)``|``x&=y``|
``__ixor__(self, other)``|``x^=y``|
``__ior__(self, other)``|``x\|=y``|
``__neg__(self)``|``-x``|
``__pos__(self)``|``+x``|
``__abs__(self)``|``abs(x)``|
``__invert__(self)``|``~x``|
``__complex__(self)``|``complex(x)``|``return self.__index__()``
``__int__(self)``|``int(x)``|``return self.__index__()``
``__float__(self)``|``float(x)``|``return self.__index__()``
``__round__(self, ndigits=None)``|``round(x)``|
``__trunc__(self)``|``math.trunc(x)``|
``__floor__(self)``|``math.floor(x)``|
``__ceil__(self)``|``math.ceil(x)``|
``__enter__(self)``|``with x:`` блок|
``__exit__(self, exc_type, exc_value, traceback)``|завершение ``with``|
Пример класса
```run-python
import math
class Point:
def __init__(self, x=0, y=0):
self.x=x
self.y=y
def __abs__(self): return math.hypot(self.x,self.y) # расстояние от начала координат
@property
def r(self): return self.__abs__() # расстояние от начала координат
@property
def phi(self): return math.atan2(self.y,self.x) # угол
def __add__(self,p): return Point(self.x+p.x,self.y+p.y)
def __sub__(self,p): return Point(self.x-p.x,self.y-p.y)
def __mul__(self,p):
if type(p) is int or type(p) is float:
return Point(p*self.x,p*self.y) # "масштабирование"
else:
return self.x*p.x+self.y*p.y # скалярное произведение
def __matmul__(self,p): return self.x*p.y-self.y*p.x # векторное произведение
def turn(self,a=None): # поворот
if a==None: return Point(-self.y,self.x) # поворот на п/2
else:
ca=math.cos(a)
sa=math.sin(a)
return Point(self.x*ca-self.y*sa,-self.x*sa+self.y*ca)
def __neg__(self): return Point(-self.x,-self.y) # поворот на п
def __str__(self): return str((self.x,self.y))