counter код используется более одного раза
Код Хэмминга. Пример работы алгоритма
Прежде всего стоит сказать, что такое Код Хэмминга и для чего он, собственно, нужен. На Википедии даётся следующее определение:
Коды Хэмминга — наиболее известные и, вероятно, первые из самоконтролирующихся и самокорректирующихся кодов. Построены они применительно к двоичной системе счисления.
Другими словами, это алгоритм, который позволяет закодировать какое-либо информационное сообщение определённым образом и после передачи (например по сети) определить появилась ли какая-то ошибка в этом сообщении (к примеру из-за помех) и, при возможности, восстановить это сообщение. Сегодня, я опишу самый простой алгоритм Хемминга, который может исправлять лишь одну ошибку.
Также стоит отметить, что существуют более совершенные модификации данного алгоритма, которые позволяют обнаруживать (и если возможно исправлять) большее количество ошибок.
Сразу стоит сказать, что Код Хэмминга состоит из двух частей. Первая часть кодирует исходное сообщение, вставляя в него в определённых местах контрольные биты (вычисленные особым образом). Вторая часть получает входящее сообщение и заново вычисляет контрольные биты (по тому же алгоритму, что и первая часть). Если все вновь вычисленные контрольные биты совпадают с полученными, то сообщение получено без ошибок. В противном случае, выводится сообщение об ошибке и при возможности ошибка исправляется.
Как это работает.
Для того, чтобы понять работу данного алгоритма, рассмотрим пример.
Подготовка
Допустим, у нас есть сообщение «habr», которое необходимо передать без ошибок. Для этого сначала нужно наше сообщение закодировать при помощи Кода Хэмминга. Нам необходимо представить его в бинарном виде.
На этом этапе стоит определиться с, так называемой, длиной информационного слова, то есть длиной строки из нулей и единиц, которые мы будем кодировать. Допустим, у нас длина слова будет равна 16. Таким образом, нам необходимо разделить наше исходное сообщение («habr») на блоки по 16 бит, которые мы будем потом кодировать отдельно друг от друга. Так как один символ занимает в памяти 8 бит, то в одно кодируемое слово помещается ровно два ASCII символа. Итак, мы получили две бинарные строки по 16 бит:
и
После этого процесс кодирования распараллеливается, и две части сообщения («ha» и «br») кодируются независимо друг от друга. Рассмотрим, как это делается на примере первой части.
Прежде всего, необходимо вставить контрольные биты. Они вставляются в строго определённых местах — это позиции с номерами, равными степеням двойки. В нашем случае (при длине информационного слова в 16 бит) это будут позиции 1, 2, 4, 8, 16. Соответственно, у нас получилось 5 контрольных бит (выделены красным цветом):
Было:
Стало:
Таким образом, длина всего сообщения увеличилась на 5 бит. До вычисления самих контрольных бит, мы присвоили им значение «0».
Вычисление контрольных бит.
Теперь необходимо вычислить значение каждого контрольного бита. Значение каждого контрольного бита зависит от значений информационных бит (как неожиданно), но не от всех, а только от тех, которые этот контрольных бит контролирует. Для того, чтобы понять, за какие биты отвечает каждых контрольный бит необходимо понять очень простую закономерность: контрольный бит с номером N контролирует все последующие N бит через каждые N бит, начиная с позиции N. Не очень понятно, но по картинке, думаю, станет яснее:
Здесь знаком «X» обозначены те биты, которые контролирует контрольный бит, номер которого справа. То есть, к примеру, бит номер 12 контролируется битами с номерами 4 и 8. Ясно, что чтобы узнать какими битами контролируется бит с номером N надо просто разложить N по степеням двойки.
Но как же вычислить значение каждого контрольного бита? Делается это очень просто: берём каждый контрольный бит и смотрим сколько среди контролируемых им битов единиц, получаем некоторое целое число и, если оно чётное, то ставим ноль, в противном случае ставим единицу. Вот и всё! Можно конечно и наоборот, если число чётное, то ставим единицу, в противном случае, ставим 0. Главное, чтобы в «кодирующей» и «декодирующей» частях алгоритм был одинаков. (Мы будем применять первый вариант).
Высчитав контрольные биты для нашего информационного слова получаем следующее:
и для второй части:
Вот и всё! Первая часть алгоритма завершена.
Декодирование и исправление ошибок.
Теперь, допустим, мы получили закодированное первой частью алгоритма сообщение, но оно пришло к нас с ошибкой. К примеру мы получили такое (11-ый бит передался неправильно):
Вся вторая часть алгоритма заключается в том, что необходимо заново вычислить все контрольные биты (так же как и в первой части) и сравнить их с контрольными битами, которые мы получили. Так, посчитав контрольные биты с неправильным 11-ым битом мы получим такую картину:
Как мы видим, контрольные биты под номерами: 1, 2, 8 не совпадают с такими же контрольными битами, которые мы получили. Теперь просто сложив номера позиций неправильных контрольных бит (1 + 2 + 8 = 11) мы получаем позицию ошибочного бита. Теперь просто инвертировав его и отбросив контрольные биты, мы получим исходное сообщение в первозданном виде! Абсолютно аналогично поступаем со второй частью сообщения.
Заключение.
В данном примере, я взял длину информационного сообщения именно 16 бит, так как мне кажется, что она наиболее оптимальная для рассмотрения примера (не слишком длинная и не слишком короткая), но конечно же длину можно взять любую. Только стоит учитывать, что в данной простой версии алгоритма на одно информационное слово можно исправить только одну ошибку.
Примечание.
На написание этого топика меня подвигло то, что в поиске я не нашёл на Хабре статей на эту тему (чему я был крайне удивлён). Поэтому я решил отчасти исправить эту ситуацию и максимально подробно показать как этот алгоритм работает. Я намеренно не приводил ни одной формулы, дабы попытаться своими словами донести процесс работы алгоритма на примере.
Как работают одноразовые пароли
Вступление
Как показывает практика, существует определенное непонимание принципов работы одноразовых паролей (это те самые, которые используются в GMail, в спец. токенах платежных систем и так далее).
Прочитав эту небольшую статью, Вы разберетесь в принципе работы одноразовых паролей на основе хэшей, а заодно напишете на Python небольшую программу, которая умеет вычислять пароли для двухэтапной аутентификации Google.
Хэш-функция
Хэш-функция позволяет взять любые данные любой длины и построить по ним короткий «цифровой отпечаток пальца». Длина значения хэш-функции не зависит от длины исходного текста; например, в случае популярного алгоритма SHA-1 длина этого отпечатка составляет 160 бит.
Чтобы понять, почему значение всегда имеет одинаковую длину и не зависит от исходного текста, можно упрощенно представить хэш-функцию в виде кодового замка с колесиками. Вначале мы выставляем все колесики в «ноль», затем идем по тексту и для каждой буквы прокручиваем колесики в соответствии с некоторыми правилами. То число, которое окажется на замке в конце, и есть значение хэш-функции. Примерами таких функций являются MD5, SHA-1, ГОСТ_Р_34.11-94.
Не придумывайте свои хэш-функции, используйте стандартные реализации (например, в случае Python):
Идея хэш-функции в том, что она работает только в одном направлении: ее очень легко подсчитать для «Войны и мира», но практически невозможно по уже готовому значению хэш-функции найти документ, который даст такое же значение. Даже если изменить в документе всего одну букву, хэш изменится полностью:
В связи с этим возникает естественное желание использовать хэш-функцию для контроля целостности сообщений, которые Алиса посылает Бобу: Алиса подсчитывает для каждого своего сообщения значение SHA-1 и вкладывает его в конверт; Боб, самостоятельно подсчитав SHA-1 текста, может сравнить свой результат с Алисиным и удостовериться, что сообщение не было изменено где-то по дороге.
Однако мы забыли о Меллори, который находится где-то между Алисой и Бобом, перехватывает их переписку и вскрывает конверты! Он вполне может изменить сообщение, после чего подсчитать для него SHA-1 и приложить к письму; Боб сверит значения и ничего не заметит.
Проверка подлинности
Подумав, Алиса и Боб при встрече договариваются, что при подсчете SHA-1 они будут временно дописывать к тексту секретное слово, например, «Secret» (конечно, в реальности Алиса и Боб решили использовать куда более длинное слово, чтобы его было сложно подобрать). Меллори не знает это слово, а следовательно, даже если изменит сообщение, то не сможет скорректировать его хэш, не так ли?
К сожалению, тут есть проблемы. Да, Меллори не может изменить тело сообщения, но (раз он знает хэш от текущего текста) он всегда может дописать в конце «P.S. На самом деле все это чушь, нам пора расстаться, Боб» и просто досчитать хэш от остатка (вспомним аналогию с кодовым замком).
Чтобы защититься от этого, мы немного усложним нашу функцию:
Теперь дописывание чего-либо в конец сообщения полностью изменит исходные данные для «внешнего» вызова SHA-1 и Меллори остается вне игры.
Алиса и Боб только что придумали то, что называется HMAC (или hash-based message authentication code): основанный на хэш-функции код проверки подлинности сообщений. В реальности HMAC, принятый как стандарт RFC2104 выглядит чуть-чуть сложнее за счет выравнивания длины ключа, пары XOR’ов внутри, участия ключа во «внутреннем» хэше, но суть не меняется.
Не придумывайте свои реализации HMAC, используйте стандартные реализации, например, HMAC-SHA1:
Одноразовые пароли
Что такое «одноразовый пароль»? Это пароль, который бесполезно перехватывать с помощью кейлоггера, подглядывания через плечо или прослушивания телефонной линии — т.к. этот пароль используется ровно один раз.
Как можно было бы реализовать эту схему? Например, Алиса может сгененировать сотню случайных паролей и отдать копию Бобу. Когда Боб позвонит в следующий раз, он продиктует самый верхний пароль в списке, Алиса сверит его со своим, после чего оба вычеркнут его. При следующем звонке они используют очередной пароль и так далее, пока они не закончатся. Это не очень удобно: хранение списков, генерация новых паролей и так далее.
Лучше реализовать эту схему в виде алгоритма. Например, паролем является его номер по порядку, умноженный на секретное число. Пусть Алиса и Боб договорились, что секретным числом является 42; тогда первым паролем будет 42, вторым 84, третьим 126 и так далее. Меллори, не знающий алгоритма и секретного числа, никогда не догадается, какой пароль будет следующим!
Конечно, алгоритм лучше выбрать посложнее. Алиса вспоминает про HMAC и предлагает Бобу считать пароль номер N по формуле: HMAC(«Secret», номер-пароля). После этого им нужно договориться о ключе (в данном случае это «Secret»), зато потом Бобу нужно только помнить, какой по счету пароль он генерирует (например, двадцатый):
Впрочем, Бобу совсем не улыбается каждый раз диктовать такой длинный пароль. Они с Алисой договариваются, что будут использовать только его часть, например, последние 6 символов.
Некоторое время все идет хорошо. До момента, пока Бобу и Алисе не надоедает вести подсчет, какой по счету пароль они используют. Кто-то подсказывает им, что в качестве аргумента HMAC() вместо номера можно использовать все, к чему Алиса и Боб имеют одновременный доступ… например, текущее время!
Наши герои синхронизируют свои часы и договариваются, что будут в качестве аргумента HMAC() использовать unix time — количество секунд, прошедших с момента наступления эпохи UNIX (в UTC). Чтобы вводить пароль не торопясь, они решают разделить время на 30 секундные «окна»; таким образом, на протяжении 30 секунд действует один и тот же пароль. Естественно, Алиса, проверяющая пароли, в течение 30 секунд не позволяет использовать пароль повторно (просто запоминая его) и тем самым оставляет его по-настоящему «одноразовым».
Теперь пароль вычисляется по следующей формуле: HMAC(«Secret», unix_timestamp / 30).
Мы получили одноразовые пароли на основе текущего времени. Сгенерировать и проверить эти пароли может только тот, кто обладает ключом («Secret» в примере выше); иначе говоря, сервер и пользователь.
Следует отметить, что одноразовые пароли могут считаться и по другим алгоритмам; главное, чтобы алгоритм и секрет были известны обеим сторонам. Но т.к. у нас есть стандарт, дальше мы будем говорить именно о нем
OATH, TOTP, HOTP, RFC… WTF?
Итак, мы только что описали основные идеи, лежащие в основе:
1) HMAC, hash-based message authentication code: RFC2104
2) HOTP, hash-based one-time password: RFC4226
3) TOTP, time-based one-time password: RFC6238
Эти идеи — один из краеугольных камней инициативы Initiative For Open Authentication (OATH), направленной на стандартизацию методов аутентификации.
Двухэтапная аутентификация Google
Одноразовые пароли, основанные на времени (и подсчитываемые на основе алгоритма TOTP RFC 6238) используются также компанией Google в приложении Google Authenticator, которое можно установить на iOS, Android, BlackBerry. Это приложение автоматически генерирует одноразовые пароли раз в 30 секунд (в дополнение к основному паролю на Google Account). Это означает, что даже если Ваш основной пароль кто-то подглядит или перехватит, без очередного одноразового пароля в систему войти будет невозможно. Удобно.
ВНИМАНИЕ : Я НЕ НЕСУ НИКАКОЙ ОТВЕТСТВЕННОСТИ ЗА ВАШИ ДЕЙСТВИЯ С ВКЛЮЧЕНИЕМ И ВЫКЛЮЧЕНИЕМ ДВУХЭТАПНОЙ АУТЕНТИФИКАЦИИ GOOGLE; ВЫ СОГЛАСНЫ, ЧТО ВЫ ВЫПОЛНЯЕТЕ ИХ НА СВОЙ СТРАХ И РИСК.
На самом деле там нет ничего страшного (есть инструкции, есть trusted-компьютеры, есть резервные коды и т.д.), но если от души постараться, бездумно нажимая на кнопки, то вполне можно лишиться доступа к своему аккаунту. И все же: если не готовы экспериментировать, не трогайте Gmail; просто скачайте приложение Google Authenticator на телефон, вручную добавьте «ключ по времени» (например, «a abc def abc def abc» или просто отсканируйте QR-код ниже).
Для начала нам нужно получить секретный ключ, который используется для создания одноразового пароля. Его можно посмотреть на странице добавления Google Authenticator’а в настройках аккаунта, он находится под QR-кодом:
Обратите внимание, что если двухэтапная аутентификация уже включена, то старый ключ узнать нельзя: придется удалить старый и сгенерировать новый. Это несложно; главное, не забыть сразу обновить ключ и в Google Authenticator, если Вы им пользуетесь.
Ключ закодирован в Base32 (для удобства уберите пробелы и переведите буквы в верхний регистр).
Программа, которая подсчитывает текущий одноразовый пароль:
Теперь можно положить рядом запущенный Google Authenticator и сравнить значения.
upd #2: если хотите поиграть с 2-step verification от dropbox, в конце секрета допишите «======» (это необходимо для паддинга и корректной работы base32-декодера).
forloop.counter указывает, сколько раз тег for прошел свой цикл
В настоящее время прохожу учебник по Django (опрос).
Я понимаю, что тег for позволяет вам циклически проходить через список или диктовку, чтобы делать то, что вы определяете.
Но какова цель подсчета числа раз, когда тег for проходит свой цикл?
Кроме того, что является примером цикла, который проходит по одному и тому же списку / dict более одного раза?
Какова цель foorloop.counter в этом случае?
2 ответа
Переменная шаблона forloop.counter возвращает текущую итерацию текущего цикла for, проиндексированного с единицы. Есть много случаев, когда можно использовать эту переменную. Пример таков:
Напечатал бы таблицу, подобную этой (конечно с соответствующим рендерингом):
Обратите внимание, что если QuerySet объектов не упорядочен каким-либо образом, их порядок может быть произвольным. Идентификатор объекта не имеет отношения к итерации цикла for.
Хотя ваш второй вопрос несколько не связан, может потребоваться перебрать список несколько раз, чтобы отобразить информацию из этого списка в разных разделах. Поэтому, возможно, у вас есть список, отображающий информацию об объекте, а затем вниз по странице другой список, отображающий другую информацию об объекте.
Однако я бы сказал, что лучше всего делать все возможное, чтобы избежать многократного повторения списка, так как это обычно не требуется.
Конечно, не беспокойтесь об этом, если вам это не нужно, но я думаю, это полезно знать.
Поле id тега позволяет тегу идентифицировать (через поле for ), для которого тег должен отображаться. В противном случае метка не знает, с какой радиокнопкой отображать.
Документация гласит это о назначении формы:
Можно напечатать таблицу, похожую на эту (конечно, с соответствующим рендерингом):
По вашему мнению, вы можете упорядочить этот список объектов в соответствии с их идентификатором / pk следующим образом:
Затем этот же шаблон отобразит вашу таблицу следующим образом:
forloop.counter или forloop.counter0 помогут вам, если вы хотите определить какой-то конкретный шаг, который вам нужен. лайк:
Цель этого цикла проста: показать все варианты, которые есть в опросе.
Функции тайминга Python: три способа контролировать ваш код
Хотя многие разработчики признают Python эффективным языком программирования, программы на чистом Python могут работать медленнее, чем их аналоги на скомпилированных языках, таких как C, Rust и Java. В этом руководстве вы узнаете, как использовать таймеры Python для отслеживания скорости выполнения ваших программ.
В этом уроке вы узнаете, как использовать:
Вы также получите базовые знания о том, как работают классы, контекстные менеджеры и декораторы. Поскольку будут приведены примеры каждой концепции, вы сможете по желанию использовать одну или несколько из них в своем коде, как для замера времени выполнения кода, так и для других применений. Каждый метод содержит свои преимущества, и вы узнаете, какие из них использовать в зависимости от ситуации. Кроме того, у вас будет рабочий таймер Python, который вы можете использовать для мониторинга ваших программ!
Таймеры Python
Во-первых, оснакомьтесь с некоторыми примерами кода, которые вы будете использовать на протяжении всего урока. Позже вы добавите в этот код таймер Python, для мониторинга его производительность. Вы также увидите некоторые из самых простых способов измерения времени выполнения этого примера.
Функции таймера Python
Если вы посмотрите на встроенный модуль time в Python, то заметите несколько функций, которые могут измерять время:
Возвращает значение (в долях секунд) счетчика производительности, то есть часов с самым высоким доступным разрешением для измерения короткого промежутка времени.
Во-первых, вы будете использовать perf_counter() для создания таймера Python. Позже вы сравните это с другими функциями таймера Python и узнаете, почему perf_counter() обычно является лучшим выбором.
Пример: Последовательность Фибоначчи
Чтобы лучше сравнить различные способы добавления таймера Python к своему коду, вы будете применять разные функции таймера Python к одному и тому же примеру кода в этом руководстве. Если у вас уже есть код, который вы хотели бы измерить, смело следуйте этим примерам.
Вычисление n-го числа ряда Фибоначчи с помощью цикла while:
Ваш первый таймер Python
Теперь вы можете добавить таймер Python к коду примера:
Обратите внимание, что perf_counter() вызывается как до, так и после вычисления значения функции. Затем печатается время, необходимое для вычисления, вычисляя разницу между двумя вызовами.
f-строки доступны только в Python 3.6 и более поздних версиях. Для получения дополнительной информации ознакомьтесь с официальной документацией Python 3.
Теперь, когда вы запустите пример, вы увидите потраченное время на вычисления:
Вы рассмотрели основы тайминга своего кода Python. В оставшейся части руководства вы узнаете, как можно обернуть Python-таймер в класс, менеджер контекста и декоратор, чтобы сделать его более консистентным и удобным в использовании.
Python класс Timer
В этом руководстве вы создадите и обновите класс Timer, который вы можете использовать для определения таймингов кода несколькими различными способами. Окончательный код также доступен в PyPI под названием codetiming. Вы можете установить в вашу систему следующим образом:
Понимание классов в Python
Создание класса таймера Python
Здесь происходит несколько разных вещей, поэтому давайте пройдемся по коду шаг за шагом.
Использование класса Timer :
Сравните это с предыдущим примером, где вы использовали perf_counter() напрямую. Структура кода довольно похожа, но теперь код стал более понятным, и это является одним из преимуществ использования классов. Тщательно выбирая имена классов, методов и атрибутов, вы можете сделать свой код очень информативным!
Использование класса Timer Python
Обратите внимание, что код очень похож на то, что вы видели ранее. В дополнение к тому, чтобы сделать код более читабельным, Timer заботится о печати прошедшего времени на консоль, что делает логгирование затраченного времени более последовательным. Когда вы запустите код, вы увидите примерно такой же вывод:
Печать прошедшего времени из Timer может быть последовательной, но, похоже, этот подход не очень гибкий. В следующем разделе вы увидите, как настроить свой класс.
Добавление большего удобства и гибкости
До сих пор вы видели, что классы подходят для случаев, когда вы хотите инкапсулировать состояние и обеспечивать согласованное поведение в вашем коде. В этом разделе вы добавим больше удобств и гибкости вашему таймеру Python:
После обновления timer.py вы можете изменить текст следующим образом:
Вот два примера, которые показывают новую функциональность в действии:
Когда вы запускаете эти примеры в интерактивной оболочке, Python автоматически печатает возвращаемое значение.
Одна тонкая проблема с этим кодом заключается в том, что вы измеряете не только время, необходимое для вычисления элемента последовательности, но и время, которое Python тратит на печать результатов на экран. Это может быть не так важно, поскольку время, потраченное на печать, должно быть незначительным по сравнению со временем, потраченным на вычисления. Тем не менее, было бы хорошо иметь возможность точно определить время.
Переменные класса могут быть доступны либо непосредственно в классе, либо через экземпляр класса:
В обоих случаях код возвращает один и тот же пустой словарь классов.
Затем добавим дополнительные имена к вашему таймеру Python. Вы можете использовать имя для двух разных целей:
Теперь вернёмся к series_numbers.py и убедиться, что измеряется только время, потраченное на вычисления:
Повторный запуск сценария даст такой же результат, как и раньше, хотя сейчас измеряется только фактическое время вычислений:
Последняя строка является способом, которым Python представляет объекты по умолчанию. Хотя вы можете почерпнуть из него некоторую информацию, она обычно не очень полезна. Вместо этого было бы неплохо увидеть такие вещи, как имя Timer или как он будет сообщать о времени.
В Python 3.7 классы данных были добавлены в стандартную библиотеку. Они обеспечивают несколько удобств для ваших классов, включая более информативную строку представления.
Вот несколько заметок о классе данных Timer :
Новый класс данных Timer работает так же, как ваш предыдущий обычный класс, за исключением того, что теперь он имеет хорошее представление:
Прежде чем закончить этот раздел, давайте взглянем на полный исходный код Timer в его нынешнем виде. Вы заметите добавление подсказок типа к коду для дополнительной документации:
Использование класса для создания таймера, Python предлагает несколько преимуществ:
Класс очень гибкий, и вы можете использовать его практически в любой ситуации, когда вы хотите отслеживать время, необходимое для выполнения кода. Тем не менее, в следующих разделах вы узнаете об использовании менеджеров контекста и декораторов, которые будут более удобными для замеров блоков кода и функций.
Менеджер контекста Python Timer
Python класс Timer прошел долгий путь! По сравнению с первым созданным таймером Python код стал достаточно мощным. Тем не менее, для использования таймера все еще есть немного стандартного кода:
Понимание контекстных менеджеров в Python
Менеджеры контекста были частью Python в течение долгого времени. Они были представлены PEP 343 в 2005 году и впервые реализованы в Python 2.5. Вы можете распознать контекстные менеджеры в коде с помощью ключевого слова with :
Наиболее распространенное использование контекстных менеджеров, вероятно, обработка различных ресурсов, такие как файлы, блокировки и соединения с базой данных. Затем менеджер контекста используется для освобождения и очистки ресурса после его использования. В следующем примере раскрывается фундаментальная структура timer.py путем печати только строк, содержащих двоеточие. Что еще более важно, он показывает общую идиому для открытия файла в Python:
Что это значит, что fp является контекстным менеджером? Технически это означает, что fp реализует протокол менеджера контекста. В основе языка Python лежит много разных протоколов. Вы можете думать о протоколе как о контракте, в котором указано, какие конкретные методы должен реализовывать ваш код.
Протокол менеджера контекста состоит из двух методов:
Вы можете увидеть, что «See you later, Rascal» печатается, даже если в коде есть ошибка.
Теперь вы знаете, что такое контекстные менеджеры и как вы можете создать свой собственный. Если вы хотите погрузиться глубже, то посмотрите contextlib в стандартной библиотеке. Он включает в себя удобные способы определения новых контекстных менеджеров, а также готовые контекстные менеджеры, которые можно использовать для закрытия объектов, устранения ошибок или даже бездействия!
Создание менеджера контекста Python Timer
Вы также должны отметить еще две тонкие детали:
Использование менеджера контекста Python Timer
Давайте посмотрим, как использовать менеджер контекста Timer для определения времени вычисления числа Фибоначчи. Вспомните, как вы использовали Timer ранее:
Запуск скрипта должен дать знакомый результат:
Есть несколько преимуществ для добавления возможностей менеджера контекста к вашему классу таймера Python:
Python Timer декоратор
Ваш класс Timer теперь очень универсален. Однако есть один вариант использования, где он может быть еще более упорядоченным. Скажем, вы хотите отслеживать время, проведенное внутри одной данной функции в вашей кодовой базе. Используя контекстный менеджер, у вас есть два основных варианта:
1. Используйте Timer каждый раз, когда вы вызываете функцию:
Если вы вызовете do_something() во многих местах, это станет громоздко и сложно в обслуживании.
2. Обернём код функцией содержащей внутри контекстный менеджер:
Понимание декораторов в Python
В качестве первого примера создадим декоратор, который ничего не делает:
Вместо этого create_multiplier() используется для создания новых функций умножения, каждая из которых основана на различном factor :
Символ @ используется для применения декораторов. В этом случае @triple означает, что triple() применяется к функции, определенной сразу после нее.
Иногда декорированные функции должны иметь правильные метаданные. @functools.wraps исправляют именно эту проблему:
С новым определением @triple метаданные сохраняются:
Обратите внимание, что knock() теперь сохраняет свое собственное имя, даже после того, как был декорирован. Это хорошая форма, чтобы использовать @functools.wraps всякий раз, когда вы определяете декоратор. Схема, которую вы можете использовать для большинства ваших декораторов, выглядит следующим образом:
Создание декоратора Timer Python
В этом разделе вы узнаете, как расширить свой таймер Python, чтобы вы также могли использовать его в качестве декоратора. Однако в качестве первого упражнения давайте создадим Python декоратор Timer с нуля.
Основываясь на приведенной выше схеме, вам нужно только решить, что делать до и после вызова декорированной функции. Это похоже на соображения о том, что делать при входе и выходе из контекстного менеджера. Вы хотите запустить таймер Python перед вызовом декорированной функции и остановить таймер Python после завершения вызова. Декоратор @timer может быть определен следующим образом:
Обратите внимание, насколько wrapper_timer() напоминает ранний шаблон, установленный вами для замеров кода Python. Вы можете применить @timer следующим образом:
Напомним, что вы также можете применить декоратор к ранее определенной функции:
Поскольку @ применяется при определении функций, в этих случаях необходимо использовать более простую форму. Одно из преимуществ использования декоратора заключается в том, что вам нужно применить его только один раз, и он будет каждый раз определять время выполнения функции:
@timer делает свою работу. Тем не менее, в некотором смысле вы вернулись к исходной точке, поскольку @timer не обладает никакой гибкостью или удобством Timer. Можете ли вы также заставить свой класс Timer действовать как декоратор?
Здесь square-это экземпляр, который может быть вызван и может содержать квадрат числа, точно так же, как функция square() в первом примере.
Это дает вам возможность добавить возможности декоратора к существующему классу таймера:
Теперь вы можете использовать Timer в качестве декоратора:
Прежде чем завершить этот раздел, знайте, что есть более простой способ превратить ваш таймер Python в декоратор. Вы уже видели некоторые сходства между контекстными менеджерами и декораторами. Они оба обычно используются для выполнения чего-то до и после выполнения некоторого заданного кода.
Использование декоратора таймера Python
Если вы сравните эту реализацию с оригинальной реализацией без какого-либо времени, то заметите, что единственными различиями являются импорт Timer в строке 3 и применение @Timer() в строке 6. Существенным преимуществом использования декораторов является то, что они обычно просты в применении, как вы видите.
Тем не менее, декоратор по-прежнему относится ко всей функции. Это означает, что ваш код учитывает время, необходимое для печати результата. Давайте запустим сценарий в последний раз:
Расположение выходных данных прошедшего времени является предательским признаком того, что ваш код также учитывает время, необходимое для печати времени. Как вы видите здесь, ваш код печатает прошедшее время после вычислений.
При использовании таймера в качестве декоратора вы увидите те же преимущества, что и при использовании контекстных менеджеров:
Однако декораторы не так гибки, как контекстные менеджеры. Вы можете применять их только для выполнения функций. Можно добавить декораторы к уже определенным функциям, но это немного неуклюже и менее распространено.
Код Timer Python
Вы можете использовать код самостоятельно, сохранив его в файле с именем timer.py и импортировать его в вашу программу. Запустим новый таймер в качестве менеджера контекста:
Этот вид таймера Python в основном полезен для мониторинга времени, которое ваш код тратит на отдельные ключевые блоки кода или функции. В следующем разделе вы получите краткий обзор альтернатив, которые можно использовать, если вы хотите оптимизировать свой код.
Другие функции таймеры в Python
Существует множество вариантов замеров выполнения вашего кода Python. В этом уроке вы узнаете, как создать гибкий и удобный класс, который можно использовать несколькими различными способами. Быстрый поиск по PyPI показывает, что уже существует множество проектов, предлагающих решения тайминга Python.
В этом разделе вы сначала узнаете больше о различных функциях, доступных в стандартной библиотеке для измерения времени, и о том, почему perf_counter() предпочтительнее. Затем вы увидите альтернативы оптимизации вашего кода, для которых таймер не очень хорошо подходит.
Использование альтернативных функций таймеров в Python
Вы использовали perf_counter() на протяжении всего этого урока для выполнения фактических измерений времени, но библиотека time Python поставляется с несколькими другими функциями, которые также измеряют время. Вот некоторые альтернативы:
Одна из причин, почему существует несколько функций, заключается в том, что Python представляет время как float. Числа с плавающей запятой по своей природе неточны. Возможно, вы уже видели подобные результаты раньше:
Float Python следует стандарту IEEE 754 для арифметики с плавающей запятой, который пытается представить все числа с плавающей запятой в 64 битах. Поскольку существует бесконечно много чисел с плавающей запятой, вы не можете выразить их в виде конечного числа битов.
IEEE 754 предписывает систему, в которой плотность чисел, которые вы можете представить, изменяется. Чем ближе вы к 1, тем больше чисел вы можете представить. Для больших чисел есть больше пространства между числами, которые вы можете выразить. Это имеет некоторые последствия, когда вы используете float для представления времени.
Здесь вы видите, что добавление наносекундного числа на самом деле влияет на результат.
Поскольку perf_counter() уже обеспечивает наносекундное разрешение, у использования perf_counter() меньше преимуществ.
Примечание: perf_counter_ns() доступен только в Python 3.7 и более поздних версиях. В этом уроке вы использовали perf_counter() в своем классе Timer. Таким образом, таймер можно использовать и в более старых версиях Python. Для получения дополнительной информации о функциях _ns в time ознакомьтесь с новыми классными функциями в Python 3.7.
Результаты могут быть разными в вашей системе.
PIP 418 описывает некоторые обоснования введения этих функций. Она включает в себя следующие краткие описания:
Как вы можете видеть, обычно это лучший выбор для вас, чтобы использовать perf_counter() для вашего таймера Python.
Оценка времени работы со временем timeit
Допустим, нужно выжать из кода последний бит производительности и задаетесь вопросом о наиболее эффективном способе преобразования списка в множество. Вы хотите сравнить, использование set() и литерал множества <. >. Для этого вы можете использовать свой таймер Python:
Этот тест, по-видимому, указывает на то, что литерал множества может быть немного быстрее. Однако эти результаты довольно неопределенные, и если вы повторно запустите код, можно получить совершенно другие результаты. Это потому, что вы только один раз пробуете код. Например, вам может не повезти, и вы можете запустить сценарий как раз в тот момент, когда ваш компьютер будет занят другими задачами.
Лучше всего воспользоваться стандартной библиотекой. Она предназначен именно для измерения времени выполнения небольших фрагментов кода. Для этого импортируем и вызовем timeit.timeit() из Python как обычную функцию в интерфейсе командной строки. Вы можете рассчитать эти два варианта следующим образом:
Примечание: будьте осторожны, когда вы используете timeit на коде, который может загружать файлы или получать доступ к базам данных. Поскольку время от времени он автоматически вызывает вашу программу несколько раз, вы можете непреднамеренно в конечном итоге заспамить сервер запросами!
Наконец, интерактивная оболочка IPython и Jupyter notebook имеют дополнительную поддержку этой функции с помощью команды %timeit magic :
Опять же, измерения показывают, что использование литерала множества происходит быстрее.
Поиск узких мест в коде с помощью профилирования
timeit отлично подходит для бенчмаркинга конкретного фрагмента кода. Однако было бы очень громоздко использовать его для проверки всех частей вашей программы и определения того, какие разделы занимают больше всего времени. Вместо этого можно использовать профилировщик.
Этот вывод показывает, что общее время выполнения составило 0.002 секунды. В нем также перечислены десять функций, на которые ваш код потратил большую часть своего времени. Здесь вы отсортированы по кумулятивному времени (cumtime), что означает, что ваш код считает время, когда данная функция вызвала другую функцию.
Столбец общее время (tottime) показывает, сколько времени ваш код провел внутри функции, исключая время в подфункциях. Вы можете видеть, что ни одна из вышеперечисленных функций на самом деле не тратит на это никакого времени. Чтобы найти, где код провел большую часть своего времени, выполните другую команду sort :
Вы можете использовать статистику, чтобы получить некоторое представление о том, где ваш код тратит большую часть своего времени, и посмотреть, сможете ли вы оптимизировать любые узкие места, которые вы найдете. Вы также можете использовать этот инструмент, чтобы лучше понять структуру вашего кода. Например, вызываемые и вызывающие команды покажут вам, какие функции вызывают и вызываются данной функцией.
Для получения более мощного интерфейса для анализа данных профиля, запустите программу KCacheGrind. Он использует свой собственный формат данных, но вы можете конвертировать данные из профиля с помощью pyprof2calltree :
Примечание: Вы также можете профилировать потребление памяти вашего кода. Это выходит за рамки данного руководства. Однако вы можете взглянуть на memory-profiler, если вам нужно контролировать потребление памяти вашими программами.
Обратите внимание, что line_profiler требует времени и добавляет изрядную часть накладных расходов к вашей среде выполнения. Более стандартный рабочий процесс заключается в том, чтобы сначала использовать cProfile для определения того, какие функции нужно просмотреть, а затем запустить line_profiler для этих функций. line_profiler не является частью стандартной библиотеки, поэтому вы должны сначала следовать инструкциям по установке, чтобы настроить его.
Перед запуском профилировщика необходимо указать ему, какие функции следует профилировать. Это выполняется, добавлением декоратора @profile в свой исходный код. Например, для профилирования Timer.stop() вы добавляете следующее в timer.py :
Вывод
В этом руководстве вы увидели несколько разных подходов к добавлению таймера Python в свой код:
Теперь вы можете добавить функции Timer Python в свой собственный код! Отслеживание скорости выполнения вашей программы в журналах поможет вам отслеживать ваши сценарии.