Вопрос

Я хочу сделать сопоставление с образцом в списках в 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 вместо двух.

Это было полезно?

Решение

Насколько я знаю, в текущем 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 .

Другие советы

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

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

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

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

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

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

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

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

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

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

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

расширенная распаковка была введена в 3.0 http://www.python.org/dev/peps/pep-3132/

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

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% случаев. Мало того, что он более понятен для чтения, он быстрее и будет работать не только со списками (например, с наборами). Если там произойдет исключение, функция будет успешно завершена и доставит ее по цепочке.

Я работаю над 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))

Ну, а зачем вам это в первую очередь в 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 :)

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

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

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

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

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]

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

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


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

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

Для вашего конкретного случая использования - конечно же, эмулировать fun (head: rest) = Haskell = ... . Определения функций уже давно поддерживают распаковку параметров:

def my_method(head, *rest):
    # ...

Начиная с Python 3.0, как упоминалось в @bpowah , Python также поддерживает распаковку при назначении:

my_list = ['alpha', 'bravo', 'charlie', 'delta', 'echo']
head, *rest = my_list
assert head == 'alpha'
assert rest == ['bravo', 'charlie', 'delta', 'echo']

Обратите внимание, что звездочка ("знак") означает "остаток от повторяемого элемента", а не "до конца". Следующее работает отлично:

first, *middle, last = my_list
assert first == 'alpha'
assert last == 'echo'
assert middle == ['bravo', 'charlie', 'delta']

first, *middle, last = ['alpha', 'bravo']
assert first == 'alpha'
assert last == 'bravo'
assert middle == []
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top