Как распарсить текстовое меню и извлечь информацию
Иногда нам достается меню не в виде аккуратного JSON, а просто куском текста: письмо от администратора, скопированное меню из чата или лог старой системы. Но даже из такого «хаоса» можно вытащить структурированные данные с помощью Python.
Представим, что у нас есть такое меню:
Наша цель — превратить это в список словарей:
### Вариант 1: Базовый парсинг со split
Для очень простых и предсказуемых форматов можно обойтись без регулярных выражений:
Результат:
Такой подход хорош, когда формат жёстко фиксирован и вы его контролируете.
### Вариант 2: Регулярные выражения для «живого» текста
Если в меню могут быть пробелы, произвольные номера позиций или даже валюта, лучше использовать
### Вариант 3: Превращаем парсер в функцию
Хорошая практика — оборачивать логику парсинга в функцию: потом вы сможете менять реализацию, не трогая остальной код.
Дальше с этим списком уже можно делать всё что угодно: считать среднюю цену, фильтровать по бюджету, строить API или генератор PDF-меню.
Парсинг текстовых меню — отличный тренировочный полигон: вы прокачиваете строки, регулярные выражения, функции и немного алгоритмическое мышление. А заодно перестаёте бояться «грязных» данных: Python умеет наводить порядок.
Иногда нам достается меню не в виде аккуратного JSON, а просто куском текста: письмо от администратора, скопированное меню из чата или лог старой системы. Но даже из такого «хаоса» можно вытащить структурированные данные с помощью Python.
Представим, что у нас есть такое меню:
1. Margherita - 350
2. Pepperoni - 420
3. Four Cheese - 500
4. Vegan Special - 390
Наша цель — превратить это в список словарей:
{"name": ..., "price": ...}.### Вариант 1: Базовый парсинг со split
Для очень простых и предсказуемых форматов можно обойтись без регулярных выражений:
menu_text = """
1. Margherita - 350
2. Pepperoni - 420
3. Four Cheese - 500
4. Vegan Special - 390
"""
items = []
for line in menu_text.strip().splitlines():
if not line.strip():
continue
# "1. Margherita - 350" -> ["1. Margherita ", " 350"]
left, price_str = line.split('-')
price = int(price_str.strip())
# "1. Margherita " -> ["1.", "Margherita"]
_, name = left.split('.', maxsplit=1)
items.append({
"name": name.strip(),
"price": price
})
print(items)
Результат:
[
{'name': 'Margherita', 'price': 350},
{'name': 'Pepperoni', 'price': 420},
...
]
Такой подход хорош, когда формат жёстко фиксирован и вы его контролируете.
### Вариант 2: Регулярные выражения для «живого» текста
Если в меню могут быть пробелы, произвольные номера позиций или даже валюта, лучше использовать
re:import re
menu_text = """
1) Margherita - 350 RUB
02. Pepperoni — 420
#3 Four Cheese: 500р
4 Vegan Special 390
"""
pattern = re.compile(
r"""
^\D*?(\d+)\D+ # номер блюда
([A-Za-z ]+?)\D+ # название
(\d+)\s* # цена
""",
re.VERBOSE
)
items = []
for line in menu_text.strip().splitlines():
match = pattern.search(line)
if not match:
continue
pos, name, price = match.groups()
items.append({
"position": int(pos),
"name": name.strip(),
"price": int(price)
})
print(items)
re.VERBOSE позволяет красиво документировать шаблон и не превращать его в нечитаемое «простыню» из символов.### Вариант 3: Превращаем парсер в функцию
Хорошая практика — оборачивать логику парсинга в функцию: потом вы сможете менять реализацию, не трогая остальной код.
def parse_menu(text: str):
import re
pattern = re.compile(r'^\s*\d+\D+([A-Za-z ]+)\D+(\d+)', re.MULTILINE)
result = []
for name, price in pattern.findall(text):
result.append({
"name": name.strip(),
"price": int(price)
})
return result
menu_items = parse_menu(menu_text)
Дальше с этим списком уже можно делать всё что угодно: считать среднюю цену, фильтровать по бюджету, строить API или генератор PDF-меню.
Парсинг текстовых меню — отличный тренировочный полигон: вы прокачиваете строки, регулярные выражения, функции и немного алгоритмическое мышление. А заодно перестаёте бояться «грязных» данных: Python умеет наводить порядок.
👍1
Использование модификаторов доступа в классах: public, private, protected
В Python нет классических модификаторов доступа как в Java или C++ — никаких
---
## Public: по умолчанию открыто
Все атрибуты без подчеркиваний считаются публичными.
Публичные атрибуты — часть интерфейса класса. Менять их снаружи — нормально (если это предусмотрено логикой).
---
## Protected: одно подчеркивание
Одно подчеркивание — это сигнал разработчикам: "не трогайте это снаружи без необходимости". Это не защита, а соглашение.
Чаще всего такие атрибуты используются для наследования: дочерний класс может работать с
---
## Private: двойное подчеркивание и name mangling
Двойное подчеркивание включает механизм name mangling: имя переписывается в
Использовать
- хотите защититься от случайного переопределения в наследниках;
- явно обозначаете: "это строго внутренняя деталь реализации".
---
## Как выбирать?
- Публичное (
- Protected (
- Private (
Главное: в Python модификаторы — это не про «запрет», а про договор между разработчиками. Уважайте подчеркивания — и ваш код станет чище и понятнее.
В Python нет классических модификаторов доступа как в Java или C++ — никаких
public, private и protected в синтаксисе. Но есть соглашения и немного магии с подчеркиваниями, которые позволяют удобно управлять доступом к данным внутри класса.---
## Public: по умолчанию открыто
Все атрибуты без подчеркиваний считаются публичными.
class User:
def __init__(self, name):
self.name = name # public
def greet(self):
return f"Hello, {self.name}!"
u = User("Alice")
print(u.name) # OK
print(u.greet()) # OK
Публичные атрибуты — часть интерфейса класса. Менять их снаружи — нормально (если это предусмотрено логикой).
---
## Protected: одно подчеркивание
Одно подчеркивание — это сигнал разработчикам: "не трогайте это снаружи без необходимости". Это не защита, а соглашение.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # public
self._balance = balance # protected (по соглашению)
def deposit(self, amount):
self._balance += amount
def get_balance(self):
return self._balance
acc = BankAccount("Bob", 100)
acc.deposit(50)
print(acc.get_balance()) # 150
# Технически можно, но «не по правилам»:
acc._balance = 1_000_000
Чаще всего такие атрибуты используются для наследования: дочерний класс может работать с
_balance, но внешний код — по идее нет.---
## Private: двойное подчеркивание и name mangling
Двойное подчеркивание включает механизм name mangling: имя переписывается в
_ИмяКласса__атрибут. Это защищает от случайного доступа и конфликтов в наследовании.class SecureAccount:
def __init__(self, pin):
self.__pin = pin # private
def check_pin(self, pin):
return pin == self.__pin
sa = SecureAccount("1234")
print(sa.check_pin("0000")) # False
# Прямой доступ не сработает:
# print(sa.__pin) # AttributeError
# Но Python не ставит абсолютных стен:
print(sa._SecureAccount__pin) # "1234"
Использовать
__name стоит, когда вы:- хотите защититься от случайного переопределения в наследниках;
- явно обозначаете: "это строго внутренняя деталь реализации".
---
## Как выбирать?
- Публичное (
name): часть API, с этим могут работать все.- Protected (
_name): внутренняя деталь, можно трогать лишь в крайних случаях и обычно внутри иерархии классов.- Private (
__name): скрытая реализация, которую вы не планируете трогать извне и в наследниках.Главное: в Python модификаторы — это не про «запрет», а про договор между разработчиками. Уважайте подчеркивания — и ваш код станет чище и понятнее.
❤2🔥2
Организация структуры проекта с использованием
Когда маленький скрипт превращается в проект, главный враг — хаос: длинные файлы, дублирование кода, бесконечные импорты. Здесь на сцену выходит скромный файл
---
### Что делает
1. Показывает Python, что папка — это пакет (module package).
2. Управляет тем, что именно будет импортироваться из пакета.
3. Позволяет удобно группировать и переэкспортировать функции и классы.
Простейшая структура проекта может выглядеть так:
---
### Пример: переэкспорт удобного интерфейса
Если импортировать так:
— импортов быстро станет много. Вместо этого настроим «витрину» в
Теперь в
---
### Инициализация пакета
Импорт:
Злоупотреблять этим не стоит: тяжелые операции (запросы в БД, сеть) лучше выносить в явные функции, но легкую инициализацию делать можно.
---
### Локальные и относительные импорты
Внутри пакета используйте относительные импорты:
Точка
---
### Итог
- превращает папку в пакет;
- формирует чистый, продуманный интерфейс кода;
- помогает контролировать импорты и структуру проекта.
Если ваш проект растет — начните с осознанного
__init__.pyКогда маленький скрипт превращается в проект, главный враг — хаос: длинные файлы, дублирование кода, бесконечные импорты. Здесь на сцену выходит скромный файл
__init__.py, который помогает превратить папку в «умный» модуль и организовать код по-взрослому.---
### Что делает
__init__.py1. Показывает Python, что папка — это пакет (module package).
2. Управляет тем, что именно будет импортироваться из пакета.
3. Позволяет удобно группировать и переэкспортировать функции и классы.
Простейшая структура проекта может выглядеть так:
my_project/
app/
__init__.py
math_utils.py
string_utils.py
main.py
app — пакет. Без __init__.py старая версия Python просто не распознала бы его как пакет, а в новых версиях вы теряете удобство настройки импорта.---
### Пример: переэкспорт удобного интерфейса
math_utils.py:def add(a, b):
return a + b
def mul(a, b):
return a * b
string_utils.py:def to_upper(text):
return text.upper()
def split_words(text):
return text.split()
Если импортировать так:
from app.math_utils import add
from app.string_utils import to_upper
— импортов быстро станет много. Вместо этого настроим «витрину» в
app/__init__.py:from .math_utils import add, mul
from .string_utils import to_upper
__all__ = ["add", "mul", "to_upper"]
Теперь в
main.py:from app import add, mul, to_upper
result = add(2, 3)
text = to_upper("hello")
__all__ определяет, что попадет при from app import * и служит документированным интерфейсом пакета.---
### Инициализация пакета
__init__.py — обычный Python-файл. В нем можно выполнять код при первом импорте пакета: логирование, проверку окружения, загрузку конфигурации.# app/__init__.py
print("App package initialized")
from .math_utils import add
Импорт:
import app # выведет: App package initialized
Злоупотреблять этим не стоит: тяжелые операции (запросы в БД, сеть) лучше выносить в явные функции, но легкую инициализацию делать можно.
---
### Локальные и относительные импорты
Внутри пакета используйте относительные импорты:
# app/string_utils.py
from .math_utils import add
def add_length(text, n):
return len(text) + add(n, 0)
Точка
. означает «из текущего пакета». Это делает пакет переносимым: вы можете переименовать app в core — внутренние импорты не сломаются.---
### Итог
__init__.py — не просто «пустой файл для галочки». Он:- превращает папку в пакет;
- формирует чистый, продуманный интерфейс кода;
- помогает контролировать импорты и структуру проекта.
Если ваш проект растет — начните с осознанного
__init__.py. Это маленький шаг в файле, но большой шаг к аккуратному, поддерживаемому коду.👍4❤1
Python для начинающих: как подружиться с mixin‑классами и перестать копировать код
Когда в проекте начинают повторяться одни и те же методы в разных классах, рука сама тянется к копипасте. Но Python предлагает более элегантный способ — mixin‑классы. Это маленькие «модули поведения», которые добавляют функциональность к классам, не превращая иерархию наследования в монстра.
### Что такое mixin?
Mixin — это класс, который:
- не самодостаточен (обычно не создают
- добавляет узкоспециализированное поведение к другим классам,
- используется через множественное наследование.
Простейший пример: хотим, чтобы разные классы умели превращаться в словарь.
Теперь можем «подмешать» его к любому классу:
Один mixin — две разные модели, никакого дублирования кода.
### Миксины для логирования
Полезный паттерн — миксин, добавляющий логирование:
Применяем:
Один раз написали
### Несколько mixin'ов сразу
Миксины отлично комбинируются:
-
- красивый
- логирование из
### Когда mixin — это плохо
Использовать mixin'ы стоит, если:
- поведение маленькое и изолированное (логирование, сериализация, кеш, валидация),
- нужно повторное использование в разных несвязанных классах.
Не стоит:
- прятать в mixin'ы огромную бизнес‑логику,
- городить глубоко вложенное множественное наследование — отладка станет адом.
Mixin — это как маленький модуль суперспособностей: добавляет силам класса остроты, но сам по себе жить не должен. Если помнить об этом, код становится и чище, и приятнее в поддержке.
Когда в проекте начинают повторяться одни и те же методы в разных классах, рука сама тянется к копипасте. Но Python предлагает более элегантный способ — mixin‑классы. Это маленькие «модули поведения», которые добавляют функциональность к классам, не превращая иерархию наследования в монстра.
### Что такое mixin?
Mixin — это класс, который:
- не самодостаточен (обычно не создают
SomeMixin() напрямую),- добавляет узкоспециализированное поведение к другим классам,
- используется через множественное наследование.
Простейший пример: хотим, чтобы разные классы умели превращаться в словарь.
class AsDictMixin:
def as_dict(self):
return {
key: value
for key, value in self.__dict__.items()
if not key.startswith("_")
}
Теперь можем «подмешать» его к любому классу:
class User(AsDictMixin):
def __init__(self, username, email):
self.username = username
self.email = email
class Product(AsDictMixin):
def __init__(self, name, price):
self.name = name
self.price = price
u = User("alice", "alice@example.com")
p = Product("Keyboard", 99.9)
print(u.as_dict())
print(p.as_dict())
Один mixin — две разные модели, никакого дублирования кода.
### Миксины для логирования
Полезный паттерн — миксин, добавляющий логирование:
import datetime as dt
class LoggerMixin:
def log(self, message):
timestamp = dt.datetime.now().isoformat(timespec="seconds")
cls_name = self.__class__.__name__
print(f"[{timestamp}] {cls_name}: {message}")
Применяем:
class PaymentService(LoggerMixin):
def pay(self, amount):
self.log(f"Start payment: {amount}")
# payment logic...
self.log("Payment done")
class NotificationService(LoggerMixin):
def send_email(self, to, subject):
self.log(f"Send email to={to}, subject={subject}")
# email logic...
ps = PaymentService()
ps.pay(100)
ns = NotificationService()
ns.send_email("bob@example.com", "Hello")
Один раз написали
LoggerMixin — пользуемся в любом сервисе.### Несколько mixin'ов сразу
Миксины отлично комбинируются:
class StrReprMixin:
def __repr__(self):
return f"<{self.__class__.__name__} {self.__dict__}>"
class Model(AsDictMixin, StrReprMixin):
pass
class Order(Model, LoggerMixin):
def __init__(self, order_id, total):
self.order_id = order_id
self.total = total
o = Order(123, 250)
o.log("Created")
print(o)
print(o.as_dict())
Order сразу получает:-
as_dict из AsDictMixin,- красивый
__repr__ из StrReprMixin,- логирование из
LoggerMixin.### Когда mixin — это плохо
Использовать mixin'ы стоит, если:
- поведение маленькое и изолированное (логирование, сериализация, кеш, валидация),
- нужно повторное использование в разных несвязанных классах.
Не стоит:
- прятать в mixin'ы огромную бизнес‑логику,
- городить глубоко вложенное множественное наследование — отладка станет адом.
Mixin — это как маленький модуль суперспособностей: добавляет силам класса остроты, но сам по себе жить не должен. Если помнить об этом, код становится и чище, и приятнее в поддержке.
👍4❤1
### Понимание GIL и влияние на многопоточность в CPython
Если вы пробовали разогнать Python многопоточностью и не получили ускорения — вы почти наверняка столкнулись с GIL. Давайте разберёмся, что это такое и когда он действительно мешает.
#### Что такое GIL простыми словами
GIL (Global Interpreter Lock) — это глобальная «блокировка интерпретатора» в CPython.
Она гарантирует, что байт-код Python выполняется только одним потоком одновременно, даже если у вас 8 ядер и 32 потока.
Зачем это сделано:
- упрощает работу со сборщиком мусора;
- упрощает реализацию многих внутренних структур;
- делает интерпретатор проще и стабильнее.
Цена — ограниченная масштабируемость по CPU в многопоточных программах на чистом Python-коде.
#### Важный нюанс: CPU-bound vs I/O-bound
Многопоточность в CPython не бесполезна. Важно, что именно делает ваш код:
- CPU-bound (много вычислений на процессоре) — GIL чаще всего убивает выгоду от потоков.
- I/O-bound (ожидание диска, сети, БД) — потоки отлично помогают прятать ожидание.
#### Классический пример, где потоки не ускоряют
На большинстве машин время выполнения будет похоже: два потока не нагружают два ядра независимо, потому что GIL не даёт им параллельно исполнять Python-байткод.
#### Где потоки действительно помогают
Когда поток ждёт I/O, многие C-функции освобождают GIL. Это позволяет другому потоку работать. Классический пример — сеть:
Здесь многопоточность почти наверняка будет существенно быстрее, потому что пока один поток ждёт ответ сервера, другой может выполняться.
#### Как обходят ограничения GIL
- multiprocessing — запускает несколько процессов, у каждого свой интерпретатор и свой GIL.
- Расширения на C / Cython — тяжёлые вычисления выполняются в C-коде, который может освобождать GIL.
- Для I/O-нагруженных задач — потоки или
Главная мысль: GIL — не баг, а особенность CPython. Он не запрещает многопоточность, но заставляет вас осознанно выбирать инструмент:
потоки для I/O, процессы и C-расширения — для тяжёлых вычислений.
Если вы пробовали разогнать Python многопоточностью и не получили ускорения — вы почти наверняка столкнулись с GIL. Давайте разберёмся, что это такое и когда он действительно мешает.
#### Что такое GIL простыми словами
GIL (Global Interpreter Lock) — это глобальная «блокировка интерпретатора» в CPython.
Она гарантирует, что байт-код Python выполняется только одним потоком одновременно, даже если у вас 8 ядер и 32 потока.
Зачем это сделано:
- упрощает работу со сборщиком мусора;
- упрощает реализацию многих внутренних структур;
- делает интерпретатор проще и стабильнее.
Цена — ограниченная масштабируемость по CPU в многопоточных программах на чистом Python-коде.
#### Важный нюанс: CPU-bound vs I/O-bound
Многопоточность в CPython не бесполезна. Важно, что именно делает ваш код:
- CPU-bound (много вычислений на процессоре) — GIL чаще всего убивает выгоду от потоков.
- I/O-bound (ожидание диска, сети, БД) — потоки отлично помогают прятать ожидание.
#### Классический пример, где потоки не ускоряют
import threading
import time
def cpu_heavy(n):
count = 0
for _ in range(n):
count += 1
return count
def run_threads():
t1 = threading.Thread(target=cpu_heavy, args=(10_000_000,))
t2 = threading.Thread(target=cpu_heavy, args=(10_000_000,))
start = time.time()
t1.start(); t2.start()
t1.join(); t2.join()
print("Threads:", time.time() - start)
def run_single():
start = time.time()
cpu_heavy(10_000_000)
cpu_heavy(10_000_000)
print("Single:", time.time() - start)
if __name__ == "__main__":
run_single()
run_threads()
На большинстве машин время выполнения будет похоже: два потока не нагружают два ядра независимо, потому что GIL не даёт им параллельно исполнять Python-байткод.
#### Где потоки действительно помогают
Когда поток ждёт I/O, многие C-функции освобождают GIL. Это позволяет другому потоку работать. Классический пример — сеть:
import threading
import requests
import time
urls = [
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/2",
]
def fetch(url):
resp = requests.get(url)
print(url, resp.status_code)
def run_threads():
start = time.time()
threads = [threading.Thread(target=fetch, args=(u,)) for u in urls]
for t in threads: t.start()
for t in threads: t.join()
print("Threads:", time.time() - start)
def run_single():
start = time.time()
for u in urls:
fetch(u)
print("Single:", time.time() - start)
if __name__ == "__main__":
run_single()
run_threads()
Здесь многопоточность почти наверняка будет существенно быстрее, потому что пока один поток ждёт ответ сервера, другой может выполняться.
#### Как обходят ограничения GIL
- multiprocessing — запускает несколько процессов, у каждого свой интерпретатор и свой GIL.
- Расширения на C / Cython — тяжёлые вычисления выполняются в C-коде, который может освобождать GIL.
- Для I/O-нагруженных задач — потоки или
asyncio.Главная мысль: GIL — не баг, а особенность CPython. Он не запрещает многопоточность, но заставляет вас осознанно выбирать инструмент:
потоки для I/O, процессы и C-расширения — для тяжёлых вычислений.
👍3
Работа со структурами путей с помощью модуля
Большинство скриптов рано или поздно начинают работать с файлами и папками. И тут новичок упирается в хаос: слэши туда, бэкслэши сюда, Windows, Linux, относительные пути… Модуль
---
### Основы: объект Path
Объект
---
### Соединение путей — как конструктор
Вместо конкатенации строк:
---
### Анализ пути: части, имя, расширение
Это удобно при разборе файлов по расширениям или при генерации новых имен.
---
### Проверка существования и создание директорий
---
### Итерация по файлам и фильтрация
---
### Чтение и запись текста
Никаких явных
---
### Преобразование в абсолютный и реальный путь
Это помогает, когда нужно логировать или передавать пути в другие системы.
---
pathlibБольшинство скриптов рано или поздно начинают работать с файлами и папками. И тут новичок упирается в хаос: слэши туда, бэкслэши сюда, Windows, Linux, относительные пути… Модуль
pathlib решает это красиво и объектно‑ориентированно.---
### Основы: объект Path
from pathlib import Path
base_dir = Path(".") # текущая директория
home_dir = Path.home() # домашняя директория пользователя
project_file = Path("src/main.py")
Объект
Path понимает, где он запущен: под Windows или Linux, и сам подбирает правильные разделители.---
### Соединение путей — как конструктор
Вместо конкатенации строк:
log_dir = Path("logs")
log_file = log_dir / "app.log" # оператор / для склейки путей
log_file станет logs/app.log или logs\app.log — в зависимости от ОС. Никаких ручных слэшей.---
### Анализ пути: части, имя, расширение
path = Path("data/archive/report_2024.csv")
print(path.name) # report_2024.csv
print(path.stem) # report_2024
print(path.suffix) # .csv
print(path.parent) # data/archive
print(list(path.parents)) # все родительские директории
Это удобно при разборе файлов по расширениям или при генерации новых имен.
---
### Проверка существования и создание директорий
reports_dir = Path("reports/2024")
if not reports_dir.exists():
reports_dir.mkdir(parents=True, exist_ok=True)
parents=True создаст все недостающие уровни. Не нужно вручную проверять каждый.---
### Итерация по файлам и фильтрация
from pathlib import Path
data_dir = Path("data")
for csv_file in data_dir.rglob("*.csv"):
print(csv_file, csv_file.stat().st_size, "bytes")
rglob("*.csv") рекурсивно находит все CSV‑файлы. stat() дает информацию о файле (размер, даты и т.д.).---
### Чтение и запись текста
text_file = Path("notes/todo.txt")
text_file.write_text("learn pathlib\nuse it everywhere", encoding="utf-8")
content = text_file.read_text(encoding="utf-8")
print(content)
Никаких явных
open(), всё через удобные методы объекта пути.---
### Преобразование в абсолютный и реальный путь
p = Path("logs/app.log")
print(p.resolve()) # абсолютный путь, с учётом реальной файловой системы
Это помогает, когда нужно логировать или передавать пути в другие системы.
---
pathlib позволяет думать о путях как о объектах с методами, а не как о хрупких строках. Освоив его один раз, вы практически забудете про головную боль с разделителями, относительными путями и ручным разбором имён файлов.👍4
Подключение к PostgreSQL с psycopg2: базовые операции
---------------------------------------------------
PostgreSQL — отличный выбор для первых серьёзных проектов на Python: надёжен, быстр и очень любим разработчиками. А библиотека
### Установка и первое подключение
Устанавливаем библиотеку:
Простейшее подключение:
### Создаём таблицу
Важно: изменения нужно подтверждать через
### Вставка данных (INSERT)
Используем параметризованный запрос — так мы защищаемся от SQL-инъекций:
### Чтение данных (SELECT)
-
- Есть ещё
### Обновление и удаление
### Корректное завершение работы
Лучше оборачивать всё в
---
---------------------------------------------------
PostgreSQL — отличный выбор для первых серьёзных проектов на Python: надёжен, быстр и очень любим разработчиками. А библиотека
psycopg2 — классический способ «подружить» Python с Postgres.### Установка и первое подключение
Устанавливаем библиотеку:
pip install psycopg2-binary
Простейшее подключение:
import psycopg2
conn = psycopg2.connect(
dbname="test_db",
user="test_user",
password="secret_password",
host="localhost",
port=5432,
)
cur = conn.cursor()
conn — это соединение с базой, cur — объект курсора, через который мы выполняем запросы.### Создаём таблицу
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
age INT
);
""")
conn.commit()
Важно: изменения нужно подтверждать через
conn.commit(). Без этого таблица «не сохранится».### Вставка данных (INSERT)
Используем параметризованный запрос — так мы защищаемся от SQL-инъекций:
insert_query = "INSERT INTO users (username, age) VALUES (%s, %s);"
data = [("alice", 25), ("bob", 30), ("charlie", 22)]
for row in data:
cur.execute(insert_query, row)
conn.commit()
%s — плейсхолдеры, реальные значения передаются вторым аргументом в execute.### Чтение данных (SELECT)
cur.execute("SELECT id, username, age FROM users WHERE age > %s;", (23,))
rows = cur.fetchall()
for row in rows:
user_id, username, age = row
print(user_id, username, age)
-
fetchall() — забрать все строки.- Есть ещё
fetchone() и fetchmany(n) для порций данных.### Обновление и удаление
cur.execute(
"UPDATE users SET age = age + 1 WHERE username = %s;",
("alice",)
)
cur.execute(
"DELETE FROM users WHERE username = %s;",
("charlie",)
)
conn.commit()
### Корректное завершение работы
cur.close()
conn.close()
Лучше оборачивать всё в
try/finally или использовать контекстные менеджеры, чтобы соединение точно закрывалось.---
psycopg2 даёт низкоуровневый, но очень прозрачный контроль над запросами. Освоив эти базовые операции — CREATE, INSERT, SELECT, UPDATE, DELETE и работу с транзакциями через commit — вы уже можете строить реальные приложения на Python + PostgreSQL.🔥4👍2
### Асинхронная загрузка данных с помощью
Представьте, что вам нужно скачать данные сразу с десятка API. Вариант «скачать по очереди» работает, но ощущается как очередь в одинокую кассу в супермаркете. Асинхронность в Python — это открытие сразу десятка касс. И один из самых удобных инструментов для этого — библиотека
---
## Почему обычный
Классический код:
Каждый запрос ждёт ответа, блокируя программу. Если каждый URL отвечает 3 секунды, а их 10 — вы получите ~30 секунд ожидания.
---
## Основная идея асинхронности
Вместо того чтобы залипать в ожидании ответа, мы даём Python возможность:
- отправить запрос,
- до ответа заняться другими задачами,
- вернуться к запросу, когда данные готовы.
Для этого используются
---
## Загрузка с
Установим библиотеку:
Простой пример параллельной загрузки:
Ключевые моменты:
-
-
-
-
Если каждый запрос «висит» 3 секунды, то при таком подходе общее время будет примерно те же 3 секунды, а не 9.
---
## Обработка ошибок и таймауты
Асинхронный код тоже должен уметь падать красиво:
Что здесь важно:
-
-
- Ошибки не ломают программу, а превращаются в аккуратные объекты с описанием.
---
Асинхронная загрузка с
aiohttp: ускоряемся по-взросломуПредставьте, что вам нужно скачать данные сразу с десятка API. Вариант «скачать по очереди» работает, но ощущается как очередь в одинокую кассу в супермаркете. Асинхронность в Python — это открытие сразу десятка касс. И один из самых удобных инструментов для этого — библиотека
aiohttp.---
## Почему обычный
requests медленныйКлассический код:
import requests
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
]
def fetch_all(urls):
data = []
for url in urls:
resp = requests.get(url)
data.append(resp.text)
return data
Каждый запрос ждёт ответа, блокируя программу. Если каждый URL отвечает 3 секунды, а их 10 — вы получите ~30 секунд ожидания.
---
## Основная идея асинхронности
Вместо того чтобы залипать в ожидании ответа, мы даём Python возможность:
- отправить запрос,
- до ответа заняться другими задачами,
- вернуться к запросу, когда данные готовы.
Для этого используются
async / await и событийный цикл asyncio.---
## Загрузка с
aiohttp: базовый примерУстановим библиотеку:
pip install aiohttp
Простой пример параллельной загрузки:
import asyncio
import aiohttp
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
]
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
task = asyncio.create_task(fetch(session, url))
tasks.append(task)
results = await asyncio.gather(*tasks)
return results
if __name__ == "__main__":
data = asyncio.run(fetch_all(urls))
for i, content in enumerate(data, start=1):
print(f"Response {i} length:", len(content))
Ключевые моменты:
-
async def создаёт асинхронную функцию.-
await говорит: «подожди результат, но не блокируй остальную программу».-
asyncio.create_task запускает корутину параллельно с другими.-
asyncio.gather ждёт, пока завершатся все задачи.Если каждый запрос «висит» 3 секунды, то при таком подходе общее время будет примерно те же 3 секунды, а не 9.
---
## Обработка ошибок и таймауты
Асинхронный код тоже должен уметь падать красиво:
import asyncio
import aiohttp
from aiohttp import ClientError
async def fetch_safe(session, url, timeout=5):
try:
async with session.get(url, timeout=timeout) as response:
response.raise_for_status()
return await response.json()
except (asyncio.TimeoutError, ClientError) as e:
return {"url": url, "error": str(e)}
async def main():
urls = [
"https://httpbin.org/json",
"https://httpbin.org/status/404",
"https://example.com:9999", # likely error
]
async with aiohttp.ClientSession() as session:
tasks = [asyncio.create_task(fetch_safe(session, url)) for url in urls]
results = await asyncio.gather(*tasks)
for item in results:
print(item)
if __name__ == "__main__":
asyncio.run(main())
Что здесь важно:
-
timeout защищает от «вечного ожидания».-
response.raise_for_status() поднимает исключение при кодах 4xx/5xx.- Ошибки не ломают программу, а превращаются в аккуратные объекты с описанием.
---
Асинхронная загрузка с
aiohttp особенно полезна при работе с API, парсинге сайтов и любых задачах, где узкое место — сеть. Освоив aiohttp, вы по-настоящему почувствуете разницу между «программой, которая просто работает» и «программой, которая работает быстро».👍5
Python для начинающих: строим граф зависимостей и ищем путь в ширину
Представьте, что у вас есть список задач, и каждая зависит от других: нельзя запустить тесты, пока не собран проект, а сборка невозможна без установки зависимостей. Это и есть граф зависимостей — мощная идея, которая лежит в основе систем сборки, планировщиков задач и даже соцсетей.
В Python граф удобно представить как словарь: вершина → список соседей.
Но часто нам нужно обратное направление: от задачи к её зависимостям. Переделаем:
Теперь цель: узнать, в каком порядке запускать задачи, чтобы не нарушить зависимости. Для начала разберём более простую вещь — поиск в ширину (BFS): он обходит граф "волнами" от стартовой вершины, сначала ближайшие, затем более дальние.
Классический BFS работает через очередь:
Проверим на нашем графе зависимостей:
Этот порядок не гарантирует строгой "сначала зависимости, потом задача", но BFS уже даёт важное:
мы находим все связанные задачи и делаем это без зацикливания.
Теперь усложним: хотим узнать, нужно ли выполнить
Используем BFS как поиск пути:
Мы только что построили простой "двигатель" зависимостей:
по нему можно понять, какие шаги нужно пройти, чтобы выполнить любую цель.
Та же идея используется в пакетных менеджерах, планировщиках задач и роутинге графов.
Представьте, что у вас есть список задач, и каждая зависит от других: нельзя запустить тесты, пока не собран проект, а сборка невозможна без установки зависимостей. Это и есть граф зависимостей — мощная идея, которая лежит в основе систем сборки, планировщиков задач и даже соцсетей.
В Python граф удобно представить как словарь: вершина → список соседей.
graph = {
"build": ["test"],
"install_deps": ["build"],
"lint": ["test"],
"test": [],
}
Но часто нам нужно обратное направление: от задачи к её зависимостям. Переделаем:
deps = {
"test": ["build", "lint"],
"build": ["install_deps"],
"lint": [],
"install_deps": []
}
Теперь цель: узнать, в каком порядке запускать задачи, чтобы не нарушить зависимости. Для начала разберём более простую вещь — поиск в ширину (BFS): он обходит граф "волнами" от стартовой вершины, сначала ближайшие, затем более дальние.
Классический BFS работает через очередь:
from collections import deque
def bfs(graph, start):
visited = set()
order = []
queue = deque([start])
while queue:
node = queue.popleft()
if node in visited:
continue
visited.add(node)
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
queue.append(neighbor)
return order
Проверим на нашем графе зависимостей:
print(bfs(deps, "test"))
# Возможный результат: ['test', 'build', 'lint', 'install_deps']
Этот порядок не гарантирует строгой "сначала зависимости, потом задача", но BFS уже даёт важное:
мы находим все связанные задачи и делаем это без зацикливания.
Теперь усложним: хотим узнать, нужно ли выполнить
install_deps, чтобы добраться до test. Используем BFS как поиск пути:
def bfs_path(graph, start, goal):
from collections import deque
queue = deque([[start]])
visited = set()
while queue:
path = queue.popleft()
node = path[-1]
if node == goal:
return path
if node in visited:
continue
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
new_path = path + [neighbor]
queue.append(new_path)
return None
print(bfs_path(deps, "test", "install_deps"))
# ['test', 'build', 'install_deps']
Мы только что построили простой "двигатель" зависимостей:
по нему можно понять, какие шаги нужно пройти, чтобы выполнить любую цель.
Та же идея используется в пакетных менеджерах, планировщиках задач и роутинге графов.
🔥3👍1
Python для начинающих: генерируем календарные события (ICS-файлы)
Хотите, чтобы ваш скрипт сам добавлял события в Google Calendar, Outlook или Apple Calendar? Для этого не нужен их API — достаточно сгенерировать простой текстовый файл формата ICS и пользователь сможет импортировать его двойным кликом.
Формат ICS — это обычный текст по стандарту iCalendar. Пример минимального события:
Сгенерируем такой файл на Python «вручную» — без сторонних библиотек.
После запуска рядом появится
Несколько важных моментов:
- Используйте
- Поля
-
Если хочется расписание насыщеннее, можно добавить описание и место:
Так вы можете генерировать приглашения на вебинары, напоминания о дедлайнах и даже целые расписания — и все это обычными строками в Python.
Хотите, чтобы ваш скрипт сам добавлял события в Google Calendar, Outlook или Apple Calendar? Для этого не нужен их API — достаточно сгенерировать простой текстовый файл формата ICS и пользователь сможет импортировать его двойным кликом.
Формат ICS — это обычный текст по стандарту iCalendar. Пример минимального события:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//MyApp//Calendar 1.0//EN
BEGIN:VEVENT
UID:123@example.com
DTSTAMP:20250115T120000Z
DTSTART:20250120T090000Z
DTEND:20250120T100000Z
SUMMARY:Morning meeting
END:VEVENT
END:VCALENDAR
Сгенерируем такой файл на Python «вручную» — без сторонних библиотек.
from datetime import datetime, timedelta, timezone
from uuid import uuid4
def format_dt(dt: datetime) -> str:
# Преобразуем в формат YYYYMMDDTHHMMSSZ
return dt.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
def create_ics_event(summary: str, start: datetime, end: datetime) -> str:
uid = f"{uuid4()}@example.com"
dtstamp = format_dt(datetime.now(timezone.utc))
lines = [
"BEGIN:VCALENDAR",
"VERSION:2.0",
"PRODID:-//PythonDemo//ICS Generator//EN",
"BEGIN:VEVENT",
f"UID:{uid}",
f"DTSTAMP:{dtstamp}",
f"DTSTART:{format_dt(start)}",
f"DTEND:{format_dt(end)}",
f"SUMMARY:{summary}",
"END:VEVENT",
"END:VCALENDAR",
]
return "\r\n".join(lines) + "\r\n"
if __name__ == "__main__":
start_dt = datetime(2025, 1, 20, 9, 0, tzinfo=timezone.utc)
end_dt = start_dt + timedelta(hours=1)
ics_content = create_ics_event("Morning meeting", start_dt, end_dt)
with open("meeting.ics", "w", encoding="utf-8") as f:
f.write(ics_content)
После запуска рядом появится
meeting.ics. Откройте его — система предложит добавить событие в календарь.Несколько важных моментов:
- Используйте
\r\n в качестве перевода строк — этого требует стандарт.- Поля
DTSTART, DTEND, DTSTAMP желательно указывать в UTC (с суффиксом Z).-
UID должен быть уникальным для события — удобно использовать uuid4().Если хочется расписание насыщеннее, можно добавить описание и место:
def create_ics_event_with_details(summary, start, end, description, location):
base = create_ics_event(summary, start, end).split("\r\n")
# Вставляем поля перед END:VEVENT
insert_index = base.index("END:VEVENT")
base.insert(insert_index, f"DESCRIPTION:{description}")
base.insert(insert_index, f"LOCATION:{location}")
return "\r\n".join(base) + "\r\n"
Так вы можете генерировать приглашения на вебинары, напоминания о дедлайнах и даже целые расписания — и все это обычными строками в Python.
👍3❤1