반복기 및 반복 가능한 객체

반복기 및 반복 가능한 객체

개념

반복자 : 데이터 수집의 요소에 액세스하는 방법으로 일반적으로 데이터를 순회하는 데 사용되지만 아래 첨자를 사용하여 목록과 같은 데이터를 얻을 수 없으므로 반복기를 반환 할 수 없습니다.

  1. Iterator : Iterator 객체, 다음 매직 함수를 구현해야합니다.

  2. Iterable : Iterable 객체, Iterator 상속, iter 매직 함수 구현 필요

예 :

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

반환 결과 :

False
True

Pycharm에서 alt + b를 사용하여 목록의 소스 코드를 입력하면 목록 클래스에 iter magic 함수가 있음을 알 수 있습니다. 즉, iter magic 함수가 구현되는 한 개체는 반복 가능한 객체입니다.

위의 예에서 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가 iterable 객체이고 iterator라는 것을 알 수 있습니다. 이는 iterator를 반환하는 iter method가 list a에 있음을 나타냅니다. 이때 next를 사용하여 a의 다음 값을 가져올 수 있지만 iterator를 기억하십시오. 의 값은 한 번만 얻을 수 있습니다.

Iterator와 Iterable의 차이점을 분류하려면 :

  1. 반복 가능한 객체 : 반복기 객체를 상속합니다. for 루프를 사용할 수 있습니다 (반복 메서드가 구현되었음을 나타냄).

  2. Iterator 객체 : next를 사용하여 다음 값을 가져올 수 있지만 (다음 메서드가 구현되었음을 나타냄) 각 값은 한 번만 가져올 수 있습니다. 간단한 반복기는 iter 매직 함수를 구현하지 않으므로 for 루프를 사용할 수 없습니다.

  3. for 루프로 사용할 수있는 한 반복 가능한 객체

  4. next () 함수를 사용할 수있는 한 반복기 객체입니다.

  5. 목록, 사전 및 문자열은 반복 가능한 객체이지만 반복기 객체는 아닙니다. 반복기 객체가 되려면 iter ()를 사용하여 변환 할 수 있습니다.

  6. 파이썬의 for 루프는 본질적으로 next ()를 사용하여 상수 호출을합니다. for 루프는 반복 가능한 객체입니다. 반복 가능한 객체에는 반복 매직 함수가 있고, 반복 객체는 반복 객체를 상속하고, 반복 객체에는 다음 매직 함수가 있습니다.

  7. 일반적으로 반복 가능한 객체는 반복기 객체를 변경합니다.

반복 가능한 객체

반복 가능한 객체가 배열에서 for 루프를 사용할 때마다 기본적으로 클래스에서 반복 매직 함수를 호출하려고합니다. 클래스에 반복 매직 함수가 있으면 먼저 반복 매직 함수를 호출합니다. 물론 반복 메서드는 하나를 반환해야합니다. 반복 할 수있는 객체입니다. 그렇지 않으면 오류가보고됩니다.

iter 매직 함수가 정의되어 있지 않으면 getitem 매직 함수를 호출하는 기본 iterator가 생성됩니다. 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을 반복 가능한 객체로 사용하고 다음으로 Iterator를 반복자로 사용하는 반복자를 언급했습니다. next ()는 반복기 객체를 받아들이고 그 역할은 반복기 객체의 다음 값을 가져 오는 것입니다. 반복자는 반복에 사용되며 필요할 때만 데이터를 생성합니다.

iterable 객체와 달리 iterable 객체는 처음에 모든 목록을 변수에 넣은 다음 getitem 메서드를 사용하여 값을 계속 반환합니다. getitem의 항목은 인덱스 값입니다.

그러나 next 메서드에는 인덱스 값이 없으므로 다음 변수의 위치를 ​​쉽게 찾을 수 있도록 인덱스 값을 직접 유지해야합니다.

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

위의 객체는 완전한 반복기 객체입니다. 자체 인덱스 값에 따라 전달 된 객체의 다음 값을 얻습니다. 전달 된 객체를 메모리로 직접 읽어 오는 반복 객체와 같지 않으므로 일부 대용량 파일의 경우 읽을 때 파일의 모든 내용을 메모리로 읽는 대신 내용을 한 줄씩 읽을 수 있습니다.

이 클래스는 반복기 객체이므로 for 루프를 사용하도록 어떻게 활성화 할 수 있습니까? 그런 다음 반복 가능한 객체가되게하고, 클래스에 반복 마법 함수를 추가하면됩니다.

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 루프를 사용하려면 이터레이터 내부에 이터 매직 함수를 추가해야합니다.

  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

이때는 class b는 반복자가 아닌 반복 가능한 객체이기 때문에 더 이상 next 메소드를 사용할 수 없습니다. 현재는 next 메소드를 사용할 수 없지만, class b가 class 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

이것은 가능하지만 설계 원칙을 위반하기 때문에 필요하지 않습니다.

반복자 디자인 패턴

반복자 패턴 : 개체의 내부 표현을 노출하지 않고 집계 개체의 다양한 요소에 순차적으로 액세스하는 방법을 제공합니다.

반복기의 디자인 패턴은 고전적인 디자인 패턴입니다. 반복기의 특성에 따라 (한 번에 많은 양의 데이터를 메모리에 읽지 않고 인덱스 값에 따라 다음 내용을 읽음) 동일한 클래스에 다음과 반복을 모두 작성하지 않는 것이 좋습니다. 달성하기 위해.

새 반복기를 만들고, 반복기를 사용하여 인덱스 값을 유지하고, 인덱스 값에 따라 얻은 개체의 값을 반환하고, 다른 반복 가능한 개체를 만들고, 반복기 메서드를 사용하여 루프 반복기의 반환 값을 용이하게합니다.

빌더

생성기 : 함수에 수익이있는 한 함수는 생성기가됩니다. 양보하기 위해 실행될 때마다 함수는 일시 중지하고 현재 실행 상태를 저장하고 현재 값으로 돌아가고 다음 메서드가 실행될 때 현재 위치에서 계속 내려갑니다.

간단한 사용법

예를 들면 :

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입니다. next (g) 또는 g : print x의 x를 사용하여 내용을 가져올 수 있습니다. 파이썬이 바이트 코드를 컴파일 할 때 객체가 생성됩니다.

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 in, first out"원칙을 충족합니다. 즉, last 호출 된 함수가 먼저 반환되어야합니다. 함수의 재귀 호출은 전형적인 예입니다. 메모리의 데이터를 "후입 선출"방식으로 처리하는 스택 세그먼트는 함수 호출에 가장 적합한 캐리어입니다. 컴파일 된 프로그래밍 언어에서 함수가 호출 된 후 함수 매개 변수, 반환 주소, 레지스터 값 및 기타 데이터 함수 본문이 실행 된 후 스택으로 푸시되고 위의 데이터가 스택에서 팝됩니다. 이것은 또한 호출 된 함수가 실행되면 수명주기가 끝났음을 의미합니다.

파이썬 인터프리터가 실행 중일 때 C 언어의 PyEval_EvalFramEx 함수를 사용하여 스택 프레임을 생성합니다. 모든 스택 프레임은 힙 메모리에 할당되며 활성 해제되지 않은 경우 항상 존재합니다.

파이썬의 스택 프레임은 힙 메모리에 할당됩니다. 이것을 이해하는 것이 매우 중요합니다! 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

코드 함수가 실행 중일 때 파이썬은 코드를 바이트 코드로 컴파일합니다. 함수에 yield가 있으면 파이썬은이 함수를 생성기로 표시합니다.이 함수가 호출되면 생성기 객체를 반환하고이 생성기 객체를 호출합니다. C 언어로 작성된 함수는 마지막 코드 실행의 위치와 변수를 기록합니다.

C 언어의 PyGenObject에는 gi_frame (마지막 코드 실행 위치 f_lasti의 마지막 코드 실행의 변수 f_locals 저장), gi_code (저장 코드), dis를 사용하여 마지막 코드 실행을 가져올 수있는 두 가지 값이 있습니다. 위치와 가치.

예를 들면 :

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 파일은 텍스트 한 줄이지 만 매우 깁니다.이 텍스트 줄은 | 기호로 구분됩니다. 읽는 방법은 무엇입니까?

파일 코드 작성 :

# -*- 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