web-dev-qa-db-ja.com

Pythonのリストのパターンマッチング

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行で何とかできるのではないかと思いました。

41
mipadi

私の知る限り、現在の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 を参照してください。

62
James Bennett

まず第一に、関数型言語の「パターンマッチング」とあなたが言及するタプルへの割り当ては、実際にはそれほど似ていないことに注意してください。関数型言語では、パターンは関数の部分的な定義を与えるために使用されます。したがって、f (x : s) = eは、fの引数の先頭と末尾を取得し、それらを使用してeを返すことを意味しませんが、iffの引数がx : s(for一部のxおよびs)、thenf (x : s)eと同じです。

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

あなたが望んでいるようにするには、通常(関数型言語でも)補助関数(他の人が言及している)またはletまたはwhere構文(無名関数を使用していると見なすことができる)に似たものを使用します。例えば:

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

または、実際のpythonでは:

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

これは、必要なワンライナーであることを除いて、基本的には他の補助機能を備えたソリューションと同じです。ただし、個別の機能よりも優れているとは限りません。

(私の答えが少し上にある場合は申し訳ありません。区別を明確にすることが重要だと思います。)

33
mweerden

これは非常に「純粋な機能」アプローチであり、Haskellでの賢明なイディオムですが、おそらくPythonにはそれほど適切ではありません。 Pythonこの方法では パターン の非常に限られた概念しかありません-そして、そのような構成を実装するには、やや厳密な型システムが必要かもしれないと思います(- erlang ここで反対するように招待されたバフ)。

あなたが持っているものはおそらくそのイディオムに到達するのと同じくらい近いですが、おそらくリストの末尾で関数を再帰的に呼び出すよりもリストの理解や命令的なアプローチを使用する方が良いでしょう。

これまでと同様に 記述数回before 、Pythonは実際には関数型言語ではありません。 FP=世界からアイデアを借用します。これは本質的に Tail Recursive とは異なり、関数型言語のアーキテクチャに埋め込まれているのを見ることができるので、大量のスタックスペースを使用せずに大規模なデータセットに対してこの種の再帰的操作を実行するのは困難です。

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

3
bpowah

HaskellやMLとは異なり、Pythonには構造のパターンマッチングが組み込まれていません。パターンマッチングを行う最もPython的な方法は、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

これは、スライスインデックスが設定されたオブジェクトでのみ機能することに注意してください。また、関数が複雑になった場合、本体に何かafterhead, 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つの明白で正しい方法です。読みやすくなるだけでなく、高速になり、リスト以外のもの(たとえば、セット)でも機能します。そこで発生するのを待っている例外がある場合、関数は喜んで失敗し、それをチェーンに配信します。

3
giltay

私は pyfpm に取り組んでいます。これは、Scalaに似た構文でPythonのパターンマッチング用ライブラリです。これを使用して、次のようなオブジェクトをアンパックできます。

from pyfpm import Unpacker

unpacker = Unpacker()

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

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

または関数の引数リストで:

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))
3
Martin Blech

さて、そもそもなぜそれを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
kender

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

もちろん、Niceの構文糖というよりもユーティリティ関数をラップする必要があります。

2
Brian

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

ただし、前のフレームをインスペクトする方法が原因で、割り当てのアンパックを使用する場合にのみ機能することに注意してください...それでも非常に便利です。

1
Jake

あなたの特定のユースケース-Haskellの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']

アスタリスク(「スプラット」)は、「最後まで」ではなく、「反復可能な残りの部分」を意味することに注意してください。以下は正常に動作します:

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 == []
0
Lyndsy Simon