Python objects and classes
Содержание:
- Краткое введение в ООП
- Constructor
- Способ 1 – Использование функции DIR () для перечисления методов в классе
- Как работает if else
- Наследование
- Исследовательский анализ данных
- Функция __init__()
- Инкапсуляция в Python
- Что такое self?
- Зачем использовать классы метаклассов вместо функций?
- Принципы ООП
- Создание Класса
- Наследование
- Сравнение данных
- Конструкторы в Python
- Дескрипторы
- Можем ли мы иметь несколько конструкторов в Python?
Краткое введение в ООП
Объектно-ориентированное программирование (ООП) – технология разработки сложного программного обеспечения, в которой программа строится в виде совокупности объектов и их взаимосвязей.
Объединение данных и действий, производимых над этими данными, в единое целое, которое называется объектом – является одним из основных принципов ООП.
Основными понятиями являются понятие класса и объекта.
Класс является типом данных, определяемым пользователем и представляет собой структуру в виде данных и методов для работы с данными.
Формально Класс — это шаблон, по которому будет сделан объект.
Объект является экземпляром класса. Объект и экземпляр - это одно и то же.
Вот пример. Форма для изготовления печенья – это класс, а само печенье это объект или экземпляр класса, т.е. это конкретное изделие. Печенье имеет размеры, цвет, состав – это атрибуты класса. Также в классе описываются методы, которые предназначены для чтения или изменения данных объекта.
В Python характеристики объекта, называются атрибутами, а действия, которые мы можем проделывать с объектами, — методами. Методами в Python называют функции, которые определяются внутри класса.
Объект = атрибуты + методы
Constructor
In Python, the constructor method is invoked automatically whenever a new object of a class is instantiated, same as constructors in C# or Java.
The constructor must have a special name and a special parameter called .
Note:
The first parameter of each method in a class must be the , which refers to the calling object.
However, you can give any name to the first parameter, not necessarily .
The following example defines a constructor.
Example: Constructor
Copy
Now, whenever you create an object of the class, the constructor method will be called, as shown below.
Example: Constructor Call on Creating Object
Copy
The constructor in Python is used to define the attributes of an instance and assign values to them.
Способ 1 – Использование функции DIR () для перечисления методов в классе
Чтобы перечислить методы этого класса, один подход – использовать функцию DIR () в Python.
Функция вернет все функции и свойства класса.
Посмотрим, что произойдет, если мы попробуем это для Отказ
print(dir(MyClass))
Выход
Хорошо, мы видим, что у нас есть наш , , и Методы перечислены! Однако как насчет всех других методов?
Ну, эти методы (те, которые начинаются с двойного поднятия), называются Методы гуляния Отказ
Они обычно называются функцией обертки. Например, Функция вызывает метод.
Фильтрация расположенных методов от выхода
Обычно нам не понадобится префиксные методы двойной подчеркивания, поэтому мы можем отфильтровать их с помощью приведенного ниже фрагмента:
method_list = print(method_list)
Выход
Ух ты! Теперь мы только получаем арифметические методы, которые мы хотели!
Однако наше настоящее решение имеет проблему.
Помните, что Вызывает как методы, так и свойства класса?
Обращение с свойствами класса
Если бы у нас была собственность внутри класса, он тоже будет перечислять. Рассмотрим ниже пример.
class MyClass(object): # MyClass property property1 = def __init__(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = a def add(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = self.state + a return self.state def subtract(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = self.state - a return self.state def multiply(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = self.state * a return self.state def divide(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = self.state / a return self.state @staticmethod def global_method(a, b): return a + b @classmethod def myclass_method(cls): return cls method_list = print(method_list)
Теперь, что вы думаете, что вывод будет?
Выход
Это дает нам Также, что не то, что мы хотим.
Нам нужно сделать еще один фильтр для дифференцировки между методом и свойством.
Но это действительно просто. Основное отличие состоит в том, что любой объект недвижимости Не Callable, в то время как методы можно назвать!
В Python мы можем использовать булевую функцию Чтобы проверить, можно ли назвать атрибут.
Давайте теперь включаем это в наш старый код.
method_list = print(method_list)
Давайте сломаемся, написав его без понимания списка:
method_list = [] # attribute is a string representing the attribute name for attribute in dir(MyClass): # Get the attribute value attribute_value = getattr(MyClass, attribute) # Check that it is callable if callable(attribute_value): # Filter all dunder (__ prefix) methods if attribute.startswith('__') == False: method_list.append(attribute) print(method_list)
Мы также изменили к Так что это удаляет вводящее в заблуждение намерения!
Давайте проверим это сейчас.
Выход
Действительно, мы действительно получим свой список методов без свойств!
Как работает if else
Синтаксис
Оператор в языке Python – это типичная условная конструкция, которую можно встретить и в большинстве других языков программирования.
Синтаксически конструкция выглядит следующим образом:
- сначала записывается часть с условным выражением, которое возвращает истину или ложь;
- затем может следовать одна или несколько необязательных частей (в других языках вы могли встречать );
- Завершается же запись этого составного оператора также необязательной частью .
Принцип работы оператора выбора в Python
Для каждой из частей существует ассоциированный с ней блок инструкций, которые выполняются в случае истинности соответствующего им условного выражения.
То есть интерпретатор начинает последовательное выполнение программы, доходит до и вычисляет значение сопутствующего условного выражения. Если условие истинно, то выполняется связанный с набор инструкций. После этого управление передается следующему участку кода, а все последующие части и часть (если они присутствуют) опускаются.
Отступы
Отступы – важная и показательная часть языка Python. Их смысл интуитивно понятен, а определить их можно, как размер или ширину пустого пространства слева от начала программного кода.
Благодаря отступам, python-интерпретатор определяет границы блоков. Все последовательно записанные инструкции, чье смещение вправо одинаково, принадлежат к одному и тому же блоку кода. Конец блока совпадает либо с концом всего файла, либо соответствует такой инструкции, которая предшествует следующей строке кода с меньшим отступом.
Таким образом, с помощью отступов появляется возможность создавать блоки на различной глубине вложенности, следуя простому принципу: чем глубже блок, тем шире отступ.
Примеры
Рассмотрим несколько практических примеров использования условного оператора.
Пример №1: создание ежедневного бэкапа (например базы данных):
Пример №2: Проверка доступа пользователя к системе. В данном примере проверяет наличие элемента в списке:
Пример №3: Валидация входных данных. В примере к нам приходят данные в формате . Нам необходимо выбрать все записи определенного формата:
Наследование
поддерживают наследование так же как обычные классы Python.
Таким образом, атрибуты, определенные в родительском классе, будут доступны и в дочернем классе.
@dataclass class Person: age: int = 0 name: str @dataclass class Student(Person): grade: int >>> s = Student(20, "John Doe", 12) >>> s.age >>> 20 >>> s.name >>> "John Doe" >>> s.grade >>> 12
Обратите внимание на тот факт, что аргументы для Student находятся в порядке полей, определенных в определении класса. Как себя ведет __post_init__ во время наследования?
Как себя ведет __post_init__ во время наследования?
Поскольку __post_init__ — это просто еще одна функция, ее вызов не меняется:
@dataclass class A: a: int def __post_init__(self): print("A") @dataclass class B(A): b: int def __post_init__(self): print("B") >>> a = B(1,2) >>> B
В приведенном выше примере вызывается только метод __post_init__ класса B. Но как нам вызвать метод __post_init__ класса A?
Поскольку это функция родительского класса, его можно вызвать с помощью super.
@dataclass class B(A): b: int def __post_init__(self): super().__post_init__() #Call post init of A print("B") >>> a = B(1,2) >>> A B
Исследовательский анализ данных
набор данныхмы будем использовать содержит 8124 экземпляров грибов с 22 функциями. Среди них мы находим форму крышки гриба, цвет крышки, цвет жабры, тип вуали и т. Д. Конечно, это также говорит нам, является ли гриб съедобным или ядовитым.
Давайте импортируем некоторые библиотеки, которые помогут нам импортировать данные и манипулировать ими. В вашей записной книжке запустите следующий код:
Общий первый шаг для проекта науки о данных состоит в том, чтобы выполнитьисследовательский анализ данных(ЭДА). Этот шаг обычно включает в себя изучение дополнительных данных, с которыми вы работаете. Возможно, вы захотите узнатьформавашего набора данных (сколько строк и столбцов), количество пустых значений и визуализировать части данных, чтобы лучше понять взаимосвязь между функциями и целью.
Импортируйте данные и увидите первые пять столбцов со следующим кодом:
Всегда хорошо иметь набор данных вданныепапка в каталоге проекта. Кроме того, мы сохраняем путь к файлу в переменной, так что, если путь когда-либо изменяется, нам нужно только изменить присвоение переменной.
После запуска этой ячейки кода вы должны увидеть первые пять строк. Вы замечаете, что каждая функция является категориальной, и для определения определенного значения используется буква. Конечно, классификатор не может принимать буквы в качестве входных данных, поэтому нам придется изменить это в конце концов.
Пока давайте посмотрим, если наш набор данныхнесбалансированный.Несбалансированный набор данных — это когдаодин класс гораздо больше, чем другой, В идеале, в контексте классификации, мы хотим равное количество экземпляров каждого класса. В противном случае нам нужно будет реализовать расширенные методы выборки, такие какпередискретизация меньшинства.
В нашем случае мы хотим посмотреть, есть ли в наборе данных равное количество ядовитых и съедобных грибов. Мы можем построить частоту каждого класса следующим образом:
И вы получите следующий график:
Подсчет каждого класса
Потрясающе! Это похоже на довольно сбалансированный набор данных с почти равным количеством ядовитых и съедобных грибов.
Теперь я хотел посмотреть, как каждая функция влияет на цель. Для этого я сделал гистограмму всех возможных значений, разделенных классом грибов. Делать это вручную для всех 22 функций не имеет смысла, поэтому мы создаем эту вспомогательную функцию:
оттенокдаст цветовой код ядовитому и съедобному классу.данныеПараметр будет содержать все функции, кроме класса гриба. Выполнение кода ячейки ниже:
Вы должны получить список из 22 участков. Вот пример вывода:
Поверхность крышки
Потратьте некоторое время на просмотр всех графиков.
Теперь посмотрим, есть ли у нас пропущенные значения. Запустите этот кусок кода:
И вы должны увидеть каждый столбец с количеством пропущенных значений. К счастью, у нас есть набор данных без пропущенных значений. Это очень редко, но мы не будем жаловаться.
Функция __init__()
У всех классов есть функция . Её можно менять в любое время. Функция выполняется всякий раз, когда из класса создаётся объект. Она может использоваться для инициализации переменных класса. становится очень полезной, когда нам нужно, чтобы класс Python всегда начинался с тех или иных свойств.
В качестве примера возьмём следующий код:
class Person: def __init__(self, name, gender): self.name = name self.gender = gender self.country = "USA"person_1 = Person("Bob", "Male")person_2 = Person("Kate", "Female")print(person_1.name)print(person_1.gender)print(person_1.country)print(person_2.name)print(person_2.gender)print(person_2.country)
Здесь у нас есть два человека с классом Person и именами Bob и Kate (типа мы их создали). В обоих классах исполнялась функция , инициализируя переменные класса для имени, пола и страны человека.
Что касается имени и пола, мы передали классу свои переменные, которые требовались в . Переменная «страна» инициализировалась при создании объекта в этой же функции, но с одним отличием: по причине того, что она не является переменной функции , значение не может быть задано извне. Поэтому у всех будет одна страна — США.
Результат этого кода будет такой:
BobMaleUSAKateFemaleUSA
Инкапсуляция в Python
Это концепция упаковки данных так, что внешний мир имеет доступ только к открытым свойствам. Некоторые свойства могут быть скрыты, чтобы уменьшить уязвимость. Это так называемая реализация сокрытия данных. Например, вы хотите купить брюки с интернет-сайта. Данные, которые вам нужны, это их стоимость и доступность. Количество предметов и их расположение — это информация, которая вас не беспокоит. Следовательно, эта информация скрыта.
В Python это реализуется путем создания private, protected и public переменных и методов экземпляра.
Private свойства имеют двойное подчеркивание (__) в начале, в то время как protected имеют одиночное подчеркивание (_). По умолчанию, все остальные переменные и методы являются public.
Private атрибуты доступны только внутри класса и недоступны для дочернего класса (если он унаследован). Protected доступны внутри класса, но доступны и дочернему классу. Все эти ограничения сняты для public атрибутов.
Следующие фрагменты кода являются примером этой концепции:
Что такое self?
Классам нужен способ, что ссылаться на самих себя. Это не из разряда нарциссичного отношения со стороны класса. Это способ сообщения между экземплярами. Слово self это способ описания любого объекта, буквально. Давайте взглянем на пример, который мне кажется наиболее полезным, когда я сталкиваюсь с чем-то новым и странным:Добавьте этот код в конец класса, который вы написали ранее и сохраните:
Python
class Vehicle(object):
«»»docstring»»»
def __init__(self, color, doors, tires):
«»»Constructor»»»
self.color = color
self.doors = doors
self.tires = tires
def brake(self):
«»»
Stop the car
«»»
return «Braking»
def drive(self):
«»»
Drive the car
«»»
return «I’m driving!»
if __name__ == «__main__»:
car = Vehicle(«blue», 5, 4)
print(car.color)
truck = Vehicle(«red», 3, 6)
print(truck.color)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
classVehicle(object) «»»docstring»»» def__init__(self,color,doors,tires) «»»Constructor»»» self.color=color self.doors=doors self.tires=tires defbrake(self) «»» Stop the car return»Braking» defdrive(self) «»» Drive the car return»I’m driving!» if__name__==»__main__» car=Vehicle(«blue»,5,4) print(car.color) truck=Vehicle(«red»,3,6) print(truck.color) |
Условия оператора if в данном примере это стандартный способ указать Пайтону на то, что вы хотите запустить код, если он выполняется как автономный файл. Если вы импортировали свой модуль в другой скрипт, то код, расположенный ниже проверки if не заработает. В любом случае, если вы запустите этот код, вы создадите два экземпляра класса автомобиля (Vehicle): класс легкового и класс грузового. Каждый экземпляр будет иметь свои собственные атрибуты и методы. Именно по этому, когда мы выводи цвета каждого экземпляра, они и отличаются друг от друга. Причина в том, что этот класс использует аргумент self, чтобы указать самому себе, что есть что. Давайте немного изменим класс, чтобы сделать методы более уникальными:
Python
class Vehicle(object):
«»»docstring»»»
def __init__(self, color, doors, tires, vtype):
«»»Constructor»»»
self.color = color
self.doors = doors
self.tires = tires
self.vtype = vtype
def brake(self):
«»»
Stop the car
«»»
return «%s braking» % self.vtype
def drive(self):
«»»
Drive the car
«»»
return «I’m driving a %s %s!» % (self.color, self.vtype)
if __name__ == «__main__»:
car = Vehicle(«blue», 5, 4, «car»)
print(car.brake())
print(car.drive())
truck = Vehicle(«red», 3, 6, «truck»)
print(truck.drive())
print(truck.brake())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
classVehicle(object) «»»docstring»»» def__init__(self,color,doors,tires,vtype) «»»Constructor»»» self.color=color self.doors=doors self.tires=tires self.vtype=vtype defbrake(self) «»» Stop the car return»%s braking»%self.vtype defdrive(self) «»» Drive the car return»I’m driving a %s %s!»%(self.color,self.vtype) if__name__==»__main__» car=Vehicle(«blue»,5,4,»car») print(car.brake()) print(car.drive()) truck=Vehicle(«red»,3,6,»truck») print(truck.drive()) print(truck.brake()) |
В этом примере мы передаем другой параметр, чтобы сообщить классу, какой тип транспортного средства мы создаем. После этого мы вызываем каждый метод для каждого экземпляра. Если вы запустите данный код, вы получите следующий вывод:
Python
car braking
I’m driving a blue car!
I’m driving a red truck!
truck braking
1 2 3 4 |
car braking I’m driving a blue car! I’mdrivingared truck! truck braking |
Это показывает, как экземпляр отслеживает свой аргумент self. Вы также могли заметить, что мы можем переместить переменные атрибутов из метода __init__ в другие методы. Это возможно потому, что все эти атрибуты связанны с аргументом self. Если бы мы этого не сделали, переменные были бы вне области видимости в конце метода __init__ .
Зачем использовать классы метаклассов вместо функций?
Поскольку __metaclass__ может принимать любые вызываемые объекты, зачем использовать класс, если он явно более сложен?
Для этого есть несколько причин:
- Ваши намерения в этом случае будут более ясно. Когда вы читаете UpperAttrMetaclass (type), вам проще понять, что будет дальше
- Вы можете использовать ООП. Метакласс может наследоваться от метакласса, переопределять родительские методы. Метаклассы могут даже использовать другие метаклассы.
- Подклассы класса будут экземплярами его метакласса, если вы указали класс метакласса, не с помощью функции метакласса.
- Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то столь же тривиального, как приведенный выше пример. Обычно они используются для чего-то сложного. Возможность создавать несколько методов и группировать их в один класс очень полезна для облегчения чтения кода.
- Вы можете подключиться к __new__, __init__ и __call__. Это позволит вам делать разные вещи. Даже если обычно вы можете делать все это в __new__, некоторым людям просто удобнее использовать __init__.
- Они называются метаклассами, черт возьми! Это должно что-то значить!
Принципы ООП
Абстракция
Абстракция – это выделение основных, наиболее значимых характеристик объекта и игнорирование второстепенных.
Любой составной объект реального мира – это абстракция. Говоря «ноутбук», вам не требуется дальнейших пояснений, вроде того, что это организованный набор пластика, металла, жидкокристаллического дисплея и микросхем. Абстракция позволяет игнорировать нерелевантные детали, поэтому для нашего сознания это один из главных способов справляться со сложностью реального мира. Если б, подходя к холодильнику, вы должны были иметь дело с отдельно металлом корпуса, пластиковыми фрагментами, лакокрасочным слоем и мотором, вы вряд ли смогли бы достать из морозилки замороженную клубнику.
Полиморфизм
Полиморфизм подразумевает возможность нескольких реализаций одной идеи. Простой пример: у вас есть класс «Персонаж», а у него есть метод «Атаковать». Для воина это будет означать удар мечом, для рейнджера – выстрел из лука, а для волшебника – чтение заклинания «Огненный Шар». В сущности, все эти три действия – атака, но в программном коде они будут реализованы совершенно по-разному.
Наследование
Это способность одного класса расширять понятие другого, и главный механизм повторного использования кода в ООП. Вернёмся к нашему автосимулятору. На уровне абстракции «Автотранспорт» мы не учитываем особенности каждого конкретного вида транспортного средства, а рассматриваем их «в целом». Если же более детализировано приглядеться, например, к грузовикам, то окажется, что у них есть такие свойства и возможности, которых нет ни у легковых, ни у пассажирских машин. Но, при этом, они всё ещё обладают всеми другими характеристиками, присущими автотранспорту.
Мы могли бы сделать отдельный класс «Грузовик», который является наследником «Автотранспорта». Объекты этого класса могли бы определять все прошлые атрибуты (цвет, год выпуска), но и получить новые. Для грузовиков это могли быть грузоподъёмность, снаряженная масса и наличие жилого отсека в кабине. А методом, который есть только у грузовиков, могла быть функция сцепления и отцепления прицепа.
Инкапсуляция
Инкапсуляция – это ещё один принцип, который нужен для безопасности и управления сложностью кода. Инкапсуляция блокирует доступ к деталям сложной концепции. Абстракция подразумевает возможность рассмотреть объект с общей точки зрения, а инкапсуляция не позволяет рассматривать этот объект с какой-либо другой.
Вы разработали для муниципальных служб класс «Квартира». У неё есть свойства вроде адреса, метража и высоты потолков. И методы, такие как получение информации о каждом из этих свойств и, главное, метод, реализующий постановку на учёт в Росреестре. Это готовая концепция, и вам не нужно чтобы кто-то мог добавлять методы «открыть дверь» и «получить место хранения денег». Это А) Небезопасно и Б) Избыточно, а также, в рамках выбранной реализации, не нужно. Работникам Росреестра не требуется заходить к вам домой, чтобы узнать высоту потолков – они пользуются только теми документами, которые вы сами им предоставили.
Создание Класса
Создание класса в Пайтоне – это очень просто. Вот простой пример:
Python
# Python 2.x syntax
class Vehicle(object):
«»»docstring»»»
def __init__(self):
«»»Constructor»»»
pass
1 2 3 4 5 6 7 8 |
# Python 2.x syntax classVehicle(object) «»»docstring»»» def__init__(self) «»»Constructor»»» pass |
Этот класс не делает ничего конкретного, тем не менее, это очень хороший инструмент для изучения. Например, чтобы создать класс, мы используем ключевое слово class, за которым следует наименование класса. В Пайтоне, конвенция указывает на то, что наименование класса должно начинаться с заглавной буквы. Далее нам нужно открыть круглые скобки, за которыми следует слово object и закрытые скобки. «object» — то, на чем основан класс, или наследуется от него. Это называется базовым классом или родительским классом. Большая часть классов в Пайтоне основаны на объекте. У классов есть особый метод, под названием __init__.
Этот метод вызывается всякий раз, когда вы создаете (или создаете экземпляр) объект на основе этого класса. Метод __init__ вызывается единожды, и не может быть вызван снова внутри программы. Другое определение метода __init__ — это конструктор, кстати, этот термин редко встречается в Пайтоне. Вы можете подумать, почему я называю это методом, а не функцией? Функция меняет свое имя на «method», когда она находится внутри класса
Обратите внимание на то, что каждый метод должен иметь как минимум один аргумент, что в случае с обычной функцией уже не вяжется. В Python 3 нам не нужно прямо указывать, что мы наследуем у объекта
Вместо этого, мы можем написать это следующим образом:
Python
# Python 3.x syntax
class Vehicle:
«»»docstring»»»
def __init__(self):
«»»Constructor»»»
pass
1 2 3 4 5 6 7 8 |
# Python 3.x syntax classVehicle «»»docstring»»» def__init__(self) «»»Constructor»»» pass |
Обратите внимание на то, что единственная разница в том, что круглые скобки нам больше не нужны, когда мы основываем наш класс на объекте. Давайте немного расширим наше определение класса и дадим ему некоторые атрибуты и методы
Python
class Vehicle(object):
«»»docstring»»»
def __init__(self, color, doors, tires):
«»»Constructor»»»
self.color = color
self.doors = doors
self.tires = tires
def brake(self):
«»»
Stop the car
«»»
return «Braking»
def drive(self):
«»»
Drive the car
«»»
return «I’m driving!»
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
classVehicle(object) «»»docstring»»» def__init__(self,color,doors,tires) «»»Constructor»»» self.color=color self.doors=doors self.tires=tires defbrake(self) «»» Stop the car return»Braking» defdrive(self) «»» Drive the car return»I’m driving!» |
В данном примере мы добавили три атрибута и два метода. Эти три атрибута являются:
Python
self.color = color
self.doors = doors
self.tires = tires
1 2 3 |
self.color=color self.doors=doors self.tires=tires |
Атрибуты описывают автомобиль. У него есть цвет, определенное количество дверей и колес. Также у него есть два метода. Метод описывает, что делает класс. В нашем случае, автомобиль может двигаться и останавливаться. Вы могли заметить, что все методы, включая первый, имеют интересный аргумент, под названием self. Давайте рассмотрим его внимательнее.
Наследование
Наследование — это способ создания нового класса на основе старого. Новый класс является производным классом (дочерним). Существующий класс является базовым классом (родительским).
Пример 3: Использование наследования в Python
# родительский класс class Bird: def __init__(self): print("Bird is ready") def whoisThis(self): print("Bird") def swim(self): print("Swim faster") # дочерний класс class Penguin(Bird): def __init__(self): # вызов функции super() super().__init__() print("Penguin is ready") def whoisThis(self): print("Penguin") def run(self): print("Run faster") peggy = Penguin() peggy.whoisThis() peggy.swim() peggy.run()
Результат работы программы:
Bird is ready Penguin is ready Penguin Swim faster Run faster
Сначала мы создали два класса: Bird (родительский класс) и Penguin (дочерний класс). Он наследует функции родительского класса. Это прослеживается в методе swim().
Дочерний класс изменил поведение родительского класса – метод whoisThis(). Также мы расширяем родительский класс, создав новый метод run().
Мы используем функцию super() перед методом __init__(), чтобы извлечь содержимое метода __init__() из родительского класса в дочерний.
Сравнение данных
Как правило, объекты данных необходимо сравнивать друг с другом.
Сравнение между двумя объектами и обычно состоит из следующих операций:
- a < b
- a > b
- a == b
- a >= b
- a <= b
В python можно определить в классах, которые могут выполнять вышеуказанные операции. Для простоты, я продемонстрирую лишь реализацию == и <.
Обычный класс
class Number: def __init__( self, val = 0): self.val = val def __eq__(self, other): return self.val == other.val def __lt__(self, other): return self.val < other.val
@dataclass(order = True) class Number: val: int = 0
Да, вот и все.
Нам не нужно определять методы __eq__ и __lt__, потому что декоратор dataclass автоматически добавляет их в определение класса при вызове с order = True
Ну, как это реализуется?
Когда вы используете dataclass, он добавляет функции __eq__ и __lt__ в определение класса. Мы уже знаем это. Как эти функции знают, что нужно проверить равенство или сделать сравнение?
Сгенерированная функцией __eq__ будет сравнивать кортеж своих атрибутов с кортежем атрибутов другого экземпляра того же класса. В нашем случае вот что эквивалентно автоматически сгенерированной функции __eq__:
def __eq__(self, other): return (self.val,) == (other.val,)
Давайте посмотрим на более сложный пример:
Мы напишем класс данных Person, которое будет содержать имя (name) и возраст (age).
@dataclass(order = True) class Person: name: str age:int = 0
Автоматически сгенерированный метод __eq__ будет эквивалентен:
def __eq__(self, other): return (self.name, self.age) == ( other.name, other.age)
Обратите внимание на порядок атрибутов. Они всегда будут генерироваться в порядке, который вы определили в определении класса данных
Аналогично, эквивалентная функция __le__ будет похожа на:
def __le__(self, other): return (self.name, self.age) <= (other.name, other.age)
Необходимость определения функции, подобной __le__, обычно возникает, когда вам нужно отсортировать список ваших объектов данных. Встроенная функция сортировки Python основана на сравнении двух объектов.
>>> import random >>> a = #generate list of random numbers >>> a >>> >>> sorted_a = sorted(a) #Sort Numbers in ascending order >>> >>> reverse_sorted_a = sorted(a, reverse = True) #Sort Numbers in descending order >>> reverse_sorted_a >>>
Конструкторы в Python
Функции класса, начинающиеся с двойного подчеркивания __, называются специальными функциями, поскольку имеют особое значение.
Особый интерес представляет функция __init __(). Эта специальная функция вызывается всякий раз, когда создается новый объект этого класса.
Этот тип функций также называется конструкторами в объектно-ориентированном программировании (ООП). Обычно мы используем его для инициализации всех переменных.
class ComplexNumber: def __init__(self, r=0, i=0): self.real = r self.imag = i def get_data(self): print(f'{self.real}+{self.imag}j') # Create a new ComplexNumber object num1 = ComplexNumber(2, 3) # Call get_data() method # Output: 2+3j num1.get_data() # Create another ComplexNumber object # and create a new attribute 'attr' num2 = ComplexNumber(5) num2.attr = 10 # Output: (5, 0, 10) print((num2.real, num2.imag, num2.attr)) # but c1 object doesn't have attribute 'attr' # AttributeError: 'ComplexNumber' object has no attribute 'attr' print(num1.attr)
Выход
2+3j (5, 0, 10) Traceback (most recent call last): File "<string>", line 27, in <module> print(num1.attr) AttributeError: 'ComplexNumber' object has no attribute 'attr'
В приведенном выше примере мы определили новый класс для представления комплексных чисел. В нем есть две функции: __init __() для инициализации переменных (по умолчанию ‒ ноль) и get_data() для правильного отображения числа.
В предыдущем шаге следует отметить интересную вещь: атрибуты объекта можно создавать «на лету». Мы создали новый атрибут attr для объекта num2 и также прочитали его. Но это не создает этот атрибут для объекта num1.
Дескрипторы
Дескрипторы — объекты, которые умеют выполнять произвольный код, когда с ними происходят какие-то действия: доступ, изменение или удаление. Пример:
Дескриптор определяет методы, которые вызываются в момент, когда происходит доступ (или удаление или редактирование) инстанса дескриптора как атрибута другого класса.
Всё это работает благодаря методу : он находит нужный атрибут и проверяет, есть ли у него метод . Если есть — вызывает его, если нет — возвращает сам атрибут.
Именно через механизм дескрипторов осуществляется доступ к атрибутам класса и инстанса. , , и тоже работают благодаря дескрипторам.
Можем ли мы иметь несколько конструкторов в Python?
В отличие от других популярных объектно-ориентированных языков программирования, Python не поддерживает перегрузку метода и перегрузки конструктора.
Однако он не будет бросать никакой ошибки, если определим несколько конструкторов в классе. Последний конструктор перезаписывает более раннее определенное определение конструктора. Давайте посмотрим на это с примером.
class Employee: def __init__(self, id): self.employee_id = id # this will overwrite earlier defined constructor def __init__(self, id, n): self.employee_id = id self.emp_name = n def work(self): print(f'{self.emp_name} is working') emp = Employee(100, 'Pankaj') emp.work() emp = Employee(100) # will raise Error emp.work()
Выход:
Pankaj is working Traceback (most recent call last): File "/Users/pankaj/Documents/PycharmProjects/AskPython/hello-world/class_examples.py", line 19, in emp = Employee(100) TypeError: __init__() missing 1 required positional argument: 'n'