""" Урок 61. Практикум 15 — весь код примеров одним файлом. Источник: subjects/python-fundamentals/course/lessons/61-practice-15/examples.html Файл собран автоматически (tools/build_lesson_examples.py): правьте страницу урока. Запуск: python lesson-61.py """ # ==================================================================== # Пример 1. re.compile + findall — база всех текстовых заданий # ==================================================================== import re text = "Order #123, code: ABC-42, id: 007" number_re = re.compile(r"\d+") print(number_re.findall(text)) # ['123', '42', '007'] # ==================================================================== # Пример 2. Очистка строки: re.sub(r"\D", "", ...) # ==================================================================== import re raw = "+49 157 1234-5678" clean = re.sub(r"\D", "", raw) print(clean) # 4915712345678 # ==================================================================== # Пример 3. Собираем шаблон немецкого номера по частям # ==================================================================== import re phone_re = re.compile(r"(?:\+49|0049)[\s\-]*1[5-7](?:[\s\-]*\d){7,9}") sample = "Call +49 157 12345678 or 0049-160-9876543 or +49 89 123456 (landline)" print(phone_re.findall(sample)) # ['+49 157 12345678', '0049-160-9876543'] # ==================================================================== # Пример 4. Валидация после regex — фильтр первого приближения # ==================================================================== def clean_and_validate(raw_match: str) -> str | None: digits = re.sub(r"\D", "", raw_match) if digits.startswith("0049"): digits = digits[4:] elif digits.startswith("49"): digits = digits[2:] if digits[:2] not in ("15", "16", "17"): return None if not (10 <= len(digits) <= 12): return None return f"+49{digits}" candidates = phone_re.findall(sample) valid = [n for n in (clean_and_validate(c) for c in candidates) if n] print(valid) # ['+4915712345678', '+491609876543'] # ==================================================================== # Пример 5. Границы слова в email: почему нельзя жадный класс символов # ==================================================================== local = r"[a-zA-Z0-9_](?:[a-zA-Z0-9._]*[a-zA-Z0-9_])?" email_re = re.compile(rf"\b{local}@[a-zA-Z0-9.-]+\.[a-zA-Z]{{2,3}}\b") sample_emails = "ok: user.name@sub.domain.com, bad: .user@example.com, bad2: user.@example.com" print(email_re.findall(sample_emails)) # ['user.name@sub.domain.com', 'user@example.com'] # ==================================================================== # Пример 6. Номер карты: повторяющаяся группа (?:\d{4}[- ]?){3}\d{4} # ==================================================================== card_re = re.compile(r"\b(?:\d{4}[- ]?){3}\d{4}\b") sample_cards = "1234 5678 9012 3456 and 1234--5678--9012--3456 and 1234567812345678" print(card_re.findall(sample_cards)) # ['1234 5678 9012 3456', '1234567812345678'] # ==================================================================== # Пример 7. JSON туда и обратно через pathlib # ==================================================================== import json from pathlib import Path students = [ {"name": "Alice", "subject": "Math", "grade": 90, "date": "12-03-2025"}, {"name": "Alice", "subject": "Math", "grade": 70, "date": "20-04-2025"}, ] json_path = Path("students.json") json_path.write_text(json.dumps(students, indent=2, ensure_ascii=False), encoding="utf-8") loaded = json.loads(json_path.read_text(encoding="utf-8")) print(loaded[0]) # {'name': 'Alice', 'subject': 'Math', 'grade': 90, 'date': '12-03-2025'} # ==================================================================== # Пример 8. Дата → квартал: datetime.strptime и своя функция # ==================================================================== from datetime import datetime def get_quarter(date_str: str) -> str | None: month = datetime.strptime(date_str, "%d-%m-%Y").month if 1 <= month <= 3: return "Q1" if 4 <= month <= 5: return "Q2" if 9 <= month <= 12: return "Q3" return None # летние месяцы пропускаем for date_str in ("12-03-2025", "20-04-2025", "06-09-2025"): print(date_str, "->", get_quarter(date_str)) # 12-03-2025 -> Q1 # 20-04-2025 -> Q2 # 06-09-2025 -> Q3 # ==================================================================== # Пример 9. Группировка без if: defaultdict(lambda: defaultdict(list)) # ==================================================================== from collections import defaultdict entries = [ {"name": "Alice", "grade": 90, "date": "12-03-2025"}, {"name": "Alice", "grade": 70, "date": "20-04-2025"}, {"name": "Alice", "grade": 80, "date": "06-09-2025"}, ] by_quarter: defaultdict[str, list[int]] = defaultdict(list) for row in entries: quarter = get_quarter(row["date"]) if quarter: by_quarter[quarter].append(row["grade"]) report = {q: round(sum(v) / len(v), 2) for q, v in by_quarter.items()} print(report) # {'Q1': 90.0, 'Q2': 70.0, 'Q3': 80.0} # ==================================================================== # Пример 10. Генератор + regex: лениво читаем файл и сразу фильтруем # ==================================================================== from pathlib import Path def emails_from_file(path: Path): with path.open(encoding="utf-8") as file: for line in file: for match in email_re.finditer(line): yield match.group() contacts = Path("contacts.txt") contacts.write_text( "Alice - alice@example.com\nnot an email line\nBob - bob.work@company.org\n", encoding="utf-8", ) print(list(emails_from_file(contacts))) # ['alice@example.com', 'bob.work@company.org']