Python中列表的模式匹配
-
04-07-2019 - |
题
我想在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
或 where
构造(您可以将其视为使用匿名函数)。例如:
(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 buffs邀请在这里不同意)。
你所拥有的东西可能与你对这个习语的接近程度很接近,但你可能最好使用列表理解或命令式方法,而不是递归调用带有列表尾部的函数。
正如所述 有几次 之前,Python实际上并不是一种功能语言。它只是借鉴了FP世界的想法。它本身并不是 Tail Recursive ,就像你希望看到嵌入在一个体系结构中的方式一样。函数式语言,因此在不使用大量堆栈空间的情况下对大型数据集进行这种递归操作会有一些困难。
3.0中引入了扩展解包 http://www.python.org/dev/peps/pep-3132/
与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%的时间。它不仅读起来更清晰,而且速度更快,而且它可以用于列表以外的其他东西(例如集合)。如果在那里等待发生异常,该功能将很快失败并将其交付给链。
我正在开发 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))
那么,为什么你首先想要它在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 :)
除了其他答案之外,请注意Python中的等效头/尾操作,包括python3对*语法的扩展通常不如Haskell的模式匹配效率低。
Python列表是作为向量实现的,因此获取尾部需要获取列表的副本。这是O(n)wrt列表的大小,而使用链接列表如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]
显然,你仍然需要包含一个实用函数,而不是它有很好的语法糖。
在python cookbook中有一个reciepe来执行此操作。我似乎现在无法找到它,但这里是代码(我稍微修改了一下)
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
但是你应该注意到,只有在使用赋值解包时才会有效,因为它对前一帧的反应方式...仍然非常有用。
对于您的特定用例 - 模拟Haskell的 fun(head:rest)= ...
,当然。函数定义支持参数解包很长一段时间:
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']
注意,星号(“splat”)表示“可迭代的剩余部分”,而不是“直到结束”。以下工作正常:
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 == []