Python для начинающих
1.24K subscribers
546 photos
3 videos
232 files
74 links
Python для начинающих
Download Telegram
Сортировка и фильтрация словарей по значениям и ключам

Словарь в Python — отличный способ хранить данные вида «ключ → значение». Но реальная магия начинается, когда нужно отсортировать или отфильтровать этот хаос. Разберёмся, как это делать красиво и понятно.

---

### Сортировка словаря по ключам и значениям

У нас есть словарь с результатами теста:

scores = {
"alice": 85,
"bob": 92,
"charlie": 78,
"david": 92
}


Сортировка по ключам (по имени):

sorted_by_name = dict(sorted(scores.items(), key=lambda item: item[0]))
print(sorted_by_name)
# {'alice': 85, 'bob': 92, 'charlie': 78, 'david': 92}


item[0] — это ключ (имя). sorted() возвращает список пар (key, value), а dict(...) снова собирает словарь.

Сортировка по значениям (по баллам):

sorted_by_score = dict(sorted(scores.items(), key=lambda item: item[1]))
print(sorted_by_score)
# {'charlie': 78, 'alice': 85, 'bob': 92, 'david': 92}


item[1] — значение (баллы).
Чтобы отсортировать по убыванию:

sorted_by_score_desc = dict(sorted(scores.items(), key=lambda item: item[1], reverse=True))


---

### Сортировка с дополнительным критерием

Представьте, что нужно отсортировать по баллам по убыванию, а при равных баллах — по имени по возрастанию.

sorted_complex = dict(sorted(
scores.items(),
key=lambda item: (-item[1], item[0])
))
print(sorted_complex)
# {'bob': 92, 'david': 92, 'alice': 85, 'charlie': 78}


Мы используем кортеж в качестве ключа сортировки: сначала минус баллы (для убывания), потом имя.

---

### Фильтрация словаря

Допустим, нам нужны только те, у кого больше 80 баллов.

filtered_scores = {
name: score
for name, score in scores.items()
if score > 80
}
print(filtered_scores)
# {'alice': 85, 'bob': 92, 'david': 92}


Это dict comprehension — мощный и читаемый способ фильтрации.

Можно фильтровать и по ключам:

filtered_by_name = {
name: score
for name, score in scores.items()
if name.startswith("a")
}
print(filtered_by_name)
# {'alice': 85}


---

### Комбинируем: фильтрация + сортировка

Например: взять только тех, у кого баллы > 80, и отсортировать по имени.

filtered = {
name: score
for name, score in scores.items()
if score > 80
}

result = dict(sorted(filtered.items(), key=lambda item: item[0]))
print(result)
# {'alice': 85, 'bob': 92, 'david': 92}


Или вообще без промежуточной переменной:

result = dict(sorted(
(
(name, score)
for name, score in scores.items()
if score > 80
),
key=lambda item: item[0]
))


---

Умение сортировать и фильтровать словари — один из тех навыков, который сразу делает код аккуратнее и понятнее. А дальше можно подключать operator.itemgetter, functools.partial и другие инструменты, но фундамент вы уже видите: sorted(), lambda, и comprehension — ваше основное оружие.
🔥4
Как выполнять задачи по расписанию с использованием schedule
Как выполнять задачи по расписанию с использованием schedule
Как выполнять задачи по расписанию с помощью schedule

Иногда нужно, чтобы скрипт сам что‑то делал: раз в минуту проверял почту, каждый день делал бэкап или раз в час напоминал «пора сделать перерыв». Писать собственный планировщик — лишняя боль, когда есть простой модуль schedule.

---

### Установка

pip install schedule


---

### Базовая идея

schedule работает по принципу:

1. Определяем задачу — обычную функцию.
2. Назначаем ей расписание.
3. Запускаем бесконечный цикл, который проверяет, не пора ли что‑то выполнить.

Простейший пример:

import schedule
import time


def send_report():
print("Sending report...")


schedule.every(10).seconds.do(send_report)

while True:
schedule.run_pending()
time.sleep(1)


Здесь send_report() запускается каждые 10 секунд.
run_pending() проверяет «наступило ли время» для задач, а sleep(1) просто не даёт циклу крутиться без паузы.

---

### Расписание посерьёзнее

schedule умеет работать с минутами, часами, днями и даже днями недели:

import schedule
import time


def backup_db():
print("Backup created")


def remind_break():
print("Time to take a break!")


schedule.every().day.at("23:30").do(backup_db) # каждый день в 23:30
schedule.every(15).minutes.do(remind_break) # каждые 15 минут
schedule.every().monday.at("09:00").do(backup_db) # каждый понедельник в 09:00

while True:
schedule.run_pending()
time.sleep(1)


Формат времени — строка "HH:MM" в 24‑часовом формате.

---

### Передача аргументов в задачи

Функция‑задача может принимать аргументы:

import schedule
import time


def send_email(to, subject):
print(f"Email to {to}: {subject}")


schedule.every().hour.do(send_email, to="admin@example.com",
subject="Hourly status")

while True:
schedule.run_pending()
time.sleep(1)


---

### Управление задачами

Иногда нужно отменить часть расписания:

import schedule


def clear_cache():
print("Cache cleared")


job = schedule.every().hour.do(clear_cache)

# Удалить конкретную задачу
schedule.cancel_job(job)

# Удалить все задачи
schedule.clear()


---

### Когда использовать schedule

schedule отлично подходит для:

- простых фоновых задач;
- учебных проектов и прототипов;
- скриптов, которые всегда крутятся на одном сервере/ПК.

Для сложных продакшн‑систем чаще берут cron, Celery и подобные инструменты, но для начала schedule даёт очень удобный и наглядный вход в мир планировщиков задач.
👍3
Создание простого API-клиента с requests и json
Создание простого API‑клиента с requests и json

Рано или поздно каждый питонист сталкивается с задачей: “Нужно что‑то забрать из интернета… программно”. Тут на сцену выходит связка requests + json — минимальный набор, чтобы подружиться с любым более‑менее современным веб‑сервером.

---

### Что такое API и зачем нам клиент?

API — это “договор” о том, как общаться с сервисом: по какому адресу стучаться, какие параметры передавать, в каком формате будет ответ.
Чаще всего ответ приходит в формате JSON — структуре, очень похожей на словари и списки Python.

Наша задача:
1. Отправить HTTP‑запрос (GET, POST, …).
2. Получить ответ.
3. Превратить JSON в привычные объекты Python.

---

### Первый запрос: получаем данные

Установим библиотеку:

pip install requests


Простейший клиент:

import requests

url = "https://jsonplaceholder.typicode.com/posts/1"
response = requests.get(url)

print(response.status_code) # 200?
print(response.text[:100]) # сырой текст ответа


Но текст — это неудобно. Лучше сразу разобрать JSON:

import json
import requests

url = "https://jsonplaceholder.typicode.com/posts/1"
response = requests.get(url)

if response.status_code == 200:
data = json.loads(response.text)
# эквивалентно: data = response.json()
print(type(data)) # <class 'dict'>
print(data["title"]) # доступ к полям, как в обычном словаре
else:
print("Request failed:", response.status_code)


---

### Параметры запроса и мини‑клиент

Обычно API принимает параметры. Например, выбираем несколько постов:

import requests

BASE_URL = "https://jsonplaceholder.typicode.com"

def get_posts(limit=5):
params = {"_limit": limit}
response = requests.get(f"{BASE_URL}/posts", params=params)
response.raise_for_status() # выбросит исключение при ошибке
return response.json()

posts = get_posts(limit=3)
for post in posts:
print(f"{post['id']}: {post['title']}")


Здесь важно:

- params — словарь, который requests превращает в query‑строку (?key=value).
- response.raise_for_status() — простой способ не забыть про обработку ошибок.

---

### Отправка данных: POST с JSON

Иногда нужно не только читать, но и отправлять информацию:

import requests

BASE_URL = "https://jsonplaceholder.typicode.com"

def create_post(title, body, user_id=1):
payload = {
"title": title,
"body": body,
"userId": user_id,
}
response = requests.post(f"{BASE_URL}/posts", json=payload)
response.raise_for_status()
return response.json()

new_post = create_post("Hello API", "This is my first API client!")
print(new_post)


Флаг json=payload:

- автоматически превращает словарь в JSON;
- выставляет заголовок Content-Type: application/json.

---

### На что обратить внимание дальше

- Тайм-ауты: requests.get(url, timeout=5)
- Заголовки: headers={"Authorization": "Bearer <token>"}
- Логику удобно оборачивать в класс ApiClient, если эндпоинтов много.

Этой базой уже можно за вечер прикрутить свой скрипт к реальному веб‑сервису: от погоды до телеграм‑ботов — везде вокруг API, а requests + json превращают их в обычные словари и списки Python.
👍2
Как распарсить текстовое меню и извлечь информацию
Как распарсить текстовое меню и извлечь информацию

Иногда нам достается меню не в виде аккуратного 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
Использование модификаторов доступа в классах: public, private, protected

В 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
Организация структуры проекта с использованием init.py
Организация структуры проекта с использованием __init__.py

Когда маленький скрипт превращается в проект, главный враг — хаос: длинные файлы, дублирование кода, бесконечные импорты. Здесь на сцену выходит скромный файл __init__.py, который помогает превратить папку в «умный» модуль и организовать код по-взрослому.

---

### Что делает __init__.py

1. Показывает 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. Это маленький шаг в файле, но большой шаг к аккуратному, поддерживаемому коду.
👍41
Как создавать и применять mixin-классы для повторного использования кода
Python для начинающих: как подружиться с 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 — это как маленький модуль суперспособностей: добавляет силам класса остроты, но сам по себе жить не должен. Если помнить об этом, код становится и чище, и приятнее в поддержке.
👍41
Понимание GIL и влияние на многопоточность в CPython
### Понимание 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 (ожидание диска, сети, БД) — потоки отлично помогают прятать ожидание.

#### Классический пример, где потоки не ускоряют

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
Работа со структурами путей с помощью модуля pathlib
Работа со структурами путей с помощью модуля 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 с psycopg2: базовые операции
---------------------------------------------------

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