Python для начинающих
1.24K subscribers
546 photos
3 videos
232 files
74 links
Python для начинающих
Download Telegram
Отладка Python-кода с помощью встроенного модуля pdb

Начинающие часто боятся отладки как чего-то «магического». На самом деле pdb — это всего лишь интерактивная пауза в вашем коде, где вы можете посмотреть, что происходит «под капотом», шаг за шагом.

---

### Зачем нужен pdb?

print-отладка быстро превращает код в кашу. pdb позволяет:

- останавливать программу в нужной точке;
- смотреть значения переменных;
- выполнять произвольные выражения;
- шагать по коду, строка за строкой.

---

### Самый простой старт: breakpoint()

С Python 3.7 появился встроенный вызов:

def divide(a, b):
result = a / b
return result

def main():
x = 10
y = 0
breakpoint() # здесь выполнение остановится
print(divide(x, y))

if __name__ == "__main__":
main()


Запускаем скрипт в терминале как обычно:

python script.py


В месте breakpoint() вы попадете в интерактивную консоль pdb с приглашением вида (Pdb).

---

### Базовые команды pdb

Находясь в (Pdb), попробуйте:

- l — показать фрагмент исходника (list).
- n — выполнить следующую строку (next).
- s — шагнуть внутрь функции (step).
- c — продолжить выполнение до следующей точки останова (continue).
- p x — вывести значение переменной x (print).
- q — выйти из отладки (quit).

Например, в нашем примере:

(Pdb) p x
10
(Pdb) p y
0
(Pdb) p divide(x, y)
ZeroDivisionError: division by zero


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

---

### Ручной запуск: python -m pdb

Иногда удобно запустить отладчик с самого старта:

python -m pdb script.py


Вы окажетесь в pdb ещё до выполнения первой строки. Тут можно заранее поставить точки останова:

(Pdb) b main
(Pdb) c


Команда b main ставит брейкпоинт на вход в функцию main.

---

### Отладка циклов и логики

Пример с циклом, где что-то идёт не так:

def sum_positive(numbers):
total = 0
for n in numbers:
if n < 0:
breakpoint()
total += n
return total

print(sum_positive([1, 2, -5, 3]))


Когда цикл дойдёт до -5, pdb остановится. Можно исследовать:

(Pdb) p n
-5
(Pdb) p total
3
(Pdb) p numbers
[1, 2, -5, 3]


Так легко понять, что отрицательные числа вообще не должны участвовать в сумме — и исправить логику.

---

### Несколько практических подсказок

- Не оставляйте breakpoint() в продакшене: вы можете обернуть его в условие по переменной окружения.
- Научитесь минимуму команд: l, n, s, c, p, q — этого достаточно, чтобы комфортно отлаживать большинство багов.
- pdb работает везде, где есть консоль: локально, в Docker-контейнере, на сервере.

pdb — лучший способ превратить «странные баги» в понятные и воспроизводимые ситуации. Чем раньше вы привыкнете к нему, тем быстрее перестанете бояться ошибок и начнёте управлять ими.
👍31
Работа с локализацией и форматами: модуль locale
Работа с локализацией и форматами: модуль locale

Если ваш скрипт должен показывать даты, деньги и числа «по‑местному», голый print() быстро перестаёт хватать. В России — запятая в дробях и пробелы между разрядами, в США — точка и запятая, в Германии — наоборот. Всё это умеет модуль locale.

---

### Базовая настройка локали

import locale

# Устанавливаем локаль по умолчанию системы
locale.setlocale(locale.LC_ALL, '')

current_locale = locale.getlocale()
print("Current locale:", current_locale)


LC_ALL — сразу все категории (числа, даты, деньги и т.д.). Можно настраивать точечно: LC_NUMERIC, LC_TIME, LC_MONETARY и др.

---

### Форматирование чисел

import locale

locale.setlocale(locale.LC_ALL, 'ru_RU.UTF-8') # может отличаться в вашей системе

value = 1234567.89
formatted = locale.format_string('%.2f', value, grouping=True)
print(formatted) # например: 1 234 567,89


Параметр grouping=True включает разделители разрядов. Если сменить локаль:

locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
print(locale.format_string('%.2f', value, grouping=True)) # 1,234,567.89


Одна и та же величина — разное отображение.

---

### Валюта по‑местному

import locale

locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')

price = 1999.99
formatted_price = locale.currency(price, grouping=True)
print(formatted_price) # например: 1.999,99 €


locale.currency() учитывает символ валюты, позицию знака, пробелы и разделители разрядов.

---

### Парсинг строк в числа

Иногда нужно не только выводить, но и «понимать» локальные числа:

import locale

locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8')

s = "1 234,56"
x = locale.atof(s) # string -> float с учётом локали
print(x) # 1234.56 (обычный Python float)


atof() и atoi() разбирают строки по локальным правилам — важно при вводе данных от пользователя из разных стран.

---

### Полезные выводы

- locale не меняет сами числа, он управляет представлением (форматом).
- Локаль влияет на весь процесс в пределах программы, так что в больших проектах её обычно настраивают централизованно.
- Для кроссплатформенности проверяйте, что нужная локаль есть в системе, и держите запасной вариант (например, C или en_US.UTF-8).

Модуль locale — это способ сделать ваш код дружелюбным к пользователю вне зависимости от того, где он живёт и какой у него разделитель дробной части.
👍3
Основы проверки кода с использованием pylint и flake8
Основы проверки кода с использованием pylint и flake8

Когда начинаешь писать на Python, код обычно «работает — и ладно». Но довольно быстро становится ясно: читать его через неделю невозможно даже самому автору. Тут на сцену выходят линтеры — инструменты автоматической проверки стиля и потенциальных ошибок. Два самых популярных: pylint и flake8.

---

### Зачем вообще нужны линтеры?

Линтеры помогают:

- находить опечатки и потенциальные баги (неиспользуемые переменные, лишние импорты);
- поддерживать единый стиль кода (отступы, длина строк, имена переменных);
- учиться писать «питоничный» код — по PEP 8 и здравому смыслу.

---

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

pip install pylint flake8


Проверка файла:

pylint my_script.py
flake8 my_script.py


---

### Простой пример «плохого» кода

# file: bad_example.py

import os, sys

def add(a,b):
return a+ b

def main():
result = add(2,2)
print("Result:",result)

main()


Типичные проблемы, которые найдут линтеры:

- несколько импортов в одной строке (import os, sys);
- несогласованные отступы;
- отсутствие пробелов вокруг операторов;
- вызов main() без проверки if __name__ == "__main__":.

---

### Как это исправить

# file: good_example.py

import os
import sys


def add(a: int, b: int) -> int:
return a + b


def main() -> None:
result = add(2, 2)
print("Result:", result)


if __name__ == "__main__":
main()


Теперь flake8 в основном будет молчать, а pylint может ещё подсказать, что os и sys нигде не используются.

---

### В чём разница между pylint и flake8?

- flake8 — лёгкий и быстрый. Фокусируется на стиле и базовых ошибках.
- pylint — гораздо более строгий: анализирует структуру проекта, связи между модулями, даёт «оценку» кода, умеет ловить более сложные проблемы.

Часто их используют вместе: flake8 — для стилистики, pylint — для более глубокой проверки.

---

### Настройка под себя

Оба инструмента можно «приручить» конфигами:

- pylint: файл .pylintrc
- flake8: файл .flake8 или раздел [flake8] в setup.cfg

Например, увеличить допустимую длину строки:

# .flake8
[flake8]
max-line-length = 100


---

Лучший момент начать использовать линтеры — сейчас. Они быстро превращают хаотичный учебный код в аккуратный и читаемый, а заодно приучают к хорошему стилю, почти как строгий, но полезный наставник.
👍2
Как организовать модульную структуру проекта в Python
Как организовать модульную структуру проекта в Python

Когда скрипт на 50 строк превращается в файл на 500, наступает момент, когда хочется просто… закрыть редактор. Тут и приходит на помощь модульная структура: разбиваем хаос на аккуратные части, которые легко читать, тестировать и переиспользовать.

---

### Что такое модуль и пакет?

- Модуль — обычный .py-файл. Например: utils.py.
- Пакет — папка с Python-модулями. Обычно содержит файл __init__.py (в современных версиях Python он не обязателен, но лучше его иметь для явности).

Простейшая структура проекта может выглядеть так:

my_project/
main.py
models.py
utils.py


Но уже на среднем проекте удобнее перейти к пакетам:

my_project/
main.py
app/
__init__.py
models.py
services.py
utils.py


---

### Пример: мини-приложение с модулями

Представим, что пишем упрощенную систему заказов.

app/models.py:

from dataclasses import dataclass

@dataclass
class Order:
id: int
amount: float
is_paid: bool = False


app/services.py:

from .models import Order
from .utils import apply_discount

def process_order(order: Order, discount: float) -> Order:
order.amount = apply_discount(order.amount, discount)
order.is_paid = True
return order


app/utils.py:

def apply_discount(amount: float, discount: float) -> float:
if not 0 <= discount <= 1:
raise ValueError("Invalid discount")
return round(amount * (1 - discount), 2)


main.py:

from app.models import Order
from app.services import process_order

def main():
order = Order(id=1, amount=100.0)
order = process_order(order, discount=0.15)
print(order)

if __name__ == "__main__":
main()


Мы разделили:

- models — только данные.
- services — бизнес-логика.
- utils — вспомогательные функции.

Каждый файл отвечает за свою зону, и код перестает быть «простыней».

---

### Относительные и абсолютные импорты

Внутри пакета удобно использовать относительные импорты:

from .models import Order
from .utils import apply_discount


А снаружи — абсолютные:

from app.services import process_order


Это делает структуру понятной: глядя на импорт, сразу видно, откуда что приходит.

---

### Зачем все это?

- Проще тестировать: можно импортировать один модуль и тестировать его изолированно.
- Легче находить код: логика не размазана по одному файлу.
- Удобно расширять проект: добавили новый модуль — остальное не пострадало.

Модульная структура — это не «красиво», это «жить можно», когда код растет. Начать лучше сразу, даже если проект пока умещается в один файл.
🔥2👍1
Создание собственных исключений для понятной обработки ошибок
Создание собственных исключений для понятной обработки ошибок

Стандартные исключения Python (ValueError, TypeError, KeyError и т.д.) хороши, но иногда они слишком общие. Если вы пишете библиотеку, игру или веб‑сервис, вам нужно чётко понимать: какая именно логическая ошибка произошла и что с ней делать. Тут и вступают в игру собственные исключения.

---

## Зачем нужны свои исключения

Представьте интернет‑магазин. Ошибка “ValueError” может означать всё что угодно: от некорректной цены до пустого списка товаров. Гораздо понятнее:

- InvalidPriceError
- OutOfStockError
- PaymentDeclinedError

Код сразу становится самодокументируемым: по имени исключения видно, что пошло не так и где это обрабатывать.

---

## Как создать своё исключение

Создание простое: наследуемся от Exception (или другого базового класса исключений):

class InvalidPriceError(Exception):
"""Raised when product price is invalid."""
pass


Используем:

def set_price(price: float) -> None:
if price <= 0:
raise InvalidPriceError(f"Price must be positive, got: {price}")


---

## Иерархия исключений

Красота начинается, когда вы строите семейство ошибок:

class ShopError(Exception):
"""Base exception for shop-related errors."""
pass


class OutOfStockError(ShopError):
pass


class PaymentError(ShopError):
pass


class PaymentDeclinedError(PaymentError):
pass


Теперь можно:

try:
process_order(order)
except PaymentDeclinedError as e:
log_warning(e)
show_message("Payment was declined, try another card.")
except ShopError as e:
log_error(e)
show_message("Something went wrong with your order.")


Преимущество: вы можете ловить как конкретные, так и все “магазинные” ошибки разом.

---

## Добавляем контекст к исключениям

Исключения могут не только “орать”, но и нести данные:

class OutOfStockError(ShopError):
def __init__(self, product_id: int, requested: int, available: int):
self.product_id = product_id
self.requested = requested
self.available = available
message = (f"Product {product_id}: requested {requested}, "
f"available {available}")
super().__init__(message)


Использование:

try:
reserve_product(product_id=42, quantity=10)
except OutOfStockError as e:
print(e.product_id, e.requested, e.available)


Так обработчик ошибок видит не только текст, но и структурированные данные.

---

## Когда точно стоит заводить своё исключение

- Ошибка связана с предметной областью (деньги, заказы, уровни в игре).
- Вам нужно по‑разному реагировать на разные типы ошибок.
- Вы пишете код, которым будут пользоваться другие разработчики.

Если же ошибка — просто “не тот тип” или “не то значение”, часто достаточно стандартных исключений.

Собственные исключения — это не “косметика”, а мощный инструмент архитектуры. Они превращают хаос “что-то упало” в чёткую систему сигналов, по которой программа может разумно реагировать на проблемы.
🔥32👍1
Простое шифрование текста с базовым алгоритмом Caesar
Простое шифрование текста с базовым алгоритмом Caesar

Когда-то, задолго до хакеров и брутфорса, шифрование могло выглядеть почти игрушкой. Один из самых простых и известных методов — шифр Цезаря. Он настолько прост, что идеально подходит для первых шагов в программировании и работе со строками в Python.

Идея простая: каждую букву заменяем другой, сдвинутой в алфавите на фиксированное число позиций. Например, при сдвиге 3 буква A превращается в D, B в E и так далее по кругу.

### Базовая реализация

Сделаем функцию, которая шифрует только латинские буквы, сохраняя регистр и игнорируя символы вне алфавита:

def caesar_encrypt(text, shift):
result = []
for ch in text:
if 'a' <= ch <= 'z':
base = ord('a')
offset = (ord(ch) - base + shift) % 26
result.append(chr(base + offset))
elif 'A' <= ch <= 'Z':
base = ord('A')
offset = (ord(ch) - base + shift) % 26
result.append(chr(base + offset))
else:
result.append(ch)
return ''.join(result)


message = "Hello, World!"
encrypted = caesar_encrypt(message, 3)
print(encrypted) # Khoor, Zruog!


Функция ord превращает символ в его числовой код, chr делает обратное, а % 26 обеспечивает «цикличность» по алфавиту.

### Расшифровка — тот же алгоритм

Дешифровать можно тем же кодом, просто меняя знак сдвига:

def caesar_decrypt(text, shift):
return caesar_encrypt(text, -shift)


decrypted = caesar_decrypt(encrypted, 3)
print(decrypted) # Hello, World!


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

### Немного автоматизации

Добавим простую «оболочку», чтобы пользователь вводил текст и сдвиг:

def run_caesar():
text = input("Enter text: ")
shift = int(input("Enter shift (e.g. 3): "))
mode = input("Mode (e for encrypt, d for decrypt): ").strip().lower()

if mode == 'e':
print("Encrypted:", caesar_encrypt(text, shift))
elif mode == 'd':
print("Decrypted:", caesar_decrypt(text, shift))
else:
print("Unknown mode")


if __name__ == "__main__":
run_caesar()


Такой простой проект полезен сразу в нескольких вещах:
- работа со строками и символами;
- понимание циклов и условий;
- практика написания маленьких, переиспользуемых функций.

Шифр Цезаря давно не считается надежным, но как учебный пример — это отличный старт, чтобы почувствовать, как из простых операций над символами рождается алгоритм шифрования.
👍2
Создание календаря событий с помощью модуля calendar
Создание календаря событий с помощью модуля calendar

Модуль calendar — один из тех, что часто игнорируют, а зря. С его помощью можно быстро сделать простой «календарь событий» прямо в консоли или подготовить данные для бота/веб-приложения.

---

### Базовый календарь на месяц

Начнем с простого вывода календаря:

import calendar

year = 2025
month = 1

cal = calendar.TextCalendar(firstweekday=0) # 0 — понедельник
print(cal.formatmonth(year, month))


TextCalendar рисует аккуратную таблицу в виде текста. Если нужен «машиночитаемый» вариант, лучше использовать monthcalendar:

import calendar

year = 2025
month = 1

weeks = calendar.monthcalendar(year, month)
for week in weeks:
print(week)


Каждая строка — неделя, нули означают «нет дня» (пустая клетка в начале/конце месяца).

---

### Добавляем события

Сделаем простейший календарь событий: у нас есть словарь events, где ключ — кортеж (year, month, day), а значение — текст события. Отметим события звездочкой:

import calendar

events = {
(2025, 1, 5): "Project deadline",
(2025, 1, 12): "Team meeting",
(2025, 1, 31): "Release day",
}

def print_events_calendar(year, month):
cal = calendar.TextCalendar(firstweekday=0)
weeks = calendar.monthcalendar(year, month)

print(f"{calendar.month_name[month]} {year}".center(20))
print("Mo Tu We Th Fr Sa Su")

for week in weeks:
row = []
for day in week:
if day == 0:
row.append(" ")
continue
mark = "*" if (year, month, day) in events else " "
cell = f"{day:2d}{mark}"
# обрежем до 3 символов, чтобы выровнять сетку
row.append(cell[:3])
print(" ".join(row))

print_events_calendar(2025, 1)


Результат — текстовый календарь, где дни с событиями помечены *. Можно менять символ на !, # и т.п.

---

### Поиск событий по дате и будням

calendar знает всё о днях недели:

import calendar
from datetime import date

def is_weekend(year, month, day):
weekday = date(year, month, day).weekday() # 0=Mon, 6=Sun
return weekday >= 5

for key, title in events.items():
y, m, d = key
day_name = calendar.day_name[date(y, m, d).weekday()]
tag = " (weekend)" if is_weekend(y, m, d) else ""
print(f"{y}-{m:02d}-{d:02d}: {title} — {day_name}{tag}")


Так можно, например, подсветить события, которые попадают на выходные, или не планировать релизы на понедельник.

---

calendar отлично подходит для прототипирования: быстрый текстовый календарь, базовая логика дат, дни недели, учет високосных лет — все уже есть «из коробки». Остается только обернуть это в интерфейс, который вам нужен.
👍3
Работа с временными зонами и utc: использование pytz
### Работа с временными зонами и UTC: почему pytz до сих пор нужен

Работа со временем — один из самых подлых участков в Python-проектах. Летнее время, разные часовые пояса, сервер в UTC, пользователь в другом конце планеты — всё это легко ломает логику. Разберём, как навести порядок с помощью модуля pytz.

---

## Наивные и «осознанные» даты

Модуль datetime в Python умеет хранить даты с информацией о временной зоне и без неё.

- Наивный datetime не знает, в каком он часовом поясе.
- Aware (tz-aware) — содержит таймзону и может корректно переводиться в другие.

from datetime import datetime

naive_dt = datetime.now()
print(naive_dt.tzinfo) # None — часовой пояс неизвестен


Такой объект опасен: сравнения и арифметика могут работать неверно, если смешивать его с датами из других зон.

---

## Добавляем pytz и приводим всё к UTC

Золотое правило: внутри приложения хранить время в UTC, а локальное время использовать только на входе/выходе.

from datetime import datetime
import pytz

utc = pytz.utc
utc_now = datetime.now(utc)
print(utc_now, utc_now.tzinfo) # Время в UTC


Теперь utc_now — «осознанный» объект, и его можно безопасно конвертировать в любой другой часовой пояс.

---

## Локальное время пользователя → UTC

Допустим, пользователь в Нью-Йорке выбирает время встречи:

from datetime import datetime
import pytz

user_tz = pytz.timezone("America/New_York")

# Пользователь ввёл время (например, из формы)
naive_meeting = datetime(2025, 3, 10, 15, 0, 0)

# Правильный способ "прикрутить" таймзону
local_meeting = user_tz.localize(naive_meeting)
utc_meeting = local_meeting.astimezone(pytz.utc)

print("Local:", local_meeting)
print("UTC: ", utc_meeting)


Важно: нельзя делать naive_meeting.replace(tzinfo=user_tz) — это обходит правила перехода на летнее/зимнее время и даёт неверный результат.

---

## UTC → локальное время пользователя

Когда храним в базе UTC, а показываем пользователю в его поясе:

import pytz

# Время из БД в UTC
stored_utc = utc_meeting # из предыдущего примера

user_tz = pytz.timezone("Asia/Tokyo")
user_time = stored_utc.astimezone(user_tz)

print("For user in Tokyo:", user_time)


pytz учитывает исторические изменения, летнее время и прочие «сюрпризы» календарей.

---

## Типичные правила, чтобы не утонуть во времени

1. Всегда сохраняй UTC в базе.
2. На границе системы:
- входящие данные → локализуешь через timezone.localize(...) → переводишь в UTC;
- исходящие данные → из UTC в нужную зону через astimezone(...).
3. Не используй replace(tzinfo=...) для смены зоны.
4. Явно задавай tzinfo, не полагайся на наивные datetime.now().

pytz выглядит простым, но помогает избежать очень дорогих ошибок во времени, особенно когда пользователи живут в разных точках мира.
👍2
Преобразование текста в речь с помощью pyttsx3
Преобразование текста в речь с помощью pyttsx3: заставляем Python говорить

Иногда куда удобнее услышать результат работы программы, чем читать его в консоли. Например, при создании голосового помощника, озвучивании уведомлений или чтении текстов вслух. Для этого в Python есть отличный модуль — pyttsx3.

pyttsx3 — офлайн‑движок синтеза речи: интернет не нужен, библиотека использует системные голосовые движки (SAPI5 на Windows, NSSpeechSynthesizer на macOS, eSpeak на Linux).

---

## Установка

pip install pyttsx3


---

## Первый голос программы

Минимальный пример:

import pyttsx3

engine = pyttsx3.init()
engine.say("Hello, Python world!")
engine.runAndWait()


Разбор по шагам:
- init() создаёт объект движка;
- say() добавляет текст в очередь;
- runAndWait() запускает озвучку и ждёт её завершения.

---

## Управление скоростью и громкостью

Сделаем речь быстрее и тише:

import pyttsx3

engine = pyttsx3.init()

rate = engine.getProperty("rate")
volume = engine.getProperty("volume")

engine.setProperty("rate", rate + 50) # быстрее
engine.setProperty("volume", volume - 0.2) # тише (0.0–1.0)

engine.say("This is a faster and quieter voice.")
engine.runAndWait()


Полезно, если вы хотите, чтобы уведомления говорились короче и быстрее, а чтение длинных текстов — медленнее и отчётливее.

---

## Выбор голоса

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

import pyttsx3

engine = pyttsx3.init()
voices = engine.getProperty("voices")

for idx, voice in enumerate(voices):
print(idx, voice.id)


Выбор голоса по индексу:

engine.setProperty("voice", voices[0].id)
engine.say("Using the first available voice.")
engine.runAndWait()


Если у вас установлены дополнительные русские/английские голоса, их можно выбрать по voice.id, проверяя в выводе нужный язык.

---

## Сохранение речи в аудиофайл

Озвучку можно не только проигрывать сразу, но и сохранять в файл, например, чтобы сделать аудиоверсию текста:

import pyttsx3

engine = pyttsx3.init()
engine.save_to_file("This text will be saved to an audio file.", "output.mp3")
engine.runAndWait()


Теперь output.mp3 можно отправить, встроить в приложение или проиграть через любой плеер.

---

pyttsx3 — отличный старт для тех, кто хочет добавить в свои программы голос: от простых напоминаний до прототипов ассистентов и читалок. Попробуйте озвучить результаты своих скриптов — Python начнёт разговаривать с вами буквально.
4
Отправка писем через SMTP с модулем smtplib
Отправка писем через SMTP с модулем smtplib

Представь: твой скрипт сам отправляет отчёт на почту каждое утро. Без ручного вмешательства, без «сейчас напишу письмо». В Python это делается в несколько строк с помощью модуля smtplib.

## Базовая отправка письма

SMTP — это протокол для отправки почты. В Python за него отвечает стандартный модуль smtplib, ничего ставить не нужно.

Простейший пример: отправим текстовое письмо через SMTP-сервер (например, Gmail):

import smtplib

smtp_server = "smtp.gmail.com"
smtp_port = 587

sender_email = "you@example.com"
password = "your_app_password"
receiver_email = "friend@example.com"

message = """\
From: you@example.com
To: friend@example.com
Subject: Test email from Python

Hello! This is a test email sent via smtplib.
"""

with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls() # шифруем соединение
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)


Ключевые моменты:

- starttls() — включает шифрование (TLS).
- login() — авторизация на почтовом сервере.
- sendmail() — отправка письма (от кого, кому, текст письма целиком).

Важно: многие почтовые сервисы требуют «пароль приложения», а не обычный пароль аккаунта.

## Форматирование письма по-взрослому

Реальные письма состоят из заголовков и тела, часто в нескольких форматах (текст + HTML). Удобнее собирать их через email-модуль.

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

smtp_server = "smtp.gmail.com"
smtp_port = 587

sender_email = "you@example.com"
password = "your_app_password"
receiver_email = "friend@example.com"

msg = MIMEMultipart("alternative")
msg["Subject"] = "Report from Python script"
msg["From"] = sender_email
msg["To"] = receiver_email

text_part = """\
Hi!
Here is your daily report in plain text.
"""

html_part = """\
<html>
<body>
<h2>Daily report</h2>
<p>This is a <b>HTML</b> version of the email.</p>
</body>
</html>
"""

msg.attach(MIMEText(text_part, "plain"))
msg.attach(MIMEText(html_part, "html"))

with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(sender_email, password)
server.send_message(msg)


Здесь send_message() принимает уже готовый объект письма, а не строку.

## Мини-чеклист по безопасности

- Не храни пароль в коде — используй переменные окружения или .env.
- Всегда используй starttls() или SMTP_SSL.
- Тестируй сначала на своём ящике, а не на почте начальника.

Умение отправлять письма из скрипта открывает массу возможностей: автоматические отчёты, уведомления о падении сервиса, напоминания о дедлайнах — всё это можно делегировать Python.
👍41🔥1
Парсинг аргументов командной строки: отличия argparse и sys.argv