2008-10-26 4 views
34

Я хочу сделать сопоставление шаблонов в списках в Python. Например, в Haskell, я могу сделать что-то вроде следующего:Сравнение совпадений списков в Python

fun (head : rest) = ... 

Так что, когда я прохожу в списке, head будет первый элемент, и rest будет хвостовые элементы.

Кроме того, в Python, я могу автоматически распаковывать кортежи:

(var1, var2) = func_that_returns_a_tuple() 

Я хочу сделать что-то подобное со списками в Python. Прямо сейчас, у меня есть функция, которая возвращает список, и кусок кода, который выполняет следующие действия:

ls = my_func() 
(head, rest) = (ls[0], ls[1:]) 

я задавался вопросом, если я что-то мог бы сделать это в одной строке в Python, вместо двух.

ответ

55

Насколько я знаю, что нет никакого способа, чтобы сделать его Однострочник в текущем Python без введения другой функции, например:

split_list = lambda lst: (lst[0], lst[1:]) 
head, rest = split_list(my_func()) 

Однако в Python 3.0 специализированный синтаксис, используемый для переменных числа подписей аргументов и аргумент распаковка будет доступна для этого типа общей последовательности распаковки, так что в 3.0 вы сможете написать:

head, *rest = my_func() 

PEP 3132 См подробность.

+0

Конечно, вы можете положить, что лямбда на одной линии со всем остальным: голову, остальное = (lambda lst: (lst [0], lst [1:])) (my_func()) – 2008-10-26 15:54:39

+11

Да, но это начинает действовать при запутывании. – 2008-10-27 13:01:45

+1

+1 для новой функции Python 3 и ссылки на PEP. – fossilet 2013-08-27 03:55:50

4

Это очень «чистый функциональный» подход и, как таковой, является разумной идиомой в Haskell, но, вероятно, это не так уместно для Python. Таким образом, у Python существует очень ограниченная концепция patterns - и я подозреваю, что вам может понадобиться несколько более жесткая система типов для реализации такой конструкции (erlang баффов, приглашенных здесь не согласиться).

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

Как было statedon a few occasionsbefore, Python на самом деле не является функциональным языком. Он просто заимствует идеи из мира FP. Это не по своей сути Tail Recursive так, как вы ожидали бы увидеть встроенным в архитектуру функционального языка, поэтому вам будет трудно выполнять такую ​​рекурсивную операцию на большом наборе данных, не используя много пространства стека.

2

Ну, почему вы хотите его в 1-строчной линии в первую очередь?

Если вы действительно хотите, вы всегда можете сделать трюк, как это:

def x(func): 
    y = func() 
    return y[0], y[1:] 

# then, instead of calling my_func() call x(my_func) 
(head, rest) = x(my_func) # that's one line :) 
32

Прежде всего, обратите внимание, что «по шаблону» функциональных языков и присвоение кортежей, о которых вы упоминаете, на самом деле не похоже. В функциональных языках шаблоны используются для частичного определения функции.Так f (x : s) = e не значит взять голова и хвост аргумента f и возврат e их использование, но это означает, что если аргумент f имеет вида x : s (для некоторых x и s), затемf (x : s) является равным e.

Назначение python больше похоже на множественное присвоение (я подозреваю, что это было его первоначальное намерение). Таким образом, вы пишете, например, x, y = y, x, чтобы обменять значения в x и y без временной переменной (как и с простой операцией присваивания). Это имеет мало общего с сопоставлением с образцом, поскольку в основном это сокращение для «одновременного» выполнения x = y и y = x. Хотя python позволяет использовать произвольные последовательности вместо списков, разделенных запятыми, я бы не предлагал называть это сопоставление шаблонов. При сопоставлении с образцом вы проверяете, соответствует ли какой-либо шаблон шаблону; в назначении python вы должны убедиться, что последовательности с обеих сторон одинаковы.

Чтобы сделать то, что вам кажется, вы, как правило, (также в функциональных языках) используете либо вспомогательную функцию (как упоминалось другими), либо что-то похожее на let или where (что вы можете рассматривать как использование анонимных функций). Например:

(head, tail) = (x[0], x[1:]) where x = my_func() 

Или, в реальном питоне:

(head, tail) = (lambda x: (x[0], x[1:]))(my_func()) 

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

(Извините, если мой ответ немного сверху. Я просто думаю, что это важно сделать различие ясно.)

1

был в рецепт будет питона поваренной книги, чтобы сделать это. я не могу показаться, чтобы найти его сейчас, но вот код (я ее немного модифицировал)


def peel(iterable,result=tuple): 
    '''Removes the requested items from the iterable and stores the remaining in a tuple 
    >>> x,y,z=peel('test') 
    >>> print repr(x),repr(y),z 
    't' 'e' ('s', 't') 
    ''' 
    def how_many_unpacked(): 
     import inspect,opcode 
     f = inspect.currentframe().f_back.f_back 
     if ord(f.f_code.co_code[f.f_lasti])==opcode.opmap['UNPACK_SEQUENCE']: 
      return ord(f.f_code.co_code[f.f_lasti+1]) 
     raise ValueError("Must be a generator on RHS of a multiple assignment!!") 
    iterator=iter(iterable) 
    hasItems=True 
    amountToUnpack=how_many_unpacked()-1 
    next=None 
    for num in xrange(amountToUnpack): 
     if hasItems:   
      try: 
       next = iterator.next() 
      except StopIteration: 
       next = None 
       hasItems = False 
     yield next 
    if hasItems: 
     yield result(iterator) 
    else: 
     yield None 

однако следует иметь в виду, что это работает только при использовании распаковку назначения из-за способа его inespects предыдущий кадр ... все же его весьма полезно.

2

В дополнение к другим ответам обратите внимание на то, что эквивалентная операция заголовка/хвоста в Python, включая расширение синтаксиса * python3, как правило, будет менее эффективной, чем сопоставление шаблонов Haskell.

Списки Python реализованы как векторы, поэтому для получения хвоста потребуется взять копию списка. Это O (n) соответствует размеру списка, тогда как реализация с использованием связанных списков, таких как Haskell, может просто использовать указатель на хвост, операцию O (1).

Единственным исключением могут быть подходы, основанные на итераторе, где список фактически не возвращен, но итератор. Однако это может быть неприменимо ко всем местам, где требуется список (например, повторение нескольких раз).

Например, Cipher's подход, если он изменен для возврата итератора, а не преобразования его в кортеж, будет иметь такое поведение.В качестве альтернативы более простой 2-деталь только метод не полагаясь на байткод будет:

def head_tail(lst): 
    it = iter(list) 
    yield it.next() 
    yield it 

>>> a, tail = head_tail([1,2,3,4,5]) 
>>> b, tail = head_tail(tail) 
>>> a,b,tail 
(1, 2, <listiterator object at 0x2b1c810>) 
>>> list(tail) 
[3, 4] 

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

2

В отличие от Haskell или ML, Python не имеет встроенного шаблона сопоставления структур. Самый Pythonic способ сделать шаблон синхронизма с примерки, за исключением блока:

def recursive_sum(x): 
    try: 
     head, tail = x[0], x[1:] 
     return head + recursive-sum(tail) 
    except IndexError: # empty list: [][0] raises IndexError 
     return 0 

Обратите внимание, что это работает только с объектами с ломтиком индексации. Кроме того, если функция усложняется, то в теле после линия head, tail может поднять IndexError, что приведет к тонким ошибкам. Тем не менее, это позволит вам делать такие вещи, как:

В Python, хвостовая рекурсия, как правило, лучше реализована в виде петли с аккумулятором, то есть:

def iterative_sum(x): 
    ret_val = 0 
    for i in x: 
     ret_val += i 
    return ret_val 

Это одна очевидно, правильно способ сделать это в 99% случаев. Чтение не только читается, но и быстрее, и оно будет работать на вещах, отличных от списков (например, наборов). Если есть какое-то исключение, ожидаемое там, функция будет успешно терпеть неудачу и довести ее до цепочки.

2

Я работаю над pyfpm, библиотекой для сопоставления шаблонов в Python с синтаксисом типа Scala. Вы можете использовать его для распаковки объектов, как это:

from pyfpm import Unpacker 

unpacker = Unpacker() 

unpacker('head :: tail') << (1, 2, 3) 

unpacker.head # 1 
unpacker.tail # (2, 3) 

Или в список аргументов функции во:

from pyfpm import match_args 

@match_args('head :: tail') 
def f(head, tail): 
    return (head, tail) 

f(1)   # (1,()) 
f(1, 2, 3, 4) # (1, (2, 3, 4))