it-swarm.com.ru

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

37
mipadi

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

59
James Bennett

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

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

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

32
mweerden

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

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

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

4
ConcernedOfTunbridgeWells

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

3
bpowah

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

3
giltay

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

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

В дополнение к другим ответам, обратите внимание, что эквивалентная операция 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]

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

2
Brian

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


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

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

1
Jake

Для вашего конкретного случая использования - эмуляция 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 == []
0
Lyndsy Simon