2008-10-26 113 views
34

我想在Python中的列表上进行一些模式匹配。例如,在Haskell,我可以做类似如下:Python中列表的模式匹配

fun (head : rest) = ... 

所以,当我通过列表中,head将是第一个元素,rest会后缘元件。

同样,在Python,我可以自动解压缩的元组:

(var1, var2) = func_that_returns_a_tuple() 

我想做的事情在Python中的列表类似的东西。现在,我有一个返回列表的功能,代码块,它具有下列功能:

ls = my_func() 
(head, rest) = (ls[0], ls[1:]) 

我想知道如果我能以某种方式做,在Python中的一行,而不是两个。

回答

55

据我知道有没有办法让它在当前的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

+0

当然,你可以把拉姆达在同一行与其他一切: 头,其余部分=( lambda lst:(lst [0],lst [1:]))(my_func()) – 2008-10-26 15:54:39

+11

是的,但开始濒临混淆。 – 2008-10-27 13:01:45

+1

+1为Python 3新功能并链接到PEP。 – fossilet 2013-08-27 03:55:50

4

这是一个非常“纯粹的功能”的方法,因此在Haskell中是一个明智的习惯用法,但它可能不适合Python。 Python只有这样一个非常有限的概念patterns - 我怀疑你可能需要一个更加僵化的类型系统来实现这种构造(erlang受邀请不同意这里的BUFF)。

你有什么可能尽可能接近你的习惯用法,但是你可能最好使用列表理解或命令式方法,而不是递归调用带有列表尾部的函数。

由于已经有statedon a few occasionsbefore,Python实际上并不是一种功能语言。它只是借鉴了FP世界的想法。这并不是固有的Tail Recursive,因为它可能会嵌入到函数式语言的体系结构中,因此在大量数据集上进行这种递归操作而不使用大量堆栈空间会遇到一些困难。

2

那么,为什么你想要它在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 :) 
32

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

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

要做到你仿佛要你通常会(也函数式语言)使用一个辅助功能(如其他人所说)或类似的东西letwhere结构(您可以为使用匿名函数把)。例如:

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

或者,在实际的Python:

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

注意,这是基本相同,不同之处在于这种被别人用辅助功能给出的解决方案是你想要的一个班轮。然而,它不一定比单独的功能更好。

(很抱歉,如果我的回答是有点过分了。我只是觉得做出明确区分是很重要的。)

1

有在Python食谱一个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 

但是你应该注意的是,只有使用赋值解包时作品,因为它inespects前一帧的方式......仍然很有用。

2

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

Python列表被实现为向量,因此获取尾部需要获取列表的副本。这是列表大小的O(n),而使用像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] 

很显然,虽然你仍然有一个效用函数来包装,而不是学生要它漂亮的语法糖。

2

与Haskell或ML不同,Python没有内置的结构模式匹配。做模式匹配的最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 

注意,这仅适用于带切片索引对象的作品。另外,如果函数变得复杂,之后的行可能会引发IndexError,这会导致细微的错误。然而,这并不让你做这样的事情:

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

这是一个很明显,右:

在Python中,尾递归通常更好,因为与蓄能器回路,即实现方式做99%的时间。不仅阅读起来更清晰,而且速度更快,而且它可以处理列表以外的其他内容(例如,集合)。如果在那里等待发生异常,该函数将很快失败并将其传递到链中。

2

我正在研究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))