Pull to refresh

Python Tips, Tricks, and Hacks (часть 2)

Reading time6 min
Views133K
Original author: David
Содержание

Списки. Свёртка списка (reduce). Прохождение по списку (range, xrange и enumerate). Проверка всех элементов списка на выполнение условия (all и any). Группировка элементов нескольких списков (zip). Еще несколько операторов для работы со списками. Продвинутые логические операции с типом set.
Словари. Создание словаря с помощью именованных аргументов. Преобразование словаря в список и обратно. «Dictionary Comprehensions».

2   Списки


2.2   Свёртка списка

К сожалению, нельзя написать программу только с помощью генераторов списков. (Я шучу… конечно, можно.) Они могут отображать и фильтровать, но нет простого способа для свертки списка. Под этим понятием я подразумеваю применение функции к первым двум элементам списка, а затем к получившемуся результату и следующему элементу, и так до конца списка. Можно реализовать это через цикл for:
Copy Source | Copy HTML
numbers = [1,2,3,4,5]
result = 1
for number in numbers:
    result *= number
# result = 120 

А можно воспользоваться встроенной функцией reduce, принимающей в качестве аргументов функцию от двух параметров и список:
Copy Source | Copy HTML
numbers = [1,2,3,4,5]
result = reduce(lambda a,b: a*b, numbers)
# result = 120 

Не так красиво, как генераторы списков, но короче обычного цикла. Стоит запомнить этот способ.

2.3   Прохождение по списку: range, xrange и enumerate

Помните, как в языке C для цикла for вы использовали переменную-счетчик вместо элементов списка? Возможно, вы уже знаете, как имитировать это поведение в Python с помощью range и xrange. Передавая число value функции range, мы получим список, содержащий элементы от 0 до value-1 включительно. Другими словами, range возвращает индексы списка указанной длины. xrange действует похоже, но более эффективно, не загружая весь список в память целиком.
Copy Source | Copy HTML
strings = ['a', 'b', 'c', 'd', 'e']
for index in xrange(len(strings)):
    print index,
# печатает "0 1 2 3 4" 

Проблема в том, что обычно вам всё равно нужны элементы списка. Что толку от индексов без них? В Python есть потрясающая встроенная функция enumerate, которая воозвращает итератор для пар индекс → значение:
Copy Source | Copy HTML
strings = ['a', 'b', 'c', 'd', 'e']
for index, string in enumerate(strings):
    print index, string,
# печатает "0 a 1 b 2 c 3 d 4 e" 

Еще один плюс состоит в том, что enumerate выглядит более читаемо, чем xrange(len()). Поэтому range и xrange полезны, наверно, только для создания списка с нуля, а не на основе других данных.

2.4   Проверка всех элементов списка на выполнение условия

Допустим, нам надо проверить, выполняется ли условие хотя бы для одного элемента. До Python 2.5 можно было писать так:
Copy Source | Copy HTML
numbers = [1,10,100,1000,10000]
if [number for number in numbers if number < 10]:
    print 'At least one element is over 10'
# Результат: "At least one element is over 10" 
<source>
Если ни один из элементов не удовлетворяет условию, генератор создаст пустой список, который считается false. В противном случае будет создан непустой список, который приводится к true. Строго говоря, не нужно проверять все элементы, достаточно остановиться после первого элемента, удовлетворающего условию. Поэтому предыдущий пример менее эффективен и может быть выбран только в том случае, если вы не можете использовать Python 2.5, но хотите уместить всю логику в одно выражение.

С помощью встроенной функции any, введенной в Python 2.5, вы можете решить эту задачу более красиво и эффективно. Функция any прервется и вернет True, как только найдет элемент, удовлетворяющий условию. Ниже я использую выражение-генератор, которое возвращает True или False для каждого элемента, и передаю его функции any. Генератор вычисляет только необходимые в данный момент значения, а any принимает их по очереди.
<source>
Copy Source | Copy HTML
numbers = [1,10,100,1000,10000]
if any(number < 10 for number in numbers):
    print 'Success'
# Результат: "Success!" 

Аналогично, может возникнуть задача проверки, что все элементы удовлетворяют условию. Без Python 2.5 придется писать так:
Copy Source | Copy HTML
numbers = [1,2,3,4,5,6,7,8,9]
if len(numbers) == len([number for number in numbers if number < 10]):
    print 'Success!'
# Результат: "Success!" 

Здесь мы фильтруем список и проверяем, уменьшилась ли его длина. Если нет, то все его элементы удовлетворяют условию. Опять же, без Python 2.5 это единственный способ уместить всю логику в одно выражение.

В Python 2.5 есть более простой путь — встроенная функция all. Легко догадаться, что она прекращает проверку после первого элемента, не удовлетворяющего условию. Эта функция работает абсолютно аналогично предыдущей.
Copy Source | Copy HTML
numbers = [1,2,3,4,5,6,7,8,9]
if all(number < 10 for number in numbers):
    print 'Success!'
# Результат: "Success!" 

2.5   Группировка элементов нескольких списков

Встроенная функция zip используется для сжимания нескольких списков в один. Она возвращает массив кортежей, причем n-й кортеж содержит n-е элементы всех массивов, переданных в качестве аргументов. Это тот случай, когда пример — лучшее объяснение:
Copy Source | Copy HTML
letters = ['a', 'b', 'c']
numbers = [1, 2, 3]
squares = [1, 4, 9]
 
zipped_list = zip(letters, numbers, squares)
# zipped_list = [('a', 1, 1), ('b', 2, 4), ('c', 3, 9)] 

Эта вещь часто используется как итератор для цикла for, извлекающая три значения за одну итерацию («for letter, number, squares in zipped_list»).

2.6   Еще несколько операторов для работы со списками

Ниже перечислены встроенные функции, в качестве аргумента принимающие любой итерируемый объект.
max и min возвращают наибольший и наименьший элемент соответственно.
sum возвращает сумму всех элементов списка. Опциональный второй параметр задает начальную сумму (по умолчанию 0).

2.7   Продвинутые логические операции с типом set.

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

Довольно распространенная задача — убедиться, что элементы списка уникальны. Это просто, достаточно преобразовать его в сет и проверить, изменилась ли длина:
Copy Source | Copy HTML
numbers = [1,2,3,3,4,1]
set(numbers)
# возвращает set([1,2,3,4])
 
if len(numbers) == len(set(numbers)):
    print 'List is unique!'
# не выводит ничего 

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

3   Словари


3.1   Создание словаря с помощью именованных аргументов

Когда я начал изучать Python, я полностью пропустил альтернативный способ создания словаря. Если передать конструктору dict именованные аргументы, они будут добавлены в возращаемый словарь. Конечно, на его ключи накладываются те же ограничения, что и на имена переменных. Вот пример:
Copy Source | Copy HTML
dict(a=1, b=2, c=3)
# возвращает {'a': 1, 'b': 2, 'c': 3} 

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

3.2   Преобразование словаря в список

Чтобы получить список ключей, достаточно привести словарь к типу list. Но лучше использовать .keys() для получения списка ключей или .iterkeys() для получения итератора. Если вам нужны значения, используйте .values() и .itervalues(). Но помните, что словари не упорядочены, поэтому полученные значения могут быть перемешаны любым мыслимым образом.

Чтобы получить и ключи, и значения в виде списка кортежей, можно использовать .items() или .iteritems(). Возможно, вы часто пользовались этим захватывающим методом:
Copy Source | Copy HTML
dictionary = {'a': 1, 'b': 2, 'c': 3}
dict_as_list = dictionary.items()
#dict_as_list = [('a', 1), ('b', 2), ('c', 3)] 

3.3   Преобразование списка в словарь

Обратная операция — превращение списка, содержащего пары ключ-значение, в словарь — делается так же просто:
Copy Source | Copy HTML
dict_as_list = [['a', 1], ['b', 2], ['c', 3]]
dictionary = dict(dict_as_list)
# dictionary = {'a': 1, 'b': 2, 'c': 3} 

Вы можете комбинировать методы, добавив именованные аргументы:
Copy Source | Copy HTML
dict_as_list = [['a', 1], ['b', 2], ['c', 3]]
dictionary = dict(dict_as_list, d=4, e=5)
# dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5} 

Превращать списка и словари друг в друга довольно удобно. Но следующий совет просто потрясающий.

3.3   «Dictionary Comprehensions»

Хотя в Python нет встроенного генератора словарей, можно сделать нечто похожее ценой небольшого беспорядка в коде. Используем .iteritems() для превращения словаря в список, передадим его выражению-генератору или генератору списков, а затем преобразуем список обратно в словарь.

Допустим, у нас есть словарь пар name:email, а мы хотим получить словарь пар name:is_email_at_a_dot_com (проверить каждый адрес на вхождение подстроки .com):
Copy Source | Copy HTML
emails = {'Dick': 'bob@example.com', 'Jane': 'jane@example.com', 'Stou': 'stou@example.net'}
 
email_at_dotcom = dict( [name, '.com' in email] for name, email in emails.iteritems() )
 
# email_at_dotcom = {'Dick': True, 'Jane': True, 'Stou': False}

Конечно, необязательно начинать и заканчивать словарем, можно в некоторых местах использовать и списки.

Это чуть менее читабельно, чем строгие генераторы списков, но я считаю, что это лучше, чем большой цикл for.

Статья целиком в PDF
Tags:
Hubs:
Total votes 69: ↑66 and ↓3+63
Comments30

Articles