Храним состояние в URL
Если вы разрабатываете веб-приложение, то рано или поздно столкнетесь с проблемой сохранения текущего состояния системы для пользователя.
Например, вы продаете через интернет элитный картофель. Покупатель заходит, настраивает фильтры поиска, видит список из 300 позиций.
Переходит на третью страницу, открывает карточку картофелины и случайно нажимает на рефреш страницы. Как поступит ваше приложение?
Я знаю такие варианты:
— Не хранить состояние вообще
— Хранить состояние локально
— Хранить набор URL-параметров
— Хранить сериализованное состояние
— Гибридные подходы
Вот особенности каждого
#код
Если вы разрабатываете веб-приложение, то рано или поздно столкнетесь с проблемой сохранения текущего состояния системы для пользователя.
Например, вы продаете через интернет элитный картофель. Покупатель заходит, настраивает фильтры поиска, видит список из 300 позиций.
Переходит на третью страницу, открывает карточку картофелины и случайно нажимает на рефреш страницы. Как поступит ваше приложение?
Я знаю такие варианты:
— Не хранить состояние вообще
— Хранить состояние локально
— Хранить набор URL-параметров
— Хранить сериализованное состояние
— Гибридные подходы
Вот особенности каждого
#код
👍25
Как передать состояние в URL-параметрах
На первый взгляд, передавать состояние в виде набора параметров URL легко и приятно.
Строку или число — элементарно:
Логическое значение — не сильно сложнее:
Дата и время обычно использует RFC 3339 (2020-01-02T10:11:12Z) или unix time (секунды после полуночи 01.01.1970), реже — миллисекунды:
Неизвестное значение часто передают как пустое или не передают вовсе, реже используют маркерное значение:
Список сложнее. Классический вариант — повторять название свойства для каждого значения, как диктует RFC 6570.
Иногда добавляют [], чтобы подчеркнуть, что это список, бывает что и с индексами:
Фанаты краткости передают название свойства один раз, а значения разделяют запятыми.
Словарь содержит пары ключ-значение. Чаще всего название основного свойства дублируют, а названия вложенных указывают в []. Реже используют точку.
Вложенность больше 2 уровней обычно не используют.
В целом, URL-параметры неплохо подходят для необходимого контекста. Они наглядные и позволяют передавать достаточно сложные структуры данных.
#код
На первый взгляд, передавать состояние в виде набора параметров URL легко и приятно.
Строку или число — элементарно:
{
"search": "potatoes",
"page": 5
}
→ ?search=potatoes&page=5
Логическое значение — не сильно сложнее:
{ "popular": true }
→ popular=true
popular=1
Дата и время обычно использует RFC 3339 (2020-01-02T10:11:12Z) или unix time (секунды после полуночи 01.01.1970), реже — миллисекунды:
{ "after": "2020-01-02" }
→ after=2020-01-02
after=1577923200
Неизвестное значение часто передают как пустое или не передают вовсе, реже используют маркерное значение:
{ "country": null }
→ country=null
country=
<empty>
Список сложнее. Классический вариант — повторять название свойства для каждого значения, как диктует RFC 6570.
Иногда добавляют [], чтобы подчеркнуть, что это список, бывает что и с индексами:
{
"country": ["bo", "za"]
}
→ country=bo&country=za
country[]=bo&country[]=za
country[0]=bo&country[1]=za
Фанаты краткости передают название свойства один раз, а значения разделяют запятыми.
→ country=bo,za
Словарь содержит пары ключ-значение. Чаще всего название основного свойства дублируют, а названия вложенных указывают в []. Реже используют точку.
{
"size": {
"min": 3,
"max": 7
}
}
→ size[min]=3&size[max]=7
size.min=3&size.max=7
Вложенность больше 2 уровней обычно не используют.
В целом, URL-параметры неплохо подходят для необходимого контекста. Они наглядные и позволяют передавать достаточно сложные структуры данных.
#код
👍26
Новости стандартной библиотеки
Когда выходит очередная версия Python, все внимание достается новым фичам языка: моржовому оператору, слиянию словарей, паттерн-матчингу. Еще много пишут об изменениях в асинхронной работе (модуль
Остальным модулям стандартной библиотеки достается незаслуженно мало внимания. Хочу это исправить и рассказать, что интересного появилось в версиях 3.8–3.10.
Планировал небольшую заметку, но не преуспел: получилась здоровенная статья. Старался выбрать только самое интересное, но все равно в обзор попали аж 17 модулей. Питон, он такой 😁
Читайте на хабре:
https://habr.com/ru/post/665020/
P.S. Для каждого описанного новшества в статье есть ссылка на интерактивную песочницу (например, graphlib)! Спасибо Степику за нее ❤️
Когда выходит очередная версия Python, все внимание достается новым фичам языка: моржовому оператору, слиянию словарей, паттерн-матчингу. Еще много пишут об изменениях в асинхронной работе (модуль
asyncio
) и типизации (модуль typing
) — эти модули на виду и бурно развиваются.Остальным модулям стандартной библиотеки достается незаслуженно мало внимания. Хочу это исправить и рассказать, что интересного появилось в версиях 3.8–3.10.
Планировал небольшую заметку, но не преуспел: получилась здоровенная статья. Старался выбрать только самое интересное, но все равно в обзор попали аж 17 модулей. Питон, он такой 😁
Читайте на хабре:
https://habr.com/ru/post/665020/
P.S. Для каждого описанного новшества в статье есть ссылка на интерактивную песочницу (например, graphlib)! Спасибо Степику за нее ❤️
🔥35👍9❤4
Компактные объекты
Питон — объектный язык. Это здорово и удобно, пока не придется создать 10 млн объектов в памяти, которые благополучно ее и съедят. Поговорим о том, как уменьшить аппетит.
Допустим, есть у вас простенький объект «питомец» с атрибутами «имя» (строка) и «стоимость» (целое). Интуитивно кажется, что самое компактное предоставление — в виде кортежа:
Замерим, сколько займет в памяти один такой красавчик:
161 байт. Будем использовать как основу для сравнения.
С чистыми кортежами, конечно, работать неудобно. Наверняка вы используете датакласс:
А что у него с размером?
Ого, какой толстенький!
Попробуем использовать именованный кортеж:
Теперь вы понимаете, за что я его так люблю. Удобный интерфейс как у датакласса — а вес как у кортежа. Идеально.
Или нет? В Python 3.10 приехали датаклассы со слотами:
Ого! Магия слотов создает специальные худощавые объекты, у которых внутри нет словаря, в отличие от обычных питонячих объектов. И такой датакласс ничуть не уступает кортежу.
Что делать, если 3.10 вам еще не завезли? Использовать
У слотовых объектов есть свои недостатки. Но они отлично подходят для простых случаев (без наследования и прочих наворотов).
P.S. Конечно, настоящий победитель — numpy-массив. Но с ним неинтересно соревноваться ツ
песочница
#stdlib
Питон — объектный язык. Это здорово и удобно, пока не придется создать 10 млн объектов в памяти, которые благополучно ее и съедят. Поговорим о том, как уменьшить аппетит.
Допустим, есть у вас простенький объект «питомец» с атрибутами «имя» (строка) и «стоимость» (целое). Интуитивно кажется, что самое компактное предоставление — в виде кортежа:
("Frank the Pigeon", 50000)
Замерим, сколько займет в памяти один такой красавчик:
def fields():
# генерит name длины 10
# и price до 100К
# ...
return (name, price)
n = 10_000
pets = [fields() for _ in range(n)]
size = round(asizeof(pets) / n)
print(f"Pet size (tuple) = {size} bytes")
Pet size (tuple) = 161 bytes
161 байт. Будем использовать как основу для сравнения.
С чистыми кортежами, конечно, работать неудобно. Наверняка вы используете датакласс:
@dataclass
class PetData:
name: str
price: int
А что у него с размером?
Pet size (dataclass) = 257 bytes
x1.60 to baseline
Ого, какой толстенький!
Попробуем использовать именованный кортеж:
class PetTuple(NamedTuple):
name: str
price: int
Pet size (named tuple) = 161 bytes
x1.00 to baseline
Теперь вы понимаете, за что я его так люблю. Удобный интерфейс как у датакласса — а вес как у кортежа. Идеально.
Или нет? В Python 3.10 приехали датаклассы со слотами:
@dataclass(slots=True)
class PetData:
name: str
price: int
Pet size (slots dataclass) = 153 bytes
x0.95 to baseline
Ого! Магия слотов создает специальные худощавые объекты, у которых внутри нет словаря, в отличие от обычных питонячих объектов. И такой датакласс ничуть не уступает кортежу.
Что делать, если 3.10 вам еще не завезли? Использовать
NamedTuple
. Или прописывать слоты вручную:@dataclass
class PetData:
__slots__ = ("name", "price")
name: str
price: int
У слотовых объектов есть свои недостатки. Но они отлично подходят для простых случаев (без наследования и прочих наворотов).
P.S. Конечно, настоящий победитель — numpy-массив. Но с ним неинтересно соревноваться ツ
песочница
#stdlib
👍61🔥11
Великий Рандом
Все знают про
Но модуль
Например, можно выбрать из диапазона с шагом:
Или случайный элемент последовательности:
А то и несколько элементов:
Можно еще и веса элементам назначить — чтобы одни выбирались чаще других:
Хотите выборку без повторений? Нет проблем:
Или можно всю последовательность перемешать:
И напоследок. Если используете случайные числа в тестах, всегда инициализируйте генератор константой, чтобы он давал воспроизводимые результаты:
А в продакшене, наоборот, вызывайте
#stdlib
Все знают про
random.randint(a, b)
, который возвращает случайное число в указанном диапазоне:random.randint(10, 99)
# 59
Но модуль
random
предоставляет намного больше возможностей. Так много, что одной заметкой и не охватишь.Например, можно выбрать из диапазона с шагом:
random.randrange(10, 99, 3)
# 91
Или случайный элемент последовательности:
numbers = [7, 9, 13, 42, 64, 99]
random.choice(numbers)
# 42
А то и несколько элементов:
numbers = range(99, 10, -1)
random.choices(numbers, k=3)
# [32, 62, 76]
Можно еще и веса элементам назначить — чтобы одни выбирались чаще других:
numbers = [7, 9, 13, 42, 64, 99]
weights = [10, 1, 1, 1, 1, 1]
random.choices(numbers, weights, k=3)
# [42, 13, 7]
random.choices(numbers, weights, k=3)
# [7, 7, 7]
random.choices(numbers, weights, k=3)
# [13, 7, 7]
Хотите выборку без повторений? Нет проблем:
numbers = [7, 9, 13, 42, 64, 99]
random.sample(numbers, k=3)
# [42, 99, 7]
Или можно всю последовательность перемешать:
numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers)
# [3, 2, 1, 5, 4]
И напоследок. Если используете случайные числа в тестах, всегда инициализируйте генератор константой, чтобы он давал воспроизводимые результаты:
random.seed(42)
А в продакшене, наоборот, вызывайте
seed()
без параметров — так питон использует генератор шума операционной системы (или текущее время, если его нет).#stdlib
👍58❤1
Задачка: летающая свинья
Допустим, вы написали утилиту, которая отправляет что угодно в полет:
Ну то есть не прям все что угодно, а любую штуку с методом
Вжух:
Не то чтобы наши герои особо успешно справлялись с задачей, но по крайней мере запуск на них работает.
Работает, и ладно. Но иногда (особенно если программа разрастается) разработчику хочется добавить немного строгости. Дать понять, что параметр
Опрос следует.
песочница
#задачка
Допустим, вы написали утилиту, которая отправляет что угодно в полет:
def launch(thing):
thing.fly()
Ну то есть не прям все что угодно, а любую штуку с методом
.fly()
. Очень удобно: одной функцией запускаем и голубя Френка, и самолет, и даже Супермена:class Frank:
def fly(self):
print("💩")
class Plane:
def fly(self):
print("Рейс задержан")
class Superman:
def fly(self):
print("ε===(っ≧ω≦)っ")
Вжух:
f = Frank()
launch(f)
# 💩
p = Plane()
launch(p)
# Рейс задержан
s = Superman()
launch(s)
# ε===(っ≧ω≦)っ
Не то чтобы наши герои особо успешно справлялись с задачей, но по крайней мере запуск на них работает.
Работает, и ладно. Но иногда (особенно если программа разрастается) разработчику хочется добавить немного строгости. Дать понять, что параметр
thing
в launch()
— это не любой объект, а обязательно летающая хреновина с методом fly()
. Как лучше это сделать?Опрос следует.
песочница
#задачка
👍4😁2🤔2
Как указать, что thing летает?
Final Results
4%
В комментарии
48%
Через базовый класс
40%
Через протокол
8%
И так сойдет
🤔9