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

http://www.siafoo.net/article/52
  • Перевод
Содержание

Списки. Свёртка списка (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
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 30
  • –18
    такое ощущение, что все эти трюки сделали для того, что бы не путаться с отступами :-)
    • +2
      В 3.2 для значений не повтор .items(), а .values()
    • +3
      Спасибо, занес в избранное. Еще пару статей — распечатаю и подошью :)
      • +6
        Спасибо за any, all & enumerate — как-то пропустил.
        • 0
          reduce убрали в 3 питоне из глобального пространства имен. Имхо правильно: лучше лишнию строку написать, чем при чтении задумываться.
          • +1
            Cапасибо за Ваши статьи.
            Как раз начал изучать Python…
            • +2
              Set по-русски это множество. Математически не совсем точно, но все же.
              • 0
                «Это чуть менее читабельно, чем строгие генераторы списков, но я считаю, что это лучше, чем большой цикл for.»

                лучше чем?
                • +7
                  Чем большой цикл for.
                  Так то!
                • –2
                  Большое спасибо, изучать тонкости питона по Вашим статьям намного интереснее, чем по книгам))
                  • +1
                    Продолжение следует.

                    Вот это радует, да! :)
                    И спасибо Вам.
                    • +1
                      Не отказался бы почитать про lambda-функции поподробнее, ну и про «накоротко замкнутые» выражения.
                      Иначе сам напишу =)
                    • +2
                      а в 3 есть нормальные Dictionary Comprehensions {i: chr(65+i) for i in range(4)}
                      reduce() ушел в модуль functools
                      xrange() стал просто range()
                      dict.keys(), dict.items(), dict.values() теперь возвращают не список, а (???)вид (view).

                      И вообще, когда мир решит, что стоит писать «гайды» уже для python 3.*?
                    • 0
                      Спасибо за «3.1 Создание словаря с помощью именованных аргументов»
                      • 0
                        А вот reduce для пустого элемента вернет ошибку, что не очень удобно. Если бы он мог принимать помимо лямбды и собственно массива еще «значение» по умолчанию, такой проблемы бы не было. Жду следующей статьи.
                        Пока для себя уяснил, что ruby хоть и медленней, но такие вещи как any, all, проход по массиву, reduce сделаны гораздо красивее.
                        • 0
                          Гвидо ФП не очень жалует, хотел из py3k чуть ли не map выкинуть, т.к. считает, что практически все эти трюки скорее снижают понятность кода, а не повышают ее. Из-за этого же лямбды такие урезанные, чтобы разработчики писали не «крутой» код, а понятный код.

                          Пописал месяц на js, где ФП-составляющая не урезанная, захотелось после этого попробовать ruby. Но вместе с тем осознаю, что в питоне из-за всех этих ограничений код и правда проще/понятнее получается, в них, определенно, есть смысл.
                          • +1
                            а я вот зашел на docs.python.org и прочитал про reduce:
                            reduce(function, iterable[, initializer])
                            ...If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty…

                            Что означает: Если не обязательный параметр initializer присутствует, то он подставляется в вычислениях перед элементами iterable, а также служит значением по умолчанию, если iterable пуст
                          • 0
                            Ну собственно трюки довольно знакомые среднестатистическому питонщику, однако спасибо, что выложили всё это в одном месте.
                            • +1
                              А у меня завтра собеседование по поводу python-разработки! Прочитал, немного порадовался с той стороны, что всё это знаю, а с другой огорчился, что не знаю как сделать более глубокое погружение в язык.
                            • 0
                              Спасибо за статью, я как то all и any упустил.
                              Я бы добавил, что в enumerate появился параметр start, так что первый индекс может отличаться от нуля.
                              И, думаю, раз уже начали рассказывать разные вкусности по работе со списками, то следует затронуть модуль itertools
                              • 0
                                может кого-то спасёт:

                                использование iteritems/itervalues/iterkeys (вместо их неитерационных аналогов) могут значительно ускорить работу в CPython.

                                использование items/values/keys порождает новые структуры и (часто) увеличивает ref counts на элементы материнского словарика. В результате может образоваться плохо воспроизводимое замедление, у меня бывало замедление более 10раз.
                                • 0
                                  Имитируя dictionary comprehensions более эффективно использовать tuple вместо list для определения пары (key, value).

                                  Т.е. вместо:

                                  dict([name, '.com' in email] for name, email in emails.iteritems())

                                  надо:

                                  dict((name, '.com' in email) for name, email in emails.iteritems())

                                  А ещё нагляднее:

                                  dict((name, '.com' in email) for (name, email) in emails.iteritems())
                                  • 0
                                    Лучший tip, trick and hack – забыть этот вполне заменимый язык:

                                    result = reduce(lambda a,b: a*b, numbers)
                                    vs
                                    result = numbers.inject :*

                                    for index, string in enumerate(strings): print index, string,
                                    vs
                                    puts strings.each_with_index.to_a.join " "

                                    len(numbers) == len(set(numbers))
                                    vs
                                    !numbers.dup.uniq!

                                    BONUS:

                                    " ".join(reversed(sentence.split()))
                                    vs
                                    sentence.split.reverse.join ' '

                                    while True:
                                    stuff()
                                    if fail_condition:
                                    break
                                    or
                                    stuff()
                                    while not fail_condition:
                                    stuff()
                                    vs
                                    begin stuff while not fail_condition

                                    etc. etc. etc.
                                    • НЛО прилетело и опубликовало эту надпись здесь

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.