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中的某一行来做这件事,而不是两件事。

据我所知,在没有引入另一个函数的情况下,没有办法使它成为当前Python的一行,例如:

 split_list = lambda lst: (lst[0], lst[1:]) head, rest = split_list(my_func()) 

但是,在Python 3.0中,用于可变参数签名和参数解包的专用语法也可用于这种types的常规序列解包,因此在3.0中,您将能够编写:

 head, *rest = my_func() 

详情请参阅PEP 3132 。

首先,请注意,函数式语言的“模式匹配”和给你提到的元组的赋值并不是那么相似。 在函数式语言中,模式用于给出函数的部分定义。 所以f (x : s) = e并不意味着取f的参数的头部和尾部,并使用它们返回e ,但这意味着如果 f的参数是x : sforms(对于某些xs ), f (x : s)等于e

Python的分配更像是一个多重任务(我怀疑这是它的初衷)。 所以,你写了,例如, x, y = y, x来交换xy的值,而不需要临时variables(就像使用简单的赋值语句一样)。 这与模式匹配无关,因为它基本上是x = yy = x的“同时”执行的简写。 虽然python允许任意序列而不是逗号分隔列表,我不会build议调用这种模式匹配。 通过模式匹配,您可以检查某个模式是否匹配; 在Python的分配你应该确保两边的序列是相同的。

做你看起来想要的东西,你通常会(在function语言中)使用一个辅助函数(如其他人所提到的)或类似的letwhere结构(你可以把它看作是使用匿名函数)。 例如:

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

或者,在实际的python中:

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

请注意,除了这是您想要的单行程外,这与其他人使用辅助function给出的解决scheme基本相同。 然而,这不一定比单独的function更好。

(对不起,如果我的回答有点过分,我认为明确区分是很重要的。)

这是一个非常“纯粹的function”的方法,因此在Haskell中是一个明智的习惯用法,但它可能不适合Python。 Python以这种方式只有一个非常有限的模式概念 – 我怀疑你可能需要一个更加严格的types系统来实现这种构造(在这里被邀请不同意的erlang buff)。

你有什么可能尽可能接近你的习惯用语,但你可能更好的使用列表理解或命令式的方法,而不是recursion地调用一个函数与尾部的列表。

如前所述,Python实际上并不是一种function性语言。 它只是借鉴了FP世界的想法。 它并不是固有的Tail Recursive ,你可能希望看到embedded到函数式语言的体系结构中,因此在大量数据集上执行这种recursion操作时会遇到一些困难,而不使用大量的堆栈空间。

那么,为什么你要在一线呢?

如果你真的想,你总是可以这样做:

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

在3.0中引入了扩展拆包functionhttp://www.python.org/dev/peps/pep-3132/

除了其他答案之外,请注意Python中等效的头部/尾部操作(包括python3的*语法扩展)通常不如Haskell的模式匹配效率高。

Python列表被实现为向量,因此获取尾部需要获取列表的副本。 这是列表大小的O(n),而使用像Haskell这样的链表的实现只能使用尾指针,O(1)操作。

唯一的例外可能是基于迭代器的方法,其中列表实际上没有返回,但迭代器是。 然而,这可能并不适用于需要列表的所有地方(例如,多次迭代)。

例如,如果修改Cipher的方法来返回迭代器而不是将其转换为元组,则会出现这种情况。 或者,不依赖于字节码的更简单的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] 

显然,尽pipe你仍然需要包装在一个效用函数中,而不是有很好的语法糖。

与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中,尾recursion通常更好地实现为具有累加器的循环,即:

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

这是一个明显的,正确的方式来做99%的时间。 阅读起来不仅更为清晰,而且速度更快,而且还可以处理列表以外的其他内容(例如,集合)。 如果在那里等待发生exception,函数将很快失败,并将其传递到链中。

我正在开发一个pyfpm ,一个Python语言模式匹配库。 你可以用它来解压这样的对象:

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

蟒食谱中有一个收件人这样做。 我似乎无法find它,但这里是代码(我修改了一点)

 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 

但是,你应该注意到,只有在使用一个任务解包因为它的思维方式… …仍然是相当有用的。