Введение в язык Питон
773123a3

Связывание выражений


Недовольный полурешениями, один из читателей - Ричарда Дейвис (Richard Davies) - поднял вопрос, можем ли мы целиком переместить связывания в отдельные выражения. Давайте попытаемся понять, зачем нам может этого захотеться, а также продемонстрируем замечательно элегантный способ этого добиться, предоставленный участником comp.lang.python.

Давайте сначала вспомним о классе Bindings, определенном в модуле functional. Используя свойства этого класса, мы смогли гарантировать, что отдельное имя имеет единственное значение в пределах области данного блока:

    #------- Python FP session with guarded rebinding -------#

    >>> from functional import *

          >>> let = Bindings()

          >>> let.car = lambda lst: lst[0]

          >>> let.car = lambda lst: lst[2]

          Traceback (innermost last):

            File "", line 1, in ?

            File "d:\tools\functional.py", line 976, in __setattr__

              raise BindingError, "Binding '%s' cannot be modified." % name

          functional.BindingError:  Binding 'car' cannot be modified.



          >>> let.car(range(10))

          0

С помощью класса Bindings нам удалось достичь желаемого результата в пределах модуля или функции, но в отношении отдельного выражения мы бессильны. Тем не менее, для семейства ML-языков вполне естественно создавать связывания в пределах отдельного выражения:

    #-------- Haskell expression-level name bindings --------#

          -- car (x:xs) = x  -- *could* create module-level binding

          list_of_list = [[1,2,3],[4,5,6],[7,8,9]]

          -- 'where' clause for expression-level binding



          firsts1 = [car x | x <- list_of_list] where car (x:xs) = x

          -- 'let' clause for expression-level binding

          firsts2 = let car (x:xs) = x in [car x | x <- list_of_list]

          -- more idiomatic higher-order 'map' technique

          firsts3 = map car list_of_list where car (x:xs) = x

          -- Result: firsts1 == firsts2 == firsts3 == [1,4,7]



Грэг Эвинг (Greg Ewing) заметил, что мы можем достичь того же эффекта, воспользовавшись списочными встраиваниями Python  (list comprehensions); мы даже можем сделать это почти столь же ясным способом, как в Haskell:



    #------ Python 2.0+ expression-level name bindings ------#

          >>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]

          >>> [car_x for x in list_of_list for car_x in (x[0],)]

          [1, 4, 7]


Этот прием - размещение выражения внутри одноэлементного кортежа в списочном встраивании - не позволяет использовать связывание на уровне выражений с функциями высшего порядка. Для использования таких функций мы все еще должны использовать область блока:



     #------- Python block-level bindings with 'map()' -------#

          >>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]

          >>> let = Bindings()

          >>> let.car = lambda l: l[0]

          >>> map(let.car,list_of_list)

          [1, 4, 7]


Неплохо, хотя если мы хотим использовать map(), область связывания остается несколько шире, чем мы того хотели. Тем не менее, можно уговорить списочное встраивание делать для нас связывание имен, даже если список - не то, что нам нужно в конечном счете:



          #---- "Stepping down" from Python list-comprehension ----#



          # Compare Haskell expression:

          # result = func car_car

          #          where

          #              car (x:xs) = x

          #              car_car = car (car list_of_list)

          #              func x = x + x^2

          >>> [func for x in list_of_list

          ...       for car in (x[0],)

          ...       for func in (car+car**2,)][0]

          2


В этом примере мы произвели арифметическое действие над первым элементом первого элемента списка list_of_list

и одновременно поименовали это действие (но только в области объемлющего выражения). В качестве "оптимизации" можно посоветовать создавать список длиной не более одного элемента, поскольку с помощью индекса [0] в конце выражения выбираем только первый элемент:



    #---- Efficient stepping down from list-comprehension ---#

          >>> [func for x in list_of_list[:1]

          ...       for car in (x[0],)

          ...       for func in (car+car**2,)][0]       2



Содержание раздела