7. Основы Python. Часть III - qa-guru/knowledge-base GitHub Wiki

Данный раздел разделен на несколько частей(каждый пункт - это ссылка на соответствующий раздел):

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



ООП

ООП - это объектно-ориентированное программирование, которое позволяет описывать объекты и их взаимодействие между собой. ООП сосредотачивается на создании объектов, которые содержат как данные, так и код, чтобы манипулировать данными. ООП позволяет создавать объекты, которые могут взаимодействовать друг с другом, обмениваясь данными и информацией. Под объектом понимается экземпляр класса. Класс - это шаблон для создания объектов. Он определяет атрибуты и методы, которые будут у объектов этого класса. Атрибуты - это переменные, которые хранят данные. А методы - это функции, которые могут быть вызваны для выполнения действий.

Пример класса:

class Person: # это класс Person
    def __init__(self, name, age): # это конструктор класса
        self.name = name # это атрибут класса
        self.age = age # это атрибут класса

    def say_hello(self): # это метод класса, который выводит приветствие и насчитывает возраст. Наследует атрибуты класса(self.name, self.age)
        print(f'Hello, my name is {self.name} and I am {self.age} years old') 

В коде выше объектом называют экземпляр класса Person. Экземпляр класса создается с помощью вызова класса, как если бы это была функция. В примере выше создание объекта выглядит так:

person = Person('John', 25) # создание объекта класса Person

В примере выше person - это объект класса Person. Он содержит атрибуты name и age, которые были переданы в конструктор класса. Также у объекта есть метод say_hello, который можно вызвать, используя точку:

person.say_hello() # вызов метода say_hello у объекта person

# Output:
# Hello, my name is John and I am 25 years old

Преимущества ООП:

  • Повторное использование кода - объекты могут быть использованы в разных частях программы.
  • Модульность кода - объекты могут быть использованы для разделения кода на более мелкие части.
  • Гибкость кода - объекты могут быть легко изменены и расширены.
  • Безопасность кода - программы, написанные с использованием ООП, обычно более надежны и безопасны.

Недостатки ООП:

  • Сложность - ООП может быть сложным для понимания и использования.
  • Производительность - ООП может быть менее эффективным с точки зрения производительности, чем другие подходы к программированию.
  • Размер - ООП может привести к созданию больших программ, которые могут быть сложными для управления.

Основные принципы ООП:

  • Абстракция
  • Инкапсуляция
  • Наследование
  • Полиморфизм

Абстракция

Нажать, чтобы раскрыть

Абстракция - это процесс создания модели объекта. Который содержит только те аспекты объекта, которые важны для решения конкретной задачи. Она позволяет скрыть сложность объекта от пользователя. В Python абстракция реализуется с помощью классов. Класс - это шаблон для создания объектов. Он определяет атрибуты и методы, которые будут у объектов этого класса.

Пример абстракции:

class Person: 
    def __init__(self, name, age): 
        self.name = name 
        self.age = age 

    def say_hello(self): 
        print(f'Hello, my name is {self.name} and I am {self.age} years old') 

person = Person('John', 25) # создание объекта класса Person
person.say_hello() # вызов метода say_hello у объекта

# Output:
# Hello, my name is John and I am 25 years old

В примере выше класс Person - это абстракция объекта человека. Он содержит атрибуты name и age, которые характеризуют человека. И метод say_hello, который позволяет человеку поздороваться. Таким образом, класс Person абстрагирует объект человека, скрывая его сложность от пользователя.

Инкапсуляция

Нажать, чтобы раскрыть

Инкапсуляция - это прием программирования, который позволяет разбить сложную систему на более простые части. Она позволяет скрыть сложность системы от пользователя.

Пример инкапсуляции:

class Person: 
    def __init__(self, name, age): 
        self._name = name 
        self._age = age 

    def get_name(self): 
        return self._name 

    def set_name(self, name): 
        self._name = name 

    def get_age(self): 
        return self._age 

    def set_age(self, age): 
        self._age = age 

person = Person('John', 25) # создание объекта класса Person
print(person.get_name()) # вызов метода get_name у объекта person
print(person.get_age()) # вызов метода get_age у объекта person

# Output:
# John
# 25

В примере выше атрибуты name и age инкапсулированы в классе Person. Они доступны только через методы get_name, set_name, get_age и set_age. Таким образом, инкапсуляция позволяет скрыть сложность атрибутов от пользователя и предоставить удобный интерфейс для работы с ними. set_name и set_age - это методы для установки значений атрибутов name и age. get_name и get_age - это методы для получения значений атрибутов name и age. Таким образом, инкапсуляция позволяет создавать более безопасный и удобный интерфейс для работы с атрибутами объекта.

Наследование

Нажать, чтобы раскрыть

Наследование - это процесс создания нового класса на основе существующего класса. Новый класс называется подклассом, а существующий класс называется супер классом. Подкласс наследует атрибуты и методы супер класса и может добавлять к ним новые атрибуты и методы.

Пример наследования:

class Student(Person): 
    def __init__(self, name, age, grade): 
        super().__init__(name, age) 
        self.grade = grade 

    def get_grade(self): 
        return self.grade 

student = Student('John', 25, 'A') # создание объекта класса Student
print(student.get_name()) # вызов метода get_name у объекта student
print(student.get_age()) # вызов метода get_age у объекта student
print(student.get_grade()) # вызов метода get_grade у объекта student

# Output:
# John
# 25
# A

В примере выше класс Student наследует атрибуты и методы класса Person. Он добавляет к ним новый атрибут grade и метод get_grade. Таким образом, наследование позволяет создавать новые классы на основе существующих классов и повторно использовать код.

Полиморфизм

Нажать, чтобы раскрыть

Полиморфизм - это возможность объектов с одинаковым интерфейсом иметь различную реализацию. Это позволяет использовать объекты разных классов в одном и том же контексте.

Пример полиморфизма:

class Dog: 
    def speak(self): 
        return 'Woof!'

class Cat:
    def speak(self): 
        return 'Meow!'

def get_pet_sound(pet):
    return pet.speak()

dog = Dog()
cat = Cat()

print(get_pet_sound(dog)) # вызов функции get_pet_sound с объектом dog
print(get_pet_sound(cat)) # вызов функции get_pet_sound с объектом cat

# Output:
# Woof!
# Meow!

В примере выше классы Dog и Cat имеют метод speak с одинаковым интерфейсом. Однако у них различная реализация этого метода. Таким образом, полиморфизм позволяет использовать объекты разных классов в одном и том же контексте. Аргумент pet функции get_pet_sound может быть объектом любого класса, который имеет метод speak. Таким образом, полиморфизм позволяет создавать более гибкий и универсальный код.

Модули и классы

Нажать, чтобы раскрыть

Модули и классы являются основными строительными блоками в Python. Модули - это файлы, содержащие определения функций, классов и переменных, которые можно использовать в разных частях программы. Классы - это шаблоны для создания объектов. Они определяют атрибуты и методы, которые будут у объектов этого класса.

Пример модуля:

# file: automation.py

import time

def wait(seconds):
    time.sleep(seconds)

class Browser:
    def __init__(self, url):
        self.url = url

    def open(self):
        print(f'Opening {self.url} in the browser')

    def close(self):
        print('Closing the browser')

В примере выше, файл automation.py содержит определение функции wait и класса Browser. Эти определения могут быть использованы в других файлах с помощью импорта:

# file: main.py

import automation

automation.wait(5) # вызов функции wait из модуля automation

browser = automation.Browser('https://www.github.com') # создание объекта класса Browser из модуля automation
browser.open() # вызов метода open у объекта browser
automation.wait(5) # ожидание 5 секунд
browser.close() # вызов метода close у объекта browser

# Output:
# Opening https://www.github.com in the browser
# Closing the browser

В примере выше, файл main.py импортирует модуль automation и использует его определения. Таким образом, модули позволяют организовать код в более мелкие части и повторно использовать его в разных частях программы.

Для чего нужен self?

Нажать, чтобы раскрыть

self - это ссылка на текущий объект. Она используется для доступа к атрибутам и методам объекта внутри его методов. В Python self - это обязательный параметр для всех методов объекта, который передается автоматически при вызове метода.

Пример использования self:

class Person: 
    def __init__(self, name, age): 
        self.name = name 
        self.age = age 

    def say_hello(self): 
        print(f'Hello, my name is {self.name} and I am {self.age} years old') 

person = Person('John', 25) # создание объекта класса Person
person.say_hello() # вызов метода say_hello у объекта

# Output:
# Hello, my name is John and I am 25 years old

В примере выше self используется для доступа к атрибутам name и age объекта внутри метода say_hello. Таким образом, self позволяет объекту взаимодействовать с самим собой.

Как используется init в классе?

Нажать, чтобы раскрыть

__init__() - это конструктор класса. Он вызывается при создании объекта класса и используется для инициализации его атрибутов. Инициализация атрибутов происходит с помощью параметров, переданных в конструктор при создании объекта.

Инициализация это процесс задания начальных значений атрибутам объекта.

Пример использования init() в классе:

class Person: 
    def __init__(self, name, age): 
        self.name = name 
        self.age = age 

person = Person('John', 25) # создание объекта класса Person
print(person.name) # доступ к атрибуту name объекта
print(person.age) # доступ к атрибуту age объекта

# Output:
# John
# 25

В примере выше конструктор класса Person принимает два параметра: name и age. Он использует их для инициализации атрибутов name и age объекта. Таким образом, init() позволяет задать начальные значения атрибутам объекта при его создании.

Что такое @classmethod?

Нажать, чтобы раскрыть

@classmethod - это декоратор, который используется для определения метода класса. Метод класса принимает первым параметром ссылку на класс, а не на объект. Он может быть вызван как от объекта, так и от класса.

Пример использования @classmethod:

class Person: 
    count = 0 # атрибут класса

    def __init__(self, name, age): 
        self.name = name 
        self.age = age 
        Person.count += 1 # увеличение счетчика при создании объекта

    @classmethod
    def get_count(cls): # метод класса
        return cls.count

person1 = Person
person2 = Person
print(person1.get_count()) # вызов метода get_count от класса
print(person2.get_count()) # вызов метода get_count от класса

# Output:
# 2 # значение атрибута count = 2 потому что создано 2 объекта
# 2

В примере выше метод get_count класса Person помечен декоратором @classmethod. Он принимает первым параметром ссылку на класс (cls) и возвращает значение атрибута count. Таким образом, метод класса позволяет получить доступ к атрибутам класса и использовать их внутри метода.

Еще пример использования @classmethod:

class Math:
    @classmethod
    def add(cls, a, b):
        return a + b

print(Math.add(2, 3)) # вызов метода add от класса
math = Math()
print(math.add(2, 3)) # вызов метода add от объекта

# Output:
# 5
# 5

В примере выше метод add класса Math помечен декоратором @classmethod. Он принимает первым параметром ссылку на класс (cls) и возвращает сумму двух чисел. Таким образом, метод класса позволяет создавать функции, которые могут быть вызваны как от объекта, так и от класса.

Что такое @staticmethod?

Нажать, чтобы раскрыть

@staticmethod - это декоратор, который используется для определения статического метода. Статический метод не принимает ссылку на объект или класс и может быть вызван как от объекта, так и от класса.

Пример использования @staticmethod:

class Person: 
    @staticmethod
    def say_hello(name): # статический метод
        print(f'Hello, my name is {name}')

Person.say_hello('John') # вызов статического метода от класса

person = Person()
person.say_hello('John') # вызов статического метода от объекта

# Output:
# Hello, my name is John
# Hello, my name is John

В примере выше метод say_hello класса Person помечен декоратором @staticmethod. Он не принимает ссылку на объект или класс и просто выводит приветствие с именем. Таким образом, статический метод позволяет создавать функции, которые могут быть вызваны как от объекта, так и от класса.

Что такое @property?

Нажать, чтобы раскрыть

@property - это декоратор, который используется для определения свойства. Свойство позволяет управлять доступом к атрибутам объекта и выполнять дополнительные действия при их чтении и записи.

Пример использования @property:

class Person: 
    def __init__(self, name): 
        self._name = name 

    @property
    def name(self): # свойство для чтения атрибута name
        return self._name

    @name.setter
    def name(self, value): # свойство для записи атрибута name
        self._name = value

person = Person('John') # создание объекта класса Person
print(person.name) # чтение атрибута name
person.name = 'Mike' # запись атрибута name
print(person.name) # чтение атрибута name

# Output:
# John
# Mike

В примере выше свойство name класса Person позволяет управлять доступом к атрибуту _name. Оно определено с помощью декораторов @property и @name.setter. Таким образом, свойство позволяет выполнять дополнительные действия при чтении и записи атрибута. .setter - это декоратор, который определяет метод для записи атрибута. Он принимает первым параметром ссылку на свойство (name) и вторым параметром значение, которое нужно записать в атрибут. Таким образом, свойство позволяет управлять доступом к атрибутам объекта и выполнять дополнительные действия при их чтении и записи.

Зачем нужны дата-классы?

Нажать, чтобы раскрыть

Дата-классы - это классы, которые используются для представления данных. Они позволяют определить атрибуты и методы, которые будут у объектов этого класса. Дата-классы позволяют создавать объекты, которые содержат как данные, так и код, чтобы манипулировать данными.

Пример использования дата-классов:

from dataclasses import dataclass

@dataclass
class Person: 
    name: str
    age: int

person = Person('John', 25) # создание объекта класса Person
print(person.name) # доступ к атрибуту name объекта
print(person.age) # доступ к атрибуту age объекта

# Output:
# John
# 25

В примере выше дата-класс Person определен с помощью декоратора @dataclass. Он содержит два атрибута: name и age. Таким образом, дата-класс позволяет определить атрибуты и методы, которые будут у объектов этого класса.

Какие типы данных удобно использовать с Enum?

Нажать, чтобы раскрыть

Enum - это перечисление, которое позволяет определить набор именованных констант. Он позволяет создавать набор значений, которые могут быть использованы в разных частях программы.

Пример использования Enum:

from enum import Enum

class Color(Enum): 
    RED = 1
    GREEN = 2
    BLUE = 3

print(Color.RED) # доступ к значению RED
print(Color.GREEN) # доступ к значению GREEN
print(Color.BLUE) # доступ к значению BLUE

# Output:
# Color.RED
# Color.GREEN
# Color.BLUE

В примере выше Enum Color определяет три именованных константы: RED, GREEN и BLUE. Таким образом, Enum позволяет определить набор именованных констант, где каждая константа имеет свое уникальное имя и значение.

Подсказка к домашнему заданию

Нажать, чтобы раскрыть

Ты увидишь полное решение задачи, если нажмешь на кнопку ниже. Ты уверен, что хочешь это сделать?

Да, я осознаю все риски, и спишу

А может подумаешь еще раз?

Уже думал, хочу списать

А может загуглишь самостоятельно?

Не помогло, дай списать

Спишешь, но не скажешь никому?

Так и думал/ла сделать

А вдруг ты сможешь сам?

Может, но не сейчас

Ладно, смотри решение, но не забудь разобраться в нем.

Обязуюсь разобраться после того как спишу
Class Product
class Product:
    def check_quantity(self, quantity) -> bool:
        """
        TODO Верните True если количество продукта больше или равно запрашиваемому
            и False в обратном случае
        """
        return self.quantity >= quantity # проверка наличия продукта, возвращает True если продукта достаточно и False если не достаточно

    def buy(self, quantity):
        """
        TODO реализуйте метод покупки
            Проверьте количество продукта используя метод check_quantity
            Если продуктов не хватает, то выбросите исключение ValueError
        """
        if self.check_quantity(quantity): # проверка наличия продукта
            self.quantity -= quantity # уменьшение количества продукта
        else:
            raise ValueError("Not enough products") # выброс исключения если продукта не хватает
Class Cart
class Cart:
    # Словарь продуктов и их количество в корзине
    products: dict[Product, int]

    def __init__(self):
        # По-умолчанию корзина пустая
        self.products = {}

    def add_product(self, product: Product, buy_count=1):
        """
        Метод добавления продукта в корзину.
        Если продукт уже есть в корзине, то увеличиваем количество
        """
        if product in self.products: # проверка наличия продукта в корзине
            self.products[product] += buy_count # увеличение количества продукта в корзине, если он уже есть
        else:
            self.products[product] = buy_count # добавление продукта в корзину

    def remove_product(self, product: Product, remove_count=None):
        """
        Метод удаления продукта из корзины.
        Если remove_count не передан, то удаляется вся позиция
        Если remove_count больше, чем количество продуктов в позиции, то удаляется вся позиция
        """
        if product in self.products: # проверка наличия продукта в корзине
            if remove_count is None or self.products[product] <= remove_count: # проверка наличия remove_count
                del self.products[product] # удаление продукта из корзины
            else:
                self.products[product] -= remove_count # уменьшаем количество продукта в корзине

    def clear(self):
        self.products.clear() # очистка корзины

    def get_total_price(self) -> float:
        total = 0 # общая стоимость
        for product, count in self.products.items(): # перебор продуктов в корзине
            total += product.price * count # подсчет общей стоимости
        return total # возвращение общей стоимости

    def buy(self):
        """
        Метод покупки.
        Учтите, что товаров может не хватать на складе.
        В этом случае нужно выбросить исключение ValueError
        """
        for product, thing in self.products.items(): # перебор продуктов в корзине
            if product.quantity < thing: # проверка наличия продукта на складе
                raise ValueError('Not enough products') # выброс исключения если продукта не хватает
            else:
                product.buy(thing) # покупка продукта
⚠️ **GitHub.com Fallback** ⚠️