Подтвердить что ты не робот

Сравнение шаблонов списков в Python

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

fun (head : rest) = ...

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

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

(var1, var2) = func_that_returns_a_tuple()

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

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

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

4b9b3361

Ответ 1

Насколько я знаю, нет способа сделать его однострочным в текущем 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.

Ответ 2

Прежде всего, обратите внимание, что "соответствие шаблонов" функциональных языков и присвоение указанным вами кортежам на самом деле не похожи. В функциональных языках шаблоны используются для частичного определения функции. Таким образом, 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()

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

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

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

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

Ответ 3

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

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

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

Ответ 4

Хорошо, почему вы хотите, чтобы это было в 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 one line :)

Ответ 6

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

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

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

Например, подход Cipher, если он изменен для возврата итератора, а не преобразования его в кортеж, будет иметь такое поведение. Альтернативно, более простой метод с двумя элементами, не полагающийся на байт-код, будет:

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]

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

Ответ 7

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

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, что приведет к тонким ошибкам. Однако это позволяет вам делать такие вещи, как:

for frob in eggs.frob_list:
    try:
        frob.spam += 1
    except AttributeError:
        eggs.no_spam_count += 1

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

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

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

Ответ 8

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

from pyfpm import Unpacker

unpacker = Unpacker()

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

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

Или в функции arglist:

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))

Ответ 9

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


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

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