Python中列表的模式匹配

时间:2021-10-30 22:35:09

I want to do some pattern matching on lists in Python. For example, in Haskell, I can do something like the following:

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

fun (head : rest) = ...

So when I pass in a list, head will be the first element, and rest will be the trailing elements.

所以当我传入一个列表时,head将是第一个元素,rest将是尾随元素。

Likewise, in Python, I can automatically unpack tuples:

同样,在Python中,我可以自动解包元组:

(var1, var2) = func_that_returns_a_tuple()

I want to do something similar with lists in Python. Right now, I have a function that returns a list, and a chunk of code that does the following:

我想用Python中的列表做类似的事情。现在,我有一个返回列表的函数,以及执行以下操作的一大块代码:

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

I wondered if I could somehow do that in one line in Python, instead of two.

我想知道我是否能以某种方式在Python中用一行来代替,而不是两行。

9 个解决方案

#1


58  

So far as I know there's no way to make it a one-liner in current Python without introducing another function, e.g.:

据我所知,在没有引入其他功能的情况下,没有办法在当前的Python中使其成为单行,例如:

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

However, in Python 3.0 the specialized syntax used for variadic argument signatures and argument unpacking will become available for this type of general sequence unpacking as well, so in 3.0 you'll be able to write:

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

head, *rest = my_func()

See PEP 3132 for details.

有关详细信息,请参阅PEP 3132。

#2


32  

First of all, please note that the "pattern matching" of functional languages and the assignment to tuples you mention are not really that similar. In functional languages the patterns are used to give partial definitions of a function. So f (x : s) = e does not mean take the head and tail of the argument of f and return e using them, but it means that if the argument of f is of the form x : s (for some x and s), then f (x : s) is equal to e.

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

The assignment of python is more like a multiple assignment (I suspect that was its original intention). So you write, for example, x, y = y, x to swap the values in x and y without needing a temporary variable (as you would with a simple assignment statement). This has little to do with pattern matching as it is basically a shorthand for the "simultaneous" execution of x = y and y = x. Although python allows arbitrary sequences instead of comma-separated lists, I would not suggest calling this pattern matching. With pattern matching you check whether or not something matches a pattern; in the python assignment you should ensure that the sequences on both sides are the same.

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

To do what you seem to want you would usually (also in functional languages) use either a auxiliary function (as mentioned by others) or something similar to let or where constructs (which you can regard as using anonymous functions). For example:

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

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

Or, in actual python:

或者,在实际的python中:

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

Note that this is essentially the same as the solutions given by others with an auxiliary function except that this is the one-liner you wanted. It is, however, not necessarily better than a separate function.

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

(Sorry if my answer is a bit over the top. I just think it's important to make the distinction clear.)

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

#3


4  

That's a very much a 'pure functional' approach and as such is a sensible idiom in Haskell but it's probably not so appropriate to Python. Python only has a very limited concept of patterns in this way - and I suspect you might need a somewhat more rigid type system to implement that sort of construct (erlang buffs invited to disagree here).

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

What you have is probably as close as you would get to that idiom, but you are probably better off using a list comprehension or imperative approach rather than recursively calling a function with the tail of the list.

你所拥有的可能就像你对这个成语那样接近,但你可能最好使用列表理解或命令式方法,而不是递归调用列表尾部的函数。

As has been stated on a few occasions before, Python is not actually a functional language. It just borrows ideas from the FP world. It is not inherently Tail Recursive in the way you would expect to see embedded in the architecture of a functional language, so you would have some difficulty doing this sort of recursive operation on a large data set without using a lot of stack space.

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

#4


3  

Unlike Haskell or ML, Python doesn't have built-in pattern-matching of structures. The most Pythonic way of doing pattern-matching is with a try-except block:

与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

Note that this only works with objects with slice indexing. Also, if the function gets complicated, something in the body after the head, tail line might raise IndexError, which will lead to subtle bugs. However, this does allow you to do things like:

请注意,这仅适用于具有切片索引的对象。此外,如果函数变得复杂,在head,tail行之后的正文中的某些东西可能会引发IndexError,这将导致细微的错误。但是,这确实允许您执行以下操作:

for frob in eggs.frob_list:
    try:
        frob.spam += 1
    except AttributeError:
        eggs.no_spam_count += 1

In Python, tail recursion is generally better implemented as a loop with an accumulator, i.e.:

在Python中,尾递归通常更好地实现为带累加器的循环,即:

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

This is the one obvious, right way to do it 99% of the time. Not only is it clearer to read, it's faster and it will work on things other than lists (sets, for instance). If there's an exception waiting to happen in there, the function will happily fail and deliver it up the chain.

这是99%的时间做到这一点的一种明显,正确的方法。它不仅更清晰,更快,而且可以处理除列表(例如集合)之外的其他事物。如果在那里等待发生异常,该功能将很快失败并将其交付给链。

#5


2  

Well, why you want it in 1-line in the first place?

那么,为什么你首先想要它在1行?

If you really want to, you can always do a trick like this:

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

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

#6


2  

extended unpacking was introduced in 3.0 http://www.python.org/dev/peps/pep-3132/

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

#7


2  

Further to the other answers, note that the equivalent head / tail operation in Python, including python3's extension of the * syntax is generally going to be less efficient than Haskell's pattern matching.

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

Python lists are implemented as vectors, so obtaining the tail will need to take a copy of the list. This is O(n) wrt the size of the list, whereas an implementaion using linked lists like Haskell can merely use the tail pointer, an O(1) operation.

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

The only exception may be iterator based approaches, where the list isn't actually returned, but an iterator is. However this may not be applicable all places where a list is desired (eg. iterating multiple times).

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

For instance, Cipher's approach, if modified to return the iterator rather than converting it to a tuple will have this behaviour. Alternatively a simpler 2-item only method not relying on the bytecode would be:

例如,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]

Obviously though you still have to wrap in a utility function rather than there being nice syntactic sugar for it.

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

#8


2  

I'm working on pyfpm, a library for pattern matching in Python with a Scala-like syntax. You can use it to unpack objects like this:

我正在使用pyfpm,这是一个用于Python中模式匹配的库,具有类似Scala的语法。您可以使用它来解压缩这样的对象:

from pyfpm import Unpacker

unpacker = Unpacker()

unpacker('head :: tail') << (1, 2, 3)

unpacker.head # 1
unpacker.tail # (2, 3)

Or in a function's arglist:

或者在函数的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))

#9


1  

there was a reciepe in the python cookbook to do this. i cant seem to find it now but here is the code (i modified it slightly)

为了做到这一点,在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

however you should note that that only works when using an assignment unpack because of the way it inespects the previous frame... still its quite useful.

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

#1


58  

So far as I know there's no way to make it a one-liner in current Python without introducing another function, e.g.:

据我所知,在没有引入其他功能的情况下,没有办法在当前的Python中使其成为单行,例如:

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

However, in Python 3.0 the specialized syntax used for variadic argument signatures and argument unpacking will become available for this type of general sequence unpacking as well, so in 3.0 you'll be able to write:

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

head, *rest = my_func()

See PEP 3132 for details.

有关详细信息,请参阅PEP 3132。

#2


32  

First of all, please note that the "pattern matching" of functional languages and the assignment to tuples you mention are not really that similar. In functional languages the patterns are used to give partial definitions of a function. So f (x : s) = e does not mean take the head and tail of the argument of f and return e using them, but it means that if the argument of f is of the form x : s (for some x and s), then f (x : s) is equal to e.

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

The assignment of python is more like a multiple assignment (I suspect that was its original intention). So you write, for example, x, y = y, x to swap the values in x and y without needing a temporary variable (as you would with a simple assignment statement). This has little to do with pattern matching as it is basically a shorthand for the "simultaneous" execution of x = y and y = x. Although python allows arbitrary sequences instead of comma-separated lists, I would not suggest calling this pattern matching. With pattern matching you check whether or not something matches a pattern; in the python assignment you should ensure that the sequences on both sides are the same.

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

To do what you seem to want you would usually (also in functional languages) use either a auxiliary function (as mentioned by others) or something similar to let or where constructs (which you can regard as using anonymous functions). For example:

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

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

Or, in actual python:

或者,在实际的python中:

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

Note that this is essentially the same as the solutions given by others with an auxiliary function except that this is the one-liner you wanted. It is, however, not necessarily better than a separate function.

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

(Sorry if my answer is a bit over the top. I just think it's important to make the distinction clear.)

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

#3


4  

That's a very much a 'pure functional' approach and as such is a sensible idiom in Haskell but it's probably not so appropriate to Python. Python only has a very limited concept of patterns in this way - and I suspect you might need a somewhat more rigid type system to implement that sort of construct (erlang buffs invited to disagree here).

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

What you have is probably as close as you would get to that idiom, but you are probably better off using a list comprehension or imperative approach rather than recursively calling a function with the tail of the list.

你所拥有的可能就像你对这个成语那样接近,但你可能最好使用列表理解或命令式方法,而不是递归调用列表尾部的函数。

As has been stated on a few occasions before, Python is not actually a functional language. It just borrows ideas from the FP world. It is not inherently Tail Recursive in the way you would expect to see embedded in the architecture of a functional language, so you would have some difficulty doing this sort of recursive operation on a large data set without using a lot of stack space.

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

#4


3  

Unlike Haskell or ML, Python doesn't have built-in pattern-matching of structures. The most Pythonic way of doing pattern-matching is with a try-except block:

与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

Note that this only works with objects with slice indexing. Also, if the function gets complicated, something in the body after the head, tail line might raise IndexError, which will lead to subtle bugs. However, this does allow you to do things like:

请注意,这仅适用于具有切片索引的对象。此外,如果函数变得复杂,在head,tail行之后的正文中的某些东西可能会引发IndexError,这将导致细微的错误。但是,这确实允许您执行以下操作:

for frob in eggs.frob_list:
    try:
        frob.spam += 1
    except AttributeError:
        eggs.no_spam_count += 1

In Python, tail recursion is generally better implemented as a loop with an accumulator, i.e.:

在Python中,尾递归通常更好地实现为带累加器的循环,即:

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

This is the one obvious, right way to do it 99% of the time. Not only is it clearer to read, it's faster and it will work on things other than lists (sets, for instance). If there's an exception waiting to happen in there, the function will happily fail and deliver it up the chain.

这是99%的时间做到这一点的一种明显,正确的方法。它不仅更清晰,更快,而且可以处理除列表(例如集合)之外的其他事物。如果在那里等待发生异常,该功能将很快失败并将其交付给链。

#5


2  

Well, why you want it in 1-line in the first place?

那么,为什么你首先想要它在1行?

If you really want to, you can always do a trick like this:

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

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

#6


2  

extended unpacking was introduced in 3.0 http://www.python.org/dev/peps/pep-3132/

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

#7


2  

Further to the other answers, note that the equivalent head / tail operation in Python, including python3's extension of the * syntax is generally going to be less efficient than Haskell's pattern matching.

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

Python lists are implemented as vectors, so obtaining the tail will need to take a copy of the list. This is O(n) wrt the size of the list, whereas an implementaion using linked lists like Haskell can merely use the tail pointer, an O(1) operation.

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

The only exception may be iterator based approaches, where the list isn't actually returned, but an iterator is. However this may not be applicable all places where a list is desired (eg. iterating multiple times).

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

For instance, Cipher's approach, if modified to return the iterator rather than converting it to a tuple will have this behaviour. Alternatively a simpler 2-item only method not relying on the bytecode would be:

例如,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]

Obviously though you still have to wrap in a utility function rather than there being nice syntactic sugar for it.

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

#8


2  

I'm working on pyfpm, a library for pattern matching in Python with a Scala-like syntax. You can use it to unpack objects like this:

我正在使用pyfpm,这是一个用于Python中模式匹配的库,具有类似Scala的语法。您可以使用它来解压缩这样的对象:

from pyfpm import Unpacker

unpacker = Unpacker()

unpacker('head :: tail') << (1, 2, 3)

unpacker.head # 1
unpacker.tail # (2, 3)

Or in a function's arglist:

或者在函数的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))

#9


1  

there was a reciepe in the python cookbook to do this. i cant seem to find it now but here is the code (i modified it slightly)

为了做到这一点,在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

however you should note that that only works when using an assignment unpack because of the way it inespects the previous frame... still its quite useful.

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