43

我想对 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 的一行而不是两行来做到这一点。

4

10 回答 10

65

据我所知,如果不引入另一个函数,就无法使其成为当前 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

于 2008-10-26T15:01:12.377 回答
33

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

python的赋值更像是一个多重赋值(我怀疑这是它的初衷)。因此,例如,您可以在不需要临时变量的情况下交换值(就像使用简单的赋值语句一样)x, y = y, x。这与模式匹配几乎没有关系,因为它基本上是“同时”执行and的简写。尽管 python 允许任意序列而不是逗号分隔的列表,但我不建议调用这种模式匹配。通过模式匹配,您可以检查某些内容是否与模式匹配;在 python 分配中,您应该确保两侧的序列相同。xyx = yy = x

要执行您似乎想要做的事情,您通常(也在函数式语言中)使用辅助函数(如其他人所提到的)或类似于letorwhere构造的东西(您可以将其视为使用匿名函数)。例如:

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

或者,在实际的 python 中:

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

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

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

于 2008-10-26T16:05:12.890 回答
4

这是一种非常“纯函数式”的方法,因此在 Haskell 中是一个明智的习惯用法,但它可能不太适合 Python。Python 在这种方式下只有非常有限的模式概念——我怀疑你可能需要一个更严格的类型系统来实现这种结构(请erlang爱好者在这里不同意)。

您所拥有的可能与该习惯用法一样接近,但您最好使用列表推导式或命令式方法,而不是递归调用带有列表尾部的函数。

如前所述 Python 实际上并不是一种函数式语言它只是借鉴了 FP 世界的想法。它本质上并不是你期望看到嵌入函数式语言架构中的尾递归,因此在不使用大量堆栈空间的情况下对大型数据集执行这种递归操作会有些困难。

于 2008-10-26T15:00:29.493 回答
4

我正在研究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))
于 2012-07-20T23:18:17.343 回答
3

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

于 2008-10-26T15:40:59.070 回答
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% 的情况下都可以做到的显而易见且正确的方法。它不仅阅读起来更清晰,而且速度更快,而且它可以处理列表以外的东西(例如集合)。如果那里有等待发生的异常,该函数将很高兴地失败并将其传递到链上。

于 2008-10-27T14:15:34.107 回答
2

好吧,为什么你首先想要它在 1-line 中?

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

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 :)
于 2008-10-26T15:03:08.973 回答
2

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

显然,尽管您仍然必须包装一个实用函数,而不是为它提供很好的语法糖。

于 2008-10-27T12:08:14.597 回答
1

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


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

但是您应该注意,这仅在使用分配解包时才有效,因为它不考虑前一帧的方式......仍然非常有用。

于 2008-10-26T16:15:17.190 回答
0

对于您的特定用例 - 模拟 Haskell's 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 == []
于 2018-08-29T16:31:58.850 回答