SmartView · Python | Изучение Python
130 subscribers
373 photos
17 links
📖 Обучение языку программирования Python с самого начала: кратко, интересно и понятно!

📚 Мы стараемся преподавать самое важное в Python, чтобы вас не нагружать.

⚡️ Boost канала: https://t.me/boost/SV_Python

🍀 Желаем приятного изучения!
Download Telegram
Создание собственных метаклассов

Метаклассы позволяют перехватывать создание классов и модифицировать их до того, как они будут окончательно созданы. Это мощный инструмент для автоматизации повторяющихся задач.

class Meta(type):
def __new__(cls, name, bases, attrs):
attrs['created_by_meta'] = True
return super().__new__(cls, name, bases, attrs)

class MyClass(metaclass=Meta):
pass

print(MyClass.created_by_meta) # True
👍3
Практическое применение метаклассов

1. ORM (Object-Relational Mapping) системы
Метаклассы используются в Django ORM и SQLAlchemy для автоматического создания моделей базы данных на основе классов Python.

2. Веб-фреймворки
Фреймворки вроде Django используют метаклассы для автоматической регистрации моделей в админ-панели.

3. API фреймворки
FastAPI и Django REST Framework используют метаклассы для автоматической генерации документации API.

class PluginMeta(type):
registry = {}

def __new__(cls, name, bases, attrs):
new_class = super().__new__(cls, name, bases, attrs)
if name != 'Plugin':
cls.registry[name] = new_class
return new_class

class Plugin(metaclass=PluginMeta):
pass

class EmailPlugin(Plugin): pass
class SMSPlugin(Plugin): pass

print(PluginMeta.registry) # {'EmailPlugin': ..., 'SMSPlugin': ...}
👍3
Одиночка (Singleton)

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

Зачем нужен Singleton?
Представьте ситуацию, когда у вас должно быть только одно подключение к базе данных, один логгер или один конфигурационный файл на все приложение. Singleton гарантирует, что не будет создано несколько экземпляров таких классов.

Проблема, которую решает Singleton:
Без Singleton разные части программы могут создавать свои собственные экземпляры класса, что приводит к:
· Расходованию лишней памяти
· Конфликтам при работе с ресурсами
· Несогласованному состоянию

class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]

class Database(metaclass=Singleton):
pass

db1 = Database()
db2 = Database()
print(db1 is db2) # True
👍3
Фабрика (Factory)

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

class Dog: 
def speak(self): return "Гав!"
class Cat:
def speak(self): return "Мяу!"

class AnimalFactory:
@staticmethod
def create_animal(animal_type):
if animal_type == "dog": return Dog()
elif animal_type == "cat": return Cat()

animal = AnimalFactory.create_animal("dog")
print(animal.speak()) # Гав!
👍3
Наблюдатель (Observer)

Наблюдатель — это поведенческий паттерн, который позволяет объектам подписываться на события других объектов и автоматически получать уведомления об их изменении. Это создает механизм подписки для уведомления нескольких объектов об изменениях.

class NewsPublisher:
def __init__(self):
self._subscribers = []

def subscribe(self, subscriber):
self._subscribers.append(subscriber)

def publish_news(self, news):
for subscriber in self._subscribers:
subscriber.update(news)

class Subscriber:
def update(self, news):
print(f"Получена новость: {news}")

publisher = NewsPublisher()
subscriber = Subscriber()
publisher.subscribe(subscriber)
publisher.publish_news("Python 3.12 выпущен!")
👍3
Стратегия (Strategy)

Стратегия — это поведенческий паттерн, который определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритмы независимо от клиентов, которые ими пользуются.

class PaymentStrategy:
def pay(self, amount): pass

class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
return f"Оплата {amount} картой"

class ShoppingCart:
def __init__(self):
self.payment_strategy = None

def checkout(self, amount):
return self.payment_strategy.pay(amount)

cart = ShoppingCart()
cart.payment_strategy = CreditCardPayment()
print(cart.checkout(1000)) # Оплата 1000 картой
👍2👏1
Адаптер (Adapter)

Адаптер — это структурный паттерн, который позволяет объектам с несовместимыми интерфейсами работать вместе. Адаптер выступает прослойкой между двумя объектами, превращая вызовы одного в вызовы понятные другому.

from abc import ABC, abstractmethod

class DataProcessor(ABC):
@abstractmethod
def process(self, data): pass

class XMLProcessor:
def process_xml(self, xml_data):
return f"Обработка XML: {xml_data}"

class XMLAdapter(DataProcessor):
def __init__(self, xml_processor):
self.xml_processor = xml_processor

def process(self, data):
xml_data = f"<data>{data}</data>"
return self.xml_processor.process_xml(xml_data)

# Использование
xml_processor = XMLProcessor()
adapter = XMLAdapter(xml_processor)
print(adapter.process("John")) # Обработка XML: <data>John</data>
👍3🔥1
Декоратор (Decorator)

Декоратор — это структурный паттерн, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обёртки». Декоратор предоставляет гибкую альтернативу наследованию для расширения функциональности.

class Coffee:
def cost(self): return 2.0
def desc(self): return "Кофе"

class MilkDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self): return self.coffee.cost() + 0.5
def desc(self): return self.coffee.desc() + " + молоко"

class SugarDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self): return self.coffee.cost() + 0.3
def desc(self): return self.coffee.desc() + " + сахар"

# Использование
coffee = SugarDecorator(MilkDecorator(Coffee()))
print(f"{coffee.desc()} = {coffee.cost()} руб.")
# Кофе + молоко + сахар = 2.8 руб
👍4🔥1
Создание пользовательских исключений

Пользовательские исключения делают код более читаемым и позволяют точно идентифицировать ошибки вашего приложения, отличая их от стандартных исключений Python.

Базовый пользовательский класс исключения:
class BankError(Exception):
pass

class InsufficientFundsError(BankError):
def __init__(self, balance, amount):
super().__init__(f"Баланс {balance}, нужно {amount}")

class Account:
def __init__(self, balance=0):
self.balance = balance

def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount

# Использование
acc = Account(100)
try:
acc.withdraw(200)
except InsufficientFundsError as e:
print(f"Ошибка: {e}") # Баланс 100, нужно 200
👍2
Наследование от встроенных исключений

Правильное наследование исключений:
В Python все исключения наследуются от базового класса Exception. При создании пользовательских исключений важно правильно выбрать родительский класс для совместимости.

class ValidationError(ValueError):
"""Наследуем от ValueError для совместимости"""
pass

def validate_age(age):
if age < 0:
raise ValidationError("Возраст не может быть отрицательным")

try:
validate_age(-5)
except ValueError as e: # Поймает ValidationError
print(f"Ошибка: {e}")
👍2
Обработка исключений в методах

Два подхода:
1. Обработка внутри метода
2. Проброс исключения наружу

Пример:
class FileReader:
def read_safe(self, filename):
"""Обработка внутри метода"""
try:
with open(filename) as f:
return f.read()
except FileNotFoundError:
return "" # Возвращаем значение по умолчанию

def read_strict(self, filename):
"""Проброс исключения наружу"""
with open(filename) as f:
return f.read() # FileNotFoundError пройдет выше

reader = FileReader()
content = reader.read_safe("нет_файла.txt") # Вернет ""
# reader.read_strict("нет_файла.txt") # Вызовет FileNotFoundError
👍1👏1
Контекстные менеджеры с классами

Классы с enter и exit:
Позволяют создавать свои контекстные менеджеры.

Пример:
class Timer:
"""Измеряет время выполнения блока кода"""
def __enter__(self):
import time
self.start = time.time()

def __exit__(self, *args):
import time
print(f"Выполнено за: {time.time() - self.start:.2f} сек.")

with Timer():
import time
time.sleep(1) # Вывод: Выполнено за: 1.00 сек.
👍2
Паттерн "Фабрика исключений"

Централизованное создание исключений:
Упрощает управление ошибками в больших проектах.

Пример:
class ExceptionFactory:
"""Создает стандартизированные исключения"""
@staticmethod
def validation_error(field, message):
return ValueError(f"[VALIDATION] {field}: {message}")

@staticmethod
def business_error(code, message):
error = RuntimeError(message)
error.code = code
return error

# Использование
try:
raise ExceptionFactory.validation_error("email", "Неверный формат")
except ValueError as e:
print(f"Ошибка: {e}") # [VALIDATION] email: Неверный формат
👍2
Модуль unittest для тестирования классов

Базовое тестирование:
Unittest — встроенный модуль для тестирования.

Пример:
import unittest

class Calculator:
def add(self, a, b):
return a + b

class TestCalculator(unittest.TestCase):
def setUp(self):
"""Подготовка перед каждым тестом"""
self.calc = Calculator()

def test_add_positive(self):
self.assertEqual(self.calc.add(2, 3), 5)

def test_add_negative(self):
self.assertEqual(self.calc.add(-1, -1), -2)

if __name__ == "__main__":
unittest.main()
👍2
Mock объекты для тестирования

Изоляция зависимостей:
Mock-объекты имитируют реальные объекты для тестирования.

Пример:
from unittest.mock import Mock

class WeatherService:
def __init__(self, api):
self.api = api

def get_temp(self, city):
return self.api.fetch(city)['temp']

# Тест
mock_api = Mock()
mock_api.fetch.return_value = {'temp': 25}
service = WeatherService(mock_api)

result = service.get_temp("Москва")
assert result == 25
mock_api.fetch.assert_called_once_with("Москва")
👍1👏1
Тестирование наследования

Проверка полиморфного поведения:
Тестируем, что дочерние классы правильно переопределяют методы.

Пример:
class Animal:
def speak(self):
return "Звук"

class Dog(Animal):
def speak(self):
return "Гав"

class TestAnimals(unittest.TestCase):
def test_inheritance(self):
dog = Dog()
self.assertEqual(dog.speak(), "Гав")
self.assertIsInstance(dog, Animal)
👍1👏1
Тестирование приватных методов

Лучше тестировать через публичный интерфейс:
Но иногда нужен прямой доступ.

Пример:
class Validator:
def _validate_email(self, email):
return "@" in email

def register_user(self, email):
if not self._validate_email(email):
raise ValueError("Неверный email")
return True

# Тестируем через публичный метод
validator = Validator()
self.assertTrue(validator.register_user("test@mail.com"))
with self.assertRaises(ValueError):
validator.register_user("invalid-email")
👍2
Паттерн "Тестовые дублеры"

Разные типы дублеров:
Mock, Stub, Fake, Spy для разных задач.

Пример:
from unittest.mock import Mock

class Database:
def get_user(self, user_id):
# Реальный код работы с БД
pass

class UserService:
def __init__(self, db):
self.db = db

def get_user_name(self, user_id):
user = self.db.get_user(user_id)
return user['name'] if user else None

# Используем Mock как тестовый дублер
mock_db = Mock()
mock_db.get_user.return_value = {'name': 'Анна', 'id': 1}
service = UserService(mock_db)

assert service.get_user_name(1) == 'Анна'
👍1🔥1
Дескрипторы

Управление доступом к атрибутам:
Дескрипторы контролируют, как устанавливаются и получаются значения атрибутов.

Пример:
class PositiveNumber:
"""Дескриптор для положительных чисел"""
def __set__(self, obj, value):
if value < 0:
raise ValueError("Число должно быть положительным")
obj.__dict__[self.name] = value

def __set_name__(self, owner, name):
self.name = name

class Account:
balance = PositiveNumber()

def __init__(self, balance):
self.balance = balance # Вызовет __set__

acc = Account(100) # OK
# acc.balance = -50 # ValueError: Число должно быть положительным
👍2
Слоты slots

Оптимизация памяти:
Ограничивает набор атрибутов и экономит память.

Пример:
class Point:
__slots__ = ['x', 'y'] # Только эти атрибуты разрешены

def __init__(self, x, y):
self.x = x
self.y = y

p = Point(1, 2)
print(p.x, p.y) # 1 2
# p.z = 3 # AttributeError: 'Point' object has no attribute 'z'
👍3