В 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)


  1. 1755
    09.12.2019 11:43

    С появлением python3.7 вместо named tuples лучше использовать dataclasses :)


    1. ZuOverture
      09.12.2019 14:04

      Мм, named tuple c регулируемой мутабельностью и дефолтными значениями без кровотечения из глаз… Спасибо.


      1. ardraeiss
        09.12.2019 14:33

        dataclasses вообще вкусная конфигурируемая штука.


    1. Murtagy
      09.12.2019 14:18

      А что насчет производтельности? Туплы очень легкие структуры


      1. dvenum
        09.12.2019 16:21

        Нагуглил вот такую табличку, в которой говорится, что dataclass быстрее, хотя и чуть дольше создавать новый объект.


      1. eugals
        09.12.2019 16:45

        Насчёт потребления памяти к dataclasses в их нынешнем виде есть много претензий. NamedTuple они в этом по-прежнему уступают.
        Проблему можно обойти, если использовать дополнительную обёртку вроде этой. Но какой смысл было так торопиться, чтобы непременно зарелизить эти dataclasses во чтобы то не стало в 3.7 без предварительного решения такого ключевого вопроса, как встроенная поддержка slots — от меня ускользает.


        1. ADR
          09.12.2019 23:02

          At least for the initial release, slots will not be supported. slots needs to be added at class creation time. The Data Class decorator is called after the class is created, so in order to add slots the decorator would have to create a new class, set slots, and return it. Because this behavior is somewhat surprising, the initial version of Data Classes will not support automatically setting slots.

          https://www.python.org/dev/peps/pep-0557/#support-for-automatically-setting-slots


    1. worldmind
      09.12.2019 14:52

      Murtagy Их минус в сравнении с кортежами — не юзают и почти что не умеют юзать __slots__ что для датакласов странно — и ошибиться легче и памяти больше расходуется.


    1. TrueMaker
      09.12.2019 19:20

      C появлением 3.7 не мало вещей так же и сломалось, что держит не мало компаний от миграции с 3.6. А также не так уже и много разработчиков будут уделять внимание новым плюшкам ради обратной совместимости. Пруфа ради чего только стоят попытки делать код обратно совместимым с Python 2.7. Если люди идут даже на такое, то о dataclasses на ближайшие лет 5 можно забыть.


  1. 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}


    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)


  1. stalker1984
    09.12.2019 12:42

    Статью надо было назвать — "три полезных для датасайнтистов <\b> функции из библиотеки ..."


    1. ardraeiss
      09.12.2019 14:34

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


    1. deadmoroz14
      09.12.2019 16:47

      Мне кажется, дата сайнтисты ванильным питоном не пользуются. Pandas в руки и вперёд.
      Первые 2 проблемы решаются из коробки.


      1. CrazyElf
        09.12.2019 19:25

        Pandas часто слишком медленный в текущей реализации, поэтому и чистый Python и Numpy массивы/матрицы очень даже в ходу у дата-сатанистов. Можно убедиться, посмотрев кернелы на Kaggle.


  1. arthuriantech
    09.12.2019 14:41

    У встроенных словарей есть метод __missing__ который может обрабатывать промахи в словаре:


    class MyDict(dict):
        def __missing__(self, key):
            print("Oh no")
            return 123

    https://docs.python.org/3/library/stdtypes.html?#index-51


    1. MikiRobot
      09.12.2019 17:56

      Так будет короче

      dictionary.get('not_found_key', 123)


      1. ardraeiss
        09.12.2019 18:06

        Разное ведь — missing для личной обработки отсутствия элемента в своём классе, get со вторым параметром для стандартных.


      1. 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'


  1. MikiRobot
    09.12.2019 17:59

    Спасибо! Информации по стандартной библиотеке очень не хватает, особенно на русском очень сложно что то найти. #sarcasm


    1. CrazyElf
      09.12.2019 19:27

      Да ну ладно, вещи реально полезные, сам через их «открытие» проходил когда-то как начинающий дата-сайентист :)


  1. alexyr
    09.12.2019 19:28

    from collections import OrderedDict

    особенно удобен для работы с JSON файлами, чтобы сохранить внутренний порядок элементов


    1. wildraid
      09.12.2019 23:28

      Если не ошибаюсь, обычный dict уже сохраняет порядок примерно с версии 3.6.


      1. alexyr
        09.12.2019 23:29

        Завидую тем, кто может писать только для третьего питона


        1. sumej
          10.12.2019 09:47

          До 1.1 2020 рукой подать а после апреля можно расслабиться https://www.python.org/dev/peps/pep-0373/


        1. netch80
          10.12.2019 10:10

          Причём для >=3.6 :)
          для 2.7 и для 3.0-3.5 порядок ключей различный даже при одинаковом seed?е.