Python-неизвестный

    На Хабре уже есть несколько статей\переводов, в которых рассказывается о неизвестных фичах\тонкостях\возможностях Пайтона. Я буду пытаться не повторять их, а дополнять, но если уж так случилось, что вы это уже где-то видели — не огорчайтесь. Я уверен, что найдется что-то интересное и для вас.

    Итак, поехали.

    Цепочки операторов сравнения (chaining comparison):

    >>> 1 < 5 < 10
    True
    >>> 1 < 11 < 10
    False
    >>> 1 < 11 > 10
    True
    

    Правда и то, что вся магия поломается если добавить скобки:

    >>> (1 < 11) > 10
    False
    

    Дока

    iter и два параметра

    Built-in функция iter возвращает итератор для переданной последовательности. Но, если передавать два параметра, то первый должен быть callable-объектом, а второй — результатом вызова первого объекта, при котором нужно прекратить итерацию. Например, читаем из файла до первой пустой строки:
    with open('mydata.txt') as fp:
        for line in iter(fp.readline, ''):
            process_line(line)
    

    Дока

    contextlib

    Позволяет легко и красиво пользоваться синтаксисом with EXPR as VAR для своих собственных объектов, функций и т.д. Пример из документации:
    from contextlib import contextmanager
    
    @contextmanager
    def tag(name):
        print "[%s]" % name
        yield
        print "[/%s]" % name
    
    >>> with tag("h1"):
    ...    print "foo"
    ...
    [h1]
    foo
    [/h1]
    

    Дока, PEP 343

    Аргументы по умолчанию

    Здесь несколько «хаков». Во первых, использование изменяемых (mutable) объектов в качестве аргументов по умолчанию — почти всегда плохая идея:
    >>> def foo(a=list()):
    ...     a.append(1)
    ...     print a
    ...
    >>> foo()
    [1]
    >>> foo()
    [1, 1]
    >>> foo()
    [1, 1, 1]
    

    Почему так? Значения агрументов по умолчанию определяются только раз при создании функции и сохраняются в свойстве func_defaults:
    >>> foo.func_defaults
    ([1, 1, 1],)
    


    Но есть в довесок и полезный рецепт. Следующий код работает не совсем так, как ожидается:
    >>> l = []
    >>> for i in range(3):
    ...     l.append(lambda: i * 2)
    ...
    >>> for x in l:
    ...     x()
    ...
    4
    4
    4
    

    Поправить его можно просто и элегантно — достаточно заменить lambda: i * 2 на lambda i=i: i * 2:
    >>> l = []
    >>> for i in range(3):
    ...     l.append(lambda i=i: i * 2)
    ...
    >>> for x in l:
    ...     x()
    ...
    0
    2
    4
    

    О именах и привязке к ним можно почитать в Execution Model.

    Ellipsis

    В зависимости от контекста ... (три точки) может быть допустимым синтаксисом. Так, list[...] передает в функцию __getitem__ объект типа Ellipsis — единственный и неповторимый в своем роде. Демонстрация:
    >>> class L(list):
    ...     def __getitem__(self, *args):
    ...             print args
    ...             return list.__getitem__(self, *args)
    ...
    >>> l[...]
    (Ellipsis,)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 4, in __getitem__
    TypeError: list indices must be integers, not ellipsis
    

    Используется эта прелесть в Numpy.

    Обмен значениями переменных

    В 10-ом классе нам на уроке информатике предлагали задачку — обменять местами значения двоих переменных, не используя третью. В Пайтоне это даже не вопрос:
    >>> a = 10
    >>> b = 5
    >>> a, b
    (10, 5)
    >>> a, b = b, a
    >>> a, b
    (5, 10)
    


    Вложенные list comprehensions

    В list comprehensions можно использовать несколько for:
    >>> l = [[1,2,3], [4,5,6]]
    >>> [lll * 2 for ll in l for lll in ll]
    [2, 4, 6, 8, 10, 12]
    


    Создание нескольких изменяемых объектов

    Например, надо создать список из 5 списков. Хочется сделать так:
    >>> l = [[]] * 5
    >>> l
    [[], [], [], [], []]
    

    Но нельзя:
    >>> l[0].append(1)
    >>> l
    [[1], [1], [1], [1], [1]]
    

    Лучше наверное вот так:
    >>> l = [[] for _ in range(5)]
    


    rot13, string_escape, unicode_escape кодировки

    Супер секретный метод шифрования Rot13:
    >>> 'Hello world!'.encode('rot13')
    'Uryyb jbeyq!'
    >>> 'Uryyb jbeyq!'.decode('rot13')
    u'Hello world!'
    

    Далее. Например, есть строка из внешнего источника, при этом в строке есть литералы \n, \t, \r и т.п. Как получить отформатированную строку (по сути, сделать безопасный eval)?
    >>> s = 'Hello\\n\\rworld!'
    >>> s
    'Hello\\n\\rworld!'
    >>> repr(s)
    "'Hello\\\\n\\\\rworld!'"
    >>> print s.decode('string_escape')
    Hello
    world!
    

    unicode_escape работает аналогично, только с юникодом, а не со строками. Так же он умеет превратить строку '\u0457' в букву "ї":
    >>> print '\u0457'.decode('unicode_escape')
    ї
    

    Перечень поддерживаемых кодировок — standard-encodings.

    textwrap

    Очень полезная либа для работы с текстами. Делаем с длинной строки много мелких:
    >>> s = "Python is a programming language that lets you work more quickly and integrate your systems more effectively. You can learn to use Python and see almost immediate gains in productivity and lower maintenance costs."
    >>> print textwrap.fill(s, 25)
    Python is a programming
    language that lets you
    work more quickly and
    integrate your systems
    more effectively. You can
    learn to use Python and
    see almost immediate
    gains in productivity and
    lower maintenance costs.
    

    Дока — там ещё много вкусного.

    itertools

    Сплошное умиления. Читать-не перечитать, особенно раздел с рецептами. Здесь вспомню grouper:
    >>> def grouper(n, iterable, fillvalue=None):
    ...     "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    ...     args = [iter(iterable)] * n
    ...     return izip_longest(fillvalue=fillvalue, *args)
    ...
    >>> list(grouper(2, [1,2,3,4,5,6]))
    [(1, 2), (3, 4), (5, 6)]
    >>> list(grouper(3, [1,2,3,4,5,6]))
    [(1, 2, 3), (4, 5, 6)]
    >>> list(grouper(4, [1,2,3,4,5,6], fillvalue=0))
    [(1, 2, 3, 4), (5, 6, 0, 0)]
    

    фишка в том, что мы работаем с одним итератором

    Заключения


    Большая часть информации из замечательного топика на StackOverFlow, остальное — собственные наблюдения. На эту тему можно писать много, поэтому старался поделится только самым интересным и полезным.
    Спасибо за внимание!
    Да прибудет с вами сила документации!

    P.S. Если есть замечания — буду очень признателен за сообщения в личку.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 51
    • +9
      Спасибо большое, некоторые вещи не знал. Например, тоже порадовал grouper. А некоторое помню ещё из книжки: обмен переменных местами, аргументы функции, перенос текста.
      И немножко режет слух «пайтон». «Питон» как-то более по-русски чтоли…
      • +6
        Уж извините, название в честь Монти Пайтон… Вы же не называете её «Монти Питон»? :)
        • +9
          Do you how russians call our python? Peeeton.
      • +5
        Пример ленивой сортировки. heapq.heapify по месту делает из списка кучу за линейное время, а heapq.heappop извлекает за тем верхний элемент.
        import heapq

        def lazy_sort(iterable):
          lst = list(iterable)
          heapq.heapify(lst)
          while lst:
            yield heapq.heappop(lst)

        print "".join([c for c in lazy_sort(u"абракадабра")])

        Результат: аааааббдкрр
        • 0
          PS. Еще один пример ленивой генерации всех перестановок. Стандартная правда более универсальная.
          def permutations(items):
            def p(items, n):
              if n == 0:
                yield []
              else:
                for i in range(n):
                  for j in p(items[:i] + items[i+1:], n - 1):
                    yield [items[i]] + j
            return p(items, len(items))

          = 0
          for i in permutations(range(10)):
            if k >= 5:
              break
            print i
            k += 1

          Результат первые 5 элементов (всего их 10!=3628800):
          [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
          [0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
          [0, 1, 2, 3, 4, 5, 6, 8, 7, 9]
          [0, 1, 2, 3, 4, 5, 6, 8, 9, 7]
          [0, 1, 2, 3, 4, 5, 6, 9, 7, 8]
          • +2
            k = 0
            for i in permutations(range(10)):
              if k >= 5:
                break
              print i
              k += 1
            

            с itertools превратится в
            list(islice(permutations(range(10)), 5))
            
            • 0
              Конечно так, проще. Я просто пример давал для понимания ленивых вычислений. Я реализация более общей permutations достаточно громоздкая.
          • –3
            Построение кучи (heapify) занимает время, выполняя по сути половину сортировки, так что не очень лениво получается.
          • +1
            А еще многие эти «неизвестности» есть в книге «Изучаем Питон».
            • +30
              Ну вот… Что же вы наделали… Сейчас HR-ы начитаются и начнут эту бесполезную муть (по большей части) на собеседованиях спрашивать…

              А вообще интересно.

              По поводу rot13 есть еще такой прикол: эту кодировку можно указывать в «магическом комментарии» кодировки python программы. Например вот работающая Python программа:

              # -*- coding: rot13 -*-
              vzcbeg urncd
              
              qrs ynml_fbeg(vgrenoyr):
                yfg = yvfg(vgrenoyr)
                urncd.urncvsl(yfg)
                juvyr yfg:
                  lvryq urncd.urnccbc(yfg)
              
              cevag "".wbva([p sbe p va ynml_fbeg(h"dhrdjrdjr")])
              
              • +5
                Отлично сочетается с комбинацией клавиш в Виме: ggg?G
              • +2
                >> Поправить его можно просто и элегантно
                и что элегантного тут?
                • +1
                  Элегантность — довольно субъективное понятие. Если вариант решения, при котором создается отдельная функция make_lambdas(i), которая возвращает готовые lambd-ы, которые работают корректно (в этом случае уже используются замыкания).

                  А вобще я согласен, что пример немного надуманный. Добавлю ссылку на Execution model — возможно даже надуманный пример воодушевит кого-то на изучение доки.
                • +4
                  Питон силён, во многом благодаря своей стандартной библиотеке. Но тока двойные for в z-конструкторах списков редко когда бывают к месту. Это и в документации отмечено, что чаще бывает проще и понятней не экономить на одной строчке и сделать нормальный for.

                  Кстати, кто как переводит list comprehension? Для себя решил в устной речи говорить конструктор списков, а в письменной указывать префикс z-, для большей точности :)
                  • 0
                    а в письменной указывать префикс z-, для большей точности :)
                    это как?
                  • +5
                    >>> (1 < 11) > 10
                    Это вроде очевидно? Булево значение перейдет в int(True), которое будет 1
                    • 0
                      >>> (1 < 11) < 10
                      True
                      действительно, пхпшникам понравится)
                      • 0
                        php -r ""«echo ((int)(1 < 11) > 10)? 'True\n': 'False\n';»""?

                        Вроде схоже выходит :).
                        В питоне тоже стоит ручками конвертировать, а то можно наткнуться на непонятности.
                        • –1
                          Так в Python boolean отсутствует вообще, там все булевые значения сразу храняться в int (0 и 1)
                          • +2
                            Здраааасьте…
                            >>> type(True), type(False)
                            (<type 'bool'>, <type 'bool'>)

                            Это условные операторы принимают не только bool, но и другие значения.
                            • –2
                              Ну здрасьте =)


                              www.python.org/dev/peps/pep-0285/ — читайте внимательно первый абзац.
                              • 0
                                «subtype» это не одно и тоже:
                                >>> 1 is 1
                                True
                                >>> 1 is True
                                False
                                >>> 0 is 0
                                True
                                >>> 0 is False
                                False
                                
                                • 0
                                  Да, не одно и то же, но это означает, что int — это не bool, но bool — это int. Булевы значения не требуют конвертации, они и так хранятся в том же виде, что и int.
                                  • 0
                                    А как вы представляете себе хранение bool значения не в виде 0/1?
                                    • –1
                                      А как вы представляете себе хранение bool значения не в виде 0/1?
                                      Видели, например, erlang или ocaml.

                                      А вобще, я сказал лишь о том, что какой-то специальной дополнительной конвертации не требуется и непонятностей при этой конвертации быть не может, так как bool — это по сути int.
                                      • +1
                                        С таким подходом можно утвержать, что double — это по сути битовый массив.
                                    • 0
                                      теоретически, bool в python можно воспринимать как подкласс int. поэтому, тот факт, что bool допускается использовать везде, где можно использовать int, следует из принципа подстановки Лисков. как они хранятся «на самом деле» не имеет ни какого значения.
                              • 0
                                Знаю, но помню что был PEP где советовали по кодировкам и по конвертации писать явно, т.к. иногда может быть непонятно.
                        • +1
                          Здорово! Вроде бы обычные вещи и многое знал, но написано от души и подобраны красивые примеры.
                          Еще один повод сказать коллегам: «Смотрите как это просто сделать на python».
                          • +2
                            Тотальное выкуривание документации (а желательно на несколько раз) натурально творит чудеса
                            • 0
                              Ее реально много.
                            • 0
                              По поводу обмена значений двух переменных без использования третей. В оригинале значения были целыми, а весь смысл был в не использовании дополнительной памяти. А кто знает, что в реальности делает a, b = b, a?
                              И вообще через третью переменную быстрее: docs.python.org/release/2.3.3/tut/node12.html 10.10
                              • +6
                                • +4
                                  Смотрим байт-код:

                                  >>> def f1():
                                  ...     a, b = 0, 1
                                  ...     b, a = a, b
                                  ...
                                  >>> def f2():
                                  ...     a, b = 0, 1
                                  ...     c = a
                                  ...     a = b
                                  ...     b = c
                                  ...
                                  
                                  >>> dis.dis(f1)
                                    2           0 LOAD_CONST               3 ((0, 1))
                                                3 UNPACK_SEQUENCE          2
                                                6 STORE_FAST               0 (a)
                                                9 STORE_FAST               1 (b)
                                  
                                    3          12 LOAD_FAST                0 (a)
                                               15 LOAD_FAST                1 (b)
                                               18 ROT_TWO
                                               19 STORE_FAST               1 (b)
                                               22 STORE_FAST               0 (a)
                                               25 LOAD_CONST               0 (None)
                                               28 RETURN_VALUE
                                  >>> dis.dis(f2)
                                    2           0 LOAD_CONST               3 ((0, 1))
                                                3 UNPACK_SEQUENCE          2
                                                6 STORE_FAST               0 (a)
                                                9 STORE_FAST               1 (b)
                                  
                                    3          12 LOAD_FAST                0 (a)
                                               15 STORE_FAST               2 ©
                                  
                                    4          18 LOAD_FAST                1 (b)
                                               21 STORE_FAST               0 (a)
                                  
                                    5          24 LOAD_FAST                2 ©
                                               27 STORE_FAST               1 (b)
                                               30 LOAD_CONST               0 (None)
                                               33 RETURN_VALUE
                                  
                                  


                                  Ищем отличия и смотрим, что такое ROT_TWO:

                                  Swaps the two top-most stack items. (дока)

                                  И так, на самом деле вариант с упаковыванием-распаковыванием списков потребляет меньше памяти. Компилятор умный, он оптимизирует.
                                • +2
                                  Инициализация словаря
                                  >>> dict(a=1, b=2, c=3)
                                  {'a': 1, 'c': 3, 'b': 2}

                                  Из питона3 (что-то портировано и на 2.7)
                                  словарь из пар
                                  >>> list(enumerate('abc'))
                                  [(0, 'a'), (1, 'b'), (2, 'c')]
                                  >>> dict(list(enumerate('abc')))
                                  {0: 'a', 1: 'b', 2: 'c'}

                                  Метод format у строк
                                  >>> "Вес {weight} кг, цена {price} руб".format(weight=1, price=2)
                                  'Вес 1 кг, цена 2 руб'


                                  Разделение списков в функциональном стиле.
                                  >>> head, *tail = list(range(5))
                                  >>> head
                                  0
                                  >>> tail
                                  [1, 2, 3, 4]
                                  • 0
                                    >>> dict(enumerate('abc'))
                                    {0: 'a', 1: 'b', 2: 'c'}
                                    

                                    Можно и без промежуточного списка
                                    • 0
                                      За что люблю питон, общаясь и читая чужой код, постоянно узнаешь изящные ходы =)
                                    • 0
                                      Метод format у строк
                                      >>> "Вес {weight} кг, цена {price} руб".format(weight=1, price=2)
                                      'Вес 1 кг, цена 2 руб'

                                      А мне и так нормально:
                                      'Вес %(weight)d кг, цена %(price)d руб' % {'weight' : 1, 'price' : 2}
                                      :)
                                      • 0
                                        В третьем питоне не будет работать :)
                                        • 0
                                          Эх, а жаль. А то format'а в 2.5 нет :(
                                      • 0
                                        по-моему, «словарь из пар» можно делать уже давно. в python2.5 точно есть.
                                      • –1
                                        согласования поправьте, а то голову сломал:
                                        «Но, если передавать два параметра, то первой должен быть callable-объектом, а второй — результатом вызова первого объект, при котором нужно прекратить итерацию»

                                        • 0
                                          Спасибо, поправил.
                                        • 0
                                          Может и было в комментах, но все-таки мне очень интересно сравнение чисел =)
                                          «Правда и то, что вся магия поломается если добавить скобки:

                                          >>> (1 < 11) > 10
                                          False»

                                          то есть 1<11 дает true, а true — это обычно единица. Так может для достижения TRUE в резулоьтате такого выражения, стоит сравнить так: (1<11)>0? у кого есть питон под рукой, попробуйте плз =)
                                          • 0
                                            Да, это будеть True, но не потому что 11 > 0, а потому что True(1) > 0.

                                            > питон под рукой
                                            http://codepad.org
                                            • 0
                                              А я оч чем выше говорил? :)
                                              «true — это обычно единица»
                                              • 0
                                                извините, отправилось раньше… дополнение:
                                                …, в результате получаем
                                                (1<11)>10
                                                1<11 = true,
                                                true = 1,
                                                1 > 0
                                          • –6
                                            Какой пайтон? Вы ебанулись так питон называть?
                                            • +1
                                              Python ([ˈpaɪθən]; па́йтон, широко используется также русскоязычное произношение пито́н) [...] Название языка произошло вовсе не от вида пресмыкающихся. Автор назвал язык в честь популярного британского комедийного телешоу 1970-х «Летающий цирк Монти Пайтона». ru.wikipedia.org/wiki/Python
                                              • 0
                                                Да, причём написал: (Q&A) нужно ли любить шоу Монти Пайтона, чтобы программировать на этом языке? — не обязательно. Хотя я люблю и то, и другое :)))

                                                А пресмыкающее он обозвал «nasty reptile».

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