PythonGuru


Гео и язык канала: Весь мир, Русский
Категория: Технологии


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

YouTube: https://www.youtube.com/@PythonGuru_
Сотрудничество: @PythonGuruTG

Связанные каналы

Гео и язык канала
Весь мир, Русский
Категория
Технологии
Статистика
Фильтр публикаций


Любопытно вышло

Сама задача по эмоциям оценена положительно, а вот ответы на нее - не очень

Был бы признателен, тем кто поставил дизлайки на ответы, пояснить, что именно в ответах вам не понравилось

Правильный ответ - TypeError
а именно, TypeError: multiply() missing 2 required arguments

Почему так вышло.

func() внутри функции wrapper, как мы помним из вчерашних пояснений, это копия декорируемой функции, т.е в нашем случае multiply

Если мы еще раз посмотрим на код, то данная функция объявлена с 2 обязательными параметрами (a, b)
И они действительно передаются в wrapper, но вызывая функцию func мы их не используем.

В итоге она вылетает с ошибкой, так как ожидаемых параметров не было

Даже боюсь спрашивать, понравилась ли задача в итоге )

Но все ж, тем кому понравилось, тисните 👍

Если остались вопросы, спрашивайте


История декораторов: как всё начиналось 🌟

В программировании не всегда существовали декораторы в том виде, к которому мы привыкли. Раньше, чтобы изменить поведение функции, разработчики обходились другими способами 🛠. Например, использовали ручное оборачивание: писали функцию-обёртку и присваивали её через явное указание, вроде:
my_func = decorator(my_func)

Вчера как раз в примерах это было

Это работало, но выглядело громоздко 😓. Были и случаи, когда функции декорировали дополнительной логикой, хотя это не бросалось в глаза так явно, как современные решения 👀.

Ещё в 1958 году язык Lisp заложил основы для таких идей 🚀, позволяя передавать функции как аргументы и возвращать новые функции. Это были первые шаги к тому, что позже назовут декораторами. В 1970-х Smalltalk и объектно-ориентированное программирование подхватили эстафету 🏃‍♂️, а в 1994 году паттерн "Decorator" из книги "Design Patterns" показал, как красиво оборачивать объекты дополнительной функциональностью 📚.

Настоящий прорыв случился в Python 🐍. В 2004 году, с выходом версии 2.4 и принятием PEP 318, появился синтаксис @decorator ✨. Теперь декоратор можно было указать прямо перед декорируемой функцией — просто, читаемо и изящно 😍. Это сделало код выразительнее, а декорирование — очевидным на первый взгляд. Позже идея прижилась и в других языках: JavaScript (ES7, 2016) и TypeScript подхватили декораторы, адаптировав их под свои нужды 🌐.

Так декораторы стали удобным инструментом, превратив громоздкие обёртки в элегантное решение для задач вроде логирования или кэширования 🎯.

#ТаймЛапсКода


Что будет выведено в результате запуска кода выше
Опрос
  •   Args: (4, 3) и затем 12
  •   TypeError
  •   Args: (4, 3)
  •   12
21 голосов


🍥 Закрепляем декораторы


🌃 Вечерний разбор.

Много правильных ответов, и это приятно

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

1️⃣ my_function, при вызове выводит текст I am working.
2️⃣ также она декорирована функцией say_hello о чем говорит @say_hello прям перед ней. А раз декорирована, то надо смотреть что мы меняем в оригинальной функции my_function
3️⃣ Смотрим, что мы делаем в функции декораторе. А там мы сначала выводим текст 'Hello', а потом запускаем func(), где func наша функция my_function (пункт 1), которая была передана в качестве параметра

Так что правильный ответ -
Hello! I am working

Остались вопросы, спрашивайте




Декораторы: что это, откуда и зачем?

Декораторы в Python — это способ добавить поведение к функциям, не меняя их код. Они помогают избежать повторений и появились в Python 2.4.
Декоратор — это функция, которая принимает другую функцию, добавляет что-то полезное (например, сообщение перед запуском) и возвращает улучшенную версию.


Как включить декораторы?

Используйте @ над функцией (например, @say_hello). Это говорит Python: "Применяй декоратор автоматически". Без @ пришлось бы писать вручную, например,
my_func = say_hello(my_func).

Синтаксис и зачем wrapper?

Декоратор — это функция с функцией внутри:


1️⃣ Пишем def say_hello(func): — название любое, но понятное (например, add_message).
2️⃣ Внутри — def wrapper():, где добавляем поведение. Название wrapper удобное, но не обязательное (можно inner).
3️⃣ Возвращаем wrapper, оно же функция, которую мы создали с использованием функции, которую декорировали

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

def say_hello(func):
def wrapper():
print("Hello!")
func()
return wrapper

@say_hello
def my_func():
print("I am working")

#без синтаксиса декоратора вызывали бы так
# my_func = say_hello(my_func)

Т.е еще раз.
Мы написали @say_hello сразу перед функцией my_func

Пайтон понял, что это - вот это
my_func = say_hello(my_func)
а именно, мы передаем нашу функцию как параметр в функцию say_hello, и перезаписываем нашу функцию my_func. Т.е say_hello что-то там творит с нашей оригинальной функцией, и опять возвращает нам функцию, которая немного изменилась.

А в самой функции say_hello, мы получили через параметр всю нашу оригинальную функцию, обьявили функцию внутри, в коде которой запускаем нашу оригинальную функцию и добавляем какой-то еще код, и в итоге вернули эту созданную функцию

Которая в итоге перезаписала нашу оригинальную функцию


‼️ И тут может возникнуть вопрос.
В пайтоне все передается по ссылкам, и наш func(), что должен запуститься внутри wrapper, он же ссылается на оригинальную функцию my_func, а мы то ее перезаписываем результатом работы декоратора, как так то?

Все дело в замыкании.
Так как wrapper, это функция, которая создается внутри другой функции, в нашем случае say_hello, то создается локальная копия функции func(), она же ссылка на оригинальный my_func() и потом используется именно локальная копия, созданная при определении декоратора.
А то, что мы в итоге перепишем саму my_func через декоратор, кто забыл то вспомним что это аналогично
my_func = say_hello(my_func)

то новый my_func это будет новый my_func, а старый сохранен локально в функции wrapper под переменной func

А зачем нужен wrapper, почему например нельзя сразу так

def say_hello(func):
print("Hello!")
func()
return say_hello

@say_hello
def my_func():
print("I am working")

my_func() # Сломается
Этот вариант не работает: декоратор должен возвращать новую функцию для вызова, а тут мы возвращаем сами себя, т.е say_hello опять же вызывает say_hello. Разово он выполнится при определении my_func(), а потом вывалится с ошибкой

Для первой информации очень много, поэтому тормознемся.

Что дальше?

Это основа. Далее разберём аргументы, настройки и практику.


Было полезно - 👍

#юзашки


Что будет выведено в коде выше
Опрос
  •   I am working Hello!
  •   Ошибка: TypeError
  •   Hello! I am working - right
  •   Ничего не выведет
24 голосов


📢 Объявляю неделю декораторов. Кто их не знал, будет прирост в знаниях

#dailyquiz


😱 Попался мне странный вопрос в quiz:

Нужна ли строка import some_module в коде

import some_module
from some_module import foo

Варианты: Да, Нет, Вызовет ошибку."

🧐Вот как бы вы ответили?

Однозначно можем сказать, что ошибки не будет, Python импортирует что угодно, проверив, загружено ли уже.

А вот "Да" или "Нет"? Не ответить:

- Нет контекста: что делает foo или some_module дальше? Если нужен только foo — ответ "Нет", если весь модуль — ответ "Да".

- Варианты спорны: оба верны в разных случаях, но в данном quiz только один ответ.

- Нечёткость: неясно, есть ли зависимости в some_module.

Интересно, что имели ввиду…

#фактЧек


Пятничный вечерний разбор.

Кто уже пьет пиво 🍻, для того скорее субботний или еще какой, более поздний вариант.

Регулярка была длинная, но что поделать, практический пример, IP как никак часто встречаются

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

Все равно расскажу, вдруг все ж есть интересующиеся.

1️⃣ Вариант не рабочий, по явно видной причине. Маска для цифр - \d{3}

Этим мы указываем, что ожидаем цифры - это \d и именно 3 подряд, это - {3}
А у нас в ip адресе, сегменты могут иметь от 1 до 3 символов, например 3-ий разряд только 1 цифра или 2 в нашем примере (192.168.1.14 например)

Так что мимо ✈️

3️⃣ Почти верный, за исключением маленькой детали

Вот так начинается группа, отвечающая за ip - (\d{1,3}\...

Выглядит все верно, ожидаем от 1 до 3 цифр подряд, пока не будет точка.
Так как правила отрабатывают по очереди, то мы остановимся уже на одной цифре, а их 3 -> 192

В итоге мы вырежем не 192.168.1.14, а 2.168.1.14, т.е 19 проигнорируем

Это происходит из-за того, что перед группой стоит .*, т.е любая последовательность символов, а потом хотя бы 1 цифра до точки, вот поэтому 19 отойдут к части шаблона .*

А вот во 2️⃣ варианте у нас перед группой появился пробельный символ \s. И там отработает, так как ожидаем любую последовательность, потом пробел(а это как раз перед началом ip) а потом от 1 до 3 цифр до точки. И в том случае все встает на свои места (т.е еще раз, так как ожидаем пробел именно перед цифрами, то 19 уже не сможет попасть в часть .*, так как она идет после пробела, и она пойдет в часть \d{1,3})

4️⃣ Вариант

Первый набор групп будет немного непредсказуемым
('Suser003', '2025-03-06 10:20:23 INFO Suser001 logged in from 192.168.1.14')

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

Почему так?

\s означает не только пробел, а также переносы строк

В итоге мы ловим (S\w+) -> Suser003

потом любая последовательность символов .*, которая должна закончиться пробельным символом \s

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

Потом идет описание нашей группы

(\d.*\.1\..*)

А это 1 цифра, потом .* любое количество символов пока не будет точка \.
потом 1, потом опять точка\ \., а потом любая последовательность

\s уже стоит за пределом группы, т.е после 1 мы ожидаем все что угодно, пока не будет пробельный символ

Т.е первая цифра - это 2(первый символ 3-ей строки), потом любое количество символов до точка 1 точка и опять любое количество до пробела или переноса строки
Если вы посмотрите на 3 строку, то она вся попадает под этот шаблон. От двойки, до 3-го сегмента ip адреса .1. и остатка Ip адреса до первого \s

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

Вот такая задача.

Познавательно - 👍
Непонятно, задавайте вопросы


Завершим эту неделю с регулярками.


Конечно, мы не рассмотрели всё, что можно, но, думаю, пора переключиться на что-то новое. Однако было бы неправильно остановиться на этой теме, не затронув именованные группы — они действительно упрощают работу с регулярными выражениями.

Именованные параметры в модуле re позволяют задавать имена для групп в шаблонах, что делает код читаемым и удобным. Вместо обращения к группам по номеру (например, group(1)), вы можете использовать понятные имена через group('имя') или groupdict(). Это особенно полезно в сложных шаблонах или когда структура может измениться — имена стабильны, в отличие от номеров. Применять их стоит, когда нужно извлечь данные и сразу связать их с логическими названиями, например, при парсинге дат или контактной информации.

Структура паттерна для именованной группы проста: те же скобки, что и для обычных групп, но добавляется ?P. Например, (?P\d{2}) — группа с именем day, захватывающая две цифры.

Пример с search:

import re

pattern = r"(?P\d{2})-(?P\d{2})-(?P\d{4})"
text = "Дата: 07-03-2025"

match = re.search(pattern, text)
print(match.group('day')) # 07
print(match.groupdict()) # {'day': '07', 'month': '03', 'year': '2025'}

Тут group('day') даёт прямой доступ к дню, а groupdict() — словарь всех групп. Это про ясность и удобство.

‼️ Но, возможно, у кого-то возник вопрос: search возвращает только первое вхождение, а что если мы хотим найти все совпадения? В таких случаях выручает finditer. Он возвращает итератор объектов Match, и для каждого совпадения можно использовать имена групп.

Пример с finditer:

import re

pattern = r"(?P\d{2})-(?P\d{2})-(?P\d{4})"
text = "Даты: 07-03-2025 и 15-04-2023"

matches = re.finditer(pattern, text)
for match in matches:
print(f"Дата: {match.group()}, День: {match.group('day')}")

Вывод:

Дата: 07-03-2025, День: 07
Дата: 15-04-2023, День: 15

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

Как вам неделя с регулярками, было полезно? 👍 👎

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


Из лога на картинке выше, мы хотим получить [('Suser001', '192.168.1.14'), ('Suser002', '192.168.1.16')], т.е только где имя пользователя начинается на большую S и 3 разряд ip адреса имеет цифру 1
Опрос
  •   r'(S\w+).*\s(\d{3}\.\d{3}\.1\.\d{3})'
  •   r'(S\w+).*(\d{1,3}\.\d{1,3}\.1\.\d{1,3})'
  •   r'(S\w+).*\s(\d{1,3}\.\d{1,3}\.1\.\d{1,3})'
  •   r'(S\w+).*\s(\d.*\.1\..*)\s'
10 голосов


Давайте последнюю на регулярки, и пока хватит с них

#dailyquiz


Уже лучше

Сегодняшняя задача включала в себя группы.

В чем отличие от предыдущих задач.

1️⃣ Первая задача, писали шаблон и в результате было все что описано в шаблоне
2️⃣ Вторая - шаблон разбили на части, где указывали, что должно вернуться в результате, а что не должно быть в результате, но должно присутствовать в тексте в определенной позиции

А тут третий вариант
Надо написать шаблон, который опишет структуру строки, что нас интересует, и вернет только то, что нам надо из этой строки

Т.е возвращаясь к нашей задаче, надо найти строки где есть слово INFO, потом где-то после него идет имя пользователя, а потом необходимо наличие слова logged

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

Вот для этого существуют группы, а чтобы re движок нас понял, мы группы обрамляем в круглые скобки

В итоге наш правильный вариант

r'INFO\s(\w+)\slogged'

Разобьем по шагам

1️⃣ Бежим по строке, пока не найдем INFO
2️⃣ После INFO сразу должен идти пробельный символ - мы это указывает через метасимвол \s
3️⃣ Дальше должна идти последовательность произвольной длины из буквенных символов или цифр (\w+) и мы ее заключили в скобки, именно так мы указали, что вернуть надо только это, в случае если весь шаблон будет советствовать
4️⃣ Опять \s - т.е после буквенно цифровой последовательности (3-ий шаг) должен идти пробельный символ
5️⃣ После пробельного символа должно быть слово logged

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

Из неправильного

r'INFO.*logged'

Тут вариант задачи первого дня, мы явно не указали что возвращать через использование групп, значит вернется все, что попало в описанный нами шаблон
['INFO User123 logged', 'INFO User124 logged']


r'INFO(.*)logged'

Этот близок, но он вернет весь текст между INFO и logged, т.е с пробелами, а нам они не вперлись )
[' User123 ', ' User124 '] - тут есть пробелы, если присмотреться


r'INFO[.*]logged'

тут дело в квадратных скобках. Если мы используем квадратные скобки, то мы указываем те символы, которые ожидаем в одной позиции, т.е этим мы указали, что между INFO и logged должен быть один символ, или точка или звездочка. Внутри [] точка и звездочка не рассматриваются как метасимволы. (Опять же, никто не мешает нам использовать квантификатор после квадратных скобок, например [.*]+ значит что любой из двух символов, точка или звездочка и это может повторяться сколько угодно, пока не будет иного правила)

на примере, вот такой текст этот шаблон отловил бы

2025-03-05 10:18:40 INFO*logged in from 192.168.1.12


результат

['INFO*logged']


👍 если принесло пользу

Остались непонятки, спрашиваем


Мы использовали в наших примерах только findall, а что если нам не надо искать все совпадения? Это как минимум тратит больше ресурсов, особенно если текст большой, а нам нужен лишь первый результат или что-то конкретное. К счастью, в модуле re есть другие варианты, которые подходят для разных задач.

1. `re.search` — ищет первое совпадение в любом месте строки. Удобно, когда нужно найти что-то одно и не важно, где оно.
Пример: "Есть ли email?" — re.search(r'\w+@\w+\.\w+', "Contact: test@example.com") → "test@example.com".

2. re.match — проверяет только начало строки. Идеально, если важно, чтобы совпадение было в самом старте.
Пример: "Начинается ли строка с числа?" — re.match(r'\d+', "123abc") → "123", а для "abc123" — ничего.

3. re.sub — заменяет совпадения, а не просто ищет. Нужно подправить текст? Это ваш выбор.
Пример: "Сменим даты" — re.sub(r'-', '/', "05-03-2025") → "05/03/2025".

4. re.split — разбивает строку по шаблону. Полезно для парсинга.
Пример: "Разделим список" — re.split(r', ', "a, b, c") → ["a", "b", "c"].

5. re.compile — ускоряет работу, если один и тот же шаблон нужен много раз.
Пример: "Ищем числа в цикле" — pattern = re.compile(r'\d+'), затем pattern.search("text 42") → "42".

Каждая функция хороша для своей задачи: search — для первого вхождения, match — для начала строки, sub — для замены, split — для разделения, а compile — для оптимизации. Попрактикуйтесь, для кого это новое, так лучше запомнится!

Было полезно — 👍
Остались вопросы, спрашивайте

#юзашки


Видео недоступно для предпросмотра
Смотреть в Telegram
🫢когда сроки горят 🔥

#мемка


Код сверху. Имеем записи логов и хотим получить имена пользователей, которые успешно залогировались, мы это определяем по наличию в строке слов INFO и logged Какой pattern корректен, чтобы получить ['User123', 'User124']
Опрос
  •   r'INFO.*logged'
  •   r'INFO\s(\w+)\slogged'
  •   r'INFO(.*)logged'
  •   r'INFO[.*]logged'
10 голосов


Задача на практическом примере

#dailyquiz


И снова Добрый вечер.

Задание, похожее на вчерашнее, но с небольшой разницей.
НЕ ВСЕ ДОЛЖНО БЫТЬ в РЕЗУЛЬТАТЕ совпадения, описанного в шаблоне

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

А сегодняшнее задание - нам надо проверить наличие # сразу перед словом, но не включать эту решетку в результат

Вот для этого, нам понадобится позитивный ретроспективный анализ (positive lookbehind)

А это вот такая конструкция

(?

r'(!#)\w+' и r'(#)\w+'


Тут мы используем группы захвата, это включу в следующие задачи

r'?

Показано 20 последних публикаций.