Frog Proger 05 декабря 2024

🐍⚡ Python для перфекционистов: 10 способов писать идеальный код

Простота Python позволяет разработчикам быстро создавать рабочие программы, но более продвинутые техники могут сделать ваш код более эффективным, гибким и элегантным.
🐍⚡ Python для перфекционистов: 10 способов писать идеальный код
Этот материал взят из нашей еженедельной email-рассылки, посвященной бэкенду. Подпишитесь, чтобы быть в числе первых, кто получит дайджест.

Использование дескрипторов для управления доступом к атрибутам

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

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

        class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f"Ожидаются данные типа {self.expected_type}, получены данные типа {type(value)}")
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        raise AttributeError("Невозможно удалить атрибут")

class Person:
    name = Typed("name", str)
    age = Typed("age", int)

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

try:
    p = Person("Алиса", 10)
    print(p.name)  
    p.age = "10"  
except TypeError as e:
    print(e)

    

Вывод:

        Алиса
Ожидаются данные типа <class 'int'>, получены данные типа <class 'str'>

    

Упрощение кода классов с помощью dataclass

Использование dataclass в Python – удобный способ упрощения кода для классов, которые предназначены только для хранения данных. Вместо ручного определения методов __init__, __repr__, __eq__ и других, можно автоматически генерировать их, используя аннотации типов. Преимущества использования dataclass:

  • Сокращение кода – нет необходимости вручную писать методы вроде __init__, __repr__, и их поведение не нужно поддерживать.
  • Читаемость – код легче читать, потому что вы видите только список полей, описывающих объект.
  • Гибкость – можно добавлять методы или настройки, чтобы изменять поведение класса.
  • Автоматизация – встроенные методы генерируются автоматически, вероятность ошибок сводится к минимуму.

Пример:

        from dataclasses import dataclass

@dataclass
class Employee:
    name: str
    last_name: str
    age: int
    position: str

emp = Employee(name="Евгений", last_name="Онегин", age=30, position="бэкендер")
print(emp)  
emp2 = Employee(name="Пьер", last_name="Безухов", age=30, position="фронтендер")
print(emp == emp2)  
print(repr(emp2))  

    

Вывод:

        Employee(name='Евгений', last_name='Онегин', age=30, position='бэкендер')
False
Employee(name='Пьер', last_name='Безухов', age=30, position='фронтендер')

    

Кастомные менеджеры контекста

Когда возможностей стандартного контекстного менеджера (например, open) недостаточно, можно создать свой, чтобы упростить управление специфическими ресурсами.

Пример: собственный контекстный менеджер для работы с базой данных

        import sqlite3
from contextlib import contextmanager
from pathlib import Path

@contextmanager
def open_db(db_path):
    db_path = Path(db_path)
    if not db_path.exists():
        raise FileNotFoundError(f"Файл базы данных {db_path} не найден.")

    connection = sqlite3.connect(db_path)
    try:
        yield connection
    finally:
        connection.commit() 
        connection.close()  
db_file = "example.db"
with open_db(db_file) as conn:
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS users (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        name TEXT,
                        age INTEGER)''')
    cursor.execute("INSERT INTO users (name, age) VALUES (?, ?)", ("Евгений", 30))
    cursor.execute("INSERT INTO users (name, age) VALUES (?, ?)", ("Татьяна", 25))
    print("Данные внесены в базу.")

    

Вывод:

        Данные внесены в базу.
    

Аннотации функций

Аннотации функций показывают, какие типы данных ожидаются на входе и какой тип будет возвращен. Они делают код понятнее, безопаснее и удобнее для работы, и особенно полезны в крупных проектах, где требуется строгий контроль типов и ясное описание поведения функций. Например, здесь сразу видно, что функция принимает length и width в виде чисел с плавающей точкой float и возвращает значение того же типа:

        def calculate_area(length: float, width: float) -> float:
    return length * width

    

Многие IDE и инструменты проверки типов (например, MyPy) могут использовать аннотации для поиска ошибок в коде еще до его выполнения. Например, если вы случайно передадите строку вместо числа, то получите предупреждение, что строка str не соответствует ожидаемому типу float:

        area = calculate_area("5.0", 3.2)  # Ошибка!
    

Декораторы для повторного использования кода

Декораторы позволяют отделить дополнительную функциональность (например, логирование, проверку прав доступа, измерение времени выполнения) от основной логики функции. Это делает код более понятным и легким в сопровождении.

Пример: измерение времени выполнения функции

        import time

def measure_time(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Функция {func.__name__} выполнена за {end - start:.4f} секунд")
        return result
    return wrapper

@measure_time
def compute():
    sum(range(10**7))
compute()

    

Вывод:

        Функция compute выполнена за 0.1000 секунд
    

Использование functools для оптимизации сложных операций с функциями

Модуль functools – мощный инструмент, который:

  • Помогает оптимизировать производительность (за счет кэширования).
  • Облегчает создание гибких и модульных функций.
  • Упрощает работу с функциями высшего порядка.

Пример: оптимизация производительности с помощью кэширования

        from functools import lru_cache

@lru_cache(maxsize=100)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100))  

    

Без кэширования вычисление больших значений Фибоначчи было бы чрезвычайно медленным из-за экспоненциальной сложности. Но с lru_cache предыдущие результаты сохраняются, что значительно сокращает время выполнения.

🐍 Библиотека питониста
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»
🐍🎓 Библиотека Python для собеса
Подтянуть свои знания по Python вы можете на нашем телеграм-канале «Библиотека Python для собеса»
🐍🧩 Библиотека задач по Python
Интересные задачи по Python для практики можно найти на нашем телеграм-канале «Библиотека задач по Python»

Эффективные структуры данных collections

Модуль collections предоставляет высокоуровневые структуры данных, которые оптимизируют задачи, связанные с обработкой коллекций. Например, defaultdict автоматически создает значения по умолчанию для отсутствующих ключей, что устраняет необходимость в проверках или дополнительных условиях. А еще он очень удобен для группировки или построения вложенных структур:

        from collections import defaultdict

grouped = defaultdict(list)
data = [("фрукты", "яблоко"), ("животное", "кошка"), ("фрукты", "банан"), ("животное", "собака")]
for key, value in data:
    grouped[key].append(value)
print(grouped)

    

Вывод:

        defaultdict(<class 'list'>, {'фрукты': ['яблоко', 'банан'], 'животное': ['кошка', 'собака']})
    

Counter упрощает подсчет:

        from collections import Counter

text = "hello world"
char_count = Counter(text)
print(char_count.most_common(3)) 

    

Вывод:

        [('l', 3), ('o', 2), ('h', 1)]
    

namedtuple упрощает создание неизменяемых объектов с именованными полями:

        from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)

    

Вывод:

        3 4
    

Упрощение многопоточности и многопроцессорности

Модуль concurrent.futures предоставляет высокоуровневый интерфейс для выполнения задач параллельно с использованием:

  • ThreadPoolExecutor – для задач ввода-вывода.
  • ProcessPoolExecutor – для вычислительно сложных задач, нагружающих процессор.

Пример: загрузка нескольких веб-страниц в разных потоках

        import requests
from concurrent.futures import ThreadPoolExecutor

def fetch_url(url):
    response = requests.get(url)
    return url, response.status_code

urls = ["https://example.com", "https://python.org", "https://proglib.io"]

with ThreadPoolExecutor(max_workers=3) as executor:
    results = executor.map(fetch_url, urls)

for url, status in results:
    print(f"{url}: {status}")

    

Вывод:

        https://example.com: 200
https://python.org: 200
https://proglib.io: 200

    

Работа с файловой системой

Модуль pathlib позволяет работать с путями как с объектами, предоставляя интуитивный синтаксис и методы, которые делают код лаконичным и понятным. Преимущества pathlib по сравнению с os.path:

  • Более простые методы и операции.
  • Код работает одинаково на всех операционных системах.
  • Есть функционал для чтения, записи, проверки путей, обхода директорий и работы с файлами.
  • Объектно-ориентированный подход делает код более структурированным.

Пример: поиск .docx-файлов во всех поддиректориях:

        from pathlib import Path
path = Path("C:/Users/Admin/Documents")
for file in path.rglob("*.docx"):
    print(file)

    

При использовании os.path код получается более громоздким:

        import os
path = "C:/Users/Admin/Documents"
for dirpath, dirnames, filenames in os.walk(path):
    for filename in filenames:
        if filename.endswith(".docx"):
            print(os.path.join(dirpath, filename))

    

Мокинг в модульных тестах

Использование мокинга в модульных тестах – мощный способ изолировать тестируемый код от внешних зависимостей (базы данных, веб-сервисы или сторонние API). Это улучшает стабильность, скорость и повторяемость тестов, а также дает возможность тестировать части системы, которые еще не интегрированы с внешними сервисами.

Пример: мокирование API для получения курса биткоина

        import requests
from unittest.mock import patch

def get_bitcoin_price():

    response = requests.get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd")
    data = response.json()
    if "bitcoin" not in data:
        raise ValueError("Ошибка получения данных от API")
    return data["bitcoin"]["usd"]

@patch('requests.get')
def test_get_bitcoin_price(mock_get):
    mock_get.return_value.json.return_value = {
        "bitcoin": {
            "usd": 99000  
        }
    }
    
    price = get_bitcoin_price()
    print(f"Текущая стоимость биткоина ${price}.")

test_get_bitcoin_price()

    

Вывод:

        Текущая стоимость биткоина $99000.
    

Поделитесь своими любимыми Python-приёмами, которые не вошли в статью – какие техники вы считаете обязательными для профессионального разработчика?

***

Python используется везде: от анализа данных до разработки веб-приложений. Если ты задумываешься о входе в IT или хочешь расширить свой технический стек, обрати внимание на новый курс по основам Python.

Ключевые особенности программы:

  • 32 практических урока с фокусом на реальные проекты
  • Два опытных преподавателя из индустрии: экс-специалист Мегафона и олимпиадный тренер
  • Работа с современными инструментами: PyCharm, Jupyter Notebook
  • Создание собственного портфолио из 4 проектов

Программа включает всё необходимое для старта:

  • От базового синтаксиса до ООП
  • Работа с API и создание ботов
  • Парсинг данных и работа с файлами
  • Основы алгоритмов и структур данных

Формат обучения адаптивный — учись в своем темпе с постоянной обратной связью от преподавателей.

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ