В Python много отличных доступных «из коробки» модулей. Один из самых полезных — collections. Он содержит «специализированные типы для создания контейнеров», являющихся альтернативами универсальным dict, list, set и tuple. Ниже мы рассмотрим три содержащихся в модуле класса, с которыми большинство питонистов сталкивались, но постоянно забывают применять на практике.
NamedTuple
Сложно переоценить полезность именованных кортежей для дата-саентистов. Скажем, занимаетесь вы созданием новой модели, постоянно добавляя новые характеристики в список, который затем планируете скормить фреймворку для машинного обучения. С ростом количества характеристик, рано или поздно вы начнёте путаться и забывать, какой индекс за что отвечает. Ещё хуже, если с вашим кодом придётся работать коллегам: они могут вообще ничего не понять.
Всего пара строк может привести скрипт в порядок. Смотрите:
from collections import namedtuple
Features = namedtuple('Features', ['age', 'gender', 'name'])
row = Features(age=22, gender='male', name='Alex')
print(row.age)
Теперь для доступа к элементам строки вместо указания индексов можно использовать имена, что делает код значительно чище и проще.
Counter
Counter, как следует из названия, считает. Звучит несложно, но дата-саентистам нужно вести подсчёты постоянно, поэтому инструмент крайне полезен на практике.
Есть несколько способов создать счётчик, но самый простой — инициализировать его списком значений:
from collections import Counter
ages = [22, 22, 25, 25, 30, 24, 26, 24, 35, 45, 52, 22, 22, 22, 25, 16, 11, 15, 40, 30]
value_counts = Counter(ages)
print(value_counts.most_common())
Запустив этот код (что, кстати, можно сделать передав соответствующий сниппет в pythonanywhere.com/gists/), вы увидите:
[(22, 5), (25, 3), (24, 2), (30, 2), (35, 1), (40, 1), (11, 1), (45, 1), (15, 1), (16, 1), (52, 1), (26, 1)]
Список кортежей в порядке убывания распространённости значений, где первый элемент кортежа — значение, а второй — как часто оно встречается в изначальном списке. Пары строк кода оказалось достаточно, чтобы узнать, что «22» — самый распространённый возраст, и встречается он 5 раз.
DefaultDict
Один из моих самых любимых инструментов стандартной библиотеки. DefaultDict — словарь с дефолтным значением для любого нового ключа. Пример:
from collections import defaultdict
my_default_dict = defaultdict(int)
for letter in 'the red fox ran as fast as it could':
my_default_dict[letter] += 1
print(my_default_dict)
Возвращает:
defaultdict(<type 'int'>, {'a': 4, ' ': 8, 'c': 1, 'e': 2, 'd': 2, 'f': 2, 'i': 1, 'h': 1, 'l': 1, 'o': 2, 'n': 1, 's': 3, 'r': 2, 'u': 1, 't': 3, 'x': 1})
При работе с обычным словарём пришлось бы постоянно проверять, существует ли ключ, и инициализировать несуществующие ключи вручную. В примере выше для каждого несуществующего ключа уже есть значение по умолчанию — 0. Это позволяет писать код чище и понятнее.
Помимо целочисленного, DefaultDict часто используют в связке с пустым списком, чтобы начинать добавление элементов без бойлерплейта.
Вперёд — к чистому коду!
Попробуйте вспомнить, можно ли применить классы из collections к задачам, которые вы недавно решали. Иногда «переоткрытие» старых модулей стандартной библиотеки приносит больше пользы, чем освоение новых инструментов.
Комментарии (26)
Ormgair
09.12.2019 12:39При работе с обычным словарём пришлось бы постоянно проверять, существует ли ключ, и инициализировать несуществующие ключи вручную.
Не обязательно, у словарей есть метод get
my_default_dict = dict() for letter in 'the red fox ran as fast as it could': my_default_dict[letter] = my_default_dict.get(letter,0) + 1 print(my_default_dict)
Вывод:
{'a': 4, ' ': 8, 'c': 1, 'e': 2, 'd': 2, 'f': 2, 'i': 1, 'h': 1, 'l': 1, 'o': 2, 'n': 1, 's': 3, 'r': 2, 'u': 1, 't': 3, 'x': 1}
evil_homer
09.12.2019 14:18А еще есть setdefault
new = {} for key, value in data: group = new.setdefault(key, []) # key might exist already group.append(value)
stalker1984
09.12.2019 12:42Статью надо было назвать — "три полезных для датасайнтистов <\b> функции из библиотеки ..."
ardraeiss
09.12.2019 14:34Справедливости ради, не только для них упомянутое полезно может быть. Но сам текст прямой речь именно про дэйтасцайнс, что есть то есть.
deadmoroz14
09.12.2019 16:47Мне кажется, дата сайнтисты ванильным питоном не пользуются. Pandas в руки и вперёд.
Первые 2 проблемы решаются из коробки.CrazyElf
09.12.2019 19:25Pandas часто слишком медленный в текущей реализации, поэтому и чистый Python и Numpy массивы/матрицы очень даже в ходу у дата-сатанистов. Можно убедиться, посмотрев кернелы на Kaggle.
arthuriantech
09.12.2019 14:41У встроенных словарей есть метод
__missing__
который может обрабатывать промахи в словаре:
class MyDict(dict): def __missing__(self, key): print("Oh no") return 123
MikiRobot
09.12.2019 17:56Так будет короче
dictionary.get('not_found_key', 123)
ardraeiss
09.12.2019 18:06Разное ведь — missing для личной обработки отсутствия элемента в своём классе, get со вторым параметром для стандартных.
arthuriantech
09.12.2019 18:14Этот метод работает только с оператором [] и предназначен для других вещей — создания собственных классов словарей с видоизмененной логикой d[key] без оверхеда:
>>> class Counter(dict): ... def __missing__(self, key): ... return 0 >>> c = Counter() >>> c['red'] 0 >>> c['red'] += 1 >>> c['red'] 1
>>> class Default(dict): ... def __missing__(self, key): ... return key ... >>> '{name} was born in {country}'.format_map(Default(name='Guido')) 'Guido was born in country'
alexyr
09.12.2019 19:28from collections import OrderedDict
особенно удобен для работы с JSON файлами, чтобы сохранить внутренний порядок элементовwildraid
09.12.2019 23:28Если не ошибаюсь, обычный dict уже сохраняет порядок примерно с версии 3.6.
alexyr
09.12.2019 23:29Завидую тем, кто может писать только для третьего питона
sumej
10.12.2019 09:47До 1.1 2020 рукой подать а после апреля можно расслабиться https://www.python.org/dev/peps/pep-0373/
netch80
10.12.2019 10:10Причём для >=3.6 :)
для 2.7 и для 3.0-3.5 порядок ключей различный даже при одинаковом seed?е.
1755
С появлением python3.7 вместо named tuples лучше использовать dataclasses :)
ZuOverture
Мм, named tuple c регулируемой мутабельностью и дефолтными значениями без кровотечения из глаз… Спасибо.
ardraeiss
dataclasses вообще вкусная конфигурируемая штука.
Murtagy
А что насчет производтельности? Туплы очень легкие структуры
dvenum
Нагуглил вот такую табличку, в которой говорится, что dataclass быстрее, хотя и чуть дольше создавать новый объект.
eugals
Насчёт потребления памяти к dataclasses в их нынешнем виде есть много претензий. NamedTuple они в этом по-прежнему уступают.
Проблему можно обойти, если использовать дополнительную обёртку вроде этой. Но какой смысл было так торопиться, чтобы непременно зарелизить эти
dataclasses
во чтобы то не стало в 3.7 без предварительного решения такого ключевого вопроса, как встроенная поддержка slots — от меня ускользает.ADR
https://www.python.org/dev/peps/pep-0557/#support-for-automatically-setting-slots
worldmind
Murtagy Их минус в сравнении с кортежами — не юзают и почти что не умеют юзать
__slots__
что для датакласов странно — и ошибиться легче и памяти больше расходуется.TrueMaker
C появлением 3.7 не мало вещей так же и сломалось, что держит не мало компаний от миграции с 3.6. А также не так уже и много разработчиков будут уделять внимание новым плюшкам ради обратной совместимости. Пруфа ради чего только стоят попытки делать код обратно совместимым с Python 2.7. Если люди идут даже на такое, то о dataclasses на ближайшие лет 5 можно забыть.