Основы 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. Есть лог-файл какого-то чата. Посчитать «разговорчивость» пользователей в нем в виде ник — количество фраз. Посчитать среднее число букв на участника чата.

    ЗЫ О чем лучше продолжать — про классы и ООП или про элементы функционального программирования?
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 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 уложиться. но сильно в ущерб читабельности это ж просто учебный пример.

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