CoolPython
4.64K subscribers
20 photos
44 links
Канал об основах Python и хороших практиках разработки. Создаём системность в обрывочных знаниях.

Тем, кто хочет понимать, что пишет!
Download Telegram
Пять способов создать словарь

Словарь -- структура, которая позволяет хранить данные в формате ключ-значение. Словари удобны для того, чтобы передавать информацию внутри программы, они быстрые, легко конвертируются в формат JSON, который используется в http запросах, и поэтому являются одним из главных инструментов разработчика.

Я знаю пять (!) способов создать словарь в Python. С помощью литералов словаря:

fish = {
"move": "water",
"eat": "insects",
"say": None
}

Используя конструктор явно:

snail = dict(
eat=”leaves”,
move=”ground”,
say=None
)

Или инициализируя его кортежами:

cow = dict([
(“move”, “ground”),
(“eat”, “grass”),
(“say, “moo”)
])

Четвертый, с помощью генераторных выражений (версия интерпретатора 3.5 и выше):

>>> animals = [“snail”, “fish”, “cow”]
>>> {animal: it for it, animal in enumerate(animals)}
{'snail': 0, 'fish': 1, 'cow': 2}

Этот трюк еще бывает полезен, если нужно поменять местами ключи и значения:

>>> {v: k for k, v in animals.items()}
{1: 'snail', 2: 'fish', 3: 'cow’}

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

И последний, который я использую, когда мне надо взять ключи из одного контейнера, а значения из другого:

>>> animals = ["frog", "snail", "bird"]
>>> numbers = [1, 2, 3]
>>> dict(zip(animals, numbers))
{'snail': 2, 'frog': 1, 'bird': 3}

Почему так много? Потому что каждый удобен под свой случай.🐍

#типы_данных #словари #основы #перед_собесом #comprehensions #загадка
🔥3
Грамотно ставим запятые

Допустим, у нас в коде есть список:

>>> capitals = ['Beijing', 'New Delhi']

Добавим в него ещё город и сделаем diff двух файлов перед коммитом.

>>> capitals = ['Beijing', 'New Delhi', 'Mexico City']

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

>>> capitals = [
'Beijing',
'New Delhi'
'Mexico City'
]

Так на diff'е будет видно, какой именно элемент появился, и делать ревью тоже будет проще. Кстати, вы заметили, что я забыла запятую? Это частая ошибка, и довольно неприятная, потому что при этом не только не будет брошено исключение, но и объединятся две последние строки:

>>> capitals
['Beijing', 'New DelhiMexico City']

Упс😕. Строки, не разделенные запятой, в Python конкатенируются. Это удобно для того, чтобы размещать в коде длинные тексты без переноса строки:

>>> why_so = ('Это очень очень '
'очень длинная строка или '
'документ на вашем сайте.')
>>> why_so
'Это очень очень очень длинная строка или документ на вашем сайте.'

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

>>> capitals = [
'Beijing',
'New Delhi',
'Mexico City',
]

Она не повлияет на выполнение программы, зато уменьшит вероятность ошибки программиста. При этом diff будет читаться лучше, потому что утилита подсветит только новую строку.

Плюсы запятой в конце коллекции:
🤖один и тот же формат всех строк
🤓меньше ошибок по невнимательности
🐙удобно ставить под контроль версий

#контейнеры #синтаксис #основы
👍4
Кеширование небольших чисел в CPython

Поговорим о небольших числах в CPython. Возьмем, например, два числа, 10 и 300:
 
>>> a = 10
>>> b = 300
>>> a is 10
True
>>> b is 300
False

Что?! Почему a указыввет на свое значение, а b y нет?

Все дело в том, что интерпретатор хранит числа в диапазоне от - 5 до 256 в специальном массиве. Поэтому когда мы пишем x = 10, то вообще-то получаем ссылку на объект, который уже существует в памяти:
 
>>> c = 1
>>> id(1)
94492411548416
>>> id(c)
94492411548416

Причем это касается любых значений, которые приводятся к целым числам этого диапазона. Можно завести число хоть в двоичном виде, хоть в hex, кеширование все равно будет работать:
 
>>> id(19)
94579033949504
>>> id(0b10011)
94579033949504
>>> id(0o23)
94579033949504
>>> id(0x13)
94579033949504

Числа вне диапазона - 5, 256 интерпретатор тоже будет пытаться оптимизировать, но только если они находятся в одной строке (или одном файле). В командной строке интерпретатора так не работает:
 
>>> d = 400
>>> d is 400
False

а так будет:
 
>>> e = 500; f = 500
>>> e is f
True

В общем и целом то, что нужно запомнить -- это что небольшие числа в интерпретаторе хранятся не так, как большие и что с ними следует пользоваться оператором ==, а не is.

И это снова не фича, а деталь реализации для экономии ресурсов. А пишу я об этом, потому что на задачу с кешем наткнулась на собеседовании, и собеседующий удивился, что я в курсе. Сказал, что узнал об этой особенности, когда начал сам проводить собеседования. Теперь это знаете и вы 🐍

#кеш #детали_реализации #low_level #identity_vs_equality
🔥2👍1
Pythonic циклы

Поговорим о циклах в Python. Вот пример цикла, который мог бы написать человек с C или Java бэкграундом:

items = [‘a’, ‘b’, ‘c’]
i = 0
while i < len(items):
print(items[i])
i += 1

Что с ним не так?
🤖здесь мы вручную отслеживаем индекс, а значит, создаем себе возможность сделать ошибку на единицу. Легко заехать в элемент items[3], заменив < на <=;
🤖 если тело цикла длинное, то частая ошибка это сделать так, что условие выхода никогда не выполнится, и войти в бесконечный цикл;
🤖 в Python можно выразительнее.

А именно:

for item in items:
print(item)

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

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

Но что если вам правда, очень, действительно нужен индекс? Иногда это бывает, и тогда на помощь спешит конструкция enumerate(), которая предоставляет и сам элемент и его индекс:

for i, item in enumerate(items):
print(i)

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

Закончу цитатой.

«В программировании есть только две сложные вещи: инвалидация кеша, выбор имени переменной, и ошибки на единицу»
(Джефф Этвуд, создатель StackOverflow).

#циклы #кодстайл #питониста #pythonic
👍21🤔1
Странная переменная в Python.

Когда человек впервые видит в программе что-то вроде

_, v in my_dict.items():
pass

он обычно теряется. Что это за переменная _?

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

>>> a + b
20
>>> _
20
>>> _ * 2
40

В интерпретаторе это позволяет вообще не писать названия переменных и работать быстрее:

>>> list()
[]
>>> _.append(‘чепуха’)
>>> _.append(‘небылица’)
>>> _.append(‘выдумка’)
>>> _
[‘чепуха’, ‘небылица’, ‘выдумка’]

При этом _ -- абсолютно валидное имя. Настолько, что в скриптах его принято использовать для dummy-переменных, значениями которых пользоваться не собираются. Например:

for _ in range(4):
print(“Шутка, повторенная дважды, становится в 4 раза смешнее”)

Или вот так:

_, c = ['чепуха', 'небылица', 'выдумка']
print(‘{} {}’.format(a, c))
'чепуха, выдумка'

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

for _, v in my_dict.items():
pass

можно было с тем же успехом использовать values().

В общем, в Python у переменной _ две функции:
🥳 она хранит последнее вычисленное интерпретатором значение,
🥳 и ее принято использовать, чтобы проигнорировать значение переменной.

#underscores #словари #соглашения #основы
🔥3
Чеклист для адаптации на новом месте.

Итак, вы прошли n кругов ада собеседований и вышли на новую работу. У меня в это время обычно стресс. Делюсь своим списком того, что я на новом проекте делаю в первую очередь. Это помогает мне справиться с переживаниями, почувствовать себя нужной и структурировать происходящее.

🐍 Завести keepass -- мне очень лень записывать пароли в хранилку. Мне кажется, что я все запомню, а потом сами знаете, чем все заканчивается.
🐍 Получить доступы в репозиторий, тикетницу, доку, мониторинг, ... И сразу же добавить все хозяйство в keepass и в закладки в браузере.
🐍 Познакомиться с сеньором/сеньоритой проекта.
🐍 Попользоваться корпоративным спортом. Иногда люди не знают, где находится спортзал, какое в нем расписание, подолгу не могут принести какую-нибудь справку, в итоге по полгода не доходят туда.
🐍 То же с ДМС, если он есть.
🐍 Пообедать с командой.
🐍 Развернуть проект локально.
🐍 Спросить, прочитать в доке или посмотреть видео про бизнес-часть проекта.
🐍 Познакомиться с парой людей не из проекта.
🐍 Посмотреть все этажи офиса.
🐍 Проследить в коде проекта какой-нибудь сценарий работы, например, <<пользователь купил билет на нашем сайте>>.
🐍 Решить первую задачу в трекере.
🐍 Задокументировать что-нибудь, не покрытое докой. Это и легкая ачивка, и сэкономит время следующим поколениям.

Обратите внимание, что
1) такой план включает в себя пункты не только про проект, но и про погружение в культуру, команду, процессы,
2) пункты не нумерованы (можно делать в любом порядке),
3) пункты детерминированы (понятно как выполнить) и небольшие (0,5 -- 4 часа).

Дополните их своими или уберите лишние.

#онбординг #new_job #soft_skillz #после_собеса #успешность
Одинарное подчеркивание в конце переменной

Недавно говорила об одинарном символе подчеркивания в Python. Теперь поговорим о подчеркивании в конце переменной:
 
my_argument_ = 10

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

single_trailing_underscore_: используется по соглашению для избежания конфликтов с ключевыми словами языка, например:
 
Tkinter.Toplevel(master, class_='ClassName')

Поэтому в этом примере лучше просто написать my_argument, без подчеркивания. А вот если вам понадобится, например, что-то из id, class, del, ..., то можно добавить _. Правда, даже в этом случае я сама обычно выбираю более объясняющее имя, например, person_id, class_name, delimeter, чтобы не путать коллег и сделать код более читаемым.

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

#underscores #нейминг #соглашения #основы #кодстайл
🔥2
Есть вот такой словарь:
 
sports ={
'Nikolai':{
'weight':92,
'height':187,
'age':32
},
'Natasha':{
'weight':65,
'height':170,
'age':24
},
'Boris':{
'weight':87,
'height':180,
'age':28
}
}


Как получить имя самого старшего спортсмена?

Решение через полчаса.
Возможное решение:
 
max(sports, key=lambda k: sports[k]['age'])
🔥2
Спасибо за отзывы🌸 Видимо, здесь можно немного рассказать про lambda-функции. На самом деле это несложно, разберемся. Попозже напишу.
List astonishment

Допустим, мы хотим написать функцию, которая будет принимать на вход список. Естественным ходом выглядит написать что-то вроде:
 
>>> def foo(data=[]):
... data.append(5)
... return data

Ждем, что при каждом новом вызовы функции список снова будет пустым. На самом же деле:
 
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]

Вот это поворот! Причина такого поведения в том, что в Python значения дефолтных аргументов вычисляются только один раз -- при объявлении функции. То есть, после того, как инструкция def выполнена, список data уже создан. При этом пока функция существует, пересоздаваться список больше не будет. Это объясняет и вот такое поведение:
 
>>> foo(data=[1, 2, 3])
[1, 2, 3, 5]
>>> foo()
[5, 5, 5]

Видно, что, когда мы передали другой список в foo(), к нему добавилось значение 5. При этом аргумент по умолчанию не изменился.

Как с этим жить? Либо вообще не использовать изменяемые объекты в качестве дефолтных значний, либо инициализировать их как None:
 
>>> def bar(data=None):
... if data is None:
... data = []
... data.append(5)
... return data

Такое определение создает новый список в локальном неймспейсе. Теперь все чисто😏
  
>>> bar()
[5]
>>> bar()
[5]
>>> bar(data=[1, 2, 3])
[1, 2, 3, 5]

На самом деле это поведение можно обратить в свою пользу. Пусть, например, в программе есть функция, которая долго вычисляет результат. Можно кешировать параметры и результат вычисления, и, если функцию вызывают еще раз с теми же парметрами, просто возвращать заранее посчитанный ответ.
 
def baz(arg1, arg2, _cache={}):
# Если есть в кеше, выходим
if (arg1, arg2) in _cache:
return _cache[(arg1, arg2)]

# Вычисляем результат
result = ...
# Сохраняем в кеш
_cache[(arg1, arg2)] = result
return result

Сохранение результатов для предотвращения повторных вычислений называется мемоизацией.

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

Закончу цитатой:
Не повторяйте одну и ту же ошибку несколько раз. Напишите функцию с этой ошибкой и вызывайте, когда будет нужно.

#функции #мемоизация #кеширование #мутабельность #иммутабельность
👍1
for ... else?

Рассмотрим пример использования ключевого слова else в цикле while:
 
>>> i = 1
>>> while i < 4:
... print(i)
... i += 1
... else:
... print('Вышли из цикла!')

Если выполнить этот цикл, то мы получим:
 
1
2
3
Вышли из цикла!

Здесь блок else выполняется только тогда, когда условие выхода из цикла перестает быть верным. Особенность здесь в том, что если вывалиться из цикла через break или через исключение, то блок else выполняться не будет.

Это работает не только с циклами while, но и с циклами for:
 
>>> for value in values:
... if value == 4:
... print('Нашли!:)')
... break
... else:
... print('Не нашли:(')

И так же можно с try-except блоками. На самом деле исключения в Python это не try-except-finally, а try-except-else-finally, ниже пример:
 
>>> num = 0
>>> try:
... result = 1 / num
... except ZeroDivisionError:
... print('Деление на ноль!')
... else:
... print('Конечное число.')
... finally:
... print('Вычисления закончены.')
...
Деление на ноль!
Вычисления закончены.

А если задать num = float('Inf'), то в результате деления мы получим 0 и вывод будет:
 
Конечное число.
Вычисления закончены.

Блок finally выполняется в любом случае. А else можно использовать, если нужно выполниь еще какой-то код в случае отсутствия исключений. Например, это может быть полезно в тестах, когда мы хотим логгировать как в случае падения теста на исключении, так и в случае, если исключений брошено не было.

В общем, есть конструкции while-else и for-else, в которых else отрабатывает в случае <<штатного>> завершения цикла. Еще else можно использовать в try-except блоке, чтобы выполнить какой-то код только в случае успешного выполнения блока try. Во всех случаях использование else позволяет более гибко управлять потоком выполнения 🐍

#циклы #исключения #else #условия #основы
👍2
Timsort

Для сортировки в CPython используется алгоритм Timsort, который его создатель Тим Петерс застенчиво назвал в честь себя😏. Timsort — это комбинация сортировки вставками и сортировки слиянием, заточенная под работу с реальными данными. Дело в том, что на практике массивы, которые нужно сортировать, часто бывают частично упорядочены и Timsort пользуется этим предположением для ускорения работы.

Логика работы Timsort на самом деле довольно прозрачная:
🐠 Timsort делит входной массив на подмассивы;
🐠 Сортирует подмассивы вставками;
🐠 Объединяет их попарно сортировкой слиянием;
🐠 И возвращает отсортированный массив.

Но дьявол кроется в деталях.

Делим на подмассивы

Части, на которые алгоритм делит входной массив, в описании алгоритма автор называет run'ами. Так на какое количество run'ов следует разделить входной массив? Здесь два соображения. Во-первых, части должны быть достаточно короткими, чтобы их было выгодно сортировать вставками. А еще мы хотим, чтобы пары последовательностей, которые позже поступят в сортировку слиянием, были примерно одинаковой длины. Дальше происходит магия, которая учитывает эти соображения и выбирает длину подпоследовательности minrun между 32 и 64 элементами. При этом если входной массив короче 64 элементов, то на части он не делится.

Сортируем run'ы

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

Сливаем подмассивы

По мере того, как упорядоченные run'ы формируются, сортировкой слияния они объединяются попарно во всё более крупные отсортированные куски. При этом, если алгоритм при слиянии последовательностей встречает два участка вида

a = [1, 2, 3, 4]
b = [5, 6, 7, 8]

то по понятным причинам может объединить их быстрее, чем поэлементным перебором. Эта модификация сортировки называется galloping, поскольку в этом режиме алгоритм переносит на новое место не один элемент, а сразу несколько. Досортировки и слияния повторяются до тех пор, пока не достигается полная упорядоченность входного массива.

Вывод

Объем алгоритма в исходнике примерно 2к строк и, как можно догадаться по описанию, код довольно запутанный. Зато за счет использования упорядоченности в реальных данных сложность Timsort в лучшем случае линейная. В худшем случае, если входные данные случайные, timsort справится за O(n log n), а это неплохо для сортировки. Кстати, несмотря на то, что Timsort изначально был придуман в Python-сообществе, сейчас он используется еще в Java, Swift и Android platform.

Вот что нужно запомнить о Timsort:
🐠 Timsort это гибридный алгоритм сортировки, сочетающий модифицированные варианты сортировки вставками и сортировки слиянием;
🐠 Timsort пользуется тем, что реальные данные часто бывают частично упорядочены;
🐠 В лучшем случае сложность O(n), в худшем O(n log n)).

Визуализацию работы алгоритма можно посмотреть здесь. Видно, как сначала строятся run'ы и как они сливаются. Обратите внимание, что в обоих прогонах всегда объединяются примерно равные по размеру части 🐍
Хочу поделиться новостью. Меня внезапно пригласили выступить на Science Slam в Питере через неделю. Science Slam -- это битва учёных в формате стенд-ап. У каждого спикера по 10 минут на рассказ о своей разработке и ещё по 5 на вопросы. Победителя определяют, замеряя с помощью шумомера, кого аудитория поддерживает больше.

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

Stay tuned🐠
​​Выиграла со своей речью про беспилотники😱 Везу домой перчатки победителя.
Введение в множества

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

В математике множество -- это набор объектов произвольной природы. В Python множество тоже может содержать переменные разных типов, например:
 
>>> A = {"My hovercraft is full of eels", 42, (3.14, 2.72)}

Но есть одно ограничение: элементы множества должны быть хэшируемыми: например, в множество можно добавить строки, числа и кортежи, но нельзя словари и списки:
 
>>> A.add([-1, 0])
TypeError: unhashable type: 'list'

При этом поскольку сами множества мутабельны, то множество множеств, как в известном парадоксе про брадобрея, сделать не получится. Но если очень хочется, можно использовать frozenset’ы: эти объекты в остальном ведут себя почти так же, но они иммутабельны и их можно добавить в множество.

Инициализировать множество можно используя фигурные скобки или через конструктор класса set(). Эти инициализации эквивалентны:
 
>>> B = {1, 2, 3}
>>> B = set((1, 2, 3))

Только не запутайтесь: такая инструкция
 
C = {}

создаст не множество, а словарь.

В версиях интерпретатора 2.7 и выше работают set comprehensions:
 
>>> C = {x for x in range(1, 5)}
>>> C
{1, 2, 3, 4}

Элементы множества, как и в математике, должны быть уникальными. На практике этим пользуются для того, чтобы исключить повторяющиеся элементы:
 
>>> D = [1, 2, 3, 3]
>>> D = list(set(D))
>>> D
[1, 2, 3]

Кстати, множества не сохраняют порядок, поэтому если он нужен, то добро пожаловать в списки. Из-за того, что порядка нет, во множествах нет ни индексирования, ни слайсов. В то же время циклы по множеству можно делать обычным pythonic-способом:
 
>>> for elem in C:
... print(elem)

Над множествами в Python можно делать те же операции, что и в математике: находить объединение, пересечение, проверять принадлежность к множеству и так далее. Для этого можно пользоваться операторами, а можно методами множеств:
 
A | B A.union(B)
A & B A.intersection(B)
A - B A.difference(B)
A <= B A.issubset(B)
A => B A.issuperset(B)


Обратите внимание, что операторы принимают только set’ы, а методы -- любые iterable контейнеры. Есть мнение, что операторы менее читаемые, но оба подхода в целом равноправны.

И еще у класса set есть методы, которые удобны для работы со множествами как с коллекциями:

add() — добавить элемент,
remove() — удалить элемент,
pop() — извлечь с удалением,
update() — объединить с другим множеством,
clear() — очистить множество.

И напоследок: один раз я больно отстрелила себе ногу, когда хотела добавить составной элемент в множество с помощью неправильного инструмента. Например, если у нас есть множество строк и мы пытаемся добавить в него еще один элемент вот так:
 
>>> F = {"Seregia", "Vasia"}
>>> F.update("Alisa")

то получаем ожидаемый результат:
 
>>> F
{'l', 'a', 'Seregia', 'Vasia', 'A', 'i', 's'}

Это абсолютно валидный код и он отработает, поэтому такую ошибку по невнимательности можно искать довольно долго. Так что не попадайте в ловушку методов add() и update().

В общем, Python set'ы сильно напоминают математические множества: могут содержать объекты разных типов, требуют уникальности элементов, не сохраняют порядок и имеют методы, позволяющие их объединять, пересекать, etc... Кроме того, множества имеют интерфейс для работы с ними как с коллекциями.

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

#основы #типы_данных #comprehensions #списки #селяви #коллекции
👍2
Умножение контейнеров

Один интересный факт из Python. Вы, наверное, знали, что для строк определен оператор умножения и что можно делать так:

>>> 'привет ' * 4
'привет привет привет привет '

То же, вообще говоря, работает с другими контейнерами, например, с кортежами:

>>> ('привет',) * 3
('привет', 'привет', 'привет')

И с этим все хорошо, пока внутри контейнера не появляется ссылка на изменяемый объект. Например, на словарь:

>>> d = [{'key': 'val'}] * 2
>>> d
[{'key': 'val'}, {'key': 'val'}]

Потому что теперь каждый из двух словарей доступен по одной и той же ссылке:

>>> d[0]['key'] = ''
>>> d
[{'key': ''}, {'key': ''}]

Упс, отстрелили себе ногу. Умножение -- это валидный способ создавать составные объекты только если вложенные неизменяемы. 🐠

#типы_данных #funfact #контейнеры #мутабельность #иммутабельность
👍1
Как зарешивать, если нервничаешь.

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

Задача не страшная, но на собеседованиях я обычно слишком нервничаю. Думать аналитически в таком состоянии я не могу, поэтому раньше это выглядело так: кандидатка получает условие и зависает в тишине на следущие пять минут. А когда атмосфера уже накаляется, начинает молча писать первое запутанное и неоптимальное решение. То ещё первое впечатление🧐

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

Я не бросаюсь сразу писать, а сначала вслух проговариваю первый наивный алгоритм, который пришел в голову. На этом шаге я не хочу супероптимально, мне надо придумать хотя бы что-то. Это даст мне ощущение первого успеха, успокоит и поможет установить контакт с собеседующим. А интервьюеру даст возможность навести меня на мысль уточняющим вопросом, если он захочет.

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

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

И, если у меня ещё остаётся время, прямо на бумаге пишу тесты.

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

🐢 Читаю условие вслух,
🐢 Проговариваю, как собираюсь решать,
🐢 Обдумываю краевые условия,
🐢 Пытаюсь оптимизировать,
🐢 Только теперь пишу код,
🐢 И, если надо -- тесты.

В то время у меня ещё не было опыта и то собеседование я провалила. Сейчас у меня есть какая-то тактика прохождения собеседований (и я ее придерживаюсь), и все проходит заметно легче.

А несколько вариантов, как поменять две переменные местами, я нашла тут. Если эта задача вам встретится, будете во всеоружии. Если на ум приходит ещё решение, отправьте его мне в обратную связь, а я сделаю отдельный пост с самым классным. 🐠
2😁1
Pickles

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

Во многих языках программирования есть встроенные инструменты для сериализации. Например, в Java есть java.io.Serializeable, в Ruby — Marshal, а в Python есть pickle. Смотрите, как просто этим инструментом сериализовать почти любой питонский объект:
 
import pickle

original = [1, 2, 3]

with open('myfile.pickle', 'wb') as outfile:
pickle.dump(original, outfile)
with open('myfile.pickle', 'rb') as infile:
identical = pickle.load(infile)

print(original == identical) # True

Вся прелесть здесь в том, что можно в пару строк сохранить объект как он есть, без необходимости писать свой сериализатор или конвертировать данные в примитивные типы JSON.

Как это работает? Идея в том, что pickle содержит информацию о том, как реконструировать объект. Если мы попробуем запринтить строку, которую формирует pickle после сериализации списка выше, то увидим две вещи: ссылку на объявление класса, какое-то количество нечитаемых символов, и значения полей. Приблизительно это читается интерпретатором как “возьми вот этот класс и положи в него значения 1, 2, 3”.

Обратите внимание, что мы сохраняем состояние объекта, а не объявление. То есть, мы знаем содержимое полей этого класса, но остаемся в неведении относительно его структуры. Это важно для пользовательских типов, потому что в программе, в которой мы собираемся распаковывать pickle, этот класс должен быть объявлен. Иначе мы не сможем его распаковать -- непонятно, какой именно объект инициализировать этими данными.

За простоту использования pickle полюбили в data science. Оказалось удобно сохранять промежуточные результаты вычислений в полях класса, чтобы к расчетам можно было вернуться позже, без необходимости обучать модель заново.

Но у pickle есть несколько минусов.

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

2. Pickle не человеко-читаемы. У pickle есть несколько серий протокола, ранние старались делать такими, чтобы их можно было читать глазами, поздние уже нет, но это все равно мало что изменило: если потом захочется поисследовать данные с помощью cat/head/grep и других полезных утилит bash, то вас будет ждать разочарование. Чтобы прочитать pickle с файловой системы, понадобится Python скрипт -- и это не очень удобно.

3. Pickle не слишком быстрые, по крайней мере, проигрывают модулю json в скорости.

4. И последнее: pickle небезопасны. Первое, что написано в документации к модулю -- это предупреждение:
 
Warning The pickle module is not secure. Only unpickle data you trust.

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

В общем, pickle это питонский формат для сериализации данных. Он удобен, когда нужно на скорую руку сохранить состояние объекта. Pickle хорошо использовать в случаях, когда думаете, что файл не проживет долго и когда нет фокуса на вопросах безопасности. Зная все это, там, где можно, я все же выбираю JSON: это быстрее, это читаемее, это безопаснее. Правда, кода нужно писать чуть больше🐠

PS: в посте пара ссылок на доку и статьи, пооткрывайте, если есть минутка.
👍1
Гид по множественному присваиванию

Уверена, что множественное присваивание в Python видели все. Выглядит оно вот так:

>>> x, y, z = 1, 2, 3
>>> x
1
>>> z
3

На самом деле на низком уровне создается tuple и в цикле инициализируется значениями 1, 2, 3. Поэтому с тем же успехом можно не опускать скобки и написать вот так:

>>> (x, y, z) = (1, 2, 3)
>>> x
1
>>> z
3

И это тоже сработает. Поэтому множественное присваивание еще называют tuple unpacking. Да и чаще всего его, кажется, используют именно с кортежами. Но вообще так можно писать с любыми iterable типами:

>>> x, y = [1, 2]
>>> y
2

И со строками тоже:

>>> x, y = 'hi'
>>> x
'h'
>>> y
'i'

Множественное присваивание, если применять его с оператором *, позволяет делать еще вот такие крутые штуки:

>>> numbers = [1, 2, 3, 4, 5, 6]
>>> first, *rest = numbers
>>> first
1
>>> rest
[2, 3, 4, 5, 6]

По сути * здесь заменяет слайсы. С тем же успехом можно было написать:

>>> first, last = numbers[0], numbers[1:]
>>> first
1
>>> last
[2, 3, 4, 5, 6]

Но через распаковку получается читаемее. Можно даже <<вырезать>> середину:

>>> first, *middle, last = numbers

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

>>> first, *_, last = [1, 2, 3, 4, 5, 6]

Кстати, если речь о вложенных объектах, то обратите внимание, что копия будет глубокой:

>>> color, point = ('blue', (0, 0, 255))
>>> color
'blue'
>>> point
(0, 0, 255)

В некоторых случаях это очень удобно.

В общем, множественное присваивание можно использовать не только с кортежами, но и списками, строками и любыми iterable типами. Его плюс в том, что оно позволяет не использовать индексы, а значит, уменьшает склонность к ошибкам и делает код читаемее. А еще оно используется в <<args, kwargs>> синтаксисе, который часто смущает начинающих, но об этом в следующий раз. 🐠
🔥1