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 では 2 行ではなく 1 行で何とかできるのではないかと考えました。

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という意味ではありませんが、の引数が(一部のおよび)の形式である場合は に等しいことを意味します。fefx : sxs f (x : s)e

python の割り当ては、複数の割り当てに似ています (本来の意図はそれだと思います)。したがって、たとえば、一時変数を必要とせずに値を交換するように記述します (単純な代入ステートメントの場合と同様) x, y = y, x。これは、基本的に and の「同時」実行の省略形であるため、パターン マッチングとはほとんど関係ありません。Python ではコンマ区切りリストの代わりに任意のシーケンスを使用できますが、このパターン マッチングを呼び出すことはお勧めしません。パターン マッチングでは、何かがパターンに一致するかどうかをチェックします。Python の割り当てでは、両側のシーケンスが同じであることを確認する必要があります。xyx = yy = x

あなたが望むと思われることを行うには、通常(関数型言語でも)補助関数(他の人が述べたように)または類似したものletまたはwhere構築物(匿名関数の使用と見なすことができます)のいずれかを使用します。例えば:

(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の世界からアイデアを借りているだけです。関数型言語のアーキテクチャに組み込まれていると思われる方法では、本質的にTail Recursiveではないため、大量のスタック スペースを使用せずに大規模なデータ セットに対してこの種の再帰操作を実行するのは困難です。

于 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

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% の確率で実行できる、明白で正しい方法の 1 つです。読みやすくなるだけでなく、より高速になり、リスト以外のもの (たとえば、セット) でも機能します。そこで発生するのを待っている例外がある場合、関数は喜んで失敗し、それをチェーンに渡します。

于 2008-10-27T14:15:34.107 に答える
3

拡張アンパックは 3.0 で導入されました http://www.python.org/dev/peps/pep-3132/

于 2008-10-26T15:40:59.070 に答える
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 :)
于 2008-10-26T15:03:08.973 に答える
2

他の回答に加えて、* 構文の python3 の拡張を含む、Python での同等のヘッド/テール操作は、一般に 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 に答える