Size: a a a

2019 September 17
Python in Depth
test message
источник
2019 September 18
Python in Depth
Чем мутабельные объекты отличаются от иммутабельных?

В python объекты бывают мутабельные и иммутабельные. Значние иммутабельного объектра нельзя изменить после того, как он был создан, а значение мутабельного можно.

Рассмотрим на примере. В Python есть встроенная функция id(). В реализации CPython она возвращает адрес, по которому объект находится в памяти. Создадим список и посмотрим, в ячейке с каким номером он окажется.

>>>beatles = [“Ringo”, “Paul”, “John”, “George”]
>>>id(beatles)
140181376779336

Видим, что список лежит по адресу 140181376779336. Теперь изменим какой-нибудь элемент списка и посмотрим, изменился ли адрес.

>>>beatles[0] = “Pete”
>>>beatles
[“Pete”, “Paul”, “John”, “George”]
>>>id(beatles)
140181376779336

Видим, что адрес списка не меняется, если мы изменяем элементы, которые в него входят. Теперь создадим в памяти строку

>>>bass = "Paul"
>>>id(bass)
140068833595776

и добавим в нее что-нибудь

>>>bass += " McCartney"
>>>bass
Paul McCartney
>>>id(bass)
140068833600432

Видим, что адрес строки изменился.

Почему так? Строка иммутабельна, то есть, после того, как она уже создана, нельзя изменить ее содержимое. Поэтому интерпретатор перезаписывает ее по новому адресу с новыми символами. А список мутабелен, поэтому его адрес не меняется оттого, что мы заменили один элемент.

Вот какие встроенные типы есть в python:

Класс   Описание       Мутабелен?
---------------------------------
bool  булевый тип               ☹️
int   целочисленный тип         ☹️
float число с плавающей запятой ☹️
list  список                    ☺️
tuple кортеж                    ☹️
str   строка                    ☹️
set   множество                 ☺️
dict  словарь                   ☺️

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

Важно отличать мутабельность от иммутабельности, так как некоторые типы данных, например, dict или set, реализованы как хэш-таблицы. Когда вы добавляете элемент в множество, интерпретатор берет хэш от этого элемента и размещает значение по адресу, который совпадает с хэшом. Если мы попытаемся положить список в множество, то получим TypeError, потому что хэш бы изменился, когда изменился бы один из элементов списка:

TypeError: unhashable type: 'list'

Поэтому мутабельные объекты, такие как словари или списки, не могут быть ключами в словаре или элементами множества. А вот строки или числа могут. И это на самом деле очень естественно🐍

#перед_собесом #основы #типы_данных #мутабельность #иммутабельность
источник
2019 September 20
Python in Depth
Какова сложность операции добавления элемента в список?

Чтобы ответить на этот вопрос, нужно разобраться, как списки устроены на низком уровне. Определение списка в исходниках CPython выглядит так:

typedef struct {
 PyObject_VAR_HEAD
 PyObject **ob_item;
 Py_ssize_t allocated;
} PyListObject;

Здесь ob_item -- это непрерывный массив указателей на элементы списка, а allocated содержит длину массива. Вот и все, что нужно знать, чтобы отвечать на вопросы о сложности операций со списками.

Во-первых, отсюда сразу видно, что получить длину массива можно очень быстро, за O(1), потому что не нужно пересчитывать все элементы.

len(a)    # O(1)

Ещё сразу понятно, что при такой реализации стоимость индексирования не зависит от длины списка или значения индекса, так как изменить значение по известному номеру ячейки тоже можно тоже за O(1).

a[10] = 5    # O(1)

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

5 in a    # O(n)

Добавление  одиночных элементов в конец списка (метод append()) в большинстве случаев работает за O(1). Но здесь есть одна деталь: место в памяти для массива указателей аллоцируется заранее. И когда занятый объем памяти истощается, интерпретатор выделяет ещё примерно 15% от объема массива. Эта периодическая экспансия время от времени несколько замедляет добавление новых элементов в список, поэтому об O(1) можно говорить условно.

a.append(5)    # ~O(1)

Еще одна распространенная потребность это конкатенировать списки, например, с помощью оператора +. Сложность конкатенации линейно зависит от длины присоединяемого списка: если его длина 100 элементов, то нужно 100 раз добавить в конец новый элемент.

a + b    # ~O(k)

Чтобы получить доступ к элементам слайса a[i:j], нужно итерироваться между индексами i и j. Поэтому сложность такой операции O(k), где k -- длина слайса. А вот удалить слайс это O(n), из-за необходимости сдвинуть все следующие за удаленным участком элементы на новые места, здесь n -- длина всего массива.

a[i:j]    # ~O(k)
del a[i:j]    # ~O(n)

Метод, который получает и удаляет последний элемента списка pop(), имеет сложность O(1), в то время как pop(i) элемента из середины списка требует O(n). Почему так? Потому что после того, как один указатель был удален из середины или начала, нужно передвинуть все следующие за ним на 1 позицию вперёд. А когда элемент был удален из конца массива, этого делать не нужно. По этой же причине введение или элемента в середину списка тоже дорогая операция и требует O(n) операций.  
     
a.pop()     #O(1)
a.pop(i)     #O(n)
a.insert(i, item) #O(n)

Обобщим:

Операция     Сложность
----------------------
len(a)            O(1)
a[i]              O(1)
a[10] = 5         O(1)
5 in a            O(n)
a.append(5)      ~O(1)
a + b             O(k)
a[i:j]            O(k)
del a[i:j]        O(n)
a.pop()           O(1)
a.pop(i)          O(n)
a.insert(i, item) O(n)

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

В общем случае, сложность добавления элемента в список зависит от того, в середину массива мы добавляем элемент или в конец. Добавить элемент в начало или середину списка это O(n), но операция append() требует только O(1)🐍

#перед_собесом #low_level #типы_данных #списки #алгоритмы #сложность #hardcore
источник
2019 September 23
Python in Depth
Что такое временная сложность алгоритма?

Часть 1.

Когда в детстве меня учили умножать числа, мне говорили, что смысл умножения в том, чтобы короче записать сумму. Например, 4 * 3 это то же, что 4 + 4 + 4.

Сведение умножения к сумме -- самый простой, наивный алгоритм умножения. А теперь я возьму мой рабочий ноут и попробую перемножить этим способом какие-нибудь большие числа, например, 4 * 10000000000:

sum = 0
for i in range(0, 10000000000):
  sum += 4

print(sum)

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

Как понять, быстро ли работает алгоритм? Можно попробовать измерить время напрямую:

import time
start = time.time()
my_awesome_alg()
end = time.time()
print(end - start)

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

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

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

Временную сложность алгоритма в худшем случае выражают с использованием нотации «O» большое. В этом случае из рассмотрения исключают члены меньшего порядка и говорят об изменении используемых ресурсов при стремлении размера входа к бесконечности. Например, если время, которое нужно алгоритму для выполнения работы, для всех входов длины n не превосходит 5n^3 + 3n, то асимптотическая временная сложность равна O(n^3). 🤓

#сложность #теория_сложности #перед_собесом #часть
источник
Python in Depth
Что такое временная сложность алгоритма?

Часть 2.

Рассмотрим несколько примеров.

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

O(n)
Алгоритм, в котором мы идём по списку, чтобы узнать, есть ли в нем значение 42, в худшем случае требует O(n) операций, где n -- длина списка. Такую же сложность имеет сравнение строк, потому что по сути оно тоже требуют обхода всей строки.

O(log n)
Проверить, есть ли значение 42 в списке, можно быстрее, чем за O(n), если список отсортирован. Тогда можно проверить на равенство элемент из середины списка. Если он равен 42, то останавливаемся. Если больше -- значит, слева можно не искать, там значения только меньше. Будем продолжать проверку, выбирая каждый раз элемент из середины оставшегося отрезка. Этот алгоритм называют бинарным поиском и он имеет логарифмическую сложность, потому что количество вариантов уменьшается каждый раз на 2, как функция, обратная степенной (она же логарифм).

O(n log n)
Можно показать, что большинство алгоритмов сортировки имеют сложность n log(n). За время n log(n) работают сортировка слиянием, кучей, и быстрая сортировка. Еще есть теорема, которая говорит, что если сортировка основана на сравнении элементов, то быстрее, чем за n log(n) отсортировать элементы не получится.

O(n^2)
За время n^2 работает обход двумерного массива, это можно представить себе как обход таблицы n * n. И ещё за это же время работают некоторые не очень эффективные по времени алгоритмы сортировки, например, сортировка пузырьком

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

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

#сложность #теория_сложности #перед_собесом #часть
источник
Python in Depth
Как писать резюме

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

Сделайте так, чтобы каждая строчка резюме работала на две эти цели и уберите из текста все, что вас к ним не приближает. Например, мое первое образование экономическое и если я иду на backend разработку, у меня нет причин о нем говорить. Но для финтех компаний у меня есть вторая версия:) То же с водительскими правами: в hh есть это поле, но его не обязательно заполнять, если не идете разрабатывать беспилотный автомобиль. Работа грузчиком или кассиром тоже не имеет отношения к программированию. Не мешайте читателю понять, стоит ли с вами поговорить лично.

Каждая запись в моем резюме отвечает на вопросы, что я сделала на прошлой работе (именно <<сделала>>, а не <<делала>>) и чему при этом научилась. Работали на проекте -- опишите этот опыт. В своей первой компании я задержалась на 2,5 года, но там можно было менять проекты или работать над несколькими в параллель. Эти опыты разные и о каждом из них я рассказываю отдельно.

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

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

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

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

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

И еще об оформлении: в нашей культуре люди читают сверху вниз и слева направо. Можно организовать резюме в две колонки, можно в одну. Запрограммируйте опыт человека, который будет идти глазами по тексту. Что он увидит в первую очередь? Что потом? К каким выводам он скорее всего придет? Если не уверены, как верстать лучше, верстайте проще.

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

#резюме #ищем_работу #soft_skillz #начинающим
источник
2019 September 24
Python in Depth
Чем == отличается от is?

==  проверяет, одинаковые ли значения у переменных.
is проверяет, указывают ли переменные на один и тот же объект.

Рассмотрим на примере. Создадим словарь и скопируем ссылку на него:

a = [1, 2, 3] 
b = a
b is a   # True
b == a   # True

Теперь создадим новый словарь с помощью копирования:

b = a[:]
b is a   # False

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

>>> b == a    # True    

То есть, a is b по сути то же, что id(a) == id(b). Поэтому если a is b вернет True, это будет значить, что и содержимое у них одинаковое. Просто потому что это один и тот же объект.

Кстати, чтобы проверить, есть ли вообще у переменной значение, обычно пользуются оператором is, а не ==:

a = 42
a is None    # False

Дело в том, что класс None реализован как синглтон. Это значит, что экземпляр этого класса существует в интерпретаторе в единственном числе и все неопределенные значения указывают на него.

a = None 
b = None
a is b    # True

Для некоторых классов оператор == тоже сработает, но так лучше не делать, потому что в пользовательских классах этот оператор можно переопределить и выстрелить себе в ногу получить интересные результаты. Поэтому в PEP8 и рекомендуют проверять на значение None именно с помощью is. 🐍

#основы #identity_vs_equality #перед_собесом #типы_данных
источник
2019 September 25
Python in Depth
Пять способов создать словарь

Словарь -- структура, которая позволяет хранить данные в формате ключ-значение. Словари удобны для того, чтобы передавать информацию внутри программы, они быстрые, легко конвертируются в формат 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 #загадка
источник
2019 September 26
Python in Depth
Грамотно ставим запятые

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

>>> 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 будет читаться лучше, потому что утилита подсветит только новую строку.

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

#контейнеры #синтаксис #основы
источник
2019 October 01
Python in Depth
Кеширование небольших чисел в 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
источник
2019 October 03
Python in Depth
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
источник
2019 October 05
Python in Depth
Странная переменная в 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 #словари #соглашения #основы
источник
2019 October 10
Python in Depth
Чеклист для адаптации на новом месте.

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

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

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

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

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

Недавно говорила об одинарном символе подчеркивания в 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 #нейминг #соглашения #основы #кодстайл
источник
2019 October 14
Python in Depth
Есть вот такой словарь:
 
sports ={  
   'Nikolai':{  
       'weight':92,
       'height':187,
       'age':32
   },
   'Natasha':{  
       'weight':65,
       'height':170,
       'age':24
   },
   'Boris':{  
       'weight':87,
       'height':180,
       'age':28
   }
}


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

Решение через полчаса.
источник
Python in Depth
Возможное решение:
 
max(sports, key=lambda k: sports[k]['age'])
источник
Python in Depth
Спасибо за отзывы🌸 Видимо, здесь можно немного рассказать про lambda-функции. На самом деле это несложно, разберемся. Попозже напишу.
источник
2019 October 16
Python in Depth
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 дефолтные аргументы инициализируются только при объявлении функции. Это значит, что изменения, которые делаются в мутабельных аргументах при вызове функции, сохраняются. Поэтому лучше не использовать списки, словари и экземпляры самописных классов как значения по умолчанию. Либо использовать специально, как кеш, в котором можно хранить полезную информацию🐍

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

#функции #мемоизация #кеширование #мутабельность #иммутабельность
источник
2019 October 18
Python in Depth
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 #условия #основы
источник
2019 October 22
Python in Depth
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'ы и как они сливаются. Обратите внимание, что в обоих прогонах всегда объединяются примерно равные по размеру части 🐍
источник