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






Это в значительной степени «чисто функциональный» подход, и поэтому он является разумной идиомой в Haskell, но, вероятно, не так подходит для Python. Таким образом, Python имеет очень ограниченную концепцию узоры - и я подозреваю, что вам может потребоваться несколько более жесткая система типов для реализации такой конструкции (сторонники эрланг приглашены не согласиться здесь).
То, что у вас есть, вероятно, максимально близко к этой идиоме, но вам, вероятно, лучше использовать понимание списка или императивный подход, чем рекурсивно вызывать функцию с хвостом списка.
Как и заявилв нескольких случаяхдо, Python на самом деле не является функциональным языком. Он просто заимствует идеи из мира FP. По сути, это не Хвостовой рекурсивный в том виде, в каком вы ожидаете увидеть его встроенным в архитектуру функционального языка, поэтому у вас возникнут некоторые трудности при выполнении такого рода рекурсивных операций с большим набором данных без использования большого пространства стека.
Насколько я знаю, в текущем 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.
Да, но это начинает приближаться к запутыванию.
Ну, а почему вы вообще хотите, чтобы это было в одну строку?
Если очень хочется, всегда можно проделать такой трюк:
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 :)
В основном потому, что - при условии «хорошего» синтаксиса - его будет легче читать.
Также потому, что «голова» и «хвост» - стандартные идиомы списков. Они не зря используются во всех структурах данных и алгоритмах. Настоящая проблема - это Python, не имеющий встроенной функции head(my_list).
расширенная распаковка была представлена в версии 3.0 http://www.python.org/dev/peps/pep-3132/
Прежде всего, обратите внимание, что «сопоставление шаблонов» функциональных языков и присвоение кортежам, о которых вы говорите, на самом деле не так похожи. В функциональных языках шаблоны используются для частичного определения функции. Таким образом, 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())
Обратите внимание, что это по сути то же самое, что и решения, предлагаемые другими с вспомогательной функцией, за исключением того, что это однострочный, который вам нужен. Однако это не обязательно лучше, чем отдельная функция.
(Извините, если мой ответ немного преувеличен. Я просто думаю, что важно четко обозначить разницу.)
в кулинарной книге 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
однако вы должны отметить, что это работает только при использовании распаковки присваивания из-за того, что он не учитывает предыдущий фрейм ... все же он довольно полезен.
В дополнение к другим ответам обратите внимание, что эквивалентная операция 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]
Очевидно, что вам все равно придется оборачивать служебную функцию, а не использовать для нее приятный синтаксический сахар.
В отличие от Haskell или ML, Python не имеет встроенного сопоставления структур с образцом. Самый питонический способ сопоставления с образцом - это блок 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% случаев. Он не только понятнее для чтения, но и быстрее, и он будет работать не только со списками (например, с наборами), но и с другими вещами. Если там ожидается исключение, функция благополучно завершится ошибкой и доставит его по цепочке.
Я работаю над 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))
очень и очень непифонично :)
Для вашего конкретного случая использования - конечно, эмуляции 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 == []
Конечно, вы можете поместить эту лямбду в одну строку со всем остальным: head, rest = (lst [0], lst [1:])) (my_func ())