Метка: библиотека

Комбинаторика в Python

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

Почему важна комбинаторика? Нет, не только лишь для решения олимпиадных задач, но также комбинаторика – один из столпов теории вероятностей, которая в свою очередь служит фундаментом для машинного обучения – одно из мощнейших трендов в ПО начале 21-го века!

В встроенном в Python модуле itertools существует ряд комбинаторных функций. Это:

  • product() – прямое (Декартово) произведение одного или нескольких итераторов.
  • permutations() – перестановки и размещения элементов множества.
  • combinations() – уникальные комбинации из элементов множества.
  • combinations_with_replacement() – комбинации с замещением (повторами, возвратами).

О каждой из них расскажу подробно. Для начала импортируем все нужные функции из модуля:

from itertools import *

Прямое произведение

Прямое, или декартово произведение двух множеств — множествоэлементами которого являются все возможные упорядоченные пары элементов исходных множеств. Проще говоря мы берем из первого множества один элемент, а потом из второго выбираем элемент и составляем их в кортеж. Так вот все способы выбрать так элементы – составят декартово произведение. Пример:

>>> A = [1, 2, 3]
>>> B = "123"
>>> print(*product(A, B))
(1, 'a') (1, 'b') (1, 'c') (2, 'a') (2, 'b') (2, 'c') (3, 'a') (3, 'b') (3, 'c')

Примечания. Во-первых, заметьте, что элементы следуют в строгом лексографическом порядке: сначала берется нулевой элемент из первой последовательности и сочетается с каждым по очереди из второй последовательности. Во-вторых, аргументами функции могут быть любые итерируемые объекты конечной длины. Я взял для примера список и строку, причем строка автоматически разбивается на символы.

В коде произведение множеств эквивалентно вложенным циклам:

>>> print(*[(a, b) for a in A for b in B])
(1, '1') (1, '2') (1, '3') (2, '1') (2, '2') (2, '3') (3, '1') (3, '2') (3, '3')

Результат такой же, но рекомендую использовать именно библиотечную функцию, так как ее реализация, наверняка, будет лучше.

Вы можете передать в функцию больше последовательностей:

>>> print(*product([1, 2, 3]))
(1,) (2,) (3,)

>>> print(*product([1, 2, 3], [10, 20, 30]))
(1, 10) (1, 20) (1, 30) (2, 10) (2, 20) (2, 30) (3, 10) (3, 20) (3, 30)

>>> print(*product([1, 2, 3], [10, 20, 30], [100, 200, 300]))
(1, 10, 100) (1, 10, 200) (1, 10, 300) (1, 20, 100) (1, 20, 200) (1, 20, 300) (1, 30, 100) (1, 30, 200) (1, 30, 300) (2, 10, 100) (2, 10, 200) (2, 10, 300) (2, 20, 100) (2, 20, 200) (2, 20, 300) (2, 30, 100) (2, 30, 200) (2, 30, 300) (3, 10, 100) (3, 10, 200) (3, 10, 300) (3, 20, 100) (3, 20, 200) (3, 20, 300) (3, 30, 100) (3, 30, 200) (3, 30, 300)

Каждый выходной элемент будет кортежем (даже в случае, если в нем только один элемент!). Также обратите внимание на то, что функция product (как и все остальные из сегодняшнего набора) возвращает не список, а особый ленивый объект. Чтобы получить все элементы, нужно преобразовать его в список функцией list:

>>> product([1, 2, 3], 'abc')
<itertools.product object at 0x101aef8c0>

>>> list(product([1, 2, 3], 'abc'))
[(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]

Количество элементов на выходе будет произведением длин всех последовательностей на входе:

N(x_1, ..., x_n) = \prod (len(x_i))

В функцию product можно передать именованный параметр repeat, который указывает сколько раз повторять цепочку вложенных циклов (по умолчанию один раз). Если repeat >= 2, то это называют декартовой степенью. То есть множество умножается на себя несколько раз. Так при repeat=2 эквивалентным кодом будет:

>>> [(a, b, a1, b1) for a in A for b in B for a1 in A for b1 in B] == list(product(A, B, repeat=2))
True

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

N(x_1, ..., x_n, repeat) = \prod (len(x_i))^{repeat}

Перестановки

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

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

>>> print(*permutations("ABC"))
('A', 'B', 'C') ('A', 'C', 'B') ('B', 'A', 'C') ('B', 'C', 'A') ('C', 'A', 'B') ('C', 'B', 'A')
Перестановки трех элементов

Второй параметр r отвечает за количество элементов в перестановках. По умолчанию будут выданы полные перестановки (длиной, равной длине n исходной последовательности), никакие элементы исходного множества не будут выброшены, а просто переставлены местами. Если задать 0 <= r <= n, в каждой выборке будет содержаться по r элементов. Иными словами из n входных элементов будем выбирать r объектов и переставлять всеми возможными способами между собой (то есть меняется и состав выбранных объектов, и их порядок). Получившиеся комбинации называются размещениями из n объектов по r.

Например размещения для двух элементов из коллекции из трех элементов:

>>> print(*permutations("ABC", 2))
('A', 'B') ('A', 'C') ('B', 'A') ('B', 'C') ('C', 'A') ('C', 'B')

# 2 из 4
>>> print(*permutations([1, 2, 3, 4], 2))
(1, 2) (1, 3) (1, 4) (2, 1) (2, 3) (2, 4) (3, 1) (3, 2) (3, 4) (4, 1) (4, 2) (4, 3)

Количество вариантов получится по формуле (n – длина исходной последовательности):

N=\frac{n!}{(n - r)!}

При r > n будет пустое множество, потому что невозможно из более короткой последовательности выбрать более длинную. Максимальное число вариантов – для полной перестановки равняется n! (факториал).

Размещения выглядят так. Сначала выбрали по 2 элемента из 3, а потом переставили их внутри групп всеми способами. Итого 6 вариантов:

Размещения по 2 из 3 элементов.

Сочетания

combinations – функция, коротая выбирает все сочетания из входной последовательности. Пусть в ней имеется n различных объектов. Будем выбирать из них r объектов всевозможными способами (то есть меняется состав выбранных объектов, но порядок не важен). Получившиеся комбинации называются сочетаниями из n объектов по r, а их число равно:

C^r_n=\frac{n!}{r!(n - r)!}

Разница сочетаний и перестановок в том, что для сочетаний нам не важен порядок, а для перестановок он важен. Пример:

>>> print(*permutations([1, 2, 3], 2))
(1, 2) (1, 3) (2, 1) (2, 3) (3, 1) (3, 2)

>>> print(*combinations([1, 2, 3], 2))
(1, 2) (1, 3) (2, 3)

(1, 2) и (2, 1) – разные перестановки, но с точки зрения сочетаний – это одно и тоже, поэтому в combinations входит только один вариант из двух.

Второй параметр r – обязателен для этой функции. 0 <= r <= n. При r > n будет пустое множество.

Вот графический пример сочетаний из 3 по 2. Как видно, их вдвое меньше, чем размещений из 3 по 2, так как варианты с перестановками внутри групп не учтены по определению:

Сочетания с повторами

Функция combinations_with_replacement описывает, сколькими способами можно составить комбинацию по r элементов из элементов n типов (элементы в комбинации могут повторяться, но порядок их не важен). Обратите внимание на слово «тип«, в простых сочетаниях элементы не повторялись внутри одной выборки, они были как бы конкретными экземплярами.

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

>>> print(*combinations_with_replacement(['red', 'white', 'black'], 2))
('red', 'red') ('red', 'white') ('red', 'black') ('white', 'white') ('white', 'black') ('black', 'black')

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

>>> print(*combinations_with_replacement(['red', 'black'], 4))
('red', 'red', 'red', 'red') ('red', 'red', 'red', 'black') ('red', 'red', 'black', 'black') ('red', 'black', 'black', 'black') ('black', 'black', 'black', 'black')

Вот графически сочетания с повторами по 2 из 3:

Вот графически сочетания с повторами по 2 из 3

Формула числа элементов на выходе такова:

N = \frac{(n+r-1)!}{r!(n-1)!}

Бонус – брутфорс пароля

Как бонус предлагаю вам применение функции product() для брутфорса паролей. Сперва мы задаем набор символов, которые могут встречаться в пароле, наш алфавит, например такой:

import string
# все буквы и цифры
alphabet = string.digits + string.ascii_letters
# 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

А потом перебираем все возможные сочетания с длинами от минимальной до максимальной. Не забываем их склеить в строку:

def brute_force(alphabet, min_len, max_len):
    # функция - склеиватель последователностей символов в строку
    joiner = ''.join

    for cur_len in range(min_len, max_len + 1):
        yield from map(joiner, product(alphabet, repeat=cur_len))

Пример применения:

# сокращенный алфавит для иллюстрации работы
alphabet = '123AB'
print(*brute_force(alphabet, 1, 3), sep=', ')

# вывод: 1, 2, 3, A, B, 11, 12, 13, 1A, 1B, 21, 22, 23, 2A, 2B, 31, 32, 33, 3A, 3B, A1, A2, A3, AA, AB, B1, B2,
 B3, BA, BB, 111, 112, 113, 11A, 11B, 121, 122, 123, 12A, 12B, 131, 132, 133, 13A, 13B, 1A1, 1A2, 1A3, 1AA, 
1AB, 1B1, 1B2, 1B3, 1BA, 1BB, 211, 212, 213, 21A, 21B, 221, 222, 223, 22A, 22B, 231, 232, 233, 23A, 23B, 
2A1, 2A2, 2A3, 2AA, 2AB, 2B1, 2B2, 2B3, 2BA, 2BB, 311, 312, 313, 31A, 31B, 321, 322, 323, 32A, 32B,
 331, 332, 333, 33A, 33B, 3A1, 3A2, 3A3, 3AA, 3AB, 3B1, 3B2, 3B3, 3BA, 3BB, A11, A12, A13, A1A, A1B,
 A21, A22, A23, A2A, A2B, A31, A32, A33, A3A, A3B, AA1, AA2, AA3, AAA, AAB, AB1, AB2, AB3, ABA, 
ABB, B11, B12, B13, B1A, B1B, B21, B22, B23, B2A, B2B, B31, B32, B33, B3A, B3B, BA1, BA2, BA3, BAA, 
BAB, BB1, BB2, BB3, BBA, BBB

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Отправить Bitcoin средствами Python

КДПВ

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

Давайте поставим библиотеку bit:

pip install bit

Ключи

Ключ – центральное понятие в мире Биткоина. Приватный ключ – это ваш кошелек. Вы храните его в секрете от всех. Публичный ключ получается из приватного. А адрес кошелька, получается из публичного ключа. Это преобразование одностороннее: нужно затратить колоссальный объем вычислительной мощности и миллиарды лет времени, чтобы к адресу подобрать приватный ключ и получить контроль над средствами. Я уже рассказывал о генерации ключей биткоин вручную. Но библиотека bit прекрасно делает это за вас.

У биткоина две сети – главная и тестовая. В каждой свои ключи и свои виды адресов. Генерация нового ключа для основной сети, где адреса обычно начинаются с цифры:

from bit import Key

key = Key()
print(k.address)  # 1C8REeQUnXE3HtLCYXVG21AryDxRXnnyar

Класс Key – псевдоним для PrivateKey:

from bit import Key, PrivateKey
print(PrivateKey is Key)  # true

Для демонстрационных целей, я буду использовать тестовую сеть. В ней монеты ничего не стоят и их легко получить. Адреса тестовой сети обычно начинаются с буквы m или n! Ключ тестовой сети описан классом PrivateKeyTestnet:

from bit import PrivateKeyTestnet

k = PrivateKeyTestnet()
print(k.address)  # mrzdZkt4GfGMBDpZnyaX3yXqG2UePQJxpM

Если мы в конструкторе класса ключа не указали параметров, то каждый раз создается новый (почти наверняка пустой – без баланса) адрес. Генерация происходит локально (без обращения к онлайн сервисам) и очень быстро. Но, если приватный ключ не сохранен, то после завершения программы доступ будет утерян. Поэтому сгенерируем приватный ключ и запишем его в блокноте. Адрес получается по свойству k.address, а приватный ключ можно получить в разных форматах, самый удобный из них – WIF (Wallet Export Format) – получаем строку методом k.to_wif():

from bit import PrivateKeyTestnet as Key

k = Key()

print('Private key:', k.to_wif())
print('Public address:', k.address)

# Private key: cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV
# Public address: mhnmzFN5gr6gvmEr1t8UcRh6rdTh6JxuDe

Также по приватному ключу можно получить еще SegWit адрес. Если очень кратко, то этот адрес будет работать быстрее, чем традиционный.

print(k.segwit_address)  # 2MsWNuzx8EfgEeGLesLmkMM6q3kajEjVnVh

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

Биткоин кран

Транзакция займет некоторое время (минут 10-20). Так что наберитесь терпения!

А пока она идет, создадим класс ключа уже из сохраненной секретной строки:

from bit import PrivateKeyTestnet as Key

k = Key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')
print(k.address)  # mhnmzFN5gr6gvmEr1t8UcRh6rdTh6JxuDe ура тот же!

Приватный ключ может быть представлен, как число, байты, HEX-строка, в WIF, DER и PEM форматах:

from bit import PrivateKeyTestnet as Key

k = Key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')

print('Int:', k.to_int(), end='\n\n')
print('Hex:', k.to_hex(), end='\n\n')
print('Bytes:', k.to_bytes(), end='\n\n')
print('WIF:', k.to_wif(), end='\n\n')
print('DER:', k.to_der(), end='\n\n')
print('PEM:', k.to_pem(), end='\n\n')

Вывод:

Int: 4397583691621789343100573085...453641742227689755261559235

Hex: 6139710fb66e82b7384b868bda1ce59a0bd216e89b8808ae503c5767e4d461c3

Bytes: b'a9q\x0f\xb6n\x82\xb78K\x86\x8b\xd...d4a\xc3'

WIF: cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV

DER: b'0\x81\x84\x02\...xb3b\x8e\x1ar\xc6'

PEM: b'-----BEGIN PRIVATE KEY-----\nMIGEA.....O\nrRnD/Ls2KOGnLG\n-----END PRIVATE KEY-----\n'

Также, удобно создавать класс ключа из WIF формата функцией wif_to_key, она сама определит тип сети и создаст нужный класс:

from bit import wif_to_key

k = wif_to_key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')
print(k)  # <PrivateKeyTestnet: mhnmzFN5gr6gvmEr1t8UcRh6rdTh6JxuDe>

Надеюсь монеты с крана вам уже дошли, и мы продолжим.

Баланс

Узнаем баланс нашего кошелька. Для этого внутри bit используются онлайн сервисы (https://insight.bitpay.com, https://blockchain.info, https://smartbit.com.au). Поэтому операция не моментальная.

from bit import PrivateKeyTestnet as Key

k = Key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')
print(k.get_balance())  # 1391025

Как видите, на тот момент на адресе лежало 1391025 сатоши. 1 сатоши = одна стомиллионная целого биткоина (10-8) – самая маленькая неделимая частичка. Библиотека bit удобна еще тем, что содержит встроенный конвертер валют, поэтому баланс можно получить в любой поддерживаемой валюте: хоть в милибиткоинах, хоть в долларах, хоть в рублях. Просто передайте название валюты аргументом:

print(k.get_balance('mbtc'), 'MBTC')  # 13.91025 MBTC
print(k.get_balance('usd'), 'USD')  # 129.84 USD
print(k.get_balance('rub'), 'RUB')  # 8087.35 RUB

Как послать монеты?

Очень просто: методом send. Создадим еще один ключ (dest_k) и пошлем ему часть биткоинов от source_k:

from bit import PrivateKeyTestnet as Key

source_k = Key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')
dest_k = Key('cP2Z27v1ZaBz3VQRRSTQRhgYt2x8BtcmAL9zi2JsKaDBHobxj5rx')

print(f'Send from {source_k.address} to {dest_k.address}')

r = source_k.send([
    (dest_k.address, 0.0042, 'btc')
])

print(r)  # ID транзакции

Как вы помните, у транзакции может быть много выходов, поэтому первый аргумент функции send – список – кому и сколько мы посылаем (кортеж: адрес, количество, валюта). В данном случае адресат у нас один ‘n2R8xiqs6BqdgtqpXRDLKrN4BLo9VD171z’, а второй неявный выход – обратно наш же адрес, чтобы получить сдачу. Вот эта транзакция выглядит так:

Через 5 минут я уже получил первое подтверждение перевода! Проверим список транзакций:

transactions = source_k.get_transactions()
print(transactions)

# ['a101ad526e9fb131b90aac220b8b6e8bf11b9b9848ab8ea6d4384dc5b4ccece0', '0770f10a7b130852e38d9af44e050c9188664c12f2d31a56a62d6648a73e1264']

# Непотраченные входы:
unspents = source_k.get_unspents()
print(unspents)

# [Unspent(amount=967861, confirmations=4, script='76a91418ee4d98c345db083114990baa17d02e988cfedb88ac', txid='a101ad526e9fb131b90aac220b8b6e8bf11b9b9848ab8ea6d4384dc5b4ccece0', txindex=1, segwit=False)]

Пример для нескольких адресатов (каждая валюта будет пересчитана по курсу в биткоин):

my_key.send([
    ('1HB5XMLmzFVj8ALj6mfBsbifRoD4miY36v', 0.0035, 'btc'),
    ('1Archive1n2C579dMsAu3iC6tWzuQJz8dN', 190, 'jpy'),
    ('129TQVAroeehD9fZpzK51NdZGQT4TqifbG', 3, 'eur'),
    ('14Tr4HaKkKuC1Lmpr2YMAuYVZRWqAdRTcr', 2.5, 'cad')
])

Если вернуть сдачу не себе, а на другой адрес – аргумент leftover:

key.send(..., leftover='адрес_для_сдачи')

Если нужно прикрепить к транзакции сообщение (до 40 байт в кодировке UTF-8) – аргумент message:

key.send(..., message='За кофе и пончик')

Функция create_transaction только создает транзакцию и подписывает ее ключом, но не посылает ее в сеть. Аргументы те же, что у send.

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

В худшем случает транзакция не дойдет до сети и не исполнится.

Комиссии

Если комиссия не указана явно, то она рассчитывается по средним значениям с учетом длины транзакции. Средняя комиссия берется из онлайн-сервиса. Но можно указать комиссию самостоятельно:

# комиссия за байт (будет умножена на кол-во байт)
source_k.create_transaction(..., fee=72)  

# комиссия за всю транзакцию целиком
source_k.create_transaction(..., fee=200, absolute_fee=True)  

Полезные константы комиссий:

from bit.network import fees
fees.DEFAULT_FEE_FAST   # 10 мин
fees.DEFAULT_FEE_HOUR   # 1 час

Советы

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

Работа с внешними онлайн сервисами может тормозить. В принципе можно поменять их приоритет, но это тема отдельной статьи. Очень хорошо, если вам доступна своя нода биткоин, тогда вы можете подключиться к ней и выполнять все действия без лишних задержек и ограничений! Вот так:

from bit.network import NetworkAPI

# тестовая нода
NetworkAPI.connect_to_node(user='user', password='password', host='localhost', port='18443', use_https=False, testnet=True)

# подключение к ноде главной сети
NetworkAPI.connect_to_node(user='user', password='password', host='domain', port='8332', use_https=True, testnet=False)

# на выбор или вместе

Храните надежно ваши приватные ключи!

Если вы брали тестовые монеты с крана, пожалуйте, верните обратно их на адрес, который вам предложат. Они могут быть полезны другим разработчикам!

Библиотека bit умеет еще работать с мульти-адресами, которые требует 2 и более подписей для выполнения транзакции.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Библиотека schedule – CRON на Python

Вам приходилось работать с CRON? Это такой сервис в nix-системах, который позволяет регулярно в определенные моменты времени запускать скрипты или программы. Штука с долгой историей, в наследство которой достался странный синтаксис для описания правил:

0 * * * * my_script

Что если бы мы хотели иметь свой CRON внутри программы Python, чтобы в нужные моменты времени вызывать функции? Да еще, чтобы у него был человеческий синтаксис? Такая библиотека есть и называется schedule.

pip install schedule

Рассмотрим пример:

import schedule
import time

def job():
    print("Работаю")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
schedule.every(5).to(10).minutes.do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)
schedule.every().minute.at(":17").do(job)

# нужно иметь свой цикл для запуска планировщика с периодом в 1 секунду:
while True:
    schedule.run_pending()
    time.sleep(1)

Как видите, правила для задания временных интервалов прекрасно читаются, словно они предложения на английском языке. Перевод пары примеров:

# спланируй.каждые(10).минут.сделать(работу)
schedule.every(10).minutes.do(job)

# спланируй.каждый().день.в(10:30).сделать(работу)
schedule.every().day.at("10:30").do(job)

В задания можно передавать параметры вот так:

def greet(name):
    print('Hello', name)

schedule.every(2).seconds.do(greet, name='Alice')

Если по какой-то причине нужно отменить задание, это делается так:

def job1():
    # возвращаем такой токен, и это задание снимается с выполнения в будущем
    return schedule.CancelJob

schedule.every().day.at('22:30').do(job1)

Если нужно отменить группу заданий, то к ним добавляют тэги:

schedule.every().day.do(greet, 'Monica').tag('daily-tasks')
schedule.every().day.do(greet, 'Derek').tag('daily-tasks')

schedule.clear('daily-tasks')  # массовая отмена по тэгу

Метод to позволяет задать случайный интервал для выполнения задания, например от 5 до 10 секунд:

schedule.every(5).to(10).seconds.do(my_job)

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

import functools

# декоратор для ловли исключений
def catch_exceptions(cancel_on_failure=False):
    def catch_exceptions_decorator(job_func):
        @functools.wraps(job_func)
        def wrapper(*args, **kwargs):
            try:
                return job_func(*args, **kwargs)
            except:
                import traceback
                print(traceback.format_exc())
                if cancel_on_failure:
                    return schedule.CancelJob
        return wrapper
    return catch_exceptions_decorator

@catch_exceptions(cancel_on_failure=True)
def bad_task():
    # даст исключение, но декоратор просто отменит эту задачу
    return 1 / 0  

schedule.every(5).minutes.do(bad_task)

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

import threading
import time
import schedule

# код задания
def job():
    print("Выполняюсь в отдельном потоке")


def run_threaded(job_func):
    job_thread = threading.Thread(target=job_func)
    job_thread.start()


schedule.every(10).seconds.do(run_threaded, job)
schedule.every(10).seconds.do(run_threaded, job)
schedule.every(10).seconds.do(run_threaded, job)
schedule.every(10).seconds.do(run_threaded, job)
schedule.every(10).seconds.do(run_threaded, job)


# бесконечный цикл, проверяющий каждую секунду, не пора ли запустить задание 
while 1:
    schedule.run_pending()
    time.sleep(1)

Celery

Если вы используете в проекте Celery, то, вероятно, вам не нужен schedule. В Celery и так есть отличный CRON:

from celery import Celery
from celery.schedules import crontab

app = Celery()

@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    # эта функция выполнится при запуске - настроим вызовы задачи test

    sender.add_periodic_task(10.0, test.s('hello'), name='add every 10')
    sender.add_periodic_task(30.0, test.s('world'), expires=10)

    # в 7 30 по понедельникам
    sender.add_periodic_task(
        crontab(hour=7, minute=30, day_of_week=1),
        test.s('Happy Mondays!'),
    )

@app.task
def test(arg):
    print(arg)

Подробнее тут.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Munch – вседозволенный объект

КДПВ

Привет. Хочу познакомить вас библиотекой Munch, которая является форком более старой библиотеки Bunch. Рассмотрим суть проблемы, которую она решает. Задать атрибуты объекта, не описывая их по одному в конструкторе. Легче понять на примере:

>>> f = object()
>>> f.x = 10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'x'

Видно, что object нам не поможет. Но в Python 3 можно сделать пустой класс, это не вызовет ошибки:

class Bunch: ...

foo = Bunch()
foo.x = 10
foo.y = 20

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

Установка:

pip install munch

Объект Munch – это наследник словаря dict, с ним можно работать как со словарем, но можно также произвольно работать с его атрибутами:

from munch import *

# пустой
b = Munch()

# задаем атрибуты
b.hello = 'world'
print(b.hello)  # world

b['hello'] += "!"
print(b.hello)  # world!

print(b.hello is b['hello'])  # True

# атрибут может быть тоже Munch
b.bar = Munch()
b.bar.baz = 100
print(b.bar.baz)  # 100

Т.е. мы может обращаться к данным как через точку (как атрибут), так и через квадратные скобки (как с обычным словарем) – это будут одни и те же данные, при условии равных имен.

Очень удобная фишка – создание Munch через конструктор, просто перечисляем ключевые слова, и они станут атрибутами:

# задать через конструктор
c = Munch(x=10, y=20, z=30)
print(c.x, c.y, c.z)  # 10 20 30

С Munch можно работать, как с обычным dict, например:

c = Munch(x=10, y=20, z=30)
print(list(c.keys()))  # список атрибутов

c.update({
    'w': 10,
    'name': 'ganesh'
})
print(c)  # Munch({'x': 10, 'y': 20, 'z': 30, 'w': 10, 'name': 'ganesh'})

print([(k, v) for k, v in c.items()])
# [('x', 10), ('y', 20), ('z', 30), ('w', 10), ('name', 'ganesh')]

Удобно сеарилизовтать такие объекты:

# JSON

b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
import json
print(json.dumps(b))
#  {"foo": {"lol": true}, "hello": 42, "ponies": "are pretty!"}

# YAML - если есть.
import yaml
print(yaml.dump(b))  # или
print(yaml.safe_dump(b))

Замечание

В библиотеку collections Python 3 уже включен объект UserDict со схожей функциональностью:

from collections import UserDict

a = UserDict()
a.p = 10

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Поддельный User-Agent

Одна из примитивных защит сайтов от парсинга – проверка HTTP заголовка User-Agent, который содержит наименование веб-браузера или клиента, делающего запрос. Если этого заголовка нет, то сервер может не выполнить запрос, раскусив, что его делает робот, а не человек. Обход защиты – имитация реального User-Agent браузера библиотекой fake_useragent. Установка:

pip install fake_useragent

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

from fake_useragent import UserAgent
ua = UserAgent()
print(ua.random)
# Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.36

ua.random – агент случайного браузера (с учетом статистики распространенности браузеров по миру). Также доступны агенты для конкретных браузеров: ua.ie, ua.msie, ua.opera, ua.chrome, ua.google, ua.firefox, ua.ff, ua.safari.

Пример отправки запроса через request:

from fake_useragent import UserAgent
import requests
ua = UserAgent()

# куда шлем (этот URL как раз ответит нам наш UA для проверки)
url = 'https://httpbin.org/user-agent'

# создаем заголовок
headers = {'User-Agent': ua.chrome}

# делаем запрос, передав заголовок
result = requests.get(url, headers=headers)
print(result.content)

Еще в классе есть метод ua.update(), что обновляет базу данных браузеров, если она устарела. Это медленный метод, он делает запрос на сервера. Его не нужно вызывать каждый раз.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈