12

Python 2.7.3 を使用しています。

カスタムの (悪いとはいえ) 反復処理と項目取得動作を備えたダミー クラスを考えてみましょう。

class FooList(list):
    def __iter__(self):
        return iter(self)
    def next(self):
        return 3
    def __getitem__(self, idx):
        return 3

例を作成し、奇妙な動作を確認します。

>>> zz = FooList([1,2,3])

>>> [x for x in zz]
# Hangs because of the self-reference in `__iter__`.

>>> zz[0]
3

>>> zz[1]
3

しかし今、関数を作成してから引数のアンパックを行いましょうzz:

def add3(a, b, c):
    return a + b + c

>>> add3(*zz)
6
# I expected either 9 or for the interpreter to hang like the comprehension!

したがって、引数のアンパッキングは何らかの方法で項目データを取得してzzいますが、実装されたイテレーターを使用してオブジェクトを反復処理したり、貧弱なイテレーターを実行し__getitem__てオブジェクトが持っているのと同じ数の項目を呼び出したりすることによってではありません。

問題は、これらのメソッドを使用しない場合、構文はどのようにしadd3(*zz)てデータ メンバーを取得するのかということです。zzこのような型からデータ メンバーを取得するための別の一般的なパターンが 1 つ欠けているだけですか?

私の目標は、イテレーションまたはアイテムの取得を実装するクラスを記述して、引数のアンパック構文がそのクラスにとって何を意味するかを変更できるかどうかを確認することです。上記の 2 つの例を試した後、引数のアンパッキングが基になるデータを取得する方法と、プログラマーがその動作に影響を与えることができるかどうかが気になりました。*argsこれについての Google は、構文の基本的な使用法を説明する大量の結果を返すだけでした。

これを行う必要があるユースケースはありませんし、それが良い考えだとは主張していません。好奇心のためにそれを行う方法を見たいだけです。

追加した

組み込み型は特別に扱われるobjectため、リスト オブジェクトを維持し、独自の get および set 動作を実装してリストをエミュレートする例を次に示します。

class FooList(object):
    def __init__(self, lst):
        self.lst = lst
    def __iter__(self): raise ValueError
    def next(self): return 3
    def __getitem__(self, idx): return self.lst.__getitem__(idx)
    def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm)

この場合、

In [234]: zz = FooList([1,2,3])

In [235]: [x for x in zz]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-235-ad3bb7659c84> in <module>()
----> 1 [x for x in zz]

<ipython-input-233-dc9284300db1> in __iter__(self)
      2     def __init__(self, lst):
      3         self.lst = lst
----> 4     def __iter__(self): raise ValueError
      5     def next(self): return 3
      6     def __getitem__(self, idx): return self.lst.__getitem__(idx)

ValueError:

In [236]: add_3(*zz)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-236-f9bbfdc2de5c> in <module>()
----> 1 add_3(*zz)

<ipython-input-233-dc9284300db1> in __iter__(self)
      2     def __init__(self, lst):
      3         self.lst = lst
----> 4     def __iter__(self): raise ValueError
      5     def next(self): return 3
      6     def __getitem__(self, idx): return self.lst.__getitem__(idx)

ValueError:

しかし代わりに、繰り返しが停止し、常に 3 が返されるようにすれば、最初のケースで試してみるために撮影していたものを取得できます。

class FooList(object):
    def __init__(self, lst):
        self.lst = lst
        self.iter_loc = -1
    def __iter__(self): return self
    def next(self): 
        if self.iter_loc < len(self.lst)-1:
            self.iter_loc += 1
            return 3
        else:
            self.iter_loc = -1
            raise StopIteration
    def __getitem__(self, idx): return self.lst.__getitem__(idx)
    def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm)

次に、これが表示されます。これは、私が最初に期待したものです。

In [247]: zz = FooList([1,2,3])

In [248]: ix = iter(zz)

In [249]: ix.next()
Out[249]: 3

In [250]: ix.next()
Out[250]: 3

In [251]: ix.next()
Out[251]: 3

In [252]: ix.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-252-29d4ae900c28> in <module>()
----> 1 ix.next()

<ipython-input-246-5479fdc9217b> in next(self)
     10         else:
     11             self.iter_loc = -1
---> 12             raise StopIteration
     13     def __getitem__(self, idx): return self.lst.__getitem__(idx)
     14     def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm)

StopIteration:

In [253]: ix = iter(zz)

In [254]: ix.next()
Out[254]: 3

In [255]: ix.next()
Out[255]: 3

In [256]: ix.next()
Out[256]: 3

In [257]: ix.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-257-29d4ae900c28> in <module>()
----> 1 ix.next()

<ipython-input-246-5479fdc9217b> in next(self)
     10         else:
     11             self.iter_loc = -1
---> 12             raise StopIteration
     13     def __getitem__(self, idx): return self.lst.__getitem__(idx)
     14     def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm)

StopIteration:

In [258]: add_3(*zz)
Out[258]: 9

In [259]: zz[0]
Out[259]: 1

In [260]: zz[1]
Out[260]: 2

In [261]: zz[2]
Out[261]: 3

In [262]: [x for x in zz]
Out[262]: [3, 3, 3]

概要

  1. 構文*argsは反復のみに依存します。組み込み型の場合、これは、組み込み型から継承するクラスで直接オーバーライドできない方法で発生します。

  2. これら 2 つは機能的に同等です。

    foo(*[x for x in args])

    foo(*args)

  3. これらは、有限のデータ構造であっても同等ではありません。

    foo(*args)

    foo(*[args[i] for i in range(len(args))])

4

1 に答える 1

12

あなたは Python の最も厄介な問題の 1 つに悩まされています: 組み込み型とそれらのサブクラスは、いくつかの場所で魔法のように扱われます。

あなたの型は からサブクラスlist化されているので、Python は魔法のようにその内部に手を伸ばして展開します。実際の反復子 API はまったく使用しません。printand内にステートメントを挿入するnext__getitem__、どちらも呼び出されていないことがわかります。この動作はオーバーライドできません。代わりに、組み込み型を再実装する独自のクラスを作成する必要があります。UserList;を使用してみてください。それが機能するかどうかは確認していません。

あなたの質問への答えは、引数のアンパックは反復を使用するということです。__getitem__ただし、明示的に定義されていない場合、反復自体は使用できます__iter__。通常の反復動作とは異なる引数のアンパック動作を定義するクラスを作成することはできません。

イテレータ プロトコル (基本的に「どのように__iter__機能するか」) は、 のような組み込み型をサブクラス化する型に適用されると想定すべきではありませんlist。ビルトインをサブクラス化すると、サブクラスは、特定の状況で、カスタマイズされたマジック メソッド ( など__iter__) を使用せずに、基礎となるビルトインのように魔法のように動作する場合があります。動作を完全かつ確実にカスタマイズしたい場合は、組み込み型からサブクラス化することはできません (もちろん、例外はありますobject)。

于 2013-10-22T19:17:05.103 に答える