13

反復可能なオブジェクトが反復によって消費されるかどうかを知る統一された方法はありますか?

crunchパラメータに反復可能なオブジェクトを要求し、それを何度も使用する特定の関数があるとします。何かのようなもの:

def crunch (vals):

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

(注: 2 つのforループを一緒にマージすることはオプションではありません)。

関数がリストではない iterable で呼び出されると、問題が発生します。次の呼び出しでは、yum関数は実行されません。

crunch(iter(range(4))

原則として、crunch関数を次のように再定義することでこれを修正できます。

def crunch (vals):
    vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

crunchしかし、呼び出しが次の場合、これは 2 倍のメモリを使用することになります。

hugeList = list(longDataStream)
crunch(hugeList)

crunch次のように定義することで、これを修正できます。

def crunch (vals):
    if type(vals) is not list:
        vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

しかし、呼び出し元のコードがデータを何かに保存する場合もあります。

  • 消費できない
  • リストではありません

例えば:

from collections import deque
hugeDeque = deque(longDataStream)
crunch(hugeDeque)

isconsumable次のように定義できるように、述語があると便利ですcrunch

def crunch (vals):
    if isconsumable(vals):
        vals = list(vals)

    for v in vals:
        chomp(v)

    for v in vals:
        yum(v)

この問題の解決策はありますか?

4

5 に答える 5

6

1 つの可能性は、 を使用して、アイテムがシーケンスであるかどうかをテストすることですisinstance(val, collections.Sequence)。非消費性はまだ完全に保証されているわけではありませんが、入手できる最高のものだと思います. Python シーケンスは長さを持たなければなりません。つまり、少なくともそれは制限のないイテレーターにはなり得ません。また、一般に、要素を事前に知っておく必要があることを意味します。これは、要素を繰り返し処理できることを意味します。それらを消費することなく。シーケンス プロトコルに適合するが再反復可能ではない病理学的クラスを作成することは可能ですが、それらを処理することはできません。

これらの型は長さを保証しないため、どちらも適切な選択ではないことに注意してくださいIterable。ただし、 と の両方をIteratorチェックできます。SizedIterable

重要なことは、関数が引数を 2 回反復することを文書化し、これをサポートするオブジェクトを渡す必要があることをユーザーに警告することです。

于 2013-03-13T08:21:37.080 に答える
5

別の追加オプションは、イテレータがそれ自体のイテレータであるかどうかを照会することです。

if iter(vals) is vals:
    vals = list(vals)

この場合、それは単なるイテレータだからです。

これは、ジェネレータ、イテレータ、ファイル、および「1回の実行」用に設計された他の多くのオブジェクト、つまり、イテレータがから戻るself__iter__()ため、それ自体がイテレータであるすべてのイテレータで機能します。

しかし、これだけでは不十分な場合があります。これは、独自のイテレータでなくても、反復時に自分自身を空にするオブジェクトがあるためです。


通常、自己消費型オブジェクトはそれ自体のイテレータになりますが、これが許可されない場合があります。

リストをラップし、反復時にこのリストを空にするクラスを想像してみてください。

class ListPart(object):
    """Liste stückweise zerlegen."""
    def __init__(self, data=None):
        if data is None: data = []
        self.data = data
    def next(self):
        try:
            return self.data.pop(0)
        except IndexError:
            raise StopIteration
    def __iter__(self):
        return self
    def __len__(self): # doesn't work with __getattr__...
        return len(self.data)

あなたが好きなように呼ぶ

l = [1, 2, 3, 4]
lp = ListPart(l)
for i in lp: process(i)
# now l is empty.

そのリストに追加のデータを追加し、同じオブジェクトを再度反復すると、プロトコルに違反する新しいデータが取得されます。

プロトコルの目的は、イテレータのnext()メソッドが起動StopIterationすると、それ以降の呼び出しでもそれを継続することです。このプロパティに従わない実装は、壊れていると見なされます。(この制約はPython 2.3で追加されました。Python2.2では、このルールに従ってさまざまなイテレーターが壊れています。)

したがって、この場合、オブジェクトは、自己消費的であるにもかかわらず、それ自体とは異なるイテレータを返す必要があります。この場合、これはで行うことができます

def __iter__(self):
    while True:
        try:
            yield l.pop(0)
        except IndexError: # pop from empty list
            return

これは、反復ごとに新しいジェネレーターを返します。これは、説明している場合はマッシュになります。

于 2013-03-13T08:27:53.923 に答える
4
def crunch (vals):
    vals1, vals2 = itertools.tee(vals, 2)

    for v in vals1:
        chomp(v)

    for v in vals2:
        yum(v)

この場合、一方のイテレータがもう一方のイテレータが開始される前に完了するためtee、内部的に全体を格納することになりますvals

于 2013-03-13T08:21:24.660 に答える
3

多くの答えは要点に近づいていますが、見逃しています。

AnIteratorは、反復処理によって消費されるオブジェクトです。それを回避する方法はありません。反復子オブジェクトの例は、 への呼び出しによって返されるもの、またはモジュールiter()内の関数によって返されるものです。itertools

オブジェクトが反復子かどうかを確認する適切な方法は、 を呼び出すことisinstance(obj, Iterator)です。これは基本的に、オブジェクトがnext()メソッド ( __next__()Python 3) を実装しているかどうかをチェックしますが、これを気にする必要はありません。

したがって、反復子は常に消費されることを覚えておいてください。例えば:

# suppose you have a list
my_list = [10, 20, 30]
# and build an iterator on the list
my_iterator = iter(my_list)
# iterate the first time over the object
for x in my_iterator:
    print x
# then again
for x in my_iterator:
    print x

これにより、リストの内容が1 回だけ出力されます。

それからIterableオブジェクトがあります。iter()iterableを呼び出すと、イテレータが返されます。このページへのコメントは、私自身が誤りを犯したので、ここで明確にします。反復可能なオブジェクトは、呼び出しごとに新しい反復子を返す必要はありません。多くのイテレータ自体はイテラブル (つまりiter()、それらを呼び出すことができます) であり、オブジェクト自体を返します。

これの簡単な例は、リスト反復子です。iter(my_list)iter(iter(my_list))は同じオブジェクトであり、これは基本的に @glglgl の回答がチェックしているものです。

イテレータ プロトコルでは、イテレータ オブジェクトが自身のイテレータとして自身を返す (したがって、イテレータ可能である) 必要があります。これは反復メカニズムが機能するために必須ではありませんが、反復子オブジェクトをループすることはできません。

以上のことから、あなたがすべきことは、Iterator が与えられているかどうかを確認することです。そうであれば、反復の結果のコピーを作成します ( を使用list())。あなたisconsumable(obj)は(すでに誰かが言ったように)isinstance(obj, Iterator)です。

これは にも機能することに注意してくださいxrange()xrange(10)オブジェクトを返しxrangeます。xrange オブジェクトを繰り返し処理するたびに、最初から始まる新しいイテレータが返されるので、コピーを作成する必要はありません。

于 2013-03-13T09:53:35.647 に答える
1

これが定義の要約です。

容器

  • __contains__メソッドを持つオブジェクト

発生器

  • イテレータを返す関数。

反復可能

  • __iter__()or__getitem__()メソッドを持つオブジェクト。
  • iterableの例には、すべてのシーケンスタイプ(list、str、tupleなど)と、dictやfileなどの非シーケンスタイプが含まれます。
  • 反復可能オブジェクトが引数として組み込み関数に渡されるとiter()、オブジェクトのイテレーターを返します。このイテレータは、値のセットを1回渡すのに適しています。

イテレータ

  • next()メソッドを持つiterable 。
  • __iter__()イテレータには、イテレータオブジェクト自体を返すメソッドが必要です 。
  • イテレータは、値のセットを1回パスするのに適しています。

順序

  • 特別なメソッドを介して整数インデックスを使用して効率的な要素アクセスをサポートし、シーケンスの長さを返すメソッド__getitem__()を定義するiterable。len()
  • いくつかの組み込みシーケンスタイプは、、、、 listおよびです。strtupleunicode
  • dictも__getitem__()and __len__()をサポートしますが、ルックアップは整数ではなく任意の不変キーを使用するため、シーケンスではなくマッピングと見なされることに注意してください。

現在、オブジェクトがイテレータ、イテレータ、またはある種のシーケンスであるかどうかをテストする方法は多数あります。これらの方法の概要と、さまざまな種類のオブジェクトを分類する方法を次に示します。

               Iterable Iterator iter_is_self Sequence MutableSeq
object                                                           
[]                 True    False        False     True       True
()                 True    False        False     True      False
set([])            True    False        False    False      False
{}                 True    False        False    False      False
deque([])          True    False        False    False      False
<listiterator>     True     True         True    False      False
<generator>        True     True         True    False      False
string             True    False        False     True      False
unicode            True    False        False     True      False
<open>             True     True         True    False      False
xrange(1)          True    False        False     True      False
Foo.__iter__       True    False        False    False      False

                Sized has_len has_iter has_contains
object                                             
[]               True    True     True         True
()               True    True     True         True
set([])          True    True     True         True
{}               True    True     True         True
deque([])        True    True     True        False
<listiterator>  False   False     True        False
<generator>     False   False     True        False
string           True    True    False         True
unicode          True    True    False         True
<open>          False   False     True        False
xrange(1)        True    True     True        False
Foo.__iter__    False   False     True        False

各列は反復可能オブジェクトを分類するための異なる方法を参照し、各行は異なる種類のオブジェクトを参照します。


import pandas as pd
import collections
import os


def col_iterable(obj):
    return isinstance(obj, collections.Iterable)


def col_iterator(obj):
    return isinstance(obj, collections.Iterator)


def col_sequence(obj):
    return isinstance(obj, collections.Sequence)


def col_mutable_sequence(obj):
    return isinstance(obj, collections.MutableSequence)


def col_sized(obj):
    return isinstance(obj, collections.Sized)


def has_len(obj):
    return hasattr(obj, '__len__')


def listtype(obj):
    return isinstance(obj, types.ListType)


def tupletype(obj):
    return isinstance(obj, types.TupleType)


def has_iter(obj):
    "Could this be a way to distinguish basestrings from other iterables?"
    return hasattr(obj, '__iter__')


def has_contains(obj):
    return hasattr(obj, '__contains__')


def iter_is_self(obj):
    "Seems identical to col_iterator"
    return iter(obj) is obj


def gen():
    yield


def short_str(obj):
    text = str(obj)
    if text.startswith('<'):
        text = text.split()[0] + '>'
    return text


def isiterable():
    class Foo(object):
        def __init__(self):
            self.data = [1, 2, 3]

        def __iter__(self):
            while True:
                try:
                    yield self.data.pop(0)
                except IndexError:  # pop from empty list
                    return

        def __repr__(self):
            return "Foo.__iter__"
    filename = 'mytestfile'
    f = open(filename, 'w')
    objs = [list(), tuple(), set(), dict(),
            collections.deque(), iter([]), gen(), 'string', u'unicode',
            f, xrange(1), Foo()]
    tests = [
        (short_str, 'object'),
        (col_iterable, 'Iterable'),
        (col_iterator, 'Iterator'),
        (iter_is_self, 'iter_is_self'),
        (col_sequence, 'Sequence'),
        (col_mutable_sequence, 'MutableSeq'),
        (col_sized, 'Sized'),
        (has_len, 'has_len'),
        (has_iter, 'has_iter'),
        (has_contains, 'has_contains'),
    ]
    funcs, labels = zip(*tests)
    data = [[test(obj) for test in funcs] for obj in objs]
    f.close()
    os.unlink(filename)
    df = pd.DataFrame(data, columns=labels)
    df = df.set_index('object')
    print(df.ix[:, 'Iterable':'MutableSeq'])
    print
    print(df.ix[:, 'Sized':])

isiterable()
于 2013-03-13T17:28:36.233 に答える