파이썬의 생성기는 Yeld만 하는 게 아니에요.

27566 단어 Pythontech
안녕하십니까?
Pythhonista 여러분, TypeHint라고 쓰여 있습니까?나는 기본적으로 쓸 줄 안다.이번에는 TypeHint에 생성기를 쓸 때 나온 의문과 조사 결과를 소개합니다.
일회용 코드가 아닌 파이썬 3이면5개 이상 사용했는데도 Type Hint를 쓰지 않은 사람이 있다면 꼭 뭘 쓰는지 토론해 보세요.TypeHint를 더하면 코드 이해가 쉬워지고 IDE의 예측 변환도 효과적으로 활용돼 개발 효율이 높아진다.TypeHint의 은혜를 최대한 누리는 VScode에 대한 환경 구축은 기사에서 참고하시기 바랍니다.
VScode와 Poetry에서 제작한 파이썬 개발 환경

원래 이른바 생성기


공식 문서에는 다음과 같은 기술이 있다.
generiterator의 함수를 되돌려줍니다.일반적인 함수와 비슷하지만 yeld식이 있다는 점에서는 다르다.이 공식은 값을 생성하는 배열에 사용되며 for 순환에서 사용할 수도 있고,next () 함수를 통해 값을 하나하나 추출할 수도 있습니다.
그렇구나.정보 generator iteratoryield가 국부 실행 상태 (국부 변수나 처리되지 않은try 문장 등 포함) 를 저장할 때마다 처리가 잠시 중단됩니다.생성기 균형기가 복구되면 중단된 위치를 가져옵니다. (일반 함수가 실행될 때마다 새로운 상태에서 시작됩니다.)
이런 기록이 있다.뭐가 그렇게 좋으세요?StackOverflow에는 지연평가를 할 수 있다는 답변이 있었다.다음은 이 답변에서 소개한 검색 시스템의 구체적인 예를 소개한다.
SQLite로 사용자 리스트를 만들고 지정된 연령 이하의 사용자의 ID 일람을 검색하는 예를 고려해 보자.먼저 사용자 테이블을 만듭니다.
# ユーザテーブル作成
def init_users_table(n: int) -> None:
    # n: ダミーユーザ数
    conn = sqlite3.connect("sample_users.db")
    cursor = conn.cursor()

    # テーブル作成
    cursor.execute("CREATE TABLE users(user_id, age)")

    # ダミーユーザ追加
    for _ in range(n):
        user_id = str(uuid4())
        age = randint(0, 100)
        cursor.execute("INSERT INTO users VALUES (?, ?)", (user_id, age))

    conn.commit()
    conn.close()
이 표에서 검색을 하는데 목록에서 검색 결과를 받아들이면 다음과 같은 내용이 됩니까?
def get_user_ids(age: int) -> List[str]:
    conn = sqlite3.connect("sample_users.db")
    cursor = conn.cursor()

    cursor.execute("SELECT user_id FROM users WHERE age <= ?", (age,))

    return cursor.fetchall()
목록의 모든 관련 레코드가 스토리지에 로드됩니다.이번 예에서 가상 사용자 수가 많지 않으면 메모리 오류가 발생하지 않을 것이다.그러나 얻은 열수가 늘어나면 메모리가 압박을 받는다.
이럴 때 생성기가 유용할 거예요.
def get_user_ids_generator(age: int) -> Generator[str, None, None]:
    conn = sqlite3.connect("sample_users.db")
    cursor = conn.cursor()

    cursor.execute("SELECT user_id FROM users WHERE age <= ?", (age,))

    for user_id in cursor.fetchall():
        yield user_id
TypeHint와 같이 목록 대신 생성기 이퀄라이저를 반환합니다.__next__ 방법이라고 하면 생성기가 1회yield를 실행한 후 다음yield에서 처리를 중단하기 때문에 조건과 일치하는 모든 사용자 ID를 하나하나 꺼낼 수 있다.(for문 내부에서 부르는 것 같다__next__.
생성기와 균형기에 대한 상세한 내용은 아래의 글을 참고하였으니 적당히 참고하시기 바랍니다.
  • 파이톤의 도해는 무엇입니까?
  • 파이톤의 이엘드, 생성기는 무엇입니까?
  • typing.Generator


    그럼, 이것은 이 보도의 주제입니다.공식 문서에는 다음과 같은 기술이 있다.
    Generator[YieldType, SendType, ReturnType]
    내가 여기서 생각한 것은 "YieldTypeyield값의 유형인 것을 알고 있다. SendType는 무엇입니까? 그리고 ReturnType 생성기를 나에게 돌려줄 수 있지 않습니까?"그러니까구체적인 예는 다음과 같이 기술되어 있다.
    def echo_round() -> Generator[int, float, str]:
        sent = yield 0
        while sent >= 0:
            sent = yield round(sent)
        return 'Done'
    
    이 코드를 볼 때의 의문을 정리했다
  • return 왜 변수에 대입됩니까?
  • yield 0 이상일 때는 언제?
  • sent때 어떻게 되나요?
  • 그렇습니다.이 의문을 없애는 것이 본 보도의 목적이다.하나씩 풀어봐.

    SendType


    우선return.전 몰랐지만 생성기 이하의 가격을 줄 수 있을 것 같아요.
    # ジェネレータを作成
    gen = make_generator()
    
    # ジェネレータに値を渡す
    gen.send(value)
    
    "SendTypeSendType로 제공되는 값의 유형입니다."
    그러니까공식 문서에 소개된 카운터의 예를 보겠습니다.
    def counter(maximum: int) -> Generator[int, int, None]:
        i = 0
        while i < maximum:
            # sendされた値をvalとして受け取る
            val = yield i
    
            # sendされていれば、カウント(i)をvalにする
            if val is not None:
                i = val
    
            # sendされていなければ、カウント(i)を1進める
            else:
                i += 1
    
    
    it = counter(10)
    print(next(it))    # 0
    print(next(it))    # 1
    print(it.send(8))  # 8
    print(next(it))    # 9
    
    그렇군요. .send를 사용하여 생성기에 값을 입력하고 계수를 진행합니다.".send 다음.send은 9가 아니라 8인가요?"그렇게 생각하지만 그렇지.똑같이 생각하는 사람은 아래의 검증을 참조하세요.
    
    def counter_next(it: Generator[int, int, None]) -> None:
        print("next")
        print("-" * 10)
        value = next(it)
        print(f"value: {value}")
        print("-" * 10)
        print()
    
    
    def counter_send(it: Generator[int, int, None], v: int) -> None:
        print("send")
        print("-" * 10)
        value = it.send(v)
        print(f"value: {value}")
        print("-" * 10)
        print()
    
    
    def counter(maximum: int) -> Generator[int, int, None]:
        i = 0
        while i < maximum:
            print(f"before count: {i}")
            val = yield i
            if val is not None:
                i = val
            else:
                i += 1
    
            print(f"after count: {i}")
    
    
    it = counter(10)
    counter_next(it)
    counter_next(it)
    counter_send(it, 8)
    counter_next(it)
    
    실행 결과
    next
    ----------
    before count: 0
    value: 0
    ----------
    
    next
    ----------
    after count: 1
    before count: 1
    value: 1
    ----------
    
    send
    ----------
    after count: 8
    before count: 8
    value: 8
    ----------
    
    next
    ----------
    after count: 9
    before count: 9
    value: 9
    ----------
    
    발생기는 next시 함수를 잠시 이탈하기 때문에 애프터와before는 상반된다.2차 이후yield는 이전에 실행된next의 후처리를 시작하여next로 바뀌었다.따라서 실행yield 후 먼저 i가 증가next한 후의 이미지입니다.

    ReturnType


    다음은yield.공식 문서에는 다음과 같은 기록이 있다.
    생성기 함수에서returnvalue는 ReturnType 방법으로 발송되었습니다__next__().이 상황이 발생하거나 함수의 끝에 도달하면 값의 생성이 끝나고 생성기는 더 많은 값을 되돌려주지 않습니다.
    이걸 정리하면...
  • 생성기 내StopIteration(value)에 있으면 return의 예외
  • 를 말합니다
  • 생성기에서 StopIteration하면 함수가 끝날 때와 같이 생성기가 더 높은 값을 되돌려주지 않습니다
  • return는 값StopIteration을 가질 수 있고, 이 값의 유형은 return value이다.
  • 그렇습니까?실제로 해보세요.
    def counter(maximum: int) -> Generator[int, int, int]:
        i = 0
        while i < maximum:
            val = yield i
            if val is not None:
                if val >= 0:
                    i = val
                else:
                    return -1
            else:
                i += 1
    
        return 0
    
    방금 카운터 예시에 추가ReturnType
  • 카운트가 마지막return까지 진행되면 반환
  • 계수가 0보다 작으면 0 다시 쓸 때.send 되돌아오기
  • 수정해 봤어요.우선 되돌아오는 경우-1부터 검증을 시작합니다.
    it = counter(3)
    print(next(it))  # 0
    print(next(it))  # 1
    print(next(it))  # 2
    print(next(it))  # StopIteration: 0 (例外発生)
    
    예상한 대로 계수가 마지막0에 발생했고 값에는 StopIteration가 있었다.다음에 우리는 되돌아오는 0의 상황을 검증할 것이다.
    print(next(it))  # 0
    try:
        it.send(-10)  # 例外発生
    except StopIteration:
        print(traceback.format_exc()) # StopIteration: -1
    print(next(it)) # StopIteration: 例外発生
    
    이 역시 예상과 같은 수치-1로 수치StopIteration다.또 한 번0이 발생한 후 StopIteration로 꺼내면 똑같이 발생next()하지만 수치는 아무것도 넣지 않는 것 같다.

    총결산


    TypeHint의 발단에서 생성기를 조사했습니다.알았어. 예전에 몰랐던 생성기 기능이 재밌어.솔직히 말해서 나는'생성기에서StopIteration,SendType 같은 것을 사용할 때가 있나'라고 생각해 왔다. 그것은 내가 파이톤에 비동기 처리를 거의 쓰지 않았기 때문이다.앞으로도 이 기사에 언급되지 않은 ReturnType를 포함한 비동기 처리를 배우고 싶다.

    좋은 웹페이지 즐겨찾기