Python中列表的模式匹配

时间:2008-10-26 14:50:40

标签: python functional-programming pattern-matching

我想在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的一行中以某种方式执行此操作,而不是两行。

10 个答案:

答案 0 :(得分:60)

据我所知,在没有引入其他功能的情况下,没有办法在当前的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

答案 1 :(得分:32)

首先,请注意函数式语言的“模式匹配”和你提到的元组的赋值并不是那么相似。在函数式语言中,模式用于给出函数的部分定义。所以f (x : s) = e并不意味着使用f的参数的头部和尾部并使用它们返回e,但这意味着 if f的参数1}}的格式为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())

请注意,这与其他具有辅助功能的解决方案基本相同,只是这是您想要的单线程。但是,它不一定比单独的功能更好。

(对不起,如果我的答案有点过头了。我认为区分清楚很重要。)

答案 2 :(得分:4)

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

你所拥有的东西可能与你对这个习语的接近程度很接近,但你可能最好使用列表理解或命令式方法,而不是递归调用带有列表尾部的函数。

stated on a few occasions before一样,Python实际上并不是一种功能语言。它只是借鉴了FP世界的想法。它本身并不是Tail Recursive所期望的嵌入在函数式语言体系结构中的方式,因此在不使用大量堆栈空间的情况下对大型数据集进行这种递归操作会有一些困难

答案 3 :(得分:3)

3.0中引入了扩展解包 http://www.python.org/dev/peps/pep-3132/

答案 4 :(得分:3)

与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%的时间。它不仅读起来更清晰,而且速度更快,而且它可以用于列表以外的其他东西(例如集合)。如果在那里等待发生异常,该功能将很快失败并将其交付给链。

答案 5 :(得分:3)

我正在使用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))

答案 6 :(得分: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 :)

答案 7 :(得分:2)

除了其他答案之外,请注意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]

显然,你仍然需要包含一个实用函数,而不是它有很好的语法糖。

答案 8 :(得分:1)

在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

但是你应该注意到,只有在使用赋值解包时才会有效,因为它对前一帧的反应方式...仍然非常有用。

答案 9 :(得分:0)

对于您的特定用例-当然可以模拟Haskell的fun (head : rest) = ...。函数定义支持参数解压缩已经有一段时间了:

def my_method(head, *rest):
    # ...

从Python 3.0开始,与@bpowah mentioned一样,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 == []