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) и вторым параметром значение, которое нужно записать в атрибут. Таким образом, свойство позволяет управлять доступом к атрибутам объекта и выполнять дополнительные действия при их чтении и записи.

Магические методы

Нажать, чтобы раскрыть **Магические методы** — это специальные методы в Python, имена которых начинаются и заканчиваются двумя подчёркиваниями: `__init__`, `__str__`, `__len__` и т.д. Интерпретатор не вызывает их напрямую, а делает это за вас при использовании соответствующего синтаксиса. Магические методы позволяют переопределять поведение стандартных операций для ваших объектов.

Основные группы магических методов

  1. Создание и инициализация объекта

    • __init__(self, ...) — инициализация экземпляра (заполняем атрибуты).
    • __new__(cls, ...) — создание объекта (используется редко, в основном при наследовании от immutable-типов).
  2. Человекочитаемое представление

    • __str__(self) — строка для пользователя (print(obj)).
    • __repr__(self) — строка для разработчика / дебага (obj в консоли, repr(obj)).
    class  User: def  __init__(self, name):
            self.name = name def  __repr__(self): return  f"User(name={self.name!r})" def  __str__(self): return self.name
    
    u = User("Elena") print(u) # Elena     (__str__)  print(repr(u)) # User(name='Elena')  (__repr__)
  3. Сравнение

    • __eq__(self, other)==
    • __ne__!=
    • __lt__, __le__, __gt__, __ge__<, <=, >, >=
            self.volume = volume def  __lt__(self, other): return self.volume < other.volume print(Box(10) < Box(20)) # True
            ``` 
  4. Коллекции и доступ по индексу

    • __len__(self)len(obj)
    • __getitem__(self, key)obj[key]
    • __setitem__(self, key, value)obj[key] = value
    • __iter__(self) — поддержка for / генераторов
  5. Операторы

    • __add__, __sub__, __mul__ и т.д. — перегрузка + - * /
    • __contains__(self, item) — оператор in
  6. Контекстный менеджер

    • __enter__(self) и __exit__(self, exc_type, exc, tb) — работа с with.

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

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

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

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

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

Принципы программирования

Нажать, чтобы раскрыть **DRY**: не дублируйте логику — извлекайте общее в функции/классы. **KISS**: делайте просто; “ясно и достаточно” лучше “умно и запутанно”. **Zen of Python (import this)**: явность, простота, читаемость, единственный очевидный способ. **YAGNI**: вам это не понадобится **SOLID:** - S — Single Responsibility, одна ответственность: один класс/файл — одна задача. - O — Open/Closed, открыт для расширения, закрыт для изменения: добавляй новое поведение, не переписывая старое. - L — Liskov Substitution, подстановка: подкласс должен вести себя как базовый — не ломать ожидания. - I — Interface Segregation, разделяй интерфейсы: лучше несколько маленьких интерфейсов, чем один «толстый». - D — Dependency Inversion, зависимости от абстракций: реализуй зависимости через интерфейсы/фикстуры, а не через «жёсткие» классы.

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

Откройте только в крайнем случае https://github.com/qa-guru/email_sender_python
⚠️ **GitHub.com Fallback** ⚠️