Python objects and classes

Краткое введение в ООП

Объектно-ориентированное программирование (ООП) – технология разработки сложного программного обеспечения, в которой программа строится в виде совокупности объектов и их взаимосвязей.

Объединение данных и действий, производимых над этими данными, в единое целое, которое называется объектом – является одним из основных принципов ООП.

Основными понятиями являются понятие класса и объекта.

Класс является типом данных, определяемым пользователем и представляет собой структуру в виде данных и методов для работы с данными.

Формально Класс — это шаблон, по которому будет сделан объект.

Объект является экземпляром класса. Объект  и экземпляр - это одно и то же.

Вот пример. Форма для изготовления печенья – это класс, а само печенье это объект или экземпляр класса, т.е. это конкретное изделие. Печенье имеет размеры, цвет, состав – это атрибуты класса. Также в классе описываются методы, которые предназначены для чтения или изменения данных объекта.

В 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 – это типичная условная конструкция, которую можно встретить и в большинстве других языков программирования.

Синтаксически конструкция выглядит следующим образом:

  1. сначала записывается часть с условным выражением, которое возвращает истину или ложь;
  2. затем может следовать одна или несколько необязательных частей (в других языках вы могли встречать );
  3. Завершается же запись этого составного оператора также необязательной частью .

Принцип работы оператора выбора в 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'
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector