Python中列表的模式匹配

问题描述 投票:38回答:10

我想在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 functional-programming pattern-matching
10个回答
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


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 == []

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

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

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


4
投票

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

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

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


3
投票

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


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


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

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

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]

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


1
投票

在python cookbook中有一个食谱可以做到这一点。我现在似乎无法找到它,但这里是代码(我稍微修改了它)


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

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

© www.soinside.com 2019 - 2024. All rights reserved.