イテレータと反復可能なオブジェクト

イテレータと反復可能なオブジェクト

概念

イテレータ:データコレクション内の要素にアクセスする方法です。通常、データをトラバースするために使用されますが、リストのようなデータを取得するために添え字を使用することはできません。つまり、イテレータを返すことはできません。

  1. イテレーター:イテレーターオブジェクト、次のマジック関数を実装する必要があります

  2. Iterable:Iteratorを継承するIterableオブジェクトはiterマジック関数を実装する必要があります

といった:

from collections import Iterable,Iterator
a = [1,2,3]
print(isinstance(a,Iterator))
print(isinstance(a,Iterable))

結果を返す:

False
True

Pycharmでalt + bを使用してリストのソースコードを入力すると、リストクラスに反復マジック関数があることがわかります。つまり、反復マジック関数が実装されている限り、オブジェクトは反復可能オブジェクトです。

上記の例では、aはリストと反復可能なオブジェクトなので、これをどのようにしてイテレータにすることができますか?iter()を使用してください。

from collections import Iterable,Iterator
a = [1,2,3]
a = iter(a)
print(isinstance(a,Iterator))
print(isinstance(a,Iterable))
print(next(a))
print('----')
for x in a:
    print(x)

結果を返す:

True
True
1
----
2
3

これで、aが反復可能なオブジェクトと反復子であることがわかります。これは、リストaに反復子を返すiterメソッドがあることを示しています。現時点では、nextを使用してaの次の値を取得できますが、反復子を覚えておいてください。の値は一度しか取得できません。

IteratorとIterableの違いを整理するには:

  1. Iterableオブジェクト:イテレータオブジェクトを継承します。forループを使用できます(iterメソッドが実装されていることを示します)

  2. イテレータオブジェクト:nextを使用して次の値(nextメソッドが実装されていることを示す)を取得できますが、各値は1回しか取得できません。単純なイテレータはiterマジック関数を実装しないため、forループを使用できません

  3. forループとして使用できる限り、反復可能なオブジェクト

  4. next()関数を使用できる限り、それはイテレータオブジェクトです

  5. リスト、辞書、文字列は反復可能なオブジェクトですが、反復オブジェクトではありません。反復オブジェクトになりたい場合は、iter()を使用して変換できます。

  6. Pythonのforループは、基本的にnext()を使用して定数呼び出しを行います。forループは反復可能なオブジェクトです。反復可能なオブジェクトは反復マジック関数を持ち、反復可能なオブジェクトは反復オブジェクトを継承し、反復オブジェクトは次の魔法関数を持っています。

  7. 一般に反復可能なオブジェクトは反復子オブジェクトを変更します

反復可能なオブジェクト

反復可能オブジェクトが配列でforループを使用するたびに、基本的にはクラスからiterマジック関数を呼び出そうとします。クラスにiterマジック関数がある場合は、最初にiterマジック関数を呼び出します。もちろん、iterメソッドは1を返す必要があることに注意してください反復可能なオブジェクト。それ以外の場合はエラーが報告されます。

iterマジック関数が定義されていない場合、デフォルトのイテレーターが作成され、getitemマジック関数が呼び出されます。2つのマジック関数iterとgetitemを定義しない場合、型は反復可能なオブジェクトではなく、エラーが報告されます。

といった:

class s:
    def __init__(self,x):
        self.x = x
    def __iter__(self):
        return iter(self.x)
        # 这里必须要返回一个可以迭代的对象
    # def __getitem__(self, item):
    #     return self.x[item]
# iter和getitem其中必须要实现一个
a = s('123')
# 这里的a就是可迭代对象
# 这里不能调用next(a)方法,因为没有定义
for x in a:
    print(x)

ここでコメントが削除されても結果は同じで、結果が返されます。

1
2
3

イテレータオブジェクト

最初に、反復可能オブジェクトとしてIterableを使用したiterについて説明し、次に反復子としてIteratorを使用してiterについて説明しました。next()は反復子オブジェクトを受け入れ、その役割は反復子オブジェクトの次の値を取得することです。反復子は反復に使用され、必要な場合にのみデータを生成します。

反復可能オブジェクトとは異なり、反復可能オブジェクトは最初にすべてのリストを変数に入れ、次にgetitemメソッドを使用して継続的に値を返します。getitemの項目はインデックス値です。

ただし、次のメソッドにはインデックス値がないため、次の変数の場所を特定しやすくするために、インデックス値を自分で管理する必要があります。

class s:
    def __init__(self,x):
        self.x = x
        # 获取传入的对象
        self.index = 0
        # 维护索引值
    def __next__(self):
        try:
            result = self.x[self.index]
            # 获取传入对象的值
        except IndexError:
            # 如果索引值错误
            raise StopIteration
        # 抛出停止迭代
        self.index += 1
        # 索引值+1,用来获取传入对象的下一个值
        return result
        # 返回传入对象的值

a = s([1,2,3])
print(next(a))
print('----------')
for x in a:
# 类中并没有iter或者getitem魔法函数,不能用for循环,会报错
    print(x)

結果を返す:

Traceback (most recent call last):
1
----------
  File "C:/CODE/Python进阶知识/迭代协议/迭代器.py", line 34, in <module>
    for x in a:
TypeError: 's' object is not iterable

上記は完全なイテレータオブジェクトです。渡されたオブジェクトの次の値を独自のインデックス値に従って取得します。渡されたオブジェクトをメモリに直接読み取る反復可能なオブジェクトとは異なり、大きなファイルの場合読み込む場合、ファイルのすべての内容をメモリに読み込むのではなく、1行ずつ内容を読み込むことができます。

このクラスはイテレータオブジェクトなので、forループを使用できるようにするにはどうすればよいですか?次に、彼を反復可能なオブジェクトにし、クラスにiter magic関数を追加します。

class s:
    def __init__(self,x):
        self.x = x
        # 获取传入的对象
        self.index = 0
        # 维护索引值
    def __next__(self):
        try:
            result = self.x[self.index]
            # 获取传入对象的值
        except IndexError:
            # 如果索引值错误
            raise StopIteration
        # 抛出停止迭代
        self.index += 1
        # 索引值+1,用来获取传入对象的下一个值
        return result
        # 返回传入对象的值
    def __iter__(self):
        return self
a = s([1,2,3])
print(next(a))
print('----------')
for x in a:
    print(x)

結果を返す:

1
----------
2
3

この時点で操作が成功したことがわかりますが、次の値を取得するとエラーが報告されるため、このオブジェクトは引き続きイテレータオブジェクトです。

知識照合

上記のコードヒントに従って、法律を取得します。

  1. Iterは、クラスを反復可能なオブジェクトにし、次にクラスを(インデックス値を維持するための)イテレータにします。

  2. 反復可能オブジェクトはforループを使用でき、反復子はnextを使用して次の値を取得できます。

  3. イテレータをイテラブルオブジェクトにしてforループを使用する場合は、イテレータ内にiterマジック関数を追加する必要があります。

  4. 反復可能オブジェクト次の魔法の関数を使用できるようにしたい場合は、クラスでiter()メソッドを使用して反復子オブジェクトにします。

class s:
    def __init__(self,x):
        self.x = x
        self.index = 0
    def __next__(self):
        try:
            result = self.x[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return result

class b:
    def __init__(self,x):
        self.x = x
    def __iter__(self):
        return s(self.x)
a = b([1,2,3])

for x in a:
    print(x)

結果を返す:

1
2
3

現時点では、クラスbはイテレータではなく反復可能なオブジェクトであるため、次のメソッドを使用できなくなりました。現時点では、次のメソッドを使用できませんが、クラスbにクラスsを継承させて、次のメソッドを取得するためにnext()メソッドを使用できます。値ですが、クラスbにはインデックス値が必要です。それ以外の場合は、次のコードでエラーが報告されます。

class s:
    def __init__(self,x):
        self.x = x
        # 获取传入的对象
        self.index = 0
        # 维护索引值
    def __next__(self):
        try:
            result = self.x[self.index]
            # 获取传入对象的值
        except IndexError:
            # 如果索引值错误
            raise StopIteration
        # 抛出停止迭代
        self.index += 1
        # 索引值+1,用来获取传入对象的下一个值
        return result
        # 返回传入对象的值
    # def __iter__(self):
    #     return self
class b(s):
    def __init__(self,x):
        self.x = x
        self.index = 0
    def __iter__(self):
        return s(self.x)
a = b([1,2,3])

print(next(a))
print(next(a))

結果を返す:

1
2

これは可能ですが、設計原則に違反しているため、必要ありません。

イテレーターの設計パターン

イテレーターパターン:オブジェクトの内部表現を公開せずに、集約オブジェクトのさまざまな要素に順次アクセスする方法を提供します。

イテレータのデザインパターンはクラシックなデザインパターンです。イテレータの特性によると(インデックス値に従って次のコンテンツを読み取り、大量のデータを一度にメモリに読み込まない)、同じクラスに次とイテレータの両方を書き込むことはお勧めしません達成するために。

新しいイテレータを作成し、イテレータを使用してインデックス値を維持し、インデックス値に従って取得したオブジェクトの値を返し、別の反復可能なオブジェクトを作成し、そしてiterメソッドを使用してループイテレータの戻り値を容易にします。

ビルダー

ジェネレーター:関数に収量がある限り、関数はジェネレーターになります。関数が実行されるたびに、関数は一時停止し、現在の実行状態を保存して現在の値に戻り、次のメソッドが実行されるときに現在の位置から下降し続けます。

簡単な使い方

例えば:

def gen():
    yield 1
    # 返回一个对象,这个对象的值是1
def ret():
    return 1
    # 返回一个数字1
g = gen()
r = ret()
print(g,r)
print(next(g))

結果を返す:

<generator object gen at 0x000001487FDA2D58> 1
1

returnが直接値1を返し、yieldが返されたジェネレーターオブジェクトであることがわかります。このオブジェクトの値は1です。n:(g)またはg:print xのxを使用して、コンテンツを取得できます。 Pythonがバイトコードをコンパイルすると、オブジェクトが生成されます。

def gen():
    yield 1
    yield 11
    yield 111
    yield 1111
    yield 11111
    yield 111111
    # 返回一个对象,这个对象内的值是1和11,111...
def ret():
    return 1
    return 3
    # 第二个return是无效的
g = gen()
r = ret()
print(g,r)
print(next(g))
for x in g:
    print(x)

結果を返す:

<generator object gen at 0x000002885FE32D58> 1
1
11
111
1111
11111
111111

イテレータの特性と同様に、一度取得した値を再度取得することはできません。また、一度にすべての結果をメモリ内で見つけることや、メモリ内のすべての内容を一度に読み取ることではありません。

カーディング特性:

  1. yieldを使用する関数はジェネレーター関数です

  2. forループを使用して値を取得できます。また、nextを使用してジェネレーター関数の値を取得することもできます

原理

関数の動作原理:関数の呼び出しは「後入れ先出し」の原則を満たします。つまり、lastと呼ばれる関数が最初に戻る必要があります。関数の再帰呼び出しは古典的な例です。明らかに、メモリ内のデータを「後入れ先出し」方式で処理するスタックセグメントは、関数呼び出しに最も適したキャリアです。コンパイルされたプログラミング言語では、関数が呼び出された後、関数パラメーター、戻りアドレス、レジスター値、およびその他のデータスタックにプッシュされ、関数本体が実行された後、上記のデータがスタックからポップされます。これは、呼び出された関数が実行されると、そのライフサイクルが終了することも意味します。

Pythonインタープリターが実行されている場合、C言語のPyEval_EvalFramEx関数を使用してスタックフレームが作成されます。すべてのスタックフレームはヒープメモリに割り当てられ、アクティブに解放されない場合は常にそこにあります。

Pythonのスタックフレームはヒープメモリに割り当てられます。これを理解することは非常に重要です。Pythonインタープリターは通常のCプログラムであるため、そのスタックフレームは通常のスタックです。しかし、それが動作するPythonスタックフレームはヒープ上にあります。驚いたことに、これは、Pythonのスタックフレームが呼び出し以外でも存続できることを意味します。(FIXME:呼び出しの終了後も存続することができます)、これはジェネレーターのコア原則の実現です。

Pythonスクリプトはpython.exeによってバイトコードにコンパイルされ、次にpython.exeはこれらのバイトコードを実行し、disを使用して関数オブジェクトのバイトコードオブジェクトを表示します。

import dis
# 查看函数程序字节码
a = 'langzi'
print(dis.dis(a))
print('-'*20)
def sb(admin):
    print(admin)
print(dis.dis(sb))

結果を返す:

  1           0 LOAD_NAME                0 (langzi)
# 加载名字 为langzi
              2 RETURN_VALUE
# 返回值
None
--------------------
 15           0 LOAD_GLOBAL              0 (print)
# 加载一个print函数
              2 LOAD_FAST                0 (admin)
# 加载传递参数为admin
              4 CALL_FUNCTION            1
# 调用这个函数
              6 POP_TOP
# 从栈的顶端把元素移除出来
              8 LOAD_CONST               0 (None)
# 因为该函数没有返回任何值,所以加载的值是none
             10 RETURN_VALUE
# 最后把load_const的值返回(个人理解)
None

コード関数が実行されているとき、Pythonはコードをバイトコードにコンパイルします。関数にイールドがある場合、Pythonはこの関数をジェネレーターとしてマークします。この関数が呼び出されると、ジェネレーターオブジェクトを返し、このジェネレーターオブジェクトを呼び出しますC言語で記述された関数は、最後のコード実行の位置と変数を記録します。

C言語のPyGenObjectには、gi_frame(最後のコード実行場所f_lastiの最後のコード実行の変数f_localsを格納)、gi_code(コードを格納)、disを使用して最後のコード実行を取得できる2つの値があります位置と価値。

例えば:

import dis
def gen():
    yield 1
    yield 2
    return 666

g = gen()
# g是生成器对象
print(dis.dis(g))
print('*'*10)
print(g.gi_frame.f_lasti)
# 这里还没有执行,返回的位置是-1
print(g.gi_frame.f_locals)
# 这里还没有执行,返回的对象是{}
next(g)
print('*'*10)
print(g.gi_frame.f_lasti)
print(g.gi_frame.f_locals)

結果を返す:

 11           0 LOAD_CONST               1 (1)
# 加载值为1
              2 YIELD_VALUE
              4 POP_TOP

 12           6 LOAD_CONST               2 (2)
              8 YIELD_VALUE
             10 POP_TOP

 13          12 LOAD_CONST               3 (666)
             14 RETURN_VALUE
None
**********
-1
# 因为还没有执行,所以获取的行数为 -1
{}
**********
2
# 这里开始执行了第一次,获取的行数是2,2对应2 YIELD_VALUE就是前面加载的数值1
{}
# g.gi_frame.f_locals 是局部变量,你都没定义那么获取的结果自然是{},你只需在代码中加上user='admin',这里的{}就会改变。

スタックフレームは実際にはスタック上ではなくヒープ上にあるため、ジェネレーターはいつでも任意の関数で再開できます。呼び出し階層内のジェネレーターの位置は固定されておらず、通常の関数実行の先入れ先出しの順序に従う必要はありません。これらの特性により、ジェネレーターは反復可能なオブジェクトを生成するためだけでなく、マルチタスクコラボレーションを実現するためにも使用できます。

つまり、ジェネレーターオブジェクトを取得している限り、ポーズの実行を続行したり待機したりするなど、ジェネレーターオブジェクトを制御できます。これは、コルーチンが実行できる理論上の原理です。

アプリケーションシナリオ

ファイルを読み取るには、open( 'xxx')。read(2019)//を使用してファイルを開き、毎回2019オフセットを読み取ります。ファイルa.txtは1行のテキストですが、非常に長いため、このテキスト行は|記号で区切られています。

ファイルコードを書く:

# -*- coding:utf-8 -*-
import random
import threading
import string
import time
t1 = time.time()
def write(x):
    with open('a.txt','a+')as a:
        a.write(x + '||')

def run():
    for x in range(10000000):
        strs = str(random.randint(1000,2000)) +random.choice(string.ascii_letters)*10
        write(strs)
for x in range(10):
    t = threading.Thread(target=run)
    t.start()
t2 = time.time()
print(t2 - t1)

ファイルコードを読む:

# -*- coding:utf-8 -*-
def readbooks(f, newline):
    # f为传入的文件名,newline为分隔符
    buf = ""
    # 缓存,处理已经读出来的数据量
    while 1:
        while newline in buf:
            # 缓存中的数据是否存在分隔符
            pos = buf.index(newline)
            # 如果存在就找到字符的位置,比如0或者1或者2
            yield buf[:pos]
            # 暂停函数,返回缓存中的从头到字符的位置
            buf = buf[pos + len(newline):]
            # 缓存变成了,字符的位置到末尾
        chunk = f.read(2010 * 10)
        # 读取2010*10的字符
        if not chunk:
            # 已经读取到了文件结尾
            yield buf
            break
        buf += chunk
        # 加到缓存
 with open('a.txt','r')as f:
     for line in readbooks(f,'||'):
         print(line)

おすすめ

転載: blog.csdn.net/a40850273/article/details/88575801