<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>С любовью о Python</title><generator>teletype.in</generator><description><![CDATA[Бережно рассказываю про Python и автоматизацию тестирования.]]></description><image><url>https://img2.teletype.in/files/15/01/1501029f-8905-4631-8d96-13e6dcb12271.png</url><title>С любовью о Python</title><link>https://teletype.in/@python3_with_love</link></image><link>https://teletype.in/@python3_with_love?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/python3_with_love?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/python3_with_love?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Fri, 17 Apr 2026 10:21:24 GMT</pubDate><lastBuildDate>Fri, 17 Apr 2026 10:21:24 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/builder_python</guid><link>https://teletype.in/@python3_with_love/builder_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/builder_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>Паттерн Builder в Python</title><pubDate>Sat, 28 Jun 2025 17:21:09 GMT</pubDate><description><![CDATA[Builder, он же строитель - порождающий паттерн проектирования в программировании, который позволяет довольно удобно работать с построением сложных объектов.]]></description><content:encoded><![CDATA[
  <p id="b_98508_d185e92a0ede">Builder, он же строитель - порождающий паттерн проектирования в программировании, который позволяет довольно удобно работать с построением сложных объектов.</p>
  <p id="b_19712_fc0139889f01">Паттерн позволяет вызывать функции объекта для его сборки цепочкой, и это может выглядеть примерно так:<br /><code>UserBuilder().with_name(“Ivan“).with_email(“i_connor@mail.ru“).with_subscription().build()</code></p>
  <h2 id="b_66012_00446a15af7e">Пример применения</h2>
  <p id="b_23412_65f3c23cdffd">Реализация паттерна может отличаться, но мы не будем рассматривать сложные примеры. Допустим, перед нами стоит задача - написать автотест для проверки запроса на создание пользователя в какой-либо системе. <code>json</code> для отправки содержит в себе поля:</p>
  <ul id="b_87666_d1e90726e59f">
    <li id="b_92108_529b0e029380">*имя: str</li>
    <li id="b_12493_2da858f868db">*фамилия: str</li>
    <li id="b_50946_e8f0a710db3e">*почта: str</li>
    <li id="b_64072_7d7862237301">подписка:</li>
    <ul id="b_05737_5e4a5cf700a3">
      <li id="b_51416_f74fb865aae1">*тип подписки: str</li>
      <li id="b_26999_76576836b7a0">*наличие подписки (да/нет): bool</li>
    </ul>
    <li id="b_60121_a33bdfdb7a08">*количество бонусных баллов: int</li>
    <li id="b_41904_7e9cddace549">*адрес: str</li>
    <li id="b_87144_4ccb5658bc80">о себе: str</li>
    <li id="b_86268_f805c37dd49e">друзья: list[str]</li>
  </ul>
  <p id="b_47408_4a8a5c30daba">где * означает обязательность поля.</p>
  <p id="b_58214_f69564bd2781">В формате <code>json</code> это выглядит так:</p>
  <pre id="SDAV" data-lang="python">{
    &quot;name&quot;: &quot;Vova&quot;,
    &quot;last_name&quot;: &quot;Ivanov&quot;,
    &quot;email&quot;: &quot;vivanov@mail.ru&quot;,
    &quot;subscription&quot;: {
        &quot;type&quot;: &quot;hd+&quot;,
        &quot;active&quot;: True
    },
    &quot;bonuses&quot;: 1500,
    &quot;address&quot;: &quot;Belgorod, Lenina str., b. 10&quot;,
    &quot;about&quot;: &quot;Some additional info&quot;,
    &quot;friends&quot;: [&quot;12354&quot;, &quot;1435462&quot;, &quot;626245&quot;, &quot;62461134&quot;]
}</pre>
  <p id="b_36319_4c30019861f9">Простор для перебора параметров довольно обширен. Рассмотрим несколько вариантов для параметризации теста:</p>
  <ol id="b_20668_35eec5cb9bf3">
    <li id="b_11351_5ee8fc909f2f">Хранить заранее заготовленные  json-ы где-то в константах или отдельно в файлах, передавать их в тест</li>
    <li id="b_96101_8c0ba4d1c59e">Сделать функцию / фикстуру с параметрами для нужных полей</li>
    <li id="b_64277_78d919e08832">Использовать faker для генерации рандомных данных и возвращать только их, без чуткого контроля над данными</li>
    <li id="b_98871_25bd49833a79">Создать удобный конструктор, где мы можем сами контролировать нужные поля, их наличие и значения</li>
  </ol>
  <p id="b_18030_ea9e50a480da">Выберем последний вариант и имплементируем наш класс-билдер.</p>
  <blockquote id="b_96379_51b3886307ac">Перед написанием кода нам понадобится установить дополнительную библиотеку faker.<br />Для этого используем команду: <code>pip install Faker</code></blockquote>
  <pre id="2IyW" data-lang="python">import copy
import random
from pprint import pprint
from faker import Faker


class UserBuilder:
    def __init__(self):
        self._final_dict = {}  # Изначально пустой словарь, который будет пополняться даннными
        self.faker = Faker(locale=&quot;ru_RU&quot;)

    # Ниже перечислены методы, отвечающие за интересующие нас поля для сборки финального json-а

    def with_name(self, name: str | None = None) -&gt; &quot;UserBuilder&quot;:
        self._final_dict[&quot;name&quot;] = name or self.faker.first_name_female()
        return self

    def with_last_name(self, last_name: str | None = None) -&gt; &quot;UserBuilder&quot;:
        self._final_dict[&quot;last_name&quot;] = last_name or self.faker.last_name_female()
        return self

    def with_email(self, email: str | None = None) -&gt; &quot;UserBuilder&quot;:
        self._final_dict[&quot;email&quot;] = email or self.faker.email()
        return self

    def with_subscription(self, is_active: bool, s_type: str) -&gt; &quot;UserBuilder&quot;:
        self._final_dict[&quot;subscription&quot;] = {&quot;active&quot;: is_active, &quot;type&quot;: s_type}
        return self

    def with_bonuses(self, amount: int | None = None) -&gt; &quot;UserBuilder&quot;:
        self._final_dict[&quot;bonuses&quot;] = amount or random.randint(500, 2000)
        return self

    def with_address(self, address: str | None = None) -&gt; &quot;UserBuilder&quot;:
        self._final_dict[&quot;address&quot;] = address or self.faker.address()
        return self

    def with_about(self, about: str | None = None) -&gt; &quot;UserBuilder&quot;:
        self._final_dict[&quot;about&quot;] = about or self.faker.text(max_nb_chars=100)
        return self

    def with_friends(self, friends_list: list[str]) -&gt; &quot;UserBuilder&quot;:
        self._final_dict[&quot;friends&quot;] = friends_list
        return self

    def build(self) -&gt; dict:
        user_data = copy.deepcopy(self._final_dict)
        self._final_dict.clear()
        return user_data  # Финальный аккорд билдера. Возвращает собранный словарь
</pre>
  <p id="b_34948_79ea4222d0c1">Коротко о том, что у нас получилось:</p>
  <ul id="b_15372_a0e98df4acec">
    <li id="b_91430_c8ce90e17a88">Создали класс с пустым словарем в приватном атрибуте <code>self._final_dict</code> в <code>__init__</code></li>
    <li id="b_33895_e8ca1c631963">Описали поля, которыми и будет заполняться наш пустой словарик. <br />- Возврат <code>self</code> здесь является важной особенностью, т.к. это позволяет нам вызывать методы цепочкой через точку. <br />- Упрощенно: при использовании билдера мы создаем объект класса и именно его и возвращаем каждый раз при вызове методов. Это позволяет всегда иметь доступ к методам и атрибутам экземпляра класса.<br />- Конструкции вида <code>email or self.faker.email()</code> позволяют нам использовать фейковую генерацию данных, если мы ничего не передали в аргумент метода.</li>
    <li id="b_89233_614f885a75ca">В методе <code>build</code> уже возвращается не экземпляр класса, а собранный нами словарь. Это завершающий метод билдера. <br />Кроме того, для безопасной работы и возможности создания одного экземпляра класса <code>UserBuilder</code> для генерации нескольких словарей, внутри реализован механизм копирования данных в отдельную переменную, а затем очистка собранного нами словаря.</li>
  </ul>
  <p id="b_00193_aede1364833e">На этом кратком объяснении и остановимся, перейдем к использованию:</p>
  <pre id="nLzY" data-lang="python"># Создаем экземпляр класса UserBuilder
builder = UserBuilder()


# Первый пользователь с полным набором полей
user1 = (
    builder
    .with_name(&quot;Егор&quot;)
    .with_last_name(&quot;Летов&quot;)
    .with_email(&quot;eletov@mail.ru&quot;)
    .with_subscription(is_active=True, s_type=&quot;hd+&quot;)
    .with_bonuses(2000)
    .with_address(&quot;Омск&quot;)
    .with_about(&quot;Музыкант&quot;)
    .with_friends([&quot;1234141&quot;, &quot;35152446&quot;])
    .build()
)


# Второй пользователь с набором полей поменьше
user2 = (
    builder
    .with_name(&quot;Денис&quot;)
    .with_last_name(&quot;Сергеев&quot;)
    .with_email(&quot;dsergeev@mail.ru&quot;)
    .with_bonuses(1000)
    .build()
)


# Третий пользователь с полностью сгенерированными данными с использованием библиотеки faker
user3 = (
    builder
    .with_name()
    .with_last_name()
    .with_email()
    .with_address()
    .build()
)


# Распечатаем все с помощью функции pprint, которая сделает наш вывод немного красивее
pprint(user1, indent=2, sort_dicts=False)
pprint(user2, indent=2, sort_dicts=False)
pprint(user3, indent=2, sort_dicts=False)
</pre>
  <p id="b_36796_ce061d00fa66">Давайте посмотрим на результат:</p>
  <pre id="Wm4H" data-lang="python">{ &#x27;name&#x27;: &#x27;Егор&#x27;,
  &#x27;last_name&#x27;: &#x27;Летов&#x27;,
  &#x27;email&#x27;: &#x27;eletov@mail.ru&#x27;,
  &#x27;subscription&#x27;: {&#x27;active&#x27;: True, &#x27;type&#x27;: &#x27;hd+&#x27;},
  &#x27;bonuses&#x27;: 2000,
  &#x27;address&#x27;: &#x27;Омск&#x27;,
  &#x27;about&#x27;: &#x27;Музыкант&#x27;,
  &#x27;friends&#x27;: [&#x27;1234141&#x27;, &#x27;35152446&#x27;]}
{ &#x27;name&#x27;: &#x27;Денис&#x27;,
  &#x27;last_name&#x27;: &#x27;Сергеев&#x27;,
  &#x27;email&#x27;: &#x27;dsergeev@mail.ru&#x27;,
  &#x27;bonuses&#x27;: 1000}
{ &#x27;name&#x27;: &#x27;Феврония&#x27;,
  &#x27;last_name&#x27;: &#x27;Кузнецова&#x27;,
  &#x27;email&#x27;: &#x27;naina_1999@example.com&#x27;,
  &#x27;address&#x27;: &#x27;с. Ухта, ш. Революционное, д. 2 к. 4/1, 906254&#x27;}</pre>
  <p id="b_08278_d02406d9b0da">Конечное использование данных может быть разным, не будем на нем концентрироваться.</p>
  <p id="b_62065_4eecd5de2d5f">Паттерны - это не магия или что-то переусложненное, а реальные механизмы, которые помогут структурировать код и облегчить работу над сложными объектами в повседневных задачах.</p>
  <p id="b_78239_997f7b2c60d3">Конкретно билдер позволяет нам собирать нужный объект как конструктор, при этом шаг за шагом контролируя весь процесс.</p>
  <p id="aSMI"></p>
  <hr />
  <p id="tO3r">Мой telegram-канал, присоединяйтесь :)</p>
  <p id="5CE9">⬇⬇⬇</p>
  <p id="c3mN"><a href="https://t.me/python3_with_love" target="_blank">https://t.me/python3_with_love</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/getters_and_setters_python</guid><link>https://teletype.in/@python3_with_love/getters_and_setters_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/getters_and_setters_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>Геттеры и сеттеры в Python</title><pubDate>Sun, 25 May 2025 13:43:51 GMT</pubDate><description><![CDATA[Для начала разберемся, что это такое и когда они нам могут пригодиться. Простыми словами, геттер получает значение приватного атрибута, а сеттер его устанавливает, вот и все.]]></description><content:encoded><![CDATA[
  <h2 id="3vNg">Постигаем геттеры и сеттеры</h2>
  <p id="5Qqy">Для начала разберемся, что это такое и когда они нам могут пригодиться. Простыми словами, геттер получает значение приватного атрибута, а сеттер его устанавливает, вот и все.</p>
  <p id="uLiD">С их помощью можно поддержать инкапсуляцию (получать доступ к приватным атрибутам) или каким-либо дополнительным образом провалидировать или обработать значения.</p>
  <h3 id="I5hr">Пара слов о property</h3>
  <p id="Nai7">Примеры сразу будем рассматривать с удобными аннотациями <code>@property</code> - свойствами. Это более &quot;питонический&quot; путь работы с поведением атрибутов, такими как получение, настройка и удаление.</p>
  <p id="13Wm">Свойства используются так же, как и обычные атрибуты, но предоставляют возможность добавлять поведение при доступе к ним.</p>
  <p id="wOXA">Как связаны свойства с геттерами и сеттерами? Когда мы получаем доступ к свойству, то автоматически вызывается связанный с ним getter-метод. Когда мы пытаемся изменить свойство, соответственно будет вызван setter-метод.</p>
  <p id="rnXs">А теперь перейдем к практике.</p>
  <h2 id="cy9y">Пример использования</h2>
  <p id="cFjg">Создадим класс <code>Product</code>, чтобы мы могли провернуть с ним некоторые процедуры:</p>
  <ul id="50XR">
    <li id="8RF7">создать конкретный продукт, задав ему имя и базовую стоимость (без НДС)</li>
    <li id="g4Y3">получить стоимость продукта с / без НДС</li>
    <li id="omTW">назначить новую цену, введя сумму с НДС, выполнить расчет базовой стоимости и записать ее в приватный атрибут.</li>
  </ul>
  <pre id="8UFr" data-lang="python">class Product:  
    def __init__(self, name: str, base_price: float) -&gt; None:  
        self.name = name  
        self.__base_price = base_price  
        self.__vat_percent = 0.13  
  
    @property  
    def price(self) -&gt; float:  
        return round(self.__base_price * (1 + self.__vat_percent), 2)  
  
    @price.setter  
    def price(self, value: float) -&gt; None:  
        # пересчитываем базовую цену, т.к. передаем в value цену с НДС
        self.__base_price = round(value / (1 + self.__vat_percent), 2)  
  
    @property  
    def base_price(self) -&gt; float:  
        return self.__base_price</pre>
  <p id="XgzF">Сразу определим, что НДС будет равен 13%.</p>
  <p id="IVtO">Под капотом некоторые обработки для свойств:</p>
  <ul id="SJRt">
    <li id="ok4J">Округление и рассчет цены с НДС при получении цены через свойство <code>price</code></li>
    <li id="2SQC">Рассчет базовой стоимости по полученной стоимости с НДС, округление и установка значения в приватный атрибут <code>__base_price</code></li>
  </ul>
  <p id="Z02R">Класс описали, посмотрим теперь на пример использования:</p>
  <pre id="lPse" data-lang="python">coffee = Product(name=&quot;Флэт уайт&quot;, base_price=300)  # Создаем наш кофе с ценой до НДС = 300 р.

print(coffee.name)  # Флэт уайт | Здесь просто название
print(coffee.price)  # 339.0 | Цена рассчитана с НДС, это произошло автоматически
  
coffee.price = 350  # Устанавливаем новую цену для кофе, сумма сразу с НДС, чтобы произошла корректная обработка и вычисление базовой стоимости
print(coffee.price)  # &gt;&gt;&gt; 349.99  | Получаем цену с НДС. Не равно 350 из-за округлений при записи
print(coffee.base_price)  # &gt;&gt;&gt; 309.73  | Базовая цена без НДС</pre>
  <p id="lRWd">Пример получился лаконичным, так как мы производим все вычисления, просто обращаясь к свойствам, не вызываем явные методы для этих операций. При этом, напрямую не обращаемся к приватным атрибутам, не нарушая принцип инкапсуляции.</p>
  <h3 id="ja70">Маленький бонус: deleter</h3>
  <p id="RyXY">Хоть в нашем примере это не имеет особого смысла, но для рассмотрения еще 1 свойства можно попробовать обновить пример так, чтобы использовать deleter. Это свойство уместно, когда нужно удалить атрибут или сбросить его в изначальное состояние, или обработать удаление атрибута каким-то определенным образом.</p>
  <p id="HpXF">Добавим в наш пример с Product следующее свойство:</p>
  <pre id="AAAK" data-lang="python">@price.deleter
def price(self):
	self.__base_price = 0</pre>
  <p id="dUag">Тогда если мы удалим атрибут, значение <code>__base_price</code> просто сбросится в ноль. Проверим:</p>
  <pre id="6FGq" data-lang="python">del coffee.price  
print(coffee.price)  # 0.0</pre>
  <h2 id="BnEv">Выводы</h2>
  <p id="0SeF">Геттеры и сеттеры:</p>
  <p id="T1ZT">✅ помогают скрыть реализацию и сохранить инкапсуляцию<br />✅ позволяют валидировать или изменять данные при доступе к ним<br />✅ обеспечивают простой и читаемый интерфейс (без явных методов)</p>
  <p id="aQpJ">Лучше использовать методы вместо свойств, если:</p>
  <ul id="KmbN">
    <li id="hQ0n">операция не выглядит как &quot;доступ к данным&quot;</li>
    <li id="Hwty">требуется более сложная логика</li>
  </ul>
  <p id="BHuf"></p>
  <hr />
  <p id="tO3r">Мой telegram-канал, присоединяйтесь :)</p>
  <p id="5CE9">⬇⬇⬇</p>
  <p id="c3mN"><a href="https://t.me/python3_with_love" target="_blank">https://t.me/python3_with_love</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/enumerate_python</guid><link>https://teletype.in/@python3_with_love/enumerate_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/enumerate_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>Функция enumerate в Python 🐍</title><pubDate>Sat, 25 Jan 2025 17:27:08 GMT</pubDate><description><![CDATA[Поговорим сегодня о маленькой, но очень важной встроенной функции, имя которой enumerate.]]></description><content:encoded><![CDATA[
  <p id="mHmq">Поговорим сегодня о маленькой, но очень важной встроенной функции, имя которой <code>enumerate</code>.</p>
  <h2 id="Uz43">Что это и когда применять</h2>
  <h3 id="Krao">Синтаксис</h3>
  <pre id="VHBB" data-lang="python">enumerate(iterable, start=0)</pre>
  <h3 id="9x63">Описание</h3>
  <p id="aWei">Функция <code>enumerate()</code> возвращает объект <code>enumerate</code>, который является итератором, создающим кортежи. Этот кортеж содержит счетчик от <code>start</code> (который по-умолчанию равен 0) и значение, полученное в результате перебора по <code>iterable</code>. Объект, переданный в <code>iterable</code>, должен быть последовательностью, итератором или другим объектом, поддерживающим метод итератора <code>__next__()</code>.</p>
  <h3 id="Xz5i">Параметры</h3>
  <ul id="efqS">
    <li id="Ol8o"><code>iterable</code> По сути, <code>iterable</code> — это любой объект, по которому можно пройтись в цикле (списки, строки, файлы и т.д.).</li>
    <li id="Pyew"><code>start</code> Позволяет задать начальное значение счетчика, что полезно, если нумерация должна начинаться не с 0, а с другого числа.</li>
  </ul>
  <p id="Seke">Если вы когда-нибудь писали цикл <code>for</code> и ловили себя на мысли: &quot;Вот бы знать, на каком я сейчас шаге итерации&quot;, то <code>enumerate</code> — это ваш новый лучший друг. Функция позволяет избавиться от необходимости инициировать и обновлять отдельную переменную-счётчик.</p>
  <h2 id="Cul8">Примеры</h2>
  <h3 id="w8xE">1. Перебор по списку</h3>
  <p id="1Rkt">Давайте рассмотрим пример:</p>
  <ul id="Nod7">
    <li id="zXk8">Создадим список</li>
    <li id="yuQ5">Выведем каждый элемент с индексом без <code>enumerate</code></li>
    <li id="ik9S">Выведем каждый элемент с индексом с использованием <code>enumerate</code></li>
  </ul>
  <p id="QgdD">Допустим, мы перебираем гипотетический шкаф с вещами и хотим знать номер каждой вещи, которую мы достали.</p>
  <pre id="GJCc" data-lang="python">list_of_things_from_closet = [&quot;толстовка&quot;, &quot;футболка&quot;, &quot;кепка&quot;, &quot;шорты&quot;, &quot;джинсы&quot;]  
  
# Печатаем каждый элемент из списка с его индексом  
for i in range(len(list_of_things_from_closet)):  
    # i + 1, т.к. в обычной жизни мы не считаем элементы с 0, а начинаем с единицы  
    print(f&quot;Вещь № {i + 1} - это {list_of_things_from_closet[i]}&quot;)</pre>
  <p id="BLCu">В консоли увидим следующее:</p>
  <pre id="ZHtX">&gt;&gt;&gt; Вещь № 1 - это толстовка
&gt;&gt;&gt; Вещь № 2 - это футболка
&gt;&gt;&gt; Вещь № 3 - это кепка
&gt;&gt;&gt; Вещь № 4 - это шорты
&gt;&gt;&gt; Вещь № 5 - это джинсы</pre>
  <p id="FmqU">Теперь попробуем реализовать ровно то же самое, но с помощью <code>enumerate</code>:</p>
  <pre id="gd8c" data-lang="python">for index, element in enumerate(list_of_things_from_closet, start=1):
    print(f&quot;Вещь № {index} - это {element}&quot;)</pre>
  <p id="emN2">Вывод получим ровно такой же.</p>
  <p id="enLy">Выглядит проще и более читаемо, не так ли?</p>
  <p id="EAAs"></p>
  <p id="bifF">Перейдем к более реальной задаче (хотя что может быть реальней, чем перебор шкафа с вещами?!)</p>
  <h3 id="QGni">2. Перебор по файлу с логами</h3>
  <p id="kBwa">Представим, что у нас есть файл с логами сервера, и нам нужно найти все строки, где упоминается ошибка (ключевое слово &quot;ERROR&quot;). При этом мы хотим знать номер строки, чтобы можно было быстро найти её в файле.</p>
  <h4 id="48KX">Создадим файл с логами</h4>
  <p id="nAHH">Для начала создадим текстовый файл <code>server_logs.txt</code> с примером логов:</p>
  <pre id="D3K4">2025-01-25 18:00:00 INFO: Server started
2025-01-25 18:05:23 ERROR: Disk space low
2025-01-25 18:10:45 WARNING: High CPU usage
2025-01-25 18:15:10 ERROR: Failed to connect to database
2025-01-25 18:20:30 INFO: Backup completed successfully
2025-01-25 18:25:00 ERROR: User authentication failed</pre>
  <h4 id="LoxY">Откроем файл и обработаем его с помощью enumerate</h4>
  <p id="22Xm">Теперь напишем небольшую функцию, которая откроет этот файл, прочитает его построчно и с помощью <code>enumerate</code> проверит, есть ли в строке ключевое слово. Если есть, выведем номер строки и само событие.</p>
  <pre id="cBrx" data-lang="python">def get_logs_by_level(level: str, filename: str = &#x27;server_logs.txt&#x27;) -&gt; None:  
    # Открываем файл с логами  
    with open(filename, &#x27;r&#x27;, encoding=&#x27;utf-8&#x27;) as file:  
        # Итерируемся по строкам с помощью enumerate  
        for line_number, line in enumerate(file, start=1):  
            # Проверяем, есть ли в строке ключевое слово, соответствующее уровню лога &quot;level&quot; (регистр не учитываем)  
            if level in line.upper():  
                # Выводим номер строки и само событие  
                print(f&quot;Строка {line_number}: {line.strip()}&quot;)  


# Попробуем напечатать только логи уровня Error:  
get_logs_by_level(level=&quot;ERROR&quot;)  

# Результат:  
# Строка 2: 2025-01-25 18:05:23 ERROR: Disk space low  
# Строка 4: 2025-01-25 18:15:10 ERROR: Failed to connect to database  
# Строка 6: 2025-01-25 18:25:00 ERROR: User authentication failed  


# А теперь напечатаем строки с ворнингами:  
get_logs_by_level(level=&quot;WARNING&quot;)  
  
# Результат:  
# Строка 3: 2025-01-25 18:10:45 WARNING: High CPU usage</pre>
  <p id="583t">Класс, все работает ровно так, как нам и хотелось.</p>
  <h2 id="IBv6">Заключение</h2>
  <p id="JueA">Функция <code>enumerate</code> — это простой, но мощный инструмент, который помогает сделать код чище и читаемее. Она избавляет от необходимости вручную управлять индексами и делает итерацию по элементам более удобной. Кроме того, использование <code>enumerate</code> делает код более безопасным, так как исключает риск ошибок, связанных с некорректным обновлением индексов вручную.</p>
  <p id="7PoJ"></p>
  <hr />
  <p id="f9mE">Мой telegram-канал, присоединяйтесь :) </p>
  <p id="sgX0">⬇⬇⬇ </p>
  <p id="NtLB"><a href="https://t.me/python3_with_love" target="_blank">https://t.me/python3_with_love</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/hash_bcrypt_python</guid><link>https://teletype.in/@python3_with_love/hash_bcrypt_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/hash_bcrypt_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>Храним пароли безопасно. Интерактивное руководство по bcrypt в Python</title><pubDate>Sun, 19 Jan 2025 11:21:42 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/d6/ea/d6ea6bf9-e4a1-4f6b-b3de-ae2c9fe785f6.png"></media:content><description><![CDATA[<img src="https://img4.teletype.in/files/fc/62/fc627b9f-94a9-4285-8c9b-8447583c1f40.png"></img>Тема болезненная, обширная и важная и касается буквально каждого из нас, хотелось бы нам этого или нет.]]></description><content:encoded><![CDATA[
  <h2 id="P25h">Краткое предисловие</h2>
  <p id="aZAm">Тема болезненная, обширная и важная и касается буквально каждого из нас, хотелось бы нам этого или нет.</p>
  <p id="q4fk">В современном мире трудно представить человека, который не имеет, например, электронной почты и не зарегистрирован ни в каком сервисе. Как известно, при регистрации мы обычно указываем логин и пароль и на этом наша сторона ответственности заканчивается (а, нет, еще в нашей ответственности создать себе надежный пароль из примерно 16 символов, который содержит и строчные и заглавные буквы, и спецсимволы и желательно вообще нечитабельный, а еще - уникальный для каждого сервиса :)). Способ хранения паролей же лежит на стороне разработчика сервиса и обычно мы об этом никогда не узнаем, так как исходные коды обычно закрыты.</p>
  <h2 id="9dLf">Чем же плохо хранение паролей в открытом виде?</h2>
  <p id="X4F1"><br />Это однозначно небезопасно и может привести к серьезным последствиям. </p>
  <p id="JhSt">Как минимум, если злоумышленник вдруг получит доступ к базе данных сервиса, он получит сразу и пары логин-пароль всех пользователей и сможет делать многие действия от имени взломанных бедолаг. </p>
  <p id="4I04">Кроме того, как мы обычно поступаем? Мы придумываем 1 пароль для многих сервисов для удобства. А если злоумышленник уже получил логин-пароль от одного сервиса, то можно считать, что он уже получил ключики от всех дверей. Осталось их только перебрать :).</p>
  <h2 id="rHgn">Погружаемся в тему</h2>
  <p id="thuE"><br />Итак, давайте в рамках статьи представим, что у нас есть вымышленный сервис доставки <code>dostavallo</code>, который хранит пароли в открытом виде, примерно так:</p>
  <p id="ZVYV">------------------------------------------ <br />| email                      | password                |<br />| ------------------ |-------------------- |<br />| user1@gmail.com | my_best_password |<br />| user2@yandex.ru | secured_password |<br />------------------------------------------<br />Поможем ему обезопаситься и не потерять пользователей!</p>
  <h3 id="mIFq">База</h3>
  <p id="iEMy">Без теории никак, поэтому немного ознакомимся с основными терминами.</p>
  <p id="jKxP"><strong>Хэш-функция</strong></p>
  <p id="tQhC">Криптографическая хэш-функция, упрощенно говоря - это математическая функция, превращающая последовательность входных данных в строку фиксированной длины, состоящей из букв и цифр.</p>
  <p id="CK0D">Главная особенность полученных хэшей в том, что они односторонние, то есть расшифровать их и получить исходный пароль уже не получится.</p>
  <p id="u7Qz"><strong>Соль</strong></p>
  <p id="KOcA">Часто в контексте безопасного хранения паролей можно услышать фразу &quot;Надо посолить пароль&quot; :) Что же это значит?</p>
  <p id="DdRa">Соль - это случайная строка данных, которая хэшируется вместе с паролем, чтобы результат хэширования был всегда уникальным. Позволяет скрыть факт использования одинаковых паролей при использовании для них разной соли.</p>
  <p id="FMHD">Соль в нашем случае нужна для защиты от атак с использованием радужных таблиц.</p>
  <figure id="cs6Y" class="m_column">
    <img src="https://img4.teletype.in/files/fc/62/fc627b9f-94a9-4285-8c9b-8447583c1f40.png" width="1256" />
  </figure>
  <p id="EeLk"><strong>Радужные таблицы</strong></p>
  <p id="tGSq">Радужные таблицы - это специальный вариант таблиц поиска для обращения криптографических хеш-функций, использующий механизм разумного компромисса между временем поиска по таблице и занимаемой памятью.</p>
  <p id="z5Tm">Проще говоря, это таблицы, которые позволяют быстро найти соответствие между хэшем и исходным паролем. Если пароли защищены солью, радужные таблицы становятся бесполезными.</p>
  <p id="CqcO"><strong>Почему не шифрование?</strong></p>
  <p id="cQhD">К слову, почему хэширование, а не шифрование? Потому что это односторонний процесс и восстановить исходный пароль уже вряд ли получится.<br />Шифрование подходит для данных, которые нужно вернуть в исходный вид, например, для передачи сообщений. В случае с паролями такой необходимости нет, поэтому хэширование предпочтительнее.</p>
  <h2 id="oRDF">За дело берется bcrypt</h2>
  <p id="zyKa"><code>bcrypt</code> - адаптивная криптографическая хеш-функция формирования ключа, используемая для защищенного хранения паролей.</p>
  <p id="M4SG">В Python уже есть готовая библиотека, которую мы и используем.</p>
  <h3 id="ogwC">Реализация</h3>
  <p id="7trR">Давайте напишем небольшой скрипт, который будет хэшировать пароли.</p>
  <p id="jZQo">Для начала устанавливаем библиотеку к себе в проект:</p>
  <pre id="I6el" data-lang="python">pip install bcrypt</pre>
  <p id="kBvM">Попробуем посолить и хэшировать пароль:</p>
  <pre id="kVP2" data-lang="python"># Наш исходный пароль: 123
password = &quot;123&quot;
 
# Генерируем соль, хэшируем пароль с солью и предоставляем его
# в виде строки из байткода с помощью функции decode()
hashed_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
 
# Теперь наш пароль выглядит примерно так:
print(hashed_password)
&gt;&gt;&gt; &#x27;$2b$12$G3gSjjkBe1Bq3zwOGbmIXOUQ9C48kYcB6lJndABNOiGxJdip1.EES&#x27;</pre>
  <p id="0GWf">Отлично! Теперь мы представили наш пароль в виде, который нельзя расшифровать, но как же теперь проверить, что наш исходный пароль соответствует хэшированному?</p>
  <p id="DOki">Все довольно просто, надо лишь использовать функцию <code>checkpw</code>, которая возвращает результат типа <code>bool</code>:</p>
  <pre id="6xSw" data-lang="python">password = &quot;123&quot;
hashed_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
 
# Проверяем, что указанный нами пароль соответствует хэшу
print(bcrypt.checkpw(password.encode(), hashed_password.encode()))
&gt;&gt;&gt; True
 
# Попробуем указать неправильный пароль и получим False
wrong_password = &quot;1234&quot;
print(bcrypt.checkpw(wrong_password.encode(), hashed_password.encode()))
&gt;&gt;&gt; False</pre>
  <p id="2QwE">Теперь мы убедились, что можно хранить пароль в хэшированном виде, который абсолютно не читаем и устойчив к атакам. Даже если он утечет, с ним мало что можно сделать. При этом, всегда можно проверить, корректен ли введенный пароль хэшированному, чтобы принять решение, пускать ли пользователя в систему.</p>
  <h2 id="TvbF">Набираем обороты и помогаем сервису dostavallo с безопасностью</h2>
  <p id="7jJK">Давайте теперь рассмотрим пример посложнее, реализуем небольшое приложение командной строки, которое будет реализовывать функционал регистрации и входа в систему, а пары логин-пароль мы будем просто хранить в json-файлике.</p>
  <blockquote id="box9">Обратите внимание, что в реальных приложениях пароли и другие данные пользователей хранятся в защищенных базах данных, а не в файлах. :)</blockquote>
  <p id="RHG6">Пример будет рассмотрен одним блоком кода с комментариями / документацией. Погнали!</p>
  <h3 id="wTDk">Реализация программы</h3>
  <pre id="cS30" data-lang="python">import bcrypt  
import json
 

# Путь до файла, в котором будут храниться данные о пользователях  
USERS_FILEPATH = &quot;users.json&quot;

  
def hash_password(password: str) -&gt; str:  
    &quot;&quot;&quot;Функция для хэширования пароля&quot;&quot;&quot;  
    return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()  
  
  
def is_password_correct(password: str, hashed_password: str) -&gt; bool:  
    &quot;&quot;&quot;Функция для проверки пароля. Если ок - возвращает True&quot;&quot;&quot;  
    return bcrypt.checkpw(password.encode(), hashed_password.encode())  
  
  
def load_users(file_path=USERS_FILEPATH) -&gt; dict:  
    &quot;&quot;&quot;Функция для загрузки пользователей из файла&quot;&quot;&quot;  
    try:  
        with open(file_path, &quot;r&quot;) as file:  
            return json.load(file)  
    except FileNotFoundError:  
        return {}  
  
  
def save_user(user: dict, file_path=USERS_FILEPATH) -&gt; None:  
    &quot;&quot;&quot;Функция для сохранения регистрационных данных пользователя (login-password) в файл&quot;&quot;&quot;  
    with open(file_path, &quot;w&quot;) as file:  
        json.dump(user, file, indent=4)  
  
  
def register_user(users: dict) -&gt; dict:  
    &quot;&quot;&quot;
    Функция регистрации пользователя.
    Внутри происходит хэширование пароля и сохранение данных пользователя в файл.
    &quot;&quot;&quot;
    username = input(&quot;Введите имя пользователя: &quot;).strip()  
    if username in users:  
        print(f&quot;Пользователь {username} уже существует.&quot;)  
        return users  
  
    password = input(&quot;Введите пароль: &quot;).strip()  
    hashed_password = hash_password(password)  
    users[username] = hashed_password  
    save_user(users)  
    print(f&quot;Пользователь {username} успешно зарегистрирован!&quot;)  
    return users  
  
  
def login_user(users: dict) -&gt; None:  
    &quot;&quot;&quot;  
    Функция для логина пользователя в систему.  
    При удачном входе, выполнение программы завершается.
    &quot;&quot;&quot;
    username = input(&quot;Введите имя пользователя: &quot;).strip()  
    if username not in users:  
        print(f&quot;Пользователь {username} не найден.&quot;)  
        return  
  
    while True:  
        password = input(&quot;Введите пароль (или нажмите &#x27;q&#x27; для возврата в главное меню): &quot;).strip()  
        if password.lower() == &#x27;q&#x27;:  
            print(&quot;Возврат в главное меню.&quot;)  
            return  
  
        if is_password_correct(password=password, hashed_password=users[username]):  
            print(f&quot;\nВведен Правильный пароль, вход выполнен. \nДобро пожаловать, {username}!&quot;)  
            exit()  
        else:  
            print(&quot;Неверный пароль! Попробуйте еще раз.&quot;)  
  
  
# Основной цикл программы  
def main():  
    users = load_users()  
    print(&quot;Добро пожаловать в сервис доставки &#x27;dostavallo&#x27;!&quot;)  
    while True:  
        print(&quot;\nВыберите действие:&quot;)  
        print(&quot;1. Зарегистрироваться&quot;)  
        print(&quot;2. Войти в систему&quot;)  
        print(&quot;3. Выход&quot;)  
  
        choice = input(&quot;Ваш выбор: &quot;).strip()  
  
        match choice:  
            case &quot;1&quot;:  
                users = register_user(users)  
            case &quot;2&quot;:  
                login_user(users)  
            case &quot;3&quot;:  
                print(&quot;Выход из программы. До свидания!&quot;)  
                break  
            case _:  
                print(&quot;Неверный выбор. Попробуйте еще раз.&quot;)  
  
  
if __name__ == &quot;__main__&quot;:  
    main()</pre>
  <h3 id="9Emq">Запускаем</h3>
  <p id="VURV">Давайте залогиним моего кота в систему, чтобы он мог заказать себе вкусных рыбов.</p>
  <p id="rzIj">Запустим программу:</p>
  <pre id="0gGj" data-lang="bash">python3 bcrypt_test.py</pre>
  <p id="Tm2J">В зависимости от системы, здесь может быть просто <code>python bcrypt_test.py</code>. Также программу можно запустить через IDE.</p>
  <p id="p2AS">После запуска программа дружелюбно приветствует нас:</p>
  <pre id="v9AS">Добро пожаловать в сервис доставки &#x27;dostavallo&#x27;!

Выберите действие:
1. Зарегистрироваться
2. Войти в систему
3. Выход
Ваш выбор: </pre>
  <p id="MJoE">Выберем регистрацию, для этого достаточно ввести цифру 1.<br />Вводим имя пользователя и пароль:</p>
  <pre id="mqgj">Ваш выбор: 1    
Введите имя пользователя: Bublik
Введите пароль: Fi$h</pre>
  <p id="TeeV">Видим сообщение об успешной регистрации и переход в главное меню:</p>
  <pre id="1rvq">Пользователь Bublik успешно зарегистрирован!

Выберите действие:
1. Зарегистрироваться
2. Войти в систему
3. Выход
Ваш выбор: </pre>
  <p id="jhut">Выберем вход, для этого введем цифру 2.<br />Далее вводим логин и пароль, которые мы только что создали:</p>
  <pre id="zOAU">Ваш выбор: 2
Введите имя пользователя: Bublik
Введите пароль (или нажмите &#x27;q&#x27; для возврата в главное меню): Fi$h

Введен Правильный пароль, вход выполнен. 
Добро пожаловать, Bublik!</pre>
  <p id="QIIm">Удача буквально преследует нас, все получилось!</p>
  <h3 id="Ad0u">А что с паролями для dostavallo?</h3>
  <p id="eimu">Давайте откроем файлик <code>users.json</code> и посмотрим, как хранятся данные наших пользователей. Внутри мы увидим примерно такое:</p>
  <pre id="0oUW" data-lang="python">{  
    &quot;Bublik&quot;: &quot;$2b$12$VgDqlE5Xcwm7ID20Aw9isuA.qQp1o.hnMN1./8xxrqS1qXdRpuCW2&quot;  
}</pre>
  <p id="DnsU">Теперь мы можем быть спокойны. Никто просто так не получит пароль нашего пользователя, даже при условии, что у него был задан довольно простой пароль.</p>
  <p id="5YQz">Злоумышленники не смогут поднять историю заказов и пошантажировать кота тем, что недавно он заказывал руководство &quot;Как заставить хозяев играть с тобой по ночам&quot;.</p>
  <h3 id="aYLt">Послесловие</h3>
  <p id="blqo">Теперь мы знаем, что соль бывает не только поваренной, а радужные таблицы - это вовсе не про закрашенные ячейки в MS Excel :)</p>
  <p id="Ogve">Познакомились поближе с хэш-функциями и спасли сервис доставки от беды, а кота - от шантажа.</p>
  <p id="tIE4">Кстати, если хотите проверить, не утекал ли ваш пароль куда-нибудь, можете сделать это с помощью сайта https://haveibeenpwned.com. Просто вводите туда свою почту и получите ответ :). </p>
  <p id="FtL5">Если увидели там что-то, поступаем просто - меняем пароль для указанного сервиса.</p>
  <p id="IsJk">Я вот себя нашел. По информации сайта, мои данные подвергались утечке 3 раза.</p>
  <p id="gfgQ"><strong>Подробнее:</strong></p>
  <ul id="4oqR">
    <li id="Vsxd">https://www.securitylab.ru/blog/personal/xiaomite-journal/353801.php</li>
    <li id="csuF">https://en.wikipedia.org/wiki/Have_I_Been_Pwned%3F</li>
  </ul>
  <p id="LGn3">Берегите себя во всех смыслах.</p>
  <hr />
  <p id="iaz2">Мой telegram-канал, присоединяйтесь :)<br />⬇⬇⬇<br /><a href="https://t.me/python3_with_love" target="_blank">https://t.me/python3_with_love</a><br /></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/dict_get</guid><link>https://teletype.in/@python3_with_love/dict_get?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/dict_get?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>Как безопасно работать со словарями в Python и избежать KeyError</title><pubDate>Sat, 18 Jan 2025 16:40:13 GMT</pubDate><description><![CDATA[Словарь (или dict) в Python, одна из базовых и очень часто используемых структур, с которой мы обычно взаимодействуем также часто, как и с любимой кружечкой, из которой пьем кофе по утрам. :)]]></description><content:encoded><![CDATA[
  <p id="S53A">Словарь (или dict) в Python, одна из базовых и очень часто используемых структур, с которой мы обычно взаимодействуем также часто, как и с любимой кружечкой, из которой пьем кофе по утрам. :)</p>
  <p id="1Da0">Мы используем их повсеместно: для работы с данными из API, хранения конфигураций и многого другого. Однако работа с отсутствующими ключами в словаре неожиданно может привести к исключению <code>KeyError</code>. В этой статье разберём, как безопасно извлекать значения из словаря, чтобы избежать подобных ошибок.</p>
  <p id="zlhc">Обычно мы получаем значение по ключу через квадратные скобки, например так:</p>
  <pre id="PhAU" data-lang="python">my_dict = {&quot;key_1&quot;: &quot;value_1&quot;, &quot;key_2&quot;: &quot;value_2&quot;}
print(my_dict[&quot;key_1&quot;])

&gt;&gt;&gt; value_1</pre>
  <p id="orTZ">Вопросов к этому способу нет, все работает как часы и мы всегда получаем ровно то что нам нужно.</p>
  <p id="eUE7">Но что, если мы работаем с словарями, полученными из внешних источников, например, http запросов, и какое либо поле является опциональным, а нам в коде надо в зависимости от этого прописать какое-то условие? Мы можем поступить ровно также, используя квадратные скобки для сравнения значений:</p>
  <pre id="UZib" data-lang="python">if new_dict[&quot;key&quot;] == &quot;value&quot;:
    print(&quot;ok!&quot;)
else:
    print(&quot;no!&quot;)</pre>
  <p id="QzEF">И это будет работать ровно до того момента, пока эта пара ключ-значение есть внутри словаря. Если же ключ не найдется, мы получим исключение <code>KeyError</code>:</p>
  <pre id="bF1z" data-lang="python">new_dict = {&quot;another_key&quot;: &quot;another_value&quot;}
 
if new_dict[&quot;key&quot;] == &quot;value&quot;:
    print(&quot;ok!&quot;)
else:
    print(&quot;no!&quot;)

&gt;&gt;&gt; KeyError: &#x27;key&#x27;</pre>
  <p id="4M85">Чтобы этого избежать, можно использовать встроенный метод словаря <code>get()</code>:</p>
  <pre id="LPNo" data-lang="python">new_dict = {&quot;another_key&quot;: &quot;another_value&quot;}
 
# Проверяем ключ безопасно с помощью get()
if new_dict.get(&quot;key&quot;) == &quot;value&quot;:
    print(&quot;ok!&quot;)
else:
    print(&quot;no!&quot;)
 
&gt;&gt;&gt; no!</pre>
  <p id="2bPY">Также можно просто проверить присутствие ключа в словаре, и в зависимости от этого уже выполнять какие-либо действия:</p>
  <pre id="ra1z" data-lang="python">new_dict = {&quot;another_key&quot;: &quot;another_value&quot;}

if new_dict.get(&quot;key&quot;):
    ...</pre>
  <p id="yO3H">Здесь мы просто проверяем объект на наличие. <br /></p>
  <p id="Mga0">По умолчанию для не найденных ключей метод <code>get()</code> подставляет значение &#x60;<code>None</code>.<br />Вместо <code>None</code> можно подставить любое необходимое значение:</p>
  <pre id="xsgH" data-lang="python">new_dict = {&quot;another_key&quot;: &quot;another_value&quot;} 
 
# Используем значение по умолчанию для отсутствующего ключа
print(new_dict.get(&quot;key&quot;, &quot;default_value&quot;))
 
&gt;&gt;&gt; default_value</pre>
  <p id="Agz2"></p>
  <p id="iggo">Методы, такие как <code>get()</code>, делают работу со словарями в Python более безопасной. Это особенно важно при работе с данными из внешних источников, где структура словаря может быть непредсказуемой. Используя эти подходы, вы не только избежите ошибок, но и сделаете ваш код более читаемым и устойчивым к неожиданным данным.</p>
  <p id="Kcwj"></p>
  <hr />
  <p id="tb1J">Мой telegram-канал, присоединяйтесь :)</p>
  <p id="Ruby">⬇⬇⬇</p>
  <p id="vKUV"><a href="https://t.me/python3_with_love" target="_blank">https://t.me/python3_with_love</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/mro_python</guid><link>https://teletype.in/@python3_with_love/mro_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/mro_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>MRO или Method Resolution Order в Python</title><pubDate>Sun, 12 Jan 2025 19:12:38 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/56/a1/56a114da-7e1a-4f3f-8e71-dc80934fefaf.png"></media:content><description><![CDATA[<img src="https://img4.teletype.in/files/bb/37/bb37bc22-7ae6-4b65-a42f-90837622736a.png"></img>MRO - Это порядок разрешения методов и о нем обычно говорят, когда речь заходит о множественном наследовании (кстати, если с ним не знакомы, настоятельно рекомендую немного покопаться в теме).]]></description><content:encoded><![CDATA[
  <h2 id="oS1g">Что такое MRO</h2>
  <p id="ATCy">MRO - Это порядок разрешения методов и о нем обычно говорят, когда речь заходит о множественном наследовании (кстати, если с ним не знакомы, настоятельно рекомендую немного покопаться в теме).</p>
  <p id="2sZF">Он определяет последовательность, по которой Python ищет методы и атрибуты в классе и его родителях. Чтобы чуть глубже понять, предлагаю рассмотреть принцип его работы на конкретном примере.</p>
  <h2 id="YHI0">Разбираемся с MRO на примере</h2>
  <p id="MQ8d">Допустим, у нас есть некий класс D, который наследуется от классов B и C, которые наследуются от класса A, который ... :) пожалуй, остановимся на этом.</p>
  <figure id="eLwU" class="m_column">
    <img src="https://img4.teletype.in/files/bb/37/bb37bc22-7ae6-4b65-a42f-90837622736a.png" width="1184" />
  </figure>
  <p id="ywNk">У этих классов определены методы <code>who_am_i</code>, которые просто печатают имя класса, которому они принадлежат. Классу D мы не будем создавать этот метод, а сделаем из него просто класс-заглушку:</p>
  <pre id="2Pwp" data-lang="python">class A:
    def who_am_i(self):
        print(&quot;A&quot;) 


class B(A): 
    def who_am_i(self):
        print(&quot;B&quot;) 


class C(A):
    def who_am_i(self):
        print(&quot;C&quot;) 


class D(B, C):
    ...
</pre>
  <p id="4qwI">Теперь создадим экземпляр класса <code>D</code> и попробуем вызвать у него метод <code>who_am_i</code>:</p>
  <pre id="rzXx" data-lang="python">d = D()
d.who_am_i()</pre>
  <p id="Q83R">Что произойдет? А вот что. В выводе мы увидим:</p>
  <pre id="4Iu1" data-lang="python">&gt;&gt;&gt; B</pre>
  <p id="NmtR">Почему именно <code>B</code>, нам может рассказать MRO. Для удобства можно просто вызвать метод <code>mro()</code> или атрибут <code>__mro__</code>:</p>
  <pre id="ptBT" data-lang="python">print(D.mro())  # или: print(D.__mro__)
&gt;&gt;&gt; (&lt;class &#x27;__main__.D&#x27;&gt;, &lt;class &#x27;__main__.B&#x27;&gt;, &lt;class &#x27;__main__.C&#x27;&gt;, &lt;class &#x27;__main__.A&#x27;&gt;, &lt;class &#x27;object&#x27;&gt;)</pre>
  <p id="rlF6">Из вывода консоли можно увидеть порядок обращений. Сначала метод ищется в классе <code>D</code>, но его там нет, поэтому Python смотрит в следующем классе - <code>B</code>. Метод <code>who_am_i</code> есть у класса <code>B</code>, поэтому именно он и вызывается. Соответственно, при печати мы и видим <code>B</code>.</p>
  <h2 id="QERg">Почему MRO работает именно так?</h2>
  <p id="OjlZ">Во всем виновата линеаризация и конкретно алгоритм <code>C3 linearization</code>. Разберем кратко, что это такое.</p>
  <p id="WYI7"><strong>Линеаризация</strong> – это упорядочение классов (включая сам класс и его родителей) в список, отсортированный в порядке их &quot;Удаленности&quot;. Этот список Python использует для поиска методов и атрибутов. А алгоритм C3 определяет три главные особенности такого порядка:</p>
  <ul id="lOXH">
    <li id="B4AE">устойчивый и расширяющийся (по старшинству)</li>
    <li id="XBRB">сохранение локального порядка старшинства</li>
    <li id="72o7">монотонность</li>
  </ul>
  <p id="9GcE">Почитать подробнее и ознакомиться с примерами <a href="https://ru.wikipedia.org/wiki/C3-%D0%BB%D0%B8%D0%BD%D0%B5%D0%B0%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F" target="_blank">можно на Вики</a></p>
  <p id="tzlx">Таким образом, MRO определяет, как Python ищет методы и атрибуты в классах при множественном наследовании, делая этот процесс предсказуемым и логичным.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/defaultdict_in_python</guid><link>https://teletype.in/@python3_with_love/defaultdict_in_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/defaultdict_in_python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>defaultdict в Python</title><pubDate>Sun, 03 Nov 2024 12:47:52 GMT</pubDate><description><![CDATA[Что еще за defaultdict и зачем нам еще один dict? Давайте об этом и поговорим в статье.]]></description><content:encoded><![CDATA[
  <p id="CdRN">Что еще за <code>defaultdict</code> и зачем нам еще один <code>dict</code>? Давайте об этом и поговорим в статье.</p>
  <h2 id="0YMU">Что такое defaultdict</h2>
  <p id="nrg6"><br />Это подкласс встроенного класса <code>dict</code>, который вызывает фабричную функцию, позволяющую задать дефолтное значение для новых / несуществующих ключей. Во всем остальном он схож с уже знакомым нам <code>dict</code>.<br />Если упростить и вывести термин, опираясь на название, то получается, что это просто словарь с значениями по умолчанию.<br /></p>
  <h3 id="VIPI">Синтаксис</h3>
  <pre id="0gJm" data-lang="python">from collections import defaultdict

defaultdict(default_factory=None, /, [...]) --&gt; dict with default factory</pre>
  <p id="005z"><br /><strong>Аргументы<br /></strong>Первый аргумент предоставляет начальное значение для атрибута default_factory, которое по умолчанию равно <code>None</code>. Все остальные аргументы обрабатываются так же, как если бы они были переданы конструктору <code>dict</code>.</p>
  <p id="NIpR"><strong>Пара слов про метод <code>__missing__()</code><br /></strong>Если аргумент <code>default_factory</code> != <code>None</code>, то этот метод и вызывается для предоставления значений по умолчанию, когда запрошенный ключ не найден.</p>
  <p id="qXAk">Чтобы в полной мере понять происходящее, давайте рассмотрим несколько примеров.</p>
  <h2 id="8i1L">Примеры. Какие проблемы решает defaultdict</h2>
  <h3 id="2Mk5">Получаем нужное нам значение по умолчанию</h3>
  <p id="pZfa">Давайте создадим 2 словаря: 1 - <code>dict</code>, другой - <code>defaultdict</code> и попробуем получить значения для существующих ключей:<br /></p>
  <pre id="bU9C" data-lang="python">from collections import defaultdict  
  
dict_1 = {&quot;first&quot;: 1, &quot;second&quot;: 2}  
dict_2 = defaultdict(int, first=1, second=2)

print(dict_1[&quot;first&quot;])  # 1
print(dict_2[&quot;first&quot;])  # 1</pre>
  <p id="Q8eW">В обоих случаях получим 1.</p>
  <p id="y9k1">А что, если запросить значение для несуществующего ключа:</p>
  <pre id="vrs0" data-lang="python">print(dict_1[&quot;missing_key&quot;])  # KeyError: &#x27;missing_key&#x27;
print(dict_2[&quot;missing_key&quot;])  # 0</pre>
  <p id="Jhsc">В первом случае мы получили исключение <code>KeyError</code>, а вот уже с <code>defaultdict</code> мы получили значение по умолчанию: <code>0</code>.</p>
  <p id="aj5O">Можно ли обойти эту ситуацию с помощью dict? Да, можно. Например, так:</p>
  <pre id="0C4u" data-lang="python">print(dict_1.get(&quot;missing_key&quot;, 0))  # 0</pre>
  <p id="PRM2">Здесь мы явно задали значение по умолчанию в виде 0, если не получится найти ключ.</p>
  <p id="Fz3q">Идем дальше.<br /></p>
  <h3 id="U8Lu">Считаем количество слов в списке</h3>
  <p id="5lrf">Допустим, у нас есть список из слов <code>list_1</code> и надо посчитать, сколько раз каждое слово встречается в списке, затем вывести все это в формате словаря.<br />Как сделать это удобно? Конечно с <code>defaultdict</code>!<br /></p>
  <pre id="uxLB" data-lang="python">
from collections import defaultdict  

list_1 = [&quot;building&quot;, &quot;thee&quot;, &quot;sun&quot;, &quot;python&quot;, &quot;sun&quot;, &quot;python&quot;, &quot;python&quot;, &quot;thee&quot;, &quot;python&quot;] 

# 1.
result = defaultdict(int)   

# 2.
for word in list_1:
    result[word] += 1

# 3.
print(dict(result))  # {&#x27;building&#x27;: 1, &#x27;thee&#x27;: 2, &#x27;sun&#x27;: 2, &#x27;python&#x27;: 4}</pre>
  <p id="1x90"><strong><br />Что здесь происходит?<br /></strong>1. Мы инициализируем <code>defaultdict</code> классом <code>int</code> для того, чтобы для каждого нового слова было задано значение по умолчанию = 0.<br />2. Проходимся по всем словам из списка <code>list_1.</code> <br />   - Если слово не встречалось ранее, в <code>result</code> создается новая пара ключ-значение, где ключ - слово, а значение - <code>0</code>, после чего оно сразу увеличивается на 1. <br />   - Если слово уже есть в словаре, то его значение просто увеличивается на 1.<br />3. Печатаем наш результат, предварительно преобразовав наш <code>defaultdict</code> в обычный <code>dict</code>.</p>
  <p id="dpzA">В результате получаем нужный нам ответ: <code>{&#x27;building&#x27;: 1, &#x27;thee&#x27;: 2, &#x27;sun&#x27;: 2, &#x27;python&#x27;: 4}</code>.</p>
  <h2 id="TYm6">Делаем выводы</h2>
  <p id="gEQZ"><br /><code>defaultdict</code> в Python — это удобный инструмент для создания словарей с заданным значением по умолчанию для новых ключей. Он упрощает код, позволяя избежать явных проверок на наличие ключа перед его использованием, что делает код более чистым и сокращает необходимость в дополнительных условиях.</p>
  <p id="T1HZ">Это особенно полезно при работе с данными, где нужно сгруппировать или подсчитать элементы. <code>defaultdict</code> поддерживает различные типы значений по умолчанию, включая списки, множества и даже пользовательские функции, что делает его гибким и мощным инструментом для многих задач.<br /></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/auto-enum</guid><link>https://teletype.in/@python3_with_love/auto-enum?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/auto-enum?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>Автоматическое присваивание значений в Enum</title><pubDate>Fri, 06 Sep 2024 20:40:01 GMT</pubDate><description><![CDATA[Представим ситуацию: вы любите все структурировать и храните много данных в енамах: Enum, StrEnum, IntEnum и так далее.]]></description><content:encoded><![CDATA[
  <p id="PF7U">Представим ситуацию: вы любите все структурировать и храните много данных в енамах: <code>Enum</code>, <code>StrEnum</code>, <code>IntEnum</code> и так далее.</p>
  <p id="zefe">В какой-то момент обязательно накопится большое количество структур, где символическое имя будет равно значению, как в примере ниже:</p>
  <pre id="p14d" data-lang="python">from enum import Enum  
  
  
class MyMood(Enum):  
    HAPPY = &quot;HAPPY&quot;  
    SAD = &quot;SAD&quot;</pre>
  <p id="zr26">Как красиво выйти из этой ситуации? Об этом мы и поговорим.</p>
  <h2 id="NipC">Функция auto()</h2>
  <hr />
  <p id="QjBi">В первую очередь, стоит обратить внимание на класс <code>auto</code> из <code>Enum</code>.</p>
  <p id="Lpsa">Он позволяет нам автоматически присваивать значения для каждого символического имени, мы можем просто указать его в виде значений:</p>
  <pre id="LSrL" data-lang="python">from enum import Enum, auto  
  
  
class MyMood(Enum):  
    HAPPY = auto()  
    SAD = auto()</pre>
  <h2 id="dqrv">Как это работает</h2>
  <hr />
  <p id="6fKb">Класс <code>auto</code> автоматически присваивает значение для символического имени в виде номера по порядку.<br />Например, для блока кода выше будут присвоены значения <code>HAPPY = 1</code>, <code>SAD = 2</code>:</p>
  <pre id="gg6w" data-lang="python">print(MyMood.HAPPY.value, MyMood.SAD.value)
&gt; 1 2</pre>
  <h2 id="vfkf">Когда это может пригодиться?</h2>
  <hr />
  <p id="66R0">Например, когда мы перебираем Enum-ы через <code>match-case</code> и значения нам не важны.</p>
  <p id="rsRi">А если важны?</p>
  <h2 id="9wxx">Что делать, если в value нужно хранить именно символическое имя?</h2>
  <hr />
  <p id="K5Zu">Представим ситуацию, когда нам очень нужно, чтобы <code>name</code> было равно <code>value</code>, и чтоб это <code>value</code> присваивалось автоматически.</p>
  <p id="tjEG">Здесь нам на помощь придет переопределение метода <code>_generate_next_value_</code>.</p>
  <h3 id="UWFR">Синтаксис</h3>
  <p id="j6td"><code>_generate_next_value_(name, start, count, last_values)</code><br />Этот метод и отвечает за увеличение значения символического имени на 1 в исходной реализации, но мы можем подстроить его под себя.</p>
  <p id="6MhP">Это можно сделать прямо внутри нужного нам Enum-а:</p>
  <pre id="1Srr" data-lang="python">class MyMood(Enum):  
    @staticmethod  
    def _generate_next_value_(name, start, count, last_values):  
        return name  
  
    HAPPY = auto()  
    SAD = auto()  </pre>
  <p id="VMVs">главное, переопределить метод перед членами Enum-а, иначе словим исключение &#x60;<code>TypeError</code>.</p>
  <p id="miyL">И тогда при выводе значений в терминал мы получим уже имена членов Enum, а не индексы:</p>
  <pre id="qrQP" data-lang="python">print(MyMood.HAPPY.value, MyMood.SAD.value)
&gt; HAPPY SAD</pre>
  <p id="OuQo">Если ситуация часто повторяется, то можно сделать одно общее решение для всех. Создадим класс <code>AutoName</code>, унаследованный от <code>Enum</code> и переопределим метод <code>_generate_next_value_</code>:  </p>
  <pre id="nhNo" data-lang="python">from enum import Enum, auto  


class AutoName(Enum):  
    @staticmethod  
    def _generate_next_value_(name, start, count, last_values):  
        return name  </pre>
  <p id="IN00">Нам остается отнаследоваться от этого класса и использовать <code>auto()</code> в качестве значений символических имен Enum-а:</p>
  <pre id="oHth" data-lang="python">class MyMood(AutoName):  
    HAPPY = auto()  
    SAD = auto()</pre>
  <p id="jb82">Убедимся, что наша шалость удалась:</p>
  <pre id="AZ8C" data-lang="python">print(MyMood.HAPPY.value, MyMood.SAD.value)
&gt; HAPPY SAD</pre>
  <p id="nX2Z">Мы получили то, что нам было нужно, избавились от дублирования и лишней копипасты, сделав код еще немного чище.<br /></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/Elegantnoe-podavlenie-isklyuchenij-s-contextlibs</guid><link>https://teletype.in/@python3_with_love/Elegantnoe-podavlenie-isklyuchenij-s-contextlibs?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/Elegantnoe-podavlenie-isklyuchenij-s-contextlibs?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>Элегантное подавление исключений с contextlib.suppress</title><pubDate>Wed, 04 Sep 2024 08:49:14 GMT</pubDate><description><![CDATA[Бывает так, что какой-либо метод при работе программы выдает исключение, но нам не надо его обрабатывать каким-нибудь хитрым образом, а достаточно лишь подавить исключение и идти дальше.]]></description><content:encoded><![CDATA[
  <p id="yzrj">Бывает так, что какой-либо метод при работе программы выдает исключение, но нам не надо его обрабатывать каким-нибудь хитрым образом, а достаточно лишь подавить исключение и идти дальше.</p>
  <p id="QQ22">Это можно сделать с помощью классического <code>try-except</code>, но есть и более элегантный способ. Когда это может пригодиться и как пользоваться? Об этом и поговорим в статье.</p>
  <h2 id="Зачем-подавлять-исключения?"><strong>Зачем подавлять исключения?</strong></h2>
  <hr />
  <p id="lksZ">Делать это без надобности строго не рекомендуется. Но бывают моменты, когда мы знаем, почему вызывается исключение и уверены, что оно не несет никаких рисков для нас. Поэтому мы временно можем его пропустить.</p>
  <p id="GS3d"><strong>Конкретный пример:</strong></p>
  <p id="PeGm">Допустим, вы пишете автотест, и после создания некоторой сущности и выполнения манипуляций с ней, вам надо эту сущность удалить.</p>
  <p id="mxcO">Все отрабатывает корректно, вот только запрос на удаление зависает в статусе <code>pending</code> и ответ не приходит, вызывается исключение по таймауту, но при этом сущность удаляется.</p>
  <p id="WY6T">Зная все это, мы можем временно подавить исключение до решения проблемы с запросом и пустить тест в работу.</p>
  <h2 id="Как-это-сделать?"><strong>Как это сделать?</strong></h2>
  <hr />
  <p id="5p9P">Рассмотрим 2 способа: канонический <code>try-except</code> и <code>contextlib.suppress</code></p>
  <h3 id="Классический-способ"><strong>Классический способ</strong></h3>
  <pre id="Q1Uu" data-lang="python">try:
    requests.delete(url=https://example.com/users/user1)
except requests.TimeoutException:
    pass
</pre>
  <h3 id="contextlib.suppress"><strong>contextlib.suppress</strong></h3>
  <p id="O66J"><strong>Синтаксис</strong></p>
  <p id="QuNj">contextlib.<strong>suppress</strong>(*<em>exceptions</em>)</p>
  <p id="I0UE"><strong>Пример</strong>:</p>
  <pre id="xjuL" data-lang="python">from contextlib import suppress

with suppress(requests.TimeoutException):
    requests.delete(url=https://example.com/users/user1)
</pre>
  <p id="dx1L"><code>suppress</code> возвращает контекстный менеджер, который подавляет переданные исключения, если они встречаются в теле выражения <code>with</code>.</p>
  <p id="GtKY">Данный контекстный менеджер предлагает нам удобный способ работы с исключениями. Но не стоит забывать, что <code>suppress</code> cледует использовать только в специфических случаях.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@python3_with_love/Nemnogo-pro-lyambda-funkcii-v-Python</guid><link>https://teletype.in/@python3_with_love/Nemnogo-pro-lyambda-funkcii-v-Python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love</link><comments>https://teletype.in/@python3_with_love/Nemnogo-pro-lyambda-funkcii-v-Python?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=python3_with_love#comments</comments><dc:creator>python3_with_love</dc:creator><title>Немного про лямбда-функции в Python</title><pubDate>Wed, 04 Sep 2024 08:46:36 GMT</pubDate><description><![CDATA[Lambda или анонимные функции - это по сути небольшие функции без имени, написанные в одну строку.]]></description><content:encoded><![CDATA[
  <h3 id="Что-это-такое?">Что это такое?</h3>
  <hr />
  <p id="0GHg">Lambda или анонимные функции - это по сути небольшие функции без имени, написанные в одну строку.</p>
  <p id="5scl">Для их объявления нам не нужно следовать классической схеме - указывать литерал <code>def</code> и имя будущей функции. Нам достаточно следовать такой конструкции:</p>
  <pre id="pdYA" data-lang="python">lambda arguments: expression</pre>
  <p id="AqQF">Это и есть весь синтаксис лямбда-функций.</p>
  <p id="8OKl">Главные моменты:</p>
  <ul id="9qnl">
    <li id="erpZ">Аргументов может быть несколько</li>
    <li id="vNGb">Выражение может быть только одно</li>
    <li id="odfO">Выражение должно быть написано в одну строку</li>
    <li id="Ru2p">Лямбда-функция всегда возвращает значение</li>
  </ul>
  <h3 id="Как-и-когда-использовать?">Как и когда использовать?</h3>
  <hr />
  <p id="TKhv">Чаще всего, лямбда-функции используют в нескольких случаях:</p>
  <ul id="N2zy">
    <li id="OTQi">Как аргумент в других функциях (часто в функциях высшего порядка)</li>
    <li id="bF9c">Для создания замыканий</li>
  </ul>
  <p id="pCxv">Рассмотрим чуть подробнее.</p>
  <h3 id="Лямбда-функция-как-аргумент-в-других-функциях">Лямбда-функция как аргумент в других функциях</h3>
  <p id="mztk">Очень удобно применять лямбда-функции при работе с <code>map()</code> и <code>filter()</code>.</p>
  <p id="0Kw8">Допустим, нам нужно преобразовать список. Здесь нам поможет функция <code>map()</code>, которая применит лямбда-функцию ко всем элементам списка:</p>
  <pre id="2eT8" data-lang="python">list_1 = [&quot;one&quot;, &quot;two&quot;, &quot;three&quot;]
new_list = list(map(lambda x: &quot;new_&quot; + x, list_1))

print(new_list)
&gt;&gt;&gt; [&#x27;new_one&#x27;, &#x27;new_two&#x27;, &#x27;new_three&#x27;]</pre>
  <p id="tmFX">Лямбда помогает нам задать нечто вроде правила, по которому будет обрабатываться объект. В нашем примере мы добавили префикс к каждому слову в списке.</p>
  <p id="BfZJ">Эту же операцию можно решить и с помощью <code>list comprehension</code> (списочных выражений):</p>
  <pre id="Ljtg" data-lang="python">new_list = [&quot;new_&quot; + x for x in list_1]</pre>
  <p id="CsLB">Теперь рассмотрим простой пример с фильтрацией:</p>
  <pre id="5aMe" data-lang="python">list_1 = [&quot;one&quot;, &quot;two&quot;, &quot;three&quot;]
new_list = list(filter(lambda x: len(x) &lt;= 3, list_1))

print(new_list)
&gt;&gt;&gt; [&#x27;one&#x27;, &#x27;two&#x27;]</pre>
  <p id="SBlN">Мы применили функцию <code>filter()</code> и получили новый список с элементами, у которых количество символов меньше или равно 3.</p>
  <p id="brGN">Эту операцию тоже можно решить с помощью <code>list comprehension</code>:</p>
  <pre id="UxRR" data-lang="python">new_list = [x for x in list_1 if len(x) &lt;= 3]</pre>
  <p id="z1Yn">Рассмотрим еще 1 применение при сортировке по ключу. Он будет интереснее, так как будем работать с объектами посложнее:</p>
  <pre id="BqBs" data-lang="python"># Создадаем класс с атрибутами экземпляра: имя, возраст и порода
class Cat:
    def __init__(self, name: str, age: int, breed: str) -&gt; None:
        self.name = name
        self.age = age
        self.breed = breed

    # Переопределяем магический метод __repr__, чтобы при печати экземпляров выводился сразу человекочитаемый текст
    def __repr__(self) -&gt; str:
        return f&quot;&lt;&lt;Cat {self.name}, {self.breed}, {self.age} years old&gt;&gt;&quot;

# Создаем экземпляры класса Cat
cat_1 = Cat(name=&quot;Mars&quot;, age=7, breed=&quot;British Shorthair&quot;)
cat_2 = Cat(name=&quot;Oleg&quot;, age=1, breed=&quot;Devon Rex&quot;)
cat_3 = Cat(name=&quot;Bublik&quot;, age=2, breed=&quot;Russian Blue&quot;)

# Создаем неотсортированный список с питомцами
list_of_cats = [cat_3, cat_1, cat_2]

# Сортируем список по имени, возрасту и породе
name_sorted = sorted(list_of_cats, key=lambda x: x.name)
age_sorted = sorted(list_of_cats, key=lambda x: x.age)
breed_sorted = sorted(list_of_cats, key=lambda x: x.breed)

print(name_sorted, age_sorted, breed_sorted, sep=&quot;\n&quot;)

[&lt;&lt;Cat Bublik, Russian Blue, 2 years old&gt;&gt;, &lt;&lt;Cat Mars, British Shorthair, 7 years old&gt;&gt;, &lt;&lt;Cat Oleg, Devon Rex, 1 years old&gt;&gt;]
[&lt;&lt;Cat Oleg, Devon Rex, 1 years old&gt;&gt;, &lt;&lt;Cat Bublik, Russian Blue, 2 years old&gt;&gt;, &lt;&lt;Cat Mars, British Shorthair, 7 years old&gt;&gt;]
[&lt;&lt;Cat Mars, British Shorthair, 7 years old&gt;&gt;, &lt;&lt;Cat Oleg, Devon Rex, 1 years old&gt;&gt;, &lt;&lt;Cat Bublik, Russian Blue, 2 years old&gt;&gt;]</pre>
  <p id="ykc4">Выглядит просто, читаемо и эффективно.</p>
  <p id="Rmqi">Идем дальше.</p>
  <h3 id="Лямбда-функции-и-замыкания">Лямбда-функции и замыкания</h3>
  <p id="WmXR">Для начала пара слов о замыканиях.</p>
  <p id="lVqd"><strong>Замыкание</strong> — это вложенная функция, которая имеет доступ к переменным внешней функции даже после закрытия этой самой внешней функции.</p>
  <p id="QJOk">С помощью замыканий можно сохранять значения и состояния между вызовами функций.</p>
  <p id="C6rP">Может звучать немного запутанно, но пока не будем вдаваться в детальное объяснение и рассмотрим пример. Более подробно обсудим замыкания в будущих статьях. Ну а после примера, в любом случае, станет понятнее.</p>
  <pre id="tl02" data-lang="python">def multiplier(x):
    return lambda y: x * y

multiply_by_2 = multiplier(x=2)
multiply_by_5 = multiplier(x=5)

print(multiply_by_2(y=2))
print(multiply_by_5(y=2))

&gt;&gt;&gt; 4
&gt;&gt;&gt; 10</pre>
  <p id="kwVa">Что происходит:</p>
  <ul id="nfz5">
    <li id="egh6">Мы объявили функцию <code>multiplier</code> с параметром x, внутри которой возвращается вложенная лямбда-функция, которая умножает <code>x</code> на <code>y</code>.</li>
    <li id="NeNG">Далее мы по сути делаем из переменных <code>multiply_by_2</code> и <code>multiply_by_5</code> функции-перемножители, которые запоминают значение переменной <code>x</code>, которое будет доступно нам всегда.</li>
    <li id="W6Vo">Затем вызываем уже внутреннюю лямбда-функцию, передавая перемножителю параметр <code>y</code>.</li>
    <li id="Hg55">В результате мы получаем произведение двух чисел - значений переменных <code>x</code> и <code>y</code>.</li>
  </ul>
  <p id="Mtp8">Таким образом, замыкания помогают на лету создавать функции, при этом частично изолировав логику выполнения (как мы видели на примере с конкретными перемножителями).</p>
  <p id="Krfz">В этой статье мы окунулись в сферу применения лямбда-функций и затронули пару таких тем как функции высшего порядка и замыкания.</p>
  <p id="FZ3k">Зачастую можно обойтись и без лямбда-функций, но в определенных моментах они могут добавить удобства и чистоты в код.</p>

]]></content:encoded></item></channel></rss>