Пользователь
0,0
рейтинг
28 июля 2008 в 17:53

Разработка → Основы Python — кратко. Часть 4. Генераторы списков

List comprehensions

Продолжим наш цикл уроков. Добрый день.

Генерация списков

Генерация списков (не знаю как адекватно перевести на русский list comprehensions) — яркий пример «синтаксического сахара». То есть конструкции, без которой легко можно обойтись, но с ней намного лучше :) Генераторы списков, как это не странно, предназначены для удобной обработки списков, к которой можно отнести и создание новых списков, и модификацию существующих.
Допустим, нам необходимо получить список нечетных чисел, не превышающих 25.
В принципе, только познакомившись с работой команды xrange решить эту проблему несложно.

>>> res = []
>>> for x in xrange(1, 25, 2):
...     res.append(x)
...
>>> print res 

В общем-то, полученный результат — целиком нас устраивает всем, кроме длинной записи. тут-то на помощь и придет наш «сахарок». В самом простом виде, он обычно

>>> res = [x for x in xrange(1, 25, 2)]
>>> print res
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23]

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

>>> res = [x**2 for x in xrange(1, 25, 2)]
>>> print res
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529]

По желанию, можно добавить дополнительные условия фильтрации. Например, доработаем наш предыдущий пример так, чтобы исключались квадраты чисел, кратных 3.

>>> res = [x**2 for x in xrange(1, 25, 2) if x % 3 != 0]
>>> print res
[1, 25, 49, 121, 169, 289, 361, 529]

Вот пример немного посложнее.

>>> dic = {'John':1200, 'Paul':1000, 'Jones':1850, 'Dorothy': 950}
>>> print "\n".join(["%s = %d" % (name, salary) for name, salary in dic.items()])
Jones = 1850
Dorothy = 950
Paul = 1000
John = 1200

Тут мы использовали генератор списков для превращения словаря в набор записей, это может быть удобно в таких задачах как сохранение конфигов или генерация HTML. Выражение "%s = %d" % (name, salary) — предназначено для форматирования строк, и по сути похоже на аналоги в С. В начале идет строка где задаются позиции для вставки значений (%s — строковое, %d — числовое). После знака % — «вставляемые» значения в виде кортежа.

В принципе — генератором можно обрабатывать и готовые списки.

Рассмотрим простой пример. Допустим, у нас имеется лог-файл, в котором хранится статистика запросов к серверу в виде «ip bytes» через пробел, по одному хосту на
строку. Примерно так:

127.0.0.1 120
10.1.1.1 210
127.0.0.1 80
10.1.1.1 215
10.1.1.1 200
10.1.1.2 210

Нам необходимо вычислить суммарный объем траффика на каждый хост и выдать его в виде списка в поряке убывания траффика.
Программу для решения данной проблемы будет весьма недлинной :) Ее можно еще сильнее сократить, но это явно пойдет в ущерб ее читабельности.

#!/usr/bin/env python
#coding: utf8

# 1 считываем из файла строки и делим их на пары IP-адрес
raw = [x.split(" ")  for x in open("log.txt")]

# 2 заполняем словарь
rmp = {}
for ip, traffic in raw:
        if ip in rmp:
                rmp[ip] += int(traffic)
        else:
                rmp[ip] = int(traffic)

# 3 переводим в список и сортируем
lst = rmp.items()
lst.sort(key = lambda (key, val): key)
# 4 получаем результат
print "\n".join(["%s\t%d" % (host, traff) for host, traff in lst])

Разберем ее пошагово.
1. В этой строке мы читаем из файла, который открываем с помощью функции open. Функция open по-умолчанию открывает файл для чтения и возвращает файловый объект, который кроме всего прочего является итерируемым. То есть по нему можно «двигаться» с помощью цикла for, чем мы и пользуемся в нашем случае. Кроме того, с помощью метода split мы делим каждую строку на пару — адрес — траффик.
2. Для удобства, мы формируем хеш, ключами в котором являются адреса, а значениями — траффик. Если ключа еще нет, то мы его созадаем, если же есть, то мы плюсуем текущий траффик к его предыдущим «наработкам». После этого цикла мы получим хеш с суммами траффика по всем хостам.
3. К сожалению, словари в Пайтоне не сортируются. Потому нам придется перевести его в список для сортировки. Следующие две строки переводят словарь в список и осуществляют его сортировку по второму полю.
4. Вот и все что осталось — «собрать» результат, как мы уже это делали раньше.

На сегодня хватит :)
Домашнее задание.
1. Реализовать функцию-генератор строки с таблицей умножения на число Х.
2. Есть лог-файл какого-то чата. Посчитать «разговорчивость» пользователей в нем в виде ник — количество фраз. Посчитать среднее число букв на участника чата.

ЗЫ О чем лучше продолжать — про классы и ООП или про элементы функционального программирования?
Павел Дмитриев @cleg
карма
149,8
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (83)

  • +1
    > к которой можно отнести и создание новых списков, и модификацию существующих.
    Как, используя list comprehension, модифицировать существующий список?
    • 0
      думаю что никак.
      хотя в питоне и можно при генерации модифицировать исходный список, но новый всё равно создаётся.
  • 0
    От условия в цикле можно избавиться используя defaultdict(int).
    • +2
      Еще можно красиво посчитать суммарный трафик:
      print "Total traffic: %d bytes" % sum (bytes for (ip, bytes) in lst)
      • 0
        спасибо, вы мне подсказываете поле дял будущей деятельности далеко вперед :)
  • 0
    Есть еще замечательная штука generator expressions, похож на list comprehension, только создается не список, а его элементы. :) Памяти ест намного меньше. Как-то так. :)
    • 0
      Так и называется, потому что создается генератор элементов :)
      Генераторы вообще отличная штука.
      • 0
        и итераторы, и декораторы и многое другое :)
    • 0
      List comprehension - это и есть генератор, пропущеный через функцию list() путём помещения в квадратные скобки. А вот если всё так не упрощать, можно получить всякие замечательные штуки, например, объект, генерирующий бесконечную последовательность, элемент за элементом.
  • +1
    Понятно, конечно, что xrange в первом примере статьи использовался как пример к последующему материалу, но применение range для создания списка нечетных числе выглядит более очевидным, да и работает быстрее. Впрочем, в Python 3000, если не ошибаюсь, range приобретет поведение xrange, а последний исчезнет.

    Спасибо за статьи!
  • 0
    dic.has_key(x) не быстрее x in dic ??
    • +1
      Вызов метода происходит дольше. И вообще, метод has_key, вроде, устаревшим уже считается.
      • 0
        именно. :) мне пришлось переучиваться.
        кстати, open вроде тоже depricated... надо пользовать file.
        • 0
          По крайней мере для 2.5 документация рекомендует для открытия файлов использовать именно open, а file оставить как тип. Поменялось ли что-то по этому поводу в 3.0, не помню.
          • 0
            http://docs.python.org/lib/bltin-file-ob…
            или я что-то неправильно понял?
            • 0
              То, что file новее open и идентичен ему, не значит, что старая функция стала deprecated. Раздел built-in functions рекомендует семантическое разделение, которое я полностью поддерживаю.
              • 0
                ну не от скуки ж ее объявили "оставленной для совместимости" :)
                • 0
                  Где именно?
                  Кстати, я сейчас быстренько собрал 3.0b2, и там file даже нет в built-ins, в отличие от open.
                  • 0
                    file() is new in Python 2.2. The older built-in open() is an alias for file().
                    ну да, в принципе это не deprecation. но меня слово older настраивает на минорный лад
          • +1
            for line in file читается лучше, чем for line in open.
            • 0
              Это уже субъективизм полнейший.
              Я обычно пишу for line in some_file и some_file закрываю явно или с помощью with ... as
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Советуют использовать k in a.
  • 0
    "list comprehensions" часто переводят, как "генераторы списков"
  • +1
    фак май брайн!
    А питон, я на него смогу перейти с php?
    • +1
      разумеется перейдешь) я с нуля питон осознал за 30 минут.. :) До этого вообще ни на чем не программил :) Отличный язык)
      • +1
        Ну ты меня успокойил, может у мну моск не столько функционален как твой))
        попробуем
        • 0
          так ты можешь все это не использовать пока. Пайтон не Хаскелл. в нем ФП - приятное дополнение, а не необходимость. пока можешь писать императивно, а потом использовать все что хочется :)
          • 0
            Ну, те же генераторы — это уж отголоски ленивых вычислений. Так что потихоньку различия стираются. :)
    • +7
      Перейти с PHP на Python легко, а вот обратно сложновато. Потому что не хочется. :)
      • 0
        Ммм.. заинтриговал!
      • 0
        в точку. не могу использовать РНР хотя и пытался :(
        • +1
          питон на то и питон, задушил?? ))))
      • +1
        Да бросьте. Вполне нормально использую оба языка. Никаких проблем не заметил :)
  • 0
    Раз уж проскочила lamda, дальше лучше про функциональное программирование. За статьи спасибо. Давно хотел почитать, но было лень, а тут все готовое :)
  • 0
    Спасибо за статью. Примеры достаточно интересные, вроде бы и раньше про это знал, однако на практике часто забываю.
    "# 2 заполняем словарь" можно чуток сократить
    rmp = {}
    for ip, traffic in raw:
    rmp[ip] = rmp.get(ip, 0) + int(traffic)
    • 0
      можно. я пытался не запутать все совсем окончательно :)
    • 0
      from collections import defaultdict
      rmp = defaultdict(int)
      for ip, bytes in raw:
      rmp[ip] += int(bytes)

      Так гораздо приятнее, по-моему :)
      • 0
        вариант не мой, но внушает :)
        l = (lambda f:
        (lambda l: l.sort(reverse = True, key = lambda (k, v): v) or l)(
        (lambda m, f:
        filter(
        lambda (i, t):
        m.__setitem__(i, m.get(i, 0) + int(t)),
        [j.split(' ') for j in file(f, 'r').readlines()]
        ) or m.items()
        )({}, f)
        )
        )('in.txt')

        print '\n'.join('%s = %d' % (i, t) for (i, t) in l)


        свернуть по вкусу.
        • 0
          тут примеры покруче ;-)
          http://ru.wikipedia.org/wiki/Brainfuck
        • +1
          руки б за такие "варианты" поотрывал...
          lftltltllormjijk - может еще так начнем писать?;)
          • 0
            это - всего лишь концепт. для демонстрации идеи что всем можно довести до безумия :)
  • 0
    на счет дельнеших уроков - я за ООП.. ибо питон сам по себе ООП язык - он на это больше их ориентирован
    • 0
      Присоединяюсь! Хотелось бы почитать о «new-style» классах.
      • 0
        class MyClass:
        # old-style class
        pass

        class MyClass(object):
        # new-style class
        pass

        Как-то так (:
        • 0
          ага, только еше super, mro и метапрограммирование.
        • 0
          ага, только еще super, mro и метапрограммирование.
          • 0
            Может быть, ты тоже что-нибудь о python напишешь? Хотя бы в личный блог.
            Например о том же мета-программировании, с практическими примерами и домашним заданием.
        • 0
          Ну, это-то я знаю... Хотелось бы почитать про идеологические различия и про то, почему в Python 3000 все классы станут «new-style».
  • 0
    Раз уш использовали xrange то и вместо for name, salary in dic.items() лучше for name, salary in dic.iteritems() :)

    А вообще спасибо) давайте продолжим ООП?)
  • +1
    Вы бы добавили про разницу range и xrange...
    range создает список, а затем по нему проходит. Если список очень большой, то он будет жить в памяти и сожрет много ресурсов.
    xrange возвращает "xrange object". Каждый элемент поочередно создается и передается, затем память освобождается.

    Например, x = range(0,10) создаст list и передаст его переменной x.
    >>> print x
    [1, 2, 3, 4, 5, 6, 7, 8, 9]

    С xrange такой фокус не прокатит:
    >>> x = xrange(0,10)
    >>> print x
    xrange(10)
    • 0
      это уже было в предыдущем уроке ;)
      • 0
        Мда? Что-то пропустил, сорри :)
      • 0
        Сейчас специально слазил - там нет ни слова про xrange :)
        • 0
          прошу прощения, мой косяк. мне упорно казалось что это я объяснил :(
    • 0
      Вместе с генераторами и расскажут;)
    • 0
      Кстати в python 3000 range будет ленивым, т.е. вместо range будет xrange. Ленивыми также будут map/filter и т.д.
  • 0
    Спасибо, очень интересно и практично. Не оторвано от реально жизни...
    Как по мне, так ООП и так мейн стрим, найти инфу, что про него, что про ООП в питоне ю не так уж и сложно.
    А вот с функциональным, все гораздо печальней. Найти материал, для чего функциональное программирование может быть полезным, не математику да еще и в питоне, не такто легко.
    Так что обеими руками за функциональное программирование.
    • 0
      http://www.developers.org.ua/archives/ad…
      вот неплохая статья "зачем" :) а "как" - постараюсь вскоре рассказать :)
      • 0
        Спасибо за ссылку! Кажется даже читал в свое время :)
        Но опять же, даже эта статья не дает простых, чётких примеров. Она интригует и манит, обещая невероятные чудеса... но не показывает их :)
        Хотя думаю на деле все проще, и разница примерно как между Windows vs Linux. Фанаты спорят до хрипоты, а разумные так или иначе используют и то и другое. :)
        Поэтому очень хочется увидеть в примерах, как это может упростит наш тяжелы, бессмысленный труд :)

        PS - чую, что как человеку разрабатывающему на JavaScript, многие вещи окажутся очень знакомыми. Я же правильно понимаю, что анонимные ф-ции, замыкания и map reduce это все из ф-циональной оперы?
        • 0
          в общем - по всем пунктам вы правы. даже и добавить нечего :)
        • 0
          нет, функции высшего порядка, замыкания и свёртки не являются прерогативой фп. лексические замыкания, например, появились первыми в императивных языках, потом их подхватили функциональные, потом императивные позаимствовали у функциональных функции высшего порядка и свёртки.
          • 0
            точнее сказать, свёртки можно было создавать на императивных языках и раньше, но в этом небыло особого смысла ввиду отсутствия функций высшего порядка.
  • 0
    list comprehension => списковое включение (?)
    • 0
      возможно. не могу спорить, но мне этот термин почему-то кажется каким-то громоздким и непонятным. ъотя омжет я не прав.
    • 0
      Общепринятого перевода нет, насколько я знаю, даже для соответствующего теоретико-множественного понятия, от которого и пошёл английский термин. Все равно тащить математические истоки не получится, ZF-нотация будет звучать совсем дико. Списковые включения, списковые выделения…
  • 0
    >print "\n".join(["%s = %d" % (name, salary) for name, salary in dic.items()])
    моожно так print "\n".join("%s = %d" % (name, salary) for name, salary in dic.items())
    Ваш вариант генерит список и он отдаётся дойну, второй вариант создаёт генератор на пододе xrange.
    А вообще продолжайте в том же духе, пусть народ пишет на питон "правильно" :)
    • 0
      у меня цель была - продемонстрировать list comprehensions в работе.
      если захотеть, можно этот пример в однострочник свернуть, но смысл?
  • 0
    if ip in rmp: rmp[ip] += int(traffic)
    else: rmp[ip] = int(traffic)

    ужос. так нельзя?

    if ip in rmp: rmp[ip] = ( rmp[ip] || 0 ) + int(traffic)
    • 0
      всмысле, просто: rmp[ip] = ( rmp[ip] || 0 ) + int(traffic)
  • 0
    А на хабре никак нельзя использовать подсветку кода? Ну или хотя бы форматирование что бы сохраняло - удобнее читать. А то через строчку - неудобно, да и преимущество форматирования табами - не так заметно.
    Ну и стоило сказать,что не надо увлекаться подобного рода сахаром в явный ужерб читабельности :)
    • 0
      подсветку - только сторонними "раскрашивалками". для пайтона я пока не нашел подходящей.
      а большие интервалы между строками - "фича" работы тега pre
      • 0
        Ядумаю, любая из популярных подойдёт. Не понравится - замените.
        Всё лучше чем плейн-текстом. Эта "фича" - сильно глаз режет.
        • 0
          честно. я нашел парочку, но они дают хабронесовместимый код. так что я не знаю пока что делать...
  • 0
    Скажите, а продолжение будет?
    И не подскажите где можно найти разнообразных задачек?
    • 0
      продолжение, надеюсь будет… когда будет время :(
      а задачки — попробуйте Python Challenge — там их дико много…
      • 0
        А на русском?
        • 0
          я не знаю, к сожалению.
  • 0
    спасибо за хороший цикл статей, надеюсь, что напишите-таки продолжение.

    и просьба-совет — было бы очень удобно, если бы в начале давались ссылки на остальные статьи этого цикла.
  • +1
    Действительно отличный цикл статей для людей, который вообще с пайтоном не знакомы (как я :)).
    Но последний пример можно реализовать в 8 строк читаемого кода на PHP (в примере я насчитал 10):
    • +1
      $lines = file( 'ip.log' );
      $data = array();
      foreach( $lines as $line ) {
      $lineData = explode( ' ', $line );
      $data[ $lineData[0] ] = isset( $lineData[0] )? $data[ $lineData[0] ] + $lineData[1]: 0;
      }
      arsort( $data );
      var_dump( $data );
      • +1
        В будущем, хотелось бы почитать об ООП в пайтоне, и если можно, сравните реализацию ООП с PHP
      • 0
        если поставить за цель — в пайтоне можно строки в 4-5 уложиться. но сильно в ущерб читабельности это ж просто учебный пример.

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