Рассмотрим основную программу и два модуля. (Функция enumerate() разбивает список на части и отправляет каждый элемент списка в цикл for, добавляя к каждому элементу число в качестве небольшого бонуса.)
Основная программа — boxes/weather.py:
from sources import daily, weekly
print("Daily forecast: ", daily.forecast())
print("Weekly forecast: ")
for number, outlook in enumerate(weekly.forecast(), 1):
····print(number, outlook)
Модуль 1: boxes/sources/daily.py.
def forecast():
····'fake daily forecast'
····return 'like yesterday'
Модуль 2: boxes/sources/weekly.py.
def forecast():
····"""Fake weekly forecast"""
····return ['snow', 'more snow', 'sleet',
········'freezing rain', 'rain', 'fog', 'hail']
В папке sources вам понадобится иметь кое-что еще — файл с именем __init__.py. Он может быть пустым, но Python он нужен для того, чтобы считать папку, которая его содержит, пакетом.
Запустите основную программу weather.py, чтобы увидеть, что произойдет:
$ python weather.py
Daily forecast: like yesterday
Weekly forecast:
1 snow
2 more snow
3 sleet
4 freezing rain
5 rain
6 fog
7 hail
Стандартная библиотека Python
Одно из основных преимуществ Python заключается в том, что у него есть собственный «запас мощности» — большая стандартная библиотека модулей, которые выполняют множество полезных задач и располагаются отдельно друг от друга, чтобы избежать разрастания ядра языка. Когда вы собираетесь писать код, зачастую сначала стоит проверить, существует ли стандартный модуль, который уже делает то, что вы хотите. Удивительно, как часто вы будете встречать эти небольшие жемчужины в стандартной библиотеке. Python также предоставляет авторитетную документацию для модулей наряду с руководством для пользователей (http://docs.python.org/3/library). Сайт Дага Хеллмана (Doug Hellmann) Python Module of the Week (http://bit.ly/py-motw) и его книга The Python Standard Library by Example («Стандартная библиотека Python в примерах»), выпущенная издательством Addison-Wesley Professional, также являются очень полезными руководствами.
В следующих главах книги показано множество стандартных модулей, которые предназначены для работы с Сетью, системами, базами данных и т. д. В этом разделе я поговорю о стандартных модулях, которые имеют более общие варианты использования.
Обработка отсутствующих ключей с помощью функций setdefault() и defaultdict()
Вы уже видели, что попытка получить доступ к словарю с помощью несуществующего ключа генерирует исключение. Использование функции словаря get() для того, чтобы вернуть значение по умолчанию, помогает этого избежать.
Функция setdefault() похожа на функцию get(), но она также присваивает элемент словарю, если заданный ключ отсутствует:
>>> periodic_table = {'Hydrogen': 1, 'Helium': 2}
>>> print(periodic_table)
{'Helium': 2, 'Hydrogen': 1}
Если ключа еще нет в словаре, будет использовано новое значение:
>>> carbon = periodic_table.setdefault('Carbon', 12)
>>> carbon
12
>>> periodic_table
{'Helium': 2, 'Carbon': 12, 'Hydrogen': 1}
Если мы пытаемся присвоить другое значение по умолчанию уже существующему ключу, будет возвращено оригинальное значение и ничто не изменится:
>>> helium = periodic_table.setdefault('Helium', 947)
>>> helium
2
>>> periodic_table
{'Helium': 2, 'Carbon': 12, 'Hydrogen': 1}
Функция defaultdict() похожа на предыдущую, но она определяет значение по умолчанию для новых ключей заранее, при создании словаря. В этом примере мы передаем функцию int, которая будет вызываться как int(), и возвращаем значение 0:
>>> from collections import defaultdict
>>> periodic_table = defaultdict(int)
Теперь любое отсутствующее значение будет заменяться целым числом (int) 0:
>>> periodic_table['Hydrogen'] = 1
>>> periodic_table['Lead']
0
>>> periodic_table
defaultdict(, {'Lead': 0, 'Hydrogen': 1})
Аргументом defaultdict() является функция, возвращающая значение, которое будет присвоено отсутствующему ключу. В следующем примере функция no_idea() будет вызываться всякий раз, когда нужно вернуть значение:
>>> from collections import defaultdict
>>>
>>> def no_idea():
…·····return 'Huh?'
…
>>> bestiary = defaultdict(no_idea)
>>> bestiary['A'] = 'Abominable Snowman'
>>> bestiary['B'] = 'Basilisk'
>>> bestiary['A']
'Abominable Snowman'
>>> bestiary['B']
'Basilisk'
>>> bestiary['C']
'Huh?'
Вы можете использовать функции int(), list() или dict(), чтобы возвращать пустые значения по умолчанию: int() возвращает 0, list() возвращает пустой список ([]) и dict() возвращает пустой словарь ({}). Если вы опустите аргумент, исходное значение нового ключа будет равно None.
Кстати, вы можете использовать lambda для того, чтобы определить функцию по умолчанию изнутри вызова:
>>> bestiary = defaultdict(lambda: 'Huh?')
>>> bestiary['E']
'Huh?'
Применение int — это один из способов создать ваш собственный прилавок:
>>> from collections import defaultdict
>>> food_counter = defaultdict(int)
>>> for food in ['spam', 'spam', 'eggs', 'spam']:
…·····food_counter[food] += 1
…
>>> for food, count in food_counter.items():
…·····print(food, count)
…
eggs 1
spam 3
В предыдущем примере, если бы food_counter был обычным словарем, а не defaultdict, Python генерировал бы исключение всякий раз, когда бы мы пытались увеличить элемент словаря food_counter[food], поскольку он был бы не инициализирован. Нам понадобилось бы сделать дополнительную работу, как показано здесь:
>>> dict_counter = {}
>>> for food in ['spam', 'spam', 'eggs', 'spam']:
…·····if not food in dict_counter:
…·········dict_counter[food] = 0
…·····dict_counter[food] += 1
…
>>> for food, count in dict_counter.items():
…·····print(food, count)
…
spam 3
eggs 1
Подсчитываем элементы с помощью функции Counter()
Если говорить о счетчиках, то в стандартной библиотеке имеется счетчик, который решает задачу, показанную в предыдущем примере, и даже больше:
>>> from collections import Counter
>>> breakfast = ['spam', 'spam', 'eggs', 'spam']
>>> breakfast_counter = Counter(breakfast)
>>> breakfast_counter
Counter({'spam': 3, 'eggs': 1})
Функция most_common() возвращает все элементы в убывающем порядке или лишь те элементы, количество которых больше, чем заданный аргумент count:
>>> breakfast_counter.most_common()
[('spam', 3), ('eggs', 1)]
>>> breakfast_counter.most_common(1)
[('spam', 3)]
Счетчики можно объединять. Для начала снова взглянем на содержимое breakfast_counter:
>>> breakfast_counter
>>> Counter({'spam': 3, 'eggs': 1})
Теперь мы создадим новый список, который называется lunch, и счетчик, который называется lunch_counter:
>>> lunch = ['eggs', 'eggs', 'bacon']
>>> lunch_counter = Counter(lunch)
>>> lunch_counter
Counter({'eggs': 2, 'bacon': 1})
Счетчики можно объединить с помощью оператора +:
>>> breakfast_counter + lunch_counter
Counter({'spam': 3, 'eggs': 3, 'bacon': 1})
Как вы можете догадаться, счетчики можно вычитать друг из друга с помощью оператора —. Что мы будем есть на завтрак, но не на обед?
>>> breakfast_counter — lunch_counter
Counter({'spam': 3})
О’кей, теперь узнаем, что мы можем съесть на обед, но не можем на завтрак:
>>> lunch_counter — breakfast_counter
Counter({'bacon': 1, 'eggs': 1})
По аналогии с множествами, показанными в главе 4, вы можете получить общие элементы с помощью оператора пересечения &:
>>> breakfast_counter & lunch_counter
Counter({'eggs': 1})
В результате пересечения был получен общий элемент ('eggs') с низким значением счетчика. Это имеет смысл: на завтрак у нас было только одно яйцо, поэтому указанное количество является общим.
Наконец, вы можете получить все элементы с помощью оператора объединения |:
>>> breakfast_counter | lunch_counter
Counter({'spam': 3, 'eggs': 2, 'bacon': 1})
Элемент 'eggs' снова оказался общим для обоих счетчиков. В отличие от сложения объединение не складывает счетчики, а выбирает тот, который имеет наибольшее значение.
Упорядочиваем по ключу с помощью OrderedDict()
Многие примеры кода, показанные в первых главах этой книги, демонстрируют, что порядок ключей в словаре нельзя предсказать: вы можете добавить в определенном порядке ключи a, b и c, но функция keys() вернет результат "c, a, b". Рассмотрим модифицированный пример из главы 1:
>>> quotes = {
…·····'Moe': 'A wise guy, huh?',
…·····'Larry': 'Ow!',
…·····'Curly': 'Nyuk nyuk!',
…·····}
>>> for stooge in quotes:
…··print(stooge)
…
Larry
Curly
Moe
Словарь OrderedDict() запоминает порядок, в котором добавлялись ключи, и возвращает их в том же порядке с помощью итератора. Попробуем создать OrderedDict из последовательности кортежей вида «ключ — значение»:
>>> from collections import OrderedDict
>>> quotes = OrderedDict([
…·····('Moe', 'A wise guy, huh?'),
…·····('Larry', 'Ow!'),
…·····('Curly', 'Nyuk nyuk!'),
…·····])
>>>
>>> for stooge in quotes:
…·····print(stooge)
…
Moe
Larry
Curly
Стек + очередь == deque
deque (произносится как «дэк») — это двухсторонняя очередь, которая имеет возможности стека и очереди. Она полезна, когда вы хотите добавить и удалить элементы с любого конца последовательности. В следующем примере мы будем двигаться с обоих концов слова к его середине, чтобы увидеть, является ли оно палиндромом. Функция popleft() удаляет крайний слева элемент deque и возвращает его, функция pop() удаляет крайний справа элемент и возвращает его. Вместе они двигаются с концов слова к его середине. Работа будет продолжаться до тех пор, пока крайние символы совпадают и пока не будет достигнута середина:
>>> def palindrome(word):
…·····from collections import deque
…·····dq = deque(word)
…·····while len(dq) > 1:
…········if dq.popleft()!= dq.pop():
…············return False
…·····return True
…
…
>>> palindrome('a')
True
>>> palindrome('racecar')
True
>>> palindrome('')
True
>>> palindrome('radar')
True
>>> palindrome('halibut')
False
Я воспользовался этим примером, чтобы было проще проиллюстрировать работу deque. Если вы действительно хотите создать программу, которая определяет палиндромы, гораздо проще было бы сравнивать строку с ее копией, вывернутой наизнанку. В Python строковой функции reverse() не существует, но можно обратить строку с помощью разбиения, как показано здесь:
>>> def another_palindrome(word):
…·····return word == word[::-1]
…
>>> another_palindrome('radar')
True
>>> another_palindrome('halibut')
False
Итерируем по структурам кода с помощью itertools
itertools содержит особые функции итератора. Каждая из них возвращает один элемент при каждом вызове из цикла for … in и запоминает свое состояние между вызовами.
Функция chain() проходит по своим аргументам, как если бы они были единым итерабельным объектом:
>>> import itertools
>>> for item in itertools.chain([1, 2], ['a', 'b']):
…·····print(item)
…
1
2
a
b
Функция cycle() является бесконечным итератором, проходящим в цикле по своим аргументам:
>>> import itertools
>>> for item in itertools.cycle([1, 2]):
…·····print(item)
…
1
2
1
2
.
.
.
…и т. д.
Функция accumulate() подсчитывает накопленные значения. По умолчанию она высчитывает сумму:
>>> import itertools
>>> for item in itertools.accumulate([1, 2, 3, 4]):
…·····print(item)
…
1
3
6
10
В качестве второго аргумента функции accumulate() вы можете передать функцию, и она будет использована вместо сложения. Функция должна принимать два аргумента и возвращать одно значение. В этом примере высчитывается произведение:
>>> import itertools
>>> def multiply(a, b):
…·····return a * b
…
>>> for item in itertools.accumulate([1, 2, 3, 4], multiply):
…·····print(item)
…
1
2
6
24
Модуль itertools имеет еще много функций, он известен благодаря определенным комбинациям и преобразованиям, которые могут сохранить кучу времени, если в них появится необходимость.
Выводим данные на экран красиво с помощью функции pprint()
Все наши примеры использовали функцию print() (или просто имя переменной в интерактивном интерпретаторе), чтобы выводить информацию на экран. Иногда результаты было трудно прочитать. Нам нужен pretty printer (красивый принтер) вроде pprint():
>>> from pprint import pprint
>>> quotes = OrderedDict([
…·····('Moe', 'A wise guy, huh?'),
…·····('Larry', 'Ow!'),
…·····('Curly', 'Nyuk nyuk!'),
…·····])
>>>
Старая добрая функция print() просто выводит всю информацию:
>>> print(quotes)
OrderedDict([('Moe', 'A wise guy, huh?'), ('Larry', 'Ow!'), ('Curly', 'Nyuk nyuk!')])
А функция pprint() пытается выровнять элементы для лучшей читаемости:
>>> pprint(quotes)
{'Moe': 'A wise guy, huh?',
'Larry': 'Ow!',
'Curly': 'Nyuk nyuk!'}
Нужно больше кода