Метка: python 3

О поиске в словарях

При разборе вложенных структур из словарей и списков (например, конфигов), удобно пользоваться блоком try-except.

Ловим IndexError, если индекс отсутствует в списке, и KeyError, если ключ отсутствует в словаре. Однако, лучше ловить LookupError, который является предком обоих исключений:

>>> issubclass(KeyError, LookupError)
True
>>> issubclass(IndexError, LookupError)
True

Пример:

config = {}

try:
    admin = config['db'][0]['admins']['list'][0]
except LookupError:
    admin = 'all'

Независимо от того, не найден ли будет какой-то ключ словаря или индекс списка – будет поймана одна и та же ошибка LookupError.

Альтернативно, вы можете сразу обновлять записи словаря (если они не найдены) методом dict.setdefault(key, default). Этот метод проверяет, есть ли ключ в словаре, если его нет, то в словарь добавляется значение по умолчанию, и оно же возвращается. А если ключ был в словаре, то вернется значение по этому ключу. Поэтому такой неуклюжий код:

if 'workers' not in config:
    config['workers'] = 8
workers = config['workers']

Может быть переписан как:

workers = config.setdefault('workers', 8) 

Заметьте, что повторный вызов с другим default не поменяет уже записанное в первый раз значение:

>>> d = {}
>>> d.setdefault('foo', 10)
10
>>> d.setdefault('foo', 20)
10

Также, вам будет интересно почитать про defaultdict, который вам создает в себе записи при доступе к ним.

Красивого всем кода!

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

Майним красивый Bitcoin адрес на Python

КДПВ: чувак долбит скалу киркой в поисках монет

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

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

Итак, я хочу, чтобы мой биткоин адрес начинался с какого-то слова, например, с названия моего канала PyWay. Нулевой символ, увы, мы изменить не можем, так как он связан с идентификатором сети биткоин (главная, segwit или тестовая). Поэтому оставим его в покое. Часть строки с первого символа должна начинаться с заданного слова:

PATTERN = 'PyWay'

# ф-ция проверки
def predicate(addr: str):
    return addr[1:].startswith(PATTERN)

Адреса всегда получаются случайными, поэтому нам нужен метод грубой силы (брутфорс) – многократная генерация адресов, пока не найдем подходящий:

from bit import Key

while True:
    k = Key()  #  новый случайны ключ
    if predicate(k.segwit_address):
        print(f'{k.segwit_address} with WIF private key {k.to_wif()}')
        break

Время поиска зависит от длины шаблона экспоненциально!

Короткие последовательности ищутся быстро, а длинные – экстремально долго.

Так 2-3 символьные слова ищутся почти моментально. 4 символа заставляют задуматься, а 5 – ждать минут 10-20. 6 символов – могут уйти часы. И так далее! Это криптография!

Чтобы ускорить поиск, запустим несколько процессов поиска с помощью модуля multiprocessing:

from bit import Key
from multiprocessing import Process, Value


PATTERN = 'PyWay'

# сколько процессов?
N_PROCESSES = 10


# предикат проверки адреса на наше желание
def predicate(addr: str):
    return addr[1:].startswith(PATTERN)


# рабочая функция
def worker(predicate: callable, stop: Value, counter: Value):
    # пока нам не посигналили о завершении из другого процесса
    while not stop.value:
        # новый ключ
        k = Key()
        
        # проверяем
        if predicate(k.segwit_address):
            print('done!')
            print(f'{k.segwit_address} with WIF private key {k.to_wif()}')
            
            # сигналим другим и выходим
            stop.value = True 
            break
            
        # каждые 10_000 итераций – точку рисуем
        counter.value += 1
        if counter.value % 10_000 == 0:
            print('.', end='')


if __name__ == '__main__':
    # эти переменные необычные - они позволяют обмениваться инфой между процессами
    stop = Value('b', False)
    counter = Value('i', 0)

    procs = []
    for worker_id in range(N_PROCESSES):
        # создадим процесс, передав рабочего и аргументы
        proc = Process(target=worker, args=(predicate, stop, counter))
        proc.start()
        procs.append(proc)

    # будем ждать пока все процессы не завершаться
    for proc in procs:
        proc.join()  # ждет процесс

FAQ

Почему у меня долго не находит даже простое слово?

Возможно, проблема в вашем слове. В нем есть символы, которых не может быть в адресе: o, O, 0, l, I. Они похожи по написанию, и были исключены, чтобы избежать лишних ошибок при передаче адресов. Естественно, в адресе не может быть русских букв и знаков пунктуации. Только английские буквы (кроме тех, что привел выше) и цифры кроме 0!

Почему бы не искать последовательно?

Если начать проверять все адреса подряд, это будет немного быстрее. Главное не начинать с 0 и с круглых чисел, иначе ключ будет легко подобрать! Пример последовательного поиска со случайной начальной точки seed:

seed = random.randint(0, 1_000_000_000_000)

while not stop.value:
    k = Key.from_int(seed)
    seed += 1

Почему процессы, а не потоки?

Из-за GIL. Потоки будут тормозить друг друга, а процессы работают независимо.

Если хочу слово в конце адреса или в середине?

Поменяйте условие:

return addr.endswith(PATTERN)  # в конце

return PATTERN in addr  # можно в середине 

PATTERN = PATTERN.lower()
return PATTERN in addr.lower()  # в любом месте без учета регистра - самый быстрый поиск

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

«Сломанный» set

Вопрос: может ли set содержать два одинаковых объекта?

Ответ: да, запросто!

Делаем класс:

class Foo:
    def __init__(self, x):
        self.x = x
    def __hash__(self):
        return self.x
    def __eq__(self, other):
        return self.x == other.x
    def __repr__(self):
        return f'Foo({self.x})'

# создаем set из трех разных объектов
hacker = Foo(20)
s = {Foo(10), hacker, Foo(30)}

print(s)  # {Foo(10), Foo(20), Foo(30)}

hacker.x = 30  # взлом системы
print(s)  # {Foo(10), Foo(30), Foo(30)}

from collections import Counter
c = Counter(s)
print(c)  # Counter({Foo(30): 2, Foo(10): 1})

Как это? set запоминает хэш объекта при вставке, а потом не следит за тем, меняется ли как-то объект или нет, это было бы очень накладно. Изначально мы вставляли 20, но потом уже поменяли его на 30, тем самым сломав set.

«Починить» такой set можно, сделав из него список, а потом новый set, тогда хэши будут заново пересчитаны. Лучше до такого не доводить!

s2 = set(list(s))
print(s2)  # {Foo(10), Foo(30)}

Примечание: а метод s.copy() не сработает, потому что он копирует уже вычисленные хэши.

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

Специально для канала @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 👈 

​​Анимация Jupyter Notebook

Сегодня мы будем анимировать график прямо внутри Jupyter Notebook. Сперва сделаем плавную отрисовку графика. Переключим режим отображения графиков в notebook:

%matplotlib notebook

Импортируем все, что нужно:

import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np

Сгенерируем наши данные:

# время (200 точек)
t = np.linspace(0, 2 * np.pi, 200)
x = np.sin(t)  # синусоида

Создадим пустой график:

fig, ax = plt.subplots()
# пределы отображения
ax.axis([0, 2 * np.pi, -2, 2])
l, = ax.plot([], [])

Функция animate будет вызываться при отрисовка каждого кадра, аргумент i – номер кадра:

def animate(i):
    # рисуем данные только от 0 до i
    # на первом кадре будет 0 точек, 
    # а на последнем - все
    l.set_data(t[:i], x[:i])

Запускаем анимацию:

fps = 30  # карды в сек
# frames - число кадров анимации
ani = animation.FuncAnimation(fig, animate, frames=len(t), interval=1000.0 / fps)

Если мы хотим анимировать сами данные, например, заставить синусоиду «плясать», то на каждом шаге перегенерируем данные заново, используя переменную i:

def animate(i):
    x = np.sin(t - i / len(t) * np.pi * 2) * np.sin(t * 15)
    l.set_data(t, x)

Можно сохранить в GIF:

ani.save('myAnimation.gif', writer='imagemagick', fps=30)

Сам ноутбук я загрузил на GitHub, но поиграться онлайн с ним не получится, надо скачать себе и запустить локально. Анимированные графики отрисовываются в реальном времени, поэтому требуют достаточно много ресурсов. Пример 3D анимации:

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