Size: a a a

PyWay – гуру Python 🐉

2019 April 11
PyWay – гуру Python 🐉
🔘 Доступ к атрибутам

Атрибуты объекта в Python – это именованные поля (данные, функции), присущие данному объекту (экземпляру, классу).
Самый простой доступ к атрибутам – через точку:

 class Foo:
   def __init__(self):
       self.x = 88  # установка значения атрибута      
f = Foo()
print(f.x)  # доступ к атрибуту через точку

Если мы обратимся к атрибуту, которого нет, то получим ошибку AttributeError. Мы можем переопределить это поведение путем реализации магических методов __getattr__ или __getattribute__.

__getattr__ вызывается, если атрибут не найден обычным способом (не был задан ранее через точку, функцию setattr, или через __dict__). Если атрибут найден, то __getattr__ НЕ вызывается.

📎 Пример. Возвращаем -1 для любого несуществующего атрибута.

 class Test:
   def __getattr__(self, item):
       print(f'__getattr__({item})')
       return -1  

t = Test()
# зададим x и y
t.x = 10
setattr(t, 'y', 33)

print(t.x)  # 10
print(t.y)  # 33  
print(t.z)  # __getattr__(z) -1

Метод __getattribute__ вызывается, когда мы пытаемся получить любой атрибут, не зависимо от того, есть он или нет. Этот метод, вызывается прежде __getattr__. Он немного хитрее. Если __getattribute__ кидает AttributeError, то будет вызвана __getattr__.

📎 Пример. Мы можем запретить чтение каких-то атрибутов:

 class Test:
   def __getattr__(self, item):
       print(f'__getattr__({item})')
       return -1

   def __getattribute__(self, item):
       print(f'__getattribute__({item})')
       if item == 'y':  # запретим получать y
           raise AttributeError
       return super().__getattribute__(item)

# зададим x и y
t = Test()
t.x = 10
t.y = 20

print(t.x)  # __getattribute__(x) 10
print(t.y)  # __getattribute__(y) __getattr__(y) -1
print(t.z)  # __getattribute__(z) __getattr__(z) -1

⚠️ Внимание! В __getattribute__ мы можем вызвать super().__getattribute__(item) или object.__getattribute__(self, item), что посути тоже самое, но не слудует делать return self.__dict__[item] или return self.__getattribute__(item) или return getattr(self, item), так как это приведет к бесконечной рекурсии.

💡 Также есть магический метод __setattr__(self, key, value), вызываемый при obj.key = value или setattr(obj, 'key', value). У него нет более длинно-названного брата-близнеца.

Для полноты картины еще есть встроенная функция getattr(object, name[, default]). Вызов getattr(x, 'y') аналогичен обращению через точку: x.y В первом случае 'y' – это строка, что позволяет нам динамически получать атрибуты объектов, в отличие от точки, которая требует фиксированного имени на этапе написания кода. В случае, если атрибут недоступен мы получим AttributeError при незаданном default или получим default (без возникновения ошибки), если default был задан третьим аргументом.
источник
2019 April 15
PyWay – гуру Python 🐉
Генерируем Bitcoin-адрес на Python

Тема криптовалют снова начинает будоражить интернет. Супер, что вам не надо идти в отделение банка с паспортом и выстаивать очередь, чтобы открыть счет. Сгенерировать кошелек Bitcoin - дело нескольких строк кода на Python.

1. Нам понадобятся библиотеки base58 и ecdsa. base58 – это кодирование бинарных данных 58-ю печатными символами (цифрами и латинскими буквами, кроме 0, O, I, l, которые похожи друг на друга). ecdsa – библиотека криптографии на эллиптических кривых.

pip install base58 ecdsa

2. Импортируем то, что нужно:

import hashlib
import ecdsa
from binascii import hexlify
from base58 import b58encode


3. Нам нужен приватный ключ, из него мы вычислим публичный ключ, а из него – адрес кошелька Bitcoin. (Обратная процедура не возможна без полного перебора до конца времен). Приватный ключ – это 32 байта данных, которые мы получим из криптографически-надежного источника случайных чисел. Вообще можно придумать свой приватный ключ самостоятельно, если так хочется. Для генерации случайного приватного ключа мы воспользуемся библиотекой ecdsa:

private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)

4. Вычислим этой же библиотекой публичный ключ и добавим спереди байт 0x4 (это признак "несжатого" публичного ключа; есть и другие форматы).

public_key = b'\04' + private_key.get_verifying_key().to_string()

5. Для получения адреса из публичного ключа вычисляем сначала RIPEMD160(SHA256(public-key)):

ripemd160 = hashlib.new('ripemd160')
ripemd160.update(hashlib.sha256(public_key).digest())

6. Дополняем его префиксом 0x0 (главная сеть Bitcoin):

r = b'\0' + ripemd160.digest()

7. Вычисляем контрольную сумму (нужна, чтобы наши денюжки не пропадали, если мы ошибемся в каком-то символе адреса). Контрольная сумма это первые 4 байта от SHA256(SHA256(r)):

checksum = hashlib.sha256(hashlib.sha256(r).digest()).digest()[0:4]

8. Получаем адрес кошелька, закодировав в base58 сложенные r и checksum:

address = b58encode(r + checksum)

9. Выведем результат:

print(f'private key: {hexlify(private_key.to_string())}')
print(f'public key uncompressed: {hexlify(public_key)}')
print(f'btc address: {address}')

Полный пример кода генерации кошельков.
Проверить ключи и адрес можно здесь.
Подробнее по теме можно почитать здесь.
источник
2019 April 16
PyWay – гуру Python 🐉
Пары из списка

Есть элегантный прием, который позволяет получить пары соседних элементов из списка. Нужно использовать функцию zip, передав в нее сам список и его же со сдвигом 1:

a = [1, 2, 3, 4, 5, 6]

for x1, x2 in zip(a, a[1:]):
   print(x1, x2)


Вывод:

1 2
2 3
3 4
4 5
5 6

#маленькиехитрости#маленькиехитрости
источник
2019 April 26
PyWay – гуру Python 🐉
🤭 В Python 3.8 будет оператор "морж"

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

Раньше делали так:

x = len(s)
if x:
   ...


Будем делать так:

if x := len(s):  # можно в 3.8
   ...

Мотивация введения оператора := состоит в том, что уже наработано много примеров кода, когда он делает запись более лаконичной, не вызывая при этом повторного вычисления выражений.

📎 Пример. Используем вычисленное однажны значение f(x) под именем y:

[y := f(x), y**2, y**3]

📎 Пример. Читаем, сохраняем в chunk и сразу проверяем условие цикла:

while chunk := file.read(8192):
  process(chunk)

📎 Пример. Можно применить в проходах по спискам, чтобы дважды не вычилслять f(x):

filtered_data = [y for x in data if (y := f(x)) is not None]

📎 Примеры можно/нельзя:

x := 5    # нельзя
(x := 5)  # можно
x = y := 0
 # нельзя
x = (y := 0)  # можно

Приоритет запятой возле нового оператора. Сравните:

x = 1, 2     # x -> (1, 2)
(x := 1, 2)  # x -> 1

P.S.: Казалось бы, почему не сделать так: if x = len(s)? Ответ: чтобы не путать с if x == len(s). В C-подобных языках это частая проблема.

Как вам оператор "морж"?
источник
2019 May 01
PyWay – гуру Python 🐉
🖼 Как сделать скриншот веб-страницы через Python?

1) Нам понадобится Selenium, чтобы управлять браузером. Документация по Selenium.

pip install selenium

2) Для примера будем управлять популярным браузером Chrome. Для него отдельно придется скачать ChromeDriver.
Установка на MacOS и Linux происходит через команды в терминале, чтобы исполняемый файл драйвера был доступен в окружении (PATH):

mv chromedriver /usr/local/bin/
chmod +x /usr/local/bin/chromedriver

3) Переходим к коду на Python. Создадим наш веб-драйвер:

from selenium import webdriver
DRIVER = 'chromedriver'
driver = webdriver.Chrome(DRIVER)

Отправляем запрос к интересующей нас веб-странице:

driver.get('https://erugame.ru/')

Делаем скриншот и сохраняем его под нужным именем:

driver.save_screenshot("screenshot.png")

Завершаем работу, закрывая окно браузера:

driver.quit()

Этот способ сохранит скриншот только видимой части страницы (обычно верхней). Если мы хотим сохранить изображение страницы целиком с учетом прокрутки до самого низа, нужно заменить вызов save_screenshot на следующий код:

element = driver.find_element_by_tag_name('body')
element.screenshot("screenshot_full.png")


Как видите, все просто! Полный код примера здесь.
источник
2019 May 06
PyWay – гуру Python 🐉
♻️ Управление памятью и сборка мусора в Python.

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

Здесь я изложу основные тезисы об управлении памятью в Python (CPython).

• В Python память управляется автоматически.
• Память для объектов, которые уже не нужны освобождается сборщиком мусора.
• Для небольших объектов (< 512 байт) Python выделяет и освобождает память блоками (в блоке может быть несколько объектов). Почему: операции с блоками памятью через ОС довольно долгие, а мелких объектов обычно много, и, таким образом, системные вызовы совершаются не так часто.
• Есть два алгоритма сборки мусора: подсчет ссылок (reference counting) и сборщик на основе поколений (generational garbage collector - gc).
• Алгоритм подсчета ссылок очень простой и эффективный, но у него есть один большой недостаток (помимо многих мелких). Он не умеет определять циклические ссылки
• Циклическими ссылками занимается gc, о ним чуть позже.
• Переменные хранят ссылки на объекты в памяти, внутри объект хранит числовое поле – количество ссылок на него (несколько переменных могут ссылаться на один объект)
• Количество ссылок увеличивается при присвоении, передаче аргументов в функцию, вставке объекта в список и т.п.
• Если число ссылок достигло 0, то объект сразу удаляется (это плюс).
• Если при удалении объект содержал ссылки на другие объекты, то и те могут удалиться, если это были последние ссылки.
• Переменные, объявленные вне функций, классов, блоков – глобальные.
• Глобальные переменные живут до конца процесса Python, счетчик их ссылок никогда не падает до нуля.
• При выходе из блока кода, ссылки созданные локальными переменными области видимости этого блока – уничтожаются.
• Функция sys.getrefcount позволит узнать число ссылок на объект (правда она накинет единицу, т.к. ее аргумент - тоже ссылка на тестируемый объект):

>>> foo = []
>>> import sys
>>> sys.getrefcount(foo)
2
>>> def bar(a): print(sys.getrefcount(a))
...
>>> bar(foo)
4
>>> sys.getrefcount(foo)
2


• Подсчет ссылок в CPython - исторически. Вокруг него много дебатов. В частности наличие GIL многим обязано этому алгоритму.
• Пример создания циклической ссылки – добавим список в себя:

lst = []
lst.append(lst)


• Цикличные ссылки обычно возникают в задачах на графы или структуры данных с отношениями между собой.
• Цикличные ссылки могут происходить только в “контейнерных” объектах (списки, словари, ...).
• GC запускается переодически по особым условиям; запуск GC создает микропаузы в работе кода.
• GC разделяет все объекты на 3 поколения. Новые объекты попадают в первое поколение.
• Как правило, большинство объектов живет недолго (пример: локальные переменные в функции). Поэтому сборка мусора в первом поколении выполняется чаще.
• Если новый объект выживает процесс сборки мусора, то он перемещается в следующее поколение. Чем выше поколение, тем реже оно сканируется на мусор.
• Во время сборки мусора объекты поколения, где он собирается, сканируются на наличие циклических ссылок; если никаких ссылок, кроме циклических нет - то объекты удаляются.
• Можно использовать инструменты из модуля weakref для создания слабых ссылок.
• Слабые ссылки не учитываются при подсчете ссылок. Если объект, на который ссылается слабая ссылка, удалится, то слабая ссылка просто обнулится, станет пустышкой.
• Подсчет ссылок не может быть отключен, а gc - может.
• В некоторых случаях полезно отключить автоматическую сборку gc.disable() и вызывать его вручную gc.collect().

Подробнее читайте в этой отличной статье.
источник
2019 May 07
PyWay – гуру Python 🐉
​​🔄 Визуализация графа ссылок

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

0) Для рисования графов понадобится graphviz Например, на MacOS вы можете установить его через Homebrew:

brew install graphviz

1) Установим библиотеку objgraph:

pip install objgraph

2) Использование. Пусть у нас есть такая структура данных:

x = ["test"]
x.append(x)
y = [x, [x], dict(x=x), set([1, 2, "test"])]


Сохраняем граф ссылок на объекты, на которые ссылается y в файл '1.png'. Обратите внимание, что show_refs принимает именно список [y], а не просто y:

import objgraph
objgraph.show_refs([y], filename='1.png')


Можно для каждого объекта вывести общее число ссылок на него:

objgraph.show_refs([y], refcounts=True, filename='2.png')

А можно узнать, кто наоборот ссылается на интересующий объект x, т.е. отследить цепочку ссылок до глобальных объектов:

objgraph.show_backrefs([x], filename='3-back.png')

Узнать статистику по самым распространенным объектам в текущей среде:

>>> objgraph.show_most_common_types(limit=5)
function           2127
dict               1193
wrapper_descriptor 1002
tuple              954
weakref            868


Или по конкретному типу глобально:

>>> objgraph.count('dict')
1195


Или среди конкретного списка объектов:

>>> objgraph.count('dict', [{'x':5}, {'y':6}])
2


В библиотеке еще много функций для отслеживания ссылок и статистик по объектам, но всего этого не вместить в небольшую заметку.

👉 Общая документация по objgraph
👉 Список функций objgraph
источник
2019 May 09
PyWay – гуру Python 🐉
🧞‍♂️ Слабые ссылки

Недавно в заметке про управление памятью в Python мы упоминали слабые ссылки. Что же это такое? Слабые ссылки позволяют получать доступ к объекту, как и обычные, однако, так сказать, они не учитываются в механизме подсчета ссылок. Другими словами, слабые ссылки не могут поддерживать объект живым, если на него не осталось больше сильных ссылок.

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

Встроенный модуль weakref отвечает за функциональность слабых ссылок.

📎 Пример. Создаем класс Foo, сильную ссылку на его экземпляр, затем слабую сслыку, проверяем:

import weakref

class Foo: ...

strong_foo = Foo()
weak_foo = weakref.ref(strong_foo)
print(weak_foo())  # вызов слабой ссылки - доступ к исходному объекту
print(weak_foo() is strong_foo)  # True

del strong_foo  # это была последняя сильная ссылка
print(weak_foo())  # None


После того, как мы избавились от единственной сильной ссылки на экземпляр класса, объект уничтожился, а слабая ссылка стала None!

Слабые ссылки можно создавать на пользовательские классы, на set и на подклассы от dict и list, но не на сами dict и list. Встроенные типы tuple, int и подобные не поддерживают слабые ссылки (да и зачем они им?).    

В weakref.ref вторым аргументом можно передать функцию, которая будет вызвана при финализации объекта слабой ссылки:

weak_foo = weakref.ref(strong_foo, lambda r: print(f'finalizing {r}'))

weakref.getweakrefcount(object) и weakref.getweakrefs(object) позволяют получить количество слабых ссылок на объект и их сами.

weakref.proxy(object[, callback]) – поздает слабый прокси-объект к объекту. Т.е. с ним можно обращаться также как и исходным, пока он не удалится. Попытка использовать прокси к уничтоженному объекту вызовет ReferenceError.

Наконец, к предназначению слабых ссылок: организацию кэшей. Есть типы:

weakref.WeakSet – как set, но элементы хранятся по слабым ссылкам и удаляются, если на них больше нет сильных ссылок
weakref.WeakKeyDictionary – как dict, но КЛЮЧИ по слабым ссылкам.
weakref.WeakValueDictionary – как dict, но ЗНАЧЕНИЯ по слабым ссылкам.

📎 Пример:

import weakref

class Foo: ...
f1, f2 = Foo(), Foo()

weak_dict = weakref.WeakValueDictionary()
weak_dict["f1"] = f1
weak_dict["f2"] = f2

def print_weak_dict(wd):
   print('weak_dict: ', *wd.items())

print_weak_dict(weak_dict)  # оба в словаре

del f2
print_weak_dict(weak_dict)  # один ушел

del f1
print_weak_dict(weak_dict)  # ничего не осталось


📎 Еще есть класс weakref.WeakMethod(method), который позволяет создать слабую ссылку на какой-то метод класса. Мне пришло в голову применение для обработчиков событий. Когда класс с обработчиком уходит от нас, то вызов слабой ссылки на метод вернет None:

import weakref

class Foo:
   def handler(self, e):
       print(f'event handled: {e}')

def fire_event(weak_handler, e):
   h = weak_handler()
   if h:
       h(e)
   else:
       print('No handler set')

f = Foo()
on_event = weakref.WeakMethod(f.handler)
fire_event(on_event, "ev1")

f = None  # обнулим, объект умрет, метод станет None
fire_event(on_event, "ev2")


📎 Наконец, можно следить за тем, когда объект будет удален:

f = Foo()
# просто установим обработчик (finalize сам никого не удаляет)
weakref.finalize(f, print, "object dead or program exit")
del f  # а вот тут print вызовется
источник
PyWay – гуру Python 🐉
А вы знали про weakref?
источник
2019 May 10
PyWay – гуру Python 🐉
🤭 Счастливой отладки

Python не запрещает создавать переменные и функции с именами, идентичными встроенным. Шутки ради переопределим print:

import sys

# счастилвой отладки!
def print(*values, sep=' ', end='\n', file=sys.stdout, flush=False):
   # шутка
   def joke(value):
       if type(value) is str:
           value = value[::-1]
       elif type(value) is int:
           value += 1
       return value

   # отсюда достанем исходную версию print
   import builtins
   return builtins.print(*map(joke, reversed(values)),
                         sep=sep, end=end, file=file, flush=flush)


print('Hello, world!', '2 x 2 =', 4)  # 5 = 2 x 2 !dlrow ,olleH


Мораль такова. Во-первых, нужно быть внимательным, когда даешь имена своим переменным и функциям, чтобы случайно не перекрыть встроенные имена, что может нарушить работу программы. Хорошая IDE вас, конечно, предупредит о перекрытии имен. Во-вторых, это иногда применимо для отладки или тестирования. Но помните, что программист, читающий ваш код, ожидает от встроенных функций их обычного нормального поведения, а не того, которое вы придумали. Лучше создать обертку с явно другим именем.
источник
2019 May 11
PyWay – гуру Python 🐉
📂 Склеиваем пути правильно

Так делать плохо:

my_path = root + '/' + user + '/' + filename

Потому что:
• В разных ОС – разные разделители пути: '/' для nix-подобных и macOS, '\\' для Windows
• В компонентах могут быть или не быть слеши – легко допустить ошибку
• Набирать это даже не удобно (имхо)

Самый простой способ правильного склеивания путей – os.path.join выберет нужный разделитель и расставит его как надо:

my_path = os.path.join(root, user, filename)

Есть еще более современный и удобный способ, который также поставляется в стандартной библиотеке Python – модуль pathlib. Это библиотека для работы с путями и файлами в стиле ООП. Примечательно, что объект Path поддерживает оператор /, который собственно и склеивает пути:

my_path = Path(root) / user / filename

У класса Path есть куча методов для получения путей в разных форматах, извлечения компонент пути, получении инфо о файлах и папках и много другое. Вот лишь некоторые из них:

>>> Path('~').expanduser()
PosixPath('/Users/bob')
>>> Path('~/../../usr').expanduser().resolve()
PosixPath('/usr')
>>> Path.cwd()
PosixPath('/Users/bob')
>>> Path('/usr/bin/foo').parts
('/', 'usr', 'bin', 'foo')
>>> Path('my/library.tar.gar').suffixes
['.tar', '.gar']
>>> Path('my/library.tar.gar').parent
PosixPath('my')
>>> str(Path('/usr/bin/foo'))
'/usr/bin/foo'
>>> sorted(Path('Projects/playground_python').glob('*.py'))
[PosixPath('Projects/playground_python/btc_gen.py'), PosixPath('Projects/playground_python/getattr.py'), ...]

>>> Path('test.txt').touch()
>>> Path('test.txt').exists()
True
>>> Path('test.txt').is_file()
True
>>> Path('test.txt').is_dir()
False
>>> Path('test.txt').is_symlink()
False

>>> Path('temp/1/foo').mkdir(parents=True, exist_ok=True)
>>> Path('temp/1/foo').resolve().as_uri()
'file:///Users/bob/temp/1/foo'
>>> Path('temp/1/foo').rmdir()


И еще очень много всего!
источник
2019 May 13
PyWay – гуру Python 🐉
Что вернет функция foo()?

def foo():
   try:
       return 'try'
   finally:
       return 'finally'

foo()
источник
PyWay – гуру Python 🐉
Ответ был finally.

Дело в том, что функция возвращает результат последнего выполненного return. А, учитывая, что блок finally всегда выполняется, то будет выполнено два return, последний из них будет return 'finally'.

Бонус по кнопке: что будет при вложенных блоках finally?
источник
2019 May 15
PyWay – гуру Python 🐉
А вы знали про hash(-1)?

(Речь идет о реализации CPython)

Встроенная функция hash возвращает целое число – хэш-сумму, которое используется при сравнении ключей словаря во время поиска, например. Для пользовательских классов hash вызывает магический метод класса  hash , а для примитивных типов уже есть встроенная реализация на Си.

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

def print_hash(x):
   print(f'hash({x}) = {hash(x)}')
for i in range(2, -4, -1):
   print_hash(i)

Вывод:

hash(2) = 2
hash(1) = 1
hash(0) = 0
hash(-1) = -2  <-- что?
hash(-2) = -2
hash(-3) = -3


Оказывается hash не возвращает -1, а конвертирует его явно в -2. Я изучил исходный код на Си и нашел это место. "Легенда гласит", что в CPython число -1 зарезервировано внутренне для индикации ошибок при выполнении этой функции.

Еще интереснее для рациональных чисел. От hash от NAN – ноль. Плюс еще пасхалка: hash от бесконечности вовращает первые несколько цифр числа π.

print_hash(-1.0)  # -2
print_hash(float('nan'))  # 0
print_hash(float('+inf'))  # 314159
print_hash(float('-inf'))  # -314159
источник
2019 May 16
PyWay – гуру Python 🐉
Еще один коварный вопрос про try и finally.

for i in range(10):
   try:
       print(1 / i)
   finally:
       print('finally')
       break


Что будет при выполнении кода?

На первой итерации цикла произойдет исключение из-за деления на 0. Блока except нет. Но тем не менее исключение все равно будет подавлено, потому что в блоке finally есть break. Вот такая особенность языка.

В будущих версиях (3.8+) тоже самое должно работать и с конструкцией continue.
источник
2019 May 17
PyWay – гуру Python 🐉
🗳 LRU-кэш

Кэш нужнен, чтобы запоминать результаты каких-то тяжелых операций: вычислений, доступа к диску или запросов в сеть. В Python есть отличный декоратор, чтобы элегантно снабдить вашу функцию кэшированием: @functools.lru_cache(maxsize=128, typed=False)

LRU значит least recently used. Кэшу задают максимальный размер, и при его достижении элементы начинают вытесняться. Первыми вытесняются элементы, неиспользованные дольше всех, а свободные места занимают свежие элементы.

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

from functools import lru_cache
import time

@lru_cache(maxsize=4)
def slow_sqr(i):
   print(f'Calculating sqr for {i}...')
   time.sleep(0.5)  # задумаемся...
   return i ** 2

for i in [1, 2, 3, 1, 3, 4, 4, 1]:
   print(f'i = {i}  => i ** 2 = {slow_sqr(i)}')

print(slow_sqr.cache_info())
# CacheInfo(hits=4, misses=4, maxsize=4, currsize=4)


📎 Пример. Можно не задавать ограничение размеру кэша. Вычислим числа Фибоначчи рекурсивным методом. Без @lru_cache этот метод экстремально неэффективный с ростом n. Однако, с кэшированием – это уже динамическое программирование и работает сравнительно быстро:
   
@lru_cache(maxsize=None)
def fib(n):
   if n < 2:
       return n
   return fib(n-1) + fib(n-2)

print(fib(500))

print(fib.cache_info())


• В основе кэша – словарь, поэтому все аргументы функции должны быть хэшируемы (hash). Надеюсь, никто не путает хэш (hash) и кэш (cache).
• Вызовы f(a=1, b=2) и f(b=2, a=1) – разные и создадут две записи в кэше.
• Рекомендуется ставить maxsize как степень двойки.
f.cache_info() – информация о размере кэша и статистике его работы
f.cache_clear() – очистка кэша
• Если аргумент декоратора typed=True, то аргументы разных типов будут кэшироваться отдельно. Пример: f(3) и f(3.0) будут отвечать разным записям в кэше.
источник
2019 May 30
PyWay – гуру Python 🐉
🎧 Качаем музыку

Вспомнил, что в начале 2000-х качал музыку с http://music.lib.ru. Сайт всегда радовал редкими и интересными композициями. Недавно совершенно случайно вспомнил о сайте, зашел, и о чудо! Он до сих пор работает и с тех пор ничуть не изменился! Ко мне пришла мысль, почему бы не скачать немного музыки и послушать. Чтобы не качать песни по одной, я решил автоматизировать эту задачу и заодно написать для вас этот туториал.

В туториале использую requests, чтобы качать страницы и файлы. BeautifulSoup, чтобы распарсить HTML и достать оттуда все ссылки на музыку.

Код на Python оформил в Jupyter Notebook, но там есть версия в виде одного целого исходника.

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

P.S. Спасибо авторам и редакторам сайта, и, конечно же, певцам и музыкантам.
источник
2019 June 08
PyWay – гуру Python 🐉
📕 dict() или {}

Разработчики предпочитают разные способы создания пустого словаря. Но равнозначны ли они?

Оказывается, что нет. Они приводят к генерации разного байт-кода. Убедимся в этом с помощью модуля dis:

>>> import dis
>>> dis.dis('{}')
 1           0 BUILD_MAP                0
             2 RETURN_VALUE
>>> dis.dis('dict()')
 1           0 LOAD_NAME                0 (dict)
             2 CALL_FUNCTION            0
             4 RETURN_VALUE


В одном случае непосредственно используюется одна команда BUILD_MAP для создания словаря, а в другом случае идет вызов функции dict, который где-то внутри себя делает BUILD_MAP.

Очевидно, это сказывается и на времени выполнения кода:

>>> from timeit import timeit
>>> timeit('{}')
0.03544308300479315
>>> timeit('dict()')
0.08697152900276706


Вывод: dict() – работает значительно медленнее. Не призываем переписывать старый код, просто на заметку.
источник
2019 June 10
PyWay – гуру Python 🐉
📕 Удаление ключа из словаря

Словарь (dict) – изменяемый тип в Python. Из словаря можно легко удалить ключ оператором del:

>>> d = {"foo":123, "bar":321}
>>> del d["foo"]
>>> d
{'bar': 321}


Что если ключа не окажается в словаре? Ответ: исключение – KeyError:

>>> del d['baz']
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
KeyError: 'baz'


Конечно, можно сделать так:

if 'baz' in d:
   del d['baz']


Или даже так:

try:
   del d['baz']
except KeyError:
   pass


Однако, есть способ удалить ключ (которого возможно нет) в одну строчку:

d.pop('baz', None)

Обратите внимание, что второй аргумент None обязателен. Кроме того, метод pop вернет удаленный элемент, что может быть полезно в каких-то случаях.
источник
2019 June 15
PyWay – гуру Python 🐉
🧙‍♂️ Магия Jupyter Notebook

Jupyter Notebook предлагает богатейшие возможности по прототипированию кода, проверке гипотез, демонстраций и научных трудов в сравнении со стандартным интерпретатором Python.

Уставновка:

pip install jupyter

Запуск. В терминале пишем:

jupyter notebook

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

Jupyter Notebook не только позволяет хранить на одной странице и код, и результат его работы, а еще текст с картинками, но и предоставляет магические функции, которые взаимодействуют с вашим Python кодом, интерпретатором и операционной системой.

Рассмотрим некоторые из них:

%magic - выведет документацию по всем-всем доступным магическим функциям.
%lsmagic - просто список этих функций.

%timeit – измеряет среднее время выполнения кусочка кода, при этом вывод гораздо более информативен, чем обычный вызов timeit.timeit; и не требует лишних import.

Сравните вот это:
def test():
 return sum(range(1000))
%timeit test()

12.5 µs ± 378 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


С этим:
>>> import timeit
>>> def test(): return sum(range(1000))
...
>>> timeit.timeit("test()", "from __main__ import test")
12.405041060002986


По-моему, первый вариант выигрывает по удобству и информативности.

%%timeit – многострочный вариант предыдущей функции. Пример:
%%timeit x = 10
x += 20
x /= 2
48.7 ns ± 0.435 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)


%pinfo [имя] или [имя]? – покажет документацию по функции или классу [имя]. Примеры:
import numpy as np
%pinfo np.random.uniform


Или
import numpy as np
np.random.uniform?


Построение графиков:
%matplotlib inline
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.plot(x, y)


%env - показать текущие переменные среды.

%env [имя]=[значение] - управление переменными среды. Пример:
%env OMP_NUM_THREADS=4

%cd – показывает или меняет рабочую директорию.

Можно вызывать системные команды прямо из блокнота через знак восклицания. Примеры:
!ls
!pip install click
# резульат выполнения системной команды можно получить в перемунную и использовать далее
output = !pip list | grep tensorflow


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

P.S. Многие магические функции также работают и в интерпретаторе IPython.
источник