Многие из нас даже и не знают что стандартная библиотека Python имеет ряд достаточно интересных и полезных модулей. Дабы не городить велосипеды в своих приложениях предлагаю ознакомиться с переводом статьи "Interesting Modules that ease our tasks" которая рассказывает о таких модулях которые облегчат вам жизнь в будущем.

Модуль Collections

Модуль Collections реализует высокопроизводительные контейнерные типы данных. В настоящее время поддерживается четыре типа данных, - Counter, deque, OrderedDict и defaultdict, а также функция для создания типов данных - namedtuple().

Специализированные контейнеры, создаваемые при помощи этого модуля, представляют собой альтернативу встроенным в Python контейнерам общего назначения: dict, list, set и tuple.

Кроме контейнеров, модуль Collections содержит абстрактные базовые классы (ABC-классы, от "abstract base classes"), которые можно использовать для проверки классов на предмет наличия того или иного интерфейса, например, поддерживает ли он хеширование или "маппинг" -отображение.

Удобные и быстрые подсчеты обеспечит словарь Counter. Например:

>>> # Подсчет встречаемости слов в списке
>>> cnt = Counter()
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
...     cnt[word] += 1
>>> cnt
Counter({'blue': 3, 'red': 2, 'green': 1})

>>> # Найти десять самых употребительных слов в "Гамлете"
>>> import re
>>> words = re.findall('\w+', open('hamlet.txt').read().lower())
>>> Counter(words).most_common(10)
[('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631),
 ('you', 554),  ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)]

deque([iterable[, maxlen]])

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

Дэки представляют собой объединение стеков и очередей (название произносится "deck" - дэк", это сокращение от "double-ended queue" - "двусторонняя очередь"). Дэки обеспечивают добавление и "выталкивание" элементов с обеих сторон, причем производительность операций ввода-вывода в обоих направлениях примерно одинакова, а память и механизм потоков используются при этом эффективно.

Хотя объекты list поддерживают сходный набор операций, они оптимизированы для быстрого выполнения операций с данными фиксированной длины и вызывают O(n) операций в памяти при выполнении pop(0) и insert(0, v) - для операций, которые меняют, одновременно, и размер, и положение представляемых данных.

Новое в версии 2.4.

Если параметр maxlen не определен или установлен в "None", дэк может вырастать до произвольных размеров. В противном случае он ограничен заданным предельным значением. Когда предельная длина дэка достигнута, при добавлении новых данных соответствующее количество элементов на противоположном конце очереди будет отброшено. Дэки с ограничением длины выполняют функцию, сходную с использованием фильтра tail в Unix. Также они полезны для отслеживания транзакций или других множеств данных, где существенны только самые последние операции:

def moving_average(iterable, n=3):
    # скользящая средняя ([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
    # См. http://ru.wikipedia.org/wiki/Скользящая_средняя
    it = iter(iterable)
    d = deque(itertools.islice(it, n-1))
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / float(n)

DefaultDict

Использование list в качестве default_factory позволяет легко сгруппировать последовательность пар "ключ-значение" в словарь списков:

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

Именованный кортеж - Namedtuple

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

>>> from collections import namedtuple
>>> Point = namedtuple('Point', 'x y', verbose=True)

Упорядоченный словарь

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

Модуль Itertools

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

  • Функции, создающие новый итератор на основе итератора существующего.
  • Функции, которые принимают элементы итераторов в качестве своих аргументов.
  • Функции для выделения частей в выводе итератора.
  • Функция для группирования вывода итератора.

Создание новых итераторов

itertools.count(n) возвращает бесконечный поток целых чисел, увеличивающихся каждый раз на 1. При необходимости можно задать стартовое значение, которое по умолчанию равно 0:

itertools.count() =>
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...
itertools.count(10) =>
  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...

itertools.cycle(iter) сохраняет копию содержимого итерируемого объекта и создает новый итератор, который возвращает все элементы от первого до последнего. Новый итератор будет повторять эту последовательность бесконечно.

itertools.cycle([1,2,3,4,5]) =>
  1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...

itertools.repeat(elem, [n]) возвращает заданный элемент n раз, или возвращает этот элемент бесконечно, если параметр n не задан.

itertools.repeat('abc') =>
  abc, abc, abc, abc, abc, abc, abc, abc, abc, abc, ...
itertools.repeat('abc', 5) =>
  abc, abc, abc, abc, abc

itertools.chain(iterA, iterB, ...) получает произвольное количество итераторов на входе, и возвращает все элементы первого итератора, потом все элементы второго и так далее, пока содержимое итераторов не будет исчерпано.

itertools.chain(['a', 'b', 'c'], (1, 2, 3)) =>
  a, b, c, 1, 2, 3

itertools.izip(iterA, iterB, ...) получает по одному элементу от каждого итерируемого объекта и возвращает их в форме кортежа:

itertools.izip(['a', 'b', 'c'], (1, 2, 3)) =>
  ('a', 1), ('b', 2), ('c', 3)

Это похоже на встроенную функцию zip(), но данная функция не конструирует в памяти полный список и не дожидается исчерпания всех входящих итераторов, прежде чем вернуть значение; вместо этого кортежи конструируются и возвращаются только по запросу (такое поведение технически обозначается термином отложенные вычисления.)

Этот итератор должен использоваться с итерируемыми объектами одинаковой длины. Если длина у них разная, результирующий поток будет иметь длину самого короткого из итерируемых множеств.

itertools.izip(['a', 'b'], (1, 2, 3)) =>
  ('a', 1), ('b', 2)

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

itertools.islice(iter, [start], stop, [step]) возвращает поток, представляющий собой срез итератора. С единственным аргументом stop функция вернет первые stop элементов. Если вы зададите стартовое значение, то получите stop-start элементов, а при задании шага step, элементы будут браться с соответствующим шагом. В отличие от срезов в строках и списках Python, здесь нельзя задавать отрицательные значения start, stop и step.

itertools.islice(range(10), 8) =>
  0, 1, 2, 3, 4, 5, 6, 7
itertools.islice(range(10), 2, 8) =>
  2, 3, 4, 5, 6, 7
itertools.islice(range(10), 2, 8, 2) =>
  2, 4, 6

itertools.tee(iter, [n]) размножает итератор; функция возвращает n независимых итераторов (по умолчанию 2). Размножение итераторов требует сохранения части содержимого исходного итератора, поскольку процесс может потребовать значительного расхода памяти, если исходный итератор обладает большим объемом и один из новых итераторов потребит больше памяти, чем остальные.

itertools.tee( itertools.count() ) =>
   iterA, iterB

where iterA ->
   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...

and   iterB ->
   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...

Вызов функций для элементов

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

itertools.imap(f, iterA, iterB, ...) возвращает поток, содержащий f(iterA[0], iterB[0]), f(iterA[1], iterB[1]), f(iterA[2], iterB[2]),...: itertools.imap(operator.add, [5, 6, 5], [1, 2, 3]) => 6, 8, 8

Модуль operator содержит набор функций, соответствующих операторам Python. Вот некоторые примеры: operator.add(a, b) (суммирует два значения), operator.ne(a, b) (то же, что и a!=b), и operator.attrgetter('id') (возвращает ссылку, по которой можно получить атрибут "id" ).

itertools.starmap(func, iter) исходит из того, что итерируемый объект вернет поток кортежей, и вызывает функцию f() с полученными кортежами в качестве аргументов:

itertools.starmap(os.path.join,
                  [('/usr', 'bin', 'java'), ('/bin', 'python'),
                   ('/usr', 'bin', 'perl'),('/usr', 'bin', 'ruby')])
=>
  /usr/bin/java, /bin/python, /usr/bin/perl, /usr/bin/ruby

Выделение подмножеств элементов

Еще одна группа функций служит для выделения подмножеств элементов в итераторе на основе предиката.

itertools.ifilter(predicate, iter) возвращает все элементы, для которых предикат оказывается истинным:

def is_even(x):
    return (x % 2) == 0

itertools.ifilter(is_even, itertools.count()) =>
  0, 2, 4, 6, 8, 10, 12, 14, ...

itertools.ifilterfalse(predicate, iter) - противоположная функция, возвращает все элементы, для которых предикат оказывается ложным:

itertools.ifilterfalse(is_even, itertools.count()) =>
  1, 3, 5, 7, 9, 11, 13, 15, ...

itertools.takewhile(predicate, iter) возвращает элементы до тех пор, пока предикат истинен. Как только предикат становится ложным, итератор сообщает об исчерпании своих результатов.

def less_than_10(x):
    return (x 
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9

itertools.takewhile(is_even, itertools.count()) =>
  0

itertools.dropwhile(predicate, iter) отбрасывает элементы до тех пор, пока предикат остается истинным, а когда предикат становится ложным, возвращает все оставшиеся элементы.

itertools.dropwhile(less_than_10, itertools.count()) =>
  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...

itertools.dropwhile(is_even, itertools.count()) =>
  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...

Группирование элементов

Последняя функция из тех, которые я хотел обсудить, это itertools.groupby(iter, key_func=None), - она наиболее сложна. Здесь key_func(elem) - функция, вычисляющая значение ключа для каждого элемента, возвращаемого итератором. Если вы не задаете ключа, то ключом будет просто сам элемент.

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

city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'),
             ('Anchorage', 'AK'), ('Nome', 'AK'),
             ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ'),
             ...
            ]

def get_state ((city, state)):
    return state

itertools.groupby(city_list, get_state) =>
  ('AL', iterator-1),
  ('AK', iterator-2),
  ('AZ', iterator-3), ...

where
iterator-1 =>
  ('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL')
iterator-2 =>
  ('Anchorage', 'AK'), ('Nome', 'AK')
iterator-3 =>
  ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ')

groupby() исходит из предположения, что содержимое итерируемого множества уже отсортировано по значению ключа. Обратите внимание - возвращаемые функцией итераторы опираются на то же самое итерируемое множество, поэтому нужно вначале выбрать все значения из итератора-1, прежде чем переходить к итератору-2 и его ключу.

8 Апрель 2012, 18:28 5 alafin
blog comments powered by Disqus