{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Объектно-ориентированное программирование в Python" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Наиболее важные особенности классов в Python:**\n", "1. Множественное наследование.\n", "2. Производный класс может переопределить любые методы базовых классов.\n", "3. В любом месте можно вызвать метод с тем же именем базового класса.\n", "4. Все атрибуты класса по умолчанию public (доступны везде), все методы - виртуальные (перегружают базовые).\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Класс\n", "Класс - это пользовательский тип. Простейшая модель определения класса выглядит следующим образом:\n", "\n", "class имя_класса:\n", "\n", " инструкция 1\n", "\n", " …\n", "\n", " инструкция N \n", "\n", "Каждая такая запись генерирует свой объект класса. Отличие от C++ в том, что в плюсах описание класса - это лишь объявление, а в питоне - это создание объекта. Есть также другой тип объекта - инстанс класса, который генерируется при вызове:\n", "\n", "инстанс-класса = имя_класса()\n", "\n", "Объект класса и инстанс класса - это два разных объекта. Первый генерируется на этапе объявления класса, второй - при вызове имени класса. Объект класса может быть один, инстансов класса может быть сколько угодно.\n", "Инструкция - это, как правило, определение функции. При определении класса создается новое пространство имен и создается объект-класс, который является оболочкой для всех инструкций.\n", "Объекты классов поддерживают два вида операций:\n", "\n", "•\tдоступ к атрибутам;\n", "\n", "•\tсоздание экземпляра класса.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Атрибуты класса\n", "Атрибуты класса бывают двух видов:\n", "\n", "•\tатрибуты данных;\n", "\n", "•\tатрибуты-методы.\n", "\n", "Атрибуты данных обычно записываются сверху. Память для атрибутов выделяется в момент их первого присваивания - либо снаружи, либо внутри метода. Методы начинаются со служебного слова def.\n", "\n", "Доступ к атрибутам выполняется по схеме obj.attrname.\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "class Simple:\n", " #'Простой класс'\n", " var = 87\n", " def f():\n", " return 'Hello world'" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "87\n", "89\n" ] } ], "source": [ "print(Simple.var)\n", "Simple.var = 89\n", "print(Simple.var)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Simple.f" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello world\n" ] } ], "source": [ "print(Simple.f())" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "int([x]) -> integer\n", "int(x, base=10) -> integer\n", "\n", "Convert a number or string to an integer, or return 0 if no arguments\n", "are given. If x is a number, return x.__int__(). For floating point\n", "numbers, this truncates towards zero.\n", "\n", "If x is not a number or if base is given, then x must be a string,\n", "bytes, or bytearray instance representing an integer literal in the\n", "given base. The literal can be preceded by '+' or '-' and be surrounded\n", "by whitespace. The base defaults to 10. Valid bases are 0 and 2-36.\n", "Base 0 means to interpret the base from the string as an integer literal.\n", ">>> int('0b100', base=0)\n", "4\n" ] } ], "source": [ "print(Simple.var.__doc__)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "smpl = Simple() # Создание экземпляра класса" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "88\n" ] } ], "source": [ "smpl.var = 88\n", "print(smpl.var)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если мы хотим, чтобы при создании выполнялись какие-то действия, нужно определить конструктор, который будет вызываться автоматически:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Simple:\n", " def __init__(self):\n", " self.list = [1]\n", " # При создании объекта smpl будет создан список list. \n", "smpl = Simple()\n", "smpl.list" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 22\n" ] } ], "source": [ "class Simple:\n", " def __init__(self, count, strr): # Конструктору можно передать аргументы\n", " self.list = []\n", " self.count = count\n", " self.str = strr\n", " \n", "s = Simple(1,'22')\n", "print(s.count, s.str)\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Yesterday\n" ] } ], "source": [ "class record:\n", " name='NoName'\n", " length=0\n", " def __init__(self, name='NoName', length=0):\n", " self.name=name\n", " self.length=length\n", "\n", "r = record('Yesterday', 10)\n", "print(r.name)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ], "source": [ "class Disc:\n", " records=list()\n", " def __init_(self):\n", " self.records=[]\n", " \n", "d = Disc()\n", "d.records.append(r)\n", "print(len(d.records))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Атрибут данных можно сделать приватным (private) (т.е. недоступным снаружи), для этого слева нужно поставить два символа подчеркивания:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "20\n", "200\n" ] }, { "ename": "AttributeError", "evalue": "'Simple' object has no attribute '__private_attr'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 10\u001b[0m \u001b[0ms\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mSimple\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;34m'22'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnot_private_attr\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 12\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__private_attr\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# ошибка\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m: 'Simple' object has no attribute '__private_attr'" ] } ], "source": [ "class Simple:\n", " #Простой класс с приватным атрибутом\n", " __private_attr = 10 \n", " not_private_attr = 30\n", " def __init__(self, count, str):\n", " self.__private_attr = 20\n", " self.not_private_attr = 200\n", " print(self.__private_attr)\n", "\n", "s = Simple(1,'22')\n", "print(s.not_private_attr)\n", "print(s.__private_attr) # ошибка" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n" ] } ], "source": [ "def method_for_simple(self, x, y): # Методы необязательно определять внутри тела класса\n", " return x + y\n", " \n", "class Simple:\n", " f = method_for_simple\n", " \n", "s = Simple()\n", "print(s.f(1, 2))\n" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Вася\n" ] } ], "source": [ "class Customer: # Пустой класс можно использовать в качестве заготовки для структуры данных\n", " pass\n", "\n", "custom = Customer()\n", "custom.name = 'Вася'\n", "custom.salary = 100000\n", "print(custom.name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### self\n", "Обычно первый аргумент в имени метода - self. Как говорит автор языка Гвидо Ван Россум, это не более чем соглашение: имя self не имеет абсолютно никакого специального значения. self - это аналог \"this\" в C++.\n", "\n", "self полезен для того, чтобы обращаться к другим атрибутам класса.\n" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[123]\n" ] } ], "source": [ "class Simple:\n", " def __init__(self):\n", " self.list = []\n", " def f1(self):\n", " self.list.append(123)\n", " def f2(self):\n", " self.f1()\n", " \n", "s = Simple()\n", "s.f2()\n", "print(s.list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Наследование\n", "\n", "Определение производного класса выглядит следующим образом:\n", "\n", " class Derived(Base):\n", "\n", "Если базовый класс определен не в текущем модуле:\n", "\n", " class Derived(module_name.Base):\n", " \n", "Разрешение имен атрибутов работает сверху вниз: если атрибут не найден в текущем классе, поиск продолжается в базовом классе, и так далее. Производные классы могут переопределить методы базовых классов — все методы являются в этом смысле виртуальными. Вызвать метод базового класса можно с префиксом:\n", "\n", " Base.method()\n", " \n", "В питоне существует ограниченная поддержка множественного наследования:\n", " \n", " class Derived(Base1,Base2,Base3):\n", "\n", "Поиск атрибута производится в следующем порядке:\n", "\n", "1.\tв Derived;\n", "2.\tв Base1, затем рекурсивно в базовых классах Base1;\n", "3.\tв Base2, затем рекурсивно в базовых классах Base2\n", "4.\tи т.д.\n", "\n", "Пример:\n", "\n", "Создадим два класса:\n", "\n", "*Person* — хранит общую информацию о людях — имя, профессия, зарплата; \n", "\n", "класс *Manager* — специализированный производный класс. \n", "\n", "В классе Person мы создадим свою версию для стандартной встроенной функции str, которая есть по умолчанию в любом питоновском классе — для этого она будет иметь префикс с двумя символами подчеркивания слева и справа. Когда мы попытаемся распечатать инстанс класса, будет вызвана __str__." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "# -*- coding: utf-8 -*-\n", "class Person:\n", " def __init__(self, name, job=None, pay=0):\n", " self.name = name\n", " self.job = job\n", " self.pay = pay\n", " def lastName(self):\n", " return self.name.split()[-1]\n", " def giveRaise(self, percent):\n", " self.pay = int(self.pay * (1 + percent))\n", " def __str__(self):\n", " return '[Person: %s, %s]' % (self.name, self.pay)\n", "\n", "class Manager(Person):\n", " def __init__(self, name, pay): \n", " Person.__init__(self, name, 'mgr', pay) \n", " def giveRaise(self, percent, bonus=100):\n", " Person.giveRaise(self, percent + bonus)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "ivan = Person('Иван Petrov') # Создаем первый инстанс класса Person\n", "john = Person('John Sidorov', job='dev', pay=100000) # Создаем второй инстанс класса Person" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Person: Иван Petrov, 0]\n", "[Person: John Sidorov, 100000]\n" ] } ], "source": [ "#Вызываем перегруженную функцию __str__\n", "print(ivan)\n", "print(john)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Petrov Sidorov\n" ] } ], "source": [ "print(ivan.lastName(), john.lastName()) # Выводим фамилию:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Person: John Sidorov, 110000]\n" ] } ], "source": [ "john.giveRaise(.10) # Начисляем премиальные:\n", "print(john)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Jones\n", "[Person: Tom Jones, 5055000]\n" ] } ], "source": [ "tom = Manager('Tom Jones', 50000) # Создаем инстанс класса Manager\n", "tom.giveRaise(.10) # Начисляем мегапремиальные\n", "print(tom.lastName())\n", "print(tom)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Некоторые выводы\n", "Основные свойства ООП - полиморфизм, наследование, инкапсуляция. \n", "\n", "Класс - это пользовательский тип, состоящий из методов и атрибутов. \n", "\n", "Инстанс класса создается путем вызова имени класса как функции с параметрами. \n", "\n", "Объект состоит из атрибутов и методов. \n", "\n", "Атрибут - это переменная, метод - это функция. Отличия метода от функции в том, что у него есть первый параметр self. \n", "\n", "Полиморфизм позволяет работать с различными типами объектов так, что не нужно задумываться о том, к какому типу они принадлежат. \n", "\n", "Объекты могут скрывать (инкапсулировать) свое внутреннее состояние. Это достигается за счет того, что доступ к атрибутам осуществляется не напрямую, а через методы. \n", "\n", "Класс может быть производным от одного или нескольких классов. Производный класс наследует все методы базового класса. Базовых классов может быть несколько. \n", "\n", "Объектный дизайн должен быть прозрачным, понятным." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Специальные методы и атрибуты классов\n", "\n", "Специальные зарезервированные методы здесь имеют префикс - двойной символ подчеркивания. С их помощью реализованы такие механизмы, как конструкторы, последовательности, итераторы, свойства, слоты и т.д.\n", "\n", "### Объекты классов и специальные методы\n", "\n", "Объект-класс создается с помощью определения класса. Объекты-классы имеют следующие атрибуты:\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "__name__ — имя класса;\n", "\n", "__module__ — имя модуля;\n", "\n", "__dict__ — словарь атрибутов класса, можно изменять этот словарь напрямую;\n", "\n", "__bases__ — кортеж базовых классов в порядке их следования;\n", "\n", "__doc__ — строка документации класса.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Экземпляры классов и специальные методы\n", "Экземпляр (инстанс) класса возвращается при вызове объекта-класса. Объект у класса может быть один, экземпляров (или инстансов) - несколько. Экземпляры имеют следующие атрибуты:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "__dict__ — словарь атрибутов класса, можно изменять этот словарь напрямую;\n", "\n", "__class__ — объект-класс, экземпляром которого является данный инстанс;\n", "\n", "__init__ — конструктор. Если в базовом классе есть конструктор, конструктор производного класса должен вызвать его;\n", "\n", "__del__ — деструктор. Если в базовом классе есть деструкор, деструктор производного класса должен вызвать его;\n", "\n", "__cmp__ — вызывается для всех операций сравнения;\n", "\n", "__hash__ — возвращает хеш-значение объекта, равное 32-битному числу;\n", "\n", "__getattr__ — возвращает атрибут, недоступный обычным способом;\n", "\n", "__setattr__ — присваивает значение атрибуту;\n", "\n", "__delattr__ — удаляет атрибут;\n", "\n", "__call__ — срабатывает при вызове экземпляра класса." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Экземпляры классов в качестве последовательностей\n", "\n", "Экземпляры классов можно использовать для эмуляции последовательностей. Для такой реализации есть встроенные методы:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "__len__ — возвращает длину последовательности;\n", "\n", "__getitem__ — получение элемента по индексу или ключу;\n", "\n", "__setitem__ — присваивание элемента с данным ключом или индексом;\n", "\n", "__delitem__ — удаление элемента с данным ключом или индексом;\n", "\n", "__getslice__ — возвращает вложенную последовательность;\n", "\n", "__setslice__ — заменяет вложенную последовательность;\n", "\n", "__delslice__ — удаляет вложенную последовательность;\n", "\n", "__contains__ — реализует оператор in." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Приведение объектов к базовым типам\n", "\n", "Объекты классов можно привести к строковому или числовому типу." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "__repr__ — возвращает формальное строковое представление объекта;\n", "\n", "__str__ — возвращает строковое представление объекта;\n", "\n", "__oct__ , __hex__ , __complex__ , __int__ , __long__ , __float__ — возвращают строковое представление в соответствующей системе \n", "счисления." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Bound и unbound методы" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Aaaammm!\n" ] } ], "source": [ "class Cat:\n", " def __init__(self):\n", " self.hungry = True\n", " def eat(self):\n", " if self.hungry:\n", " print('I am hungry...')\n", " self.hungry = False\n", " else:\n", " print('No, thanks!')\n", " \n", "class Barsik(Cat):\n", " def __init__(self):\n", " self.sound = 'Aaaammm!'\n", " print(self.sound)\n", "\n", "brs = Barsik() # Создаем экземпляр производного класса" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'Barsik' object has no attribute 'hungry'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mbrs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0meat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36meat\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhungry\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0meat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhungry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'I am hungry...'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhungry\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mFalse\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mAttributeError\u001b[0m: 'Barsik' object has no attribute 'hungry'" ] } ], "source": [ "brs.eat()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На первый взгляд, странная ошибка, поскольку атрибут hungry есть в базовом классе. На самом деле, конструктор производного класса - перегруженный, при этом конструктор базового класса не вызывается, и его нужно явно вызвать. Это можно сделать двумя путями. Первый вариант считается устаревшим:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "# Первый вариант\n", "class Barsik(Cat):\n", " def __init__(self):\n", " Cat.__init__(self) \n", " self.sound = 'Aaaammm!'\n", " print(self.sound)" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Aaaammm!\n", "I am hungry...\n", "No, thanks!\n" ] } ], "source": [ "brs = Barsik() \n", "brs.eat()\n", "\n", "brs.eat()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь мы напрямую вызываем конструктор базового класса, не создавая инстанс базового класса Cat - поэтому такой базовый конструктор относится к категории unbound-методов. Есть еще методы, которые вызываются для инстансов классов и называются bound-методами. Для вызова bound-метода в качестве первого параметра методу нужно передать инстанс класса.\n", "\n", "Второй вариант: нужно вызвать стандартный метод super для базового конструктора:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Aaaammm!\n", "I am hungry...\n" ] } ], "source": [ "class Barsik(Cat):\n", " def __init__(self):\n", " super(Barsik, self).__init__()\n", " self.sound = 'Aaaammm!'\n", " print(self.sound)\n", "\n", "brs = Barsik()\n", "brs.eat()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## super()" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "class O:\n", " def method(self):\n", " print('I am O')\n", "class A(O):\n", " def method(self):\n", " super().method()\n", " print('I am A')\n", "class B(O):\n", " def method(self):\n", " super().method()\n", " print('I am B')\n", "class C(A, B):\n", " def method(self):\n", " super().method()\n", " print('I am C')" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "I am O\n", "I am A\n" ] } ], "source": [ "A().method()" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "I am O\n", "I am B\n", "I am A\n", "I am C\n" ] } ], "source": [ "C().method()" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[__main__.C, __main__.A, __main__.B, __main__.O, object]" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "C.mro() # Аббревиатура MRO – method resolution order, порядок разрешения методов" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Статические методы\n", "Статический метод - функция, определенная вне класса и не имеющая атрибута self." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of instances created: 3\n", "Number of instances created: 3\n" ] } ], "source": [ "class Spam:\n", " numInstances = 0\n", " def __init__(self):\n", " Spam.numInstances = Spam.numInstances + 1\n", "\n", "def printNumInstances( ):\n", " print(\"Number of instances created: \", Spam.numInstances)\n", " \n", "a=Spam()\n", "b=Spam()\n", "c=Spam()\n", "printNumInstances()\n", "a=1\n", "printNumInstances()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Статический метод может быть определен и внутри класса — для этого используется ключевое слово staticmethod, причем метод может быть вызван как статически, так и через инстанс." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n", "5\n" ] } ], "source": [ "class Multi:\n", " def imeth(self, x):\n", " print(self, x)\n", " def smeth(x):\n", " print(x)\n", " def cmeth(cls, x):\n", " print(cls, x)\n", " smeth = staticmethod(smeth)\n", " cmeth = classmethod(cmeth)\n", " \n", "Multi.smeth(3) \n", "obj=Multi()\n", "obj.smeth(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Методы класса определяются с помощью ключевого слова classmethod — здесь автоматически питон передает в качестве первого параметра сам класс (cls)." ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 7\n", " 10\n" ] } ], "source": [ "Multi.cmeth(7)\n", "obj.cmeth(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Итератор" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итераторы хороши там, где списки не подходят в силу того, что занимают много памяти, а итератор возвращает его конкретное значение. В классе нужно определить два стандартных метода — __iter__ и next. Метод __iter__ будет возвращать объект через метод next" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "class Rev:\n", " def __init__(self, data):\n", " self.data = data\n", " self.index = len(data)\n", " \n", " def __iter__(self):\n", " return self\n", " \n", " def __next__(self):\n", " if self.index == 0:\n", " raise StopIteration\n", " self.index = self.index - 1\n", " return self.data[self.index] \n" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "7\n", "6\n", "5\n", "4\n", "3\n", "2\n", "1\n" ] } ], "source": [ "for char in Rev('1234567'):\n", " print(char)\n" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['5', '4', '3', '2', '1']" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rvr = list(Rev('12345'))\n", "rvr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Property\n", "Property — атрибут класса, возвращаемый через стандартную функцию property, которая в качестве аргументов принимает другие функции класса." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class DateOffset:\n", " def __init__(self):\n", " self.start = 0\n", " \n", " def _get_offset(self):\n", " self.start += 5\n", " return self.start \n", " \n", " offset = property(_get_offset)\n", " \n", " \n", "d = DateOffset()\n", "print(d.offset)\n", "print(d.offset)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Singleton" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Данный паттерн позволяет создать всего один инстанс для класса. Используется метод __new__." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Singleton(object):\n", " def __new__(cls, *args, **kw):\n", " if not hasattr(cls, '_instance'):\n", " orig = super(Singleton, cls)\n", " cls._instance = orig.__new__(cls, *args, **kw)\n", " return cls._instance\n", " \n", "one = Singleton()\n", "two = Singleton()\n", "print(id(one))\n", "print(id(two))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Слоты" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Слоты — это список атрибутов, задаваемый в заголовке класса с помощью __slots__. В инстансе необходимо назначить атрибут, прежде чем пользоваться им." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class limiter(object):\n", " __slots__ = ['age', 'name', 'job']\n", " \n", "x=limiter() \n", "x.age = 20\n", "print(x.age)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Функтор" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функтор — это класс, имеющий метод __call__ — при этом объект можно вызвать как функцию.\n", "Пример. Пусть у нас имеется класс Person, имеется коллекция объектов этого класса- people, нужно отсортировать эту коллекцию по фамилиям. Для этого можно использовать функтор Sortkey." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class SortKey:\n", " def __init__(self, *attribute_names):\n", " self.attribute_names = attribute_names\n", " \n", " def __call__(self, instance):\n", " values = []\n", " for attribute_name in self.attribute_names:\n", " values.append(getattr(instance, attribute_name))\n", " return values\n", "\n", "class Person:\n", " def __init__(self, forename, surname, email):\n", " self.forename = forename\n", " self.surname = surname\n", " self.email = email\n", "\n", "people=[]\n", "p=Person('Petrov','','')\n", "people.append(p)\n", "p=Person('Sidorov','','')\n", "people.append(p)\n", "p=Person(u'Ivanov','','')\n", "people.append(p)\n", "for p in people:\n", " print(p.forename)\n", "print()\n", "people.sort(key=SortKey(\"forename\"))\n", "for p in people:\n", " print(p.forename)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Дескриптор" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Дескриптор - это класс, который хранит и контролирует атрибуты других классов. Вообще любой класс, который имплементирует один из специальных методов - __get__ , __set__ , __delete__, является дескриптором." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ExternalStorage:\n", " __slots__ = (\"attribute_name\",)\n", " __storage = {}\n", "\n", " def __init__(self, attribute_name):\n", " self.attribute_name = attribute_name\n", " \n", " def __set__(self, instance, value):\n", " self.__storage[id(instance), self.attribute_name] = value\n", " \n", " def __get__(self, instance, owner=None):\n", " if instance is None:\n", " return self\n", " return self.__storage[id(instance), self.attribute_name]\n", " \n", "class Point:\n", " __slots__ = ()\n", " x = ExternalStorage(\"x\")\n", " y = ExternalStorage(\"y\")\n", " def __init__(self, x=0, y=0):\n", " self.x = x\n", " self.y = y\n", " \n", "p1=Point(1,2) \n", "p2=Point(3,4)\n", "print(p1)\n", "print(p1.x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В данном случае класс Point не имеет собственных атрибутов x, y, хотя вызывает их так, как будто они есть — на самом деле они хранятся в дескрипторе ExternalStorage.\n", "\n", "## Sequence\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Последовательность реализуется с помощью методов __getitem__, __setitem__. В данном примере класс MySequence возвращает по индексу элемент последовательности неопределенной длины, представляющей собой арифметическую прогрессию вида: 1 3 5 7 ... Здесь нельзя применить стандартные методы __del__ , __len__." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class MySequence:\n", " def __init__(self, start=0, step=1):\n", " self.start = start \n", " self.step = step \n", " self.changed = {} \n", " def __getitem__(self, key):\n", " return self.start + key*self.step \n", " def __setitem__(self, key, value):\n", " self.changed[key] = value \n", " \n", "s = MySequence(1,2)\n", "print(s[0])\n", "print(s[1])\n", "print(s[100])\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Заключение\n", "\n", "Классы в python имеют большой набор встроенных методов и атрибутов, которые позволяют гибко использовать модель объектно-ориентированного программирования и упрощают решение стандартных задач и алгоритмов. Методы могут быть статическими в зависимости от природы объекта, что позволяет смешивать объектно-ориентированную и функциональную архитектуру. Вызов методов базового класса имеет собственную семантику. Последовательности и мапы, реализованные на базе итераторов, экономят ресурсы и память. Property упрощают сложную реализацию атрибутов класса. Функторы обращаются с коллекцией объектов пользовательского типа так, как будто это стандартные типы. Дескрипторы реализуют различную логику хранения атрибутов класса. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.9" } }, "nbformat": 4, "nbformat_minor": 2 }