제너레이터 위임 학습하기

Beginner

This tutorial is from open-source community. Access the source code

소개

이 랩에서는 yield from 문을 사용하여 Python 에서 제너레이터 위임을 배우게 됩니다. Python 3.3 에 도입된 이 기능은 제너레이터와 코루틴에 의존하는 코드를 단순화합니다.

제너레이터는 호출 간에 상태를 유지하면서 실행을 일시 중지하고 재개할 수 있는 특수한 함수입니다. yield from 문은 다른 제너레이터로 제어 권한을 위임하는 우아한 방법을 제공하여 코드 가독성과 유지 관리성을 향상시킵니다.

목표:

  • yield from 문의 목적 이해
  • yield from을 사용하여 다른 제너레이터에 위임하는 방법 배우기
  • 이 지식을 코루틴 기반 코드를 단순화하는 데 적용하기
  • 현대적인 async/await 구문과의 연결 이해

작업할 파일:

  • cofollow.py - 코루틴 유틸리티 함수 포함
  • server.py - 간단한 네트워크 서버 구현 포함

yield from 문 이해하기

이 단계에서는 Python 의 yield from 문을 살펴보겠습니다. 이 문은 제너레이터 작업 시 강력한 도구이며, 다른 제너레이터로 작업을 위임하는 프로세스를 단순화합니다. 이 단계를 마치면 yield from이 무엇인지, 어떻게 작동하는지, 그리고 서로 다른 제너레이터 간의 값 전달을 어떻게 처리하는지 이해하게 될 것입니다.

yield from이란 무엇인가?

yield from 문은 Python 3.3 에 도입되었습니다. 주요 목적은 하위 제너레이터로의 작업 위임을 단순화하는 것입니다. 하위 제너레이터는 메인 제너레이터가 작업을 위임할 수 있는 또 다른 제너레이터입니다.

일반적으로 제너레이터가 다른 제너레이터에서 값을 yield 하도록 하려면 루프를 사용해야 합니다. 예를 들어, yield from이 없으면 다음과 같은 코드를 작성합니다.

def delegating_generator():
    for value in subgenerator():
        yield value

이 코드에서 delegating_generatorfor 루프를 사용하여 subgenerator가 생성한 값을 반복하고 각 값을 하나씩 yield 합니다.

그러나 yield from 문을 사용하면 코드가 훨씬 간단해집니다.

def delegating_generator():
    yield from subgenerator()

이 한 줄의 코드는 이전 예제의 루프와 동일한 결과를 얻습니다. 하지만 yield from은 단순한 단축키가 아닙니다. 또한 호출자와 하위 제너레이터 간의 양방향 통신을 관리합니다. 즉, 위임 제너레이터로 전송된 모든 값은 하위 제너레이터로 직접 전달됩니다.

기본 예제

yield from이 실제로 어떻게 작동하는지 확인하기 위해 간단한 예제를 만들어 보겠습니다.

  1. 먼저, 편집기에서 cofollow.py 파일을 열어야 합니다. 이렇게 하려면 cd 명령을 사용하여 올바른 디렉토리로 이동합니다. 터미널에서 다음 명령을 실행합니다.
cd /home/labex/project
  1. 다음으로, cofollow.py 파일에 두 개의 함수를 추가합니다. subgen 함수는 0 부터 4 까지의 숫자를 yield 하는 간단한 제너레이터입니다. main_gen 함수는 yield from을 사용하여 이러한 숫자의 생성을 subgen에 위임한 다음 문자열 'Done'을 yield 합니다. 다음 코드를 cofollow.py 파일의 끝에 추가합니다.
def subgen():
    for i in range(5):
        yield i

def main_gen():
    yield from subgen()
    yield 'Done'
  1. 이제 이러한 함수를 테스트해 보겠습니다. Python 셸을 열고 다음 코드를 실행합니다.
from cofollow import subgen, main_gen

## Test subgen directly
for x in subgen():
    print(x)

## Test main_gen that delegates to subgen
for x in main_gen():
    print(x)

이 코드를 실행하면 다음과 같은 출력이 표시됩니다.

0
1
2
3
4

0
1
2
3
4
Done

이 출력은 yield from을 통해 main_gensubgen에서 생성된 모든 값을 호출자에게 직접 전달할 수 있음을 보여줍니다.

yield from을 사용한 값 전달

yield from의 가장 강력한 기능 중 하나는 양방향으로 값 전달을 처리하는 능력입니다. 이를 보여주기 위해 더 복잡한 예제를 만들어 보겠습니다.

  1. 다음 함수를 cofollow.py 파일에 추가합니다.
def accumulator():
    total = 0
    while True:
        value = yield total
        if value is None:
            break
        total += value

def caller():
    acc = accumulator()
    yield from acc
    yield 'Total accumulated'

accumulator 함수는 실행 중인 합계를 추적하는 코루틴입니다. 현재 합계를 yield 한 다음 새 값을 받을 때까지 기다립니다. None을 받으면 루프를 중지합니다. caller 함수는 accumulator의 인스턴스를 생성하고 yield from을 사용하여 모든 send 및 receive 작업을 위임합니다.

  1. Python 셸에서 이러한 함수를 테스트합니다.
from cofollow import caller

c = caller()
print(next(c))  ## Start the coroutine
print(c.send(1))  ## Send value 1, get accumulated value
print(c.send(2))  ## Send value 2, get accumulated value
print(c.send(3))  ## Send value 3, get accumulated value
print(c.send(None))  ## Send None to exit the accumulator

이 코드를 실행하면 다음과 같은 출력이 표시됩니다.

0
1
3
6
'Total accumulated'

이 출력은 yield from이 소진될 때까지 모든 send 및 receive 작업을 하위 제너레이터에 완전히 위임함을 보여줍니다.

이제 yield from의 기본 사항을 이해했으므로 다음 단계에서 더 실용적인 응용 프로그램으로 이동하겠습니다.

코루틴에서 yield from 사용하기

이 단계에서는 더 실용적인 응용 프로그램을 위해 yield from 문을 코루틴과 함께 사용하는 방법을 살펴보겠습니다. 코루틴은 Python 에서 강력한 개념이며, 코루틴과 함께 yield from을 사용하는 방법을 이해하면 코드를 크게 단순화할 수 있습니다.

코루틴과 메시지 전달

코루틴은 yield 문을 통해 값을 받을 수 있는 특수한 함수입니다. 데이터 처리 및 이벤트 처리와 같은 작업에 매우 유용합니다. cofollow.py 파일에는 consumer 데코레이터가 있습니다. 이 데코레이터는 코루틴을 자동으로 첫 번째 yield 지점까지 진행시켜 코루틴을 설정하는 데 도움이 됩니다. 즉, 코루틴을 수동으로 시작할 필요가 없으며 데코레이터가 이를 처리합니다.

값을 받고 해당 유형을 검증하는 코루틴을 만들어 보겠습니다. 방법은 다음과 같습니다.

  1. 먼저, 편집기에서 cofollow.py 파일을 엽니다. 터미널에서 다음 명령을 사용하여 올바른 디렉토리로 이동할 수 있습니다.
cd /home/labex/project
  1. 다음으로, cofollow.py 파일의 끝에 다음 receive 함수를 추가합니다. 이 함수는 메시지를 받고 해당 유형을 검증하는 코루틴입니다.
def receive(expected_type):
    """
    A coroutine that receives a message and validates its type.
    Returns the received message if it matches the expected type.
    """
    msg = yield
    assert isinstance(msg, expected_type), f'Expected type {expected_type}'
    return msg

이 함수는 다음을 수행합니다.

  • 표현식 없이 yield를 사용하여 값을 받습니다. 코루틴에 값이 전송되면 이 yield 문이 해당 값을 캡처합니다.
  • isinstance 함수를 사용하여 수신된 값이 예상 유형인지 확인합니다. 유형이 일치하지 않으면 AssertionError를 발생시킵니다.
  • 유형 검사가 통과하면 값을 반환합니다.
  1. 이제 receive 함수와 함께 yield from을 사용하는 코루틴을 만들어 보겠습니다. 이 새로운 코루틴은 정수만 받고 출력합니다.
@consumer
def print_ints():
    """
    A coroutine that receives and prints integers only.
    Uses yield from to delegate to the receive coroutine.
    """
    while True:
        val = yield from receive(int)
        print('Got:', val)
  1. 이 코루틴을 테스트하려면 Python 셸을 열고 다음 코드를 실행합니다.
from cofollow import print_ints

p = print_ints()
p.send(42)
p.send(13)
try:
    p.send('13')  ## This should raise an AssertionError
except AssertionError as e:
    print(f"Error: {e}")

다음과 같은 출력이 표시됩니다.

Got: 42
Got: 13
Error: Expected type <class 'int'>

코루틴에서 yield from이 작동하는 방식 이해하기

print_ints 코루틴에서 yield from receive(int)를 사용하면 다음 단계가 발생합니다.

  1. 제어가 receive 코루틴으로 위임됩니다. 즉, print_ints 코루틴이 일시 중지되고 receive 코루틴이 실행을 시작합니다.
  2. receive 코루틴은 yield를 사용하여 값을 받습니다. 값의 전송을 기다립니다.
  3. print_ints로 값이 전송되면 실제로 receive에서 받습니다. yield from 문은 print_ints에서 receive로 값을 전달하는 역할을 합니다.
  4. receive 코루틴은 수신된 값의 유형을 검증합니다. 유형이 올바르면 값을 반환합니다.
  5. 반환된 값은 print_ints 코루틴의 yield from 표현식의 결과가 됩니다. 즉, print_intsval 변수에는 receive에서 반환된 값이 할당됩니다.

yield from을 사용하면 yielding 및 receiving 을 직접 처리해야 하는 경우보다 코드를 더 읽기 쉽게 만들 수 있습니다. 코루틴 간의 값 전달의 복잡성을 추상화합니다.

더 발전된 유형 검사 코루틴 만들기

더 복잡한 유형 검증을 처리하도록 유틸리티 함수를 확장해 보겠습니다. 방법은 다음과 같습니다.

  1. 다음 함수를 cofollow.py 파일에 추가합니다.
def receive_dict():
    """Receive and validate a dictionary"""
    result = yield from receive(dict)
    return result

def receive_str():
    """Receive and validate a string"""
    result = yield from receive(str)
    return result

@consumer
def process_data():
    """Process different types of data using the receive utilities"""
    while True:
        print("Waiting for a string...")
        name = yield from receive_str()
        print(f"Got string: {name}")

        print("Waiting for a dictionary...")
        data = yield from receive_dict()
        print(f"Got dictionary with {len(data)} items: {data}")

        print("Processing complete for this round.")
  1. 새 코루틴을 테스트하려면 Python 셸을 열고 다음 코드를 실행합니다.
from cofollow import process_data

proc = process_data()
proc.send("John Doe")
proc.send({"age": 30, "city": "New York"})
proc.send("Jane Smith")
try:
    proc.send(123)  ## This should raise an AssertionError
except AssertionError as e:
    print(f"Error: {e}")

다음과 같은 출력이 표시됩니다.

Waiting for a string...
Got string: John Doe
Waiting for a dictionary...
Got dictionary with 2 items: {'age': 30, 'city': 'New York'}
Processing complete for this round.
Waiting for a string...
Got string: Jane Smith
Waiting for a dictionary...
Error: Expected type <class 'dict'>

yield from 문은 코드를 더 깔끔하고 읽기 쉽게 만듭니다. 코루틴 간의 메시지 전달 세부 사항에 얽매이지 않고 프로그램의 상위 수준 논리에 집중할 수 있습니다.

제너레이터로 소켓 래핑하기

이 단계에서는 제너레이터를 사용하여 소켓 작업을 래핑하는 방법을 배우겠습니다. 이는 특히 비동기 프로그래밍과 관련하여 매우 중요한 개념입니다. 비동기 프로그래밍을 사용하면 프로그램이 다른 작업이 완료될 때까지 기다리지 않고 여러 작업을 동시에 처리할 수 있습니다. 제너레이터를 사용하여 소켓 작업을 래핑하면 코드를 더 효율적으로 만들고 관리하기 쉽게 만들 수 있습니다.

문제 이해하기

server.py 파일에는 제너레이터를 사용하는 간단한 네트워크 서버 구현이 포함되어 있습니다. 현재 코드를 살펴보겠습니다. 이 코드는 서버의 기반이며, 변경하기 전에 이를 이해하는 것이 중요합니다.

def tcp_server(address, handler):
    sock = socket(AF_INET, SOCK_STREAM)
    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sock.bind(address)
    sock.listen(5)
    while True:
        yield 'recv', sock
        client, addr = sock.accept()
        tasks.append(handler(client, addr))

def echo_handler(client, address):
    print('Connection from', address)
    while True:
        yield 'recv', client
        data = client.recv(1000)
        if not data:
            break
        yield 'send', client
        client.send(b'GOT:' + data)
    print('Connection closed')
    client.close()

이 코드에서는 yield 키워드를 사용합니다. yield 키워드는 Python 에서 제너레이터를 만드는 데 사용됩니다. 제너레이터는 함수의 실행을 일시 중지하고 재개할 수 있는 특수한 유형의 반복자입니다. 여기에서 yield는 서버가 연결을 받을 준비가 되었거나 클라이언트 핸들러가 데이터를 받거나 보낼 준비가 되었음을 나타내는 데 사용됩니다. 그러나 수동 yield 문은 이벤트 루프의 내부 작동 방식을 사용자에게 노출합니다. 즉, 사용자는 이벤트 루프가 작동하는 방식을 알아야 하므로 코드를 이해하고 유지 관리하기 어려울 수 있습니다.

GenSocket 클래스 만들기

제너레이터로 소켓 작업을 래핑하기 위해 GenSocket 클래스를 만들어 보겠습니다. 이렇게 하면 코드가 더 깔끔하고 읽기 쉬워집니다. 소켓 작업을 클래스에 캡슐화함으로써 사용자로부터 이벤트 루프의 세부 정보를 숨기고 서버의 상위 수준 논리에 집중할 수 있습니다.

  1. 편집기에서 server.py 파일을 엽니다.
cd /home/labex/project

이 명령은 현재 디렉토리를 server.py 파일이 있는 프로젝트 디렉토리로 변경합니다. 올바른 디렉토리에 있으면 원하는 텍스트 편집기에서 파일을 열 수 있습니다.

  1. 파일의 끝, 기존 함수 앞에 다음 GenSocket 클래스를 추가합니다.
class GenSocket:
    """
    A generator-based wrapper for socket operations.
    """
    def __init__(self, sock):
        self.sock = sock

    def accept(self):
        """Accept a connection and return a new GenSocket"""
        yield 'recv', self.sock
        client, addr = self.sock.accept()
        return GenSocket(client), addr

    def recv(self, maxsize):
        """Receive data from the socket"""
        yield 'recv', self.sock
        return self.sock.recv(maxsize)

    def send(self, data):
        """Send data to the socket"""
        yield 'send', self.sock
        return self.sock.send(data)

    def __getattr__(self, name):
        """Forward any other attributes to the underlying socket"""
        return getattr(self.sock, name)

GenSocket 클래스는 소켓 작업의 래퍼 역할을 합니다. __init__ 메서드는 소켓 객체로 클래스를 초기화합니다. accept, recv, send 메서드는 해당 소켓 작업을 수행하고 yield를 사용하여 작업이 준비되었음을 나타냅니다. __getattr__ 메서드를 사용하면 클래스가 다른 모든 속성을 기본 소켓 객체로 전달할 수 있습니다.

  1. 이제 tcp_serverecho_handler 함수를 수정하여 GenSocket 클래스를 사용합니다.
def tcp_server(address, handler):
    sock = GenSocket(socket(AF_INET, SOCK_STREAM))
    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sock.bind(address)
    sock.listen(5)
    while True:
        client, addr = yield from sock.accept()
        tasks.append(handler(client, addr))

def echo_handler(client, address):
    print('Connection from', address)
    while True:
        data = yield from client.recv(1000)
        if not data:
            break
        yield from client.send(b'GOT:' + data)
    print('Connection closed')
    client.close()

명시적 yield 'recv', sockyield 'send', client 문이 더 깔끔한 yield from 표현식으로 대체된 방식을 확인하십시오. yield from 키워드는 실행을 다른 제너레이터로 위임하는 데 사용됩니다. 이렇게 하면 코드를 더 읽기 쉽게 만들고 사용자로부터 이벤트 루프의 세부 정보를 숨길 수 있습니다. 이제 코드는 일반 함수 호출과 더 유사하게 보이며 사용자는 이벤트 루프의 내부 작동 방식에 대해 걱정할 필요가 없습니다.

  1. 서버가 어떻게 사용되는지 보여주기 위해 간단한 테스트 함수를 추가해 보겠습니다.
def run_server():
    """Start the server on port 25000"""
    tasks.append(tcp_server(('localhost', 25000), echo_handler))
    try:
        event_loop()
    except KeyboardInterrupt:
        print("Server stopped")

if __name__ == '__main__':
    print("Starting echo server on port 25000...")
    print("Press Ctrl+C to stop")
    run_server()

이 코드는 더 읽기 쉽고 유지 관리하기 쉽습니다. GenSocket 클래스는 yielding 로직을 캡슐화하여 서버 코드가 이벤트 루프의 세부 사항이 아닌 상위 수준 흐름에 집중할 수 있도록 합니다. run_server 함수는 포트 25000 에서 서버를 시작하고 KeyboardInterrupt 예외를 처리하여 사용자가 Ctrl+C를 눌러 서버를 중지할 수 있도록 합니다.

이점 이해하기

yield from 접근 방식은 다음과 같은 몇 가지 이점을 제공합니다.

  1. 더 깔끔한 코드: 소켓 작업이 일반 함수 호출과 더 유사하게 보입니다. 이렇게 하면 특히 초보자의 경우 코드를 더 쉽게 읽고 이해할 수 있습니다.
  2. 추상화: 이벤트 루프의 세부 정보가 사용자로부터 숨겨집니다. 사용자는 서버 코드를 사용하기 위해 이벤트 루프가 작동하는 방식을 알 필요가 없습니다.
  3. 가독성: 코드는 수행하는 방식이 아닌 수행하는 작업을 더 잘 표현합니다. 이렇게 하면 코드가 더 자체 설명적이고 유지 관리하기 쉬워집니다.
  4. 유지 관리성: 이벤트 루프에 대한 변경 사항은 서버 코드에 대한 변경 사항을 필요로 하지 않습니다. 즉, 나중에 이벤트 루프를 수정해야 하는 경우 서버 코드에 영향을 주지 않고 그렇게 할 수 있습니다.

이 패턴은 다음 단계에서 살펴볼 최신 async/await 구문에 대한 디딤돌입니다. async/await 구문은 Python 에서 비동기 코드를 작성하는 더 발전되고 깔끔한 방법이며, yield from 패턴을 이해하면 더 쉽게 전환할 수 있습니다.

제너레이터에서 Async/Await로

이 마지막 단계에서는 Python 의 yield from 패턴이 어떻게 현대적인 async/await 구문으로 발전했는지 살펴보겠습니다. 이러한 진화를 이해하는 것은 제너레이터와 비동기 프로그래밍 간의 연결을 파악하는 데 도움이 되므로 매우 중요합니다. 비동기 프로그래밍을 사용하면 프로그램이 각 작업이 완료될 때까지 기다리지 않고 여러 작업을 처리할 수 있으므로 네트워크 프로그래밍 및 기타 I/O 기반 작업에 특히 유용합니다.

제너레이터와 Async/Await 간의 연결

Python 3.5 에 도입된 async/await 구문은 제너레이터 및 yield from 기능을 기반으로 구축되었습니다. 내부적으로 async 함수는 제너레이터를 사용하여 구현됩니다. 즉, 제너레이터에 대해 배운 개념은 async/await가 작동하는 방식과 직접적으로 관련이 있습니다.

제너레이터 사용에서 async/await 구문으로 전환하려면 다음 단계를 따라야 합니다.

  1. types 모듈에서 @coroutine 데코레이터를 사용합니다. 이 데코레이터는 제너레이터 기반 함수를 async/await와 함께 사용할 수 있는 형태로 변환하는 데 도움이 됩니다.
  2. yield from을 사용하는 함수를 asyncawait를 사용하도록 변환합니다. 이렇게 하면 코드를 더 읽기 쉽게 만들고 작업의 비동기적 특성을 더 잘 표현할 수 있습니다.
  3. 기본 코루틴을 처리하도록 이벤트 루프를 업데이트합니다. 이벤트 루프는 비동기 작업을 예약하고 실행하는 역할을 합니다.

GenSocket 클래스 업데이트

이제 GenSocket 클래스를 수정하여 @coroutine 데코레이터와 함께 작동하도록 하겠습니다. 이렇게 하면 클래스를 async/await 컨텍스트에서 사용할 수 있습니다.

  1. 편집기에서 server.py 파일을 엽니다. 터미널에서 다음 명령을 실행하여 이 작업을 수행할 수 있습니다.
cd /home/labex/project
  1. server.py 파일의 맨 위에 coroutine에 대한 import 를 추가합니다. 이 import 는 @coroutine 데코레이터를 사용하기 위해 필요합니다.
from types import coroutine
  1. @coroutine 데코레이터를 사용하도록 GenSocket 클래스를 업데이트합니다. 이 데코레이터는 제너레이터 기반 메서드를 awaitable 코루틴으로 변환합니다. 즉, await 키워드와 함께 사용할 수 있습니다.
class GenSocket:
    """
    A generator-based wrapper for socket operations
    that works with async/await.
    """
    def __init__(self, sock):
        self.sock = sock

    @coroutine
    def accept(self):
        """Accept a connection and return a new GenSocket"""
        yield 'recv', self.sock
        client, addr = self.sock.accept()
        return GenSocket(client), addr

    @coroutine
    def recv(self, maxsize):
        """Receive data from the socket"""
        yield 'recv', self.sock
        return self.sock.recv(maxsize)

    @coroutine
    def send(self, data):
        """Send data to the socket"""
        yield 'send', self.sock
        return self.sock.send(data)

    def __getattr__(self, name):
        """Forward any other attributes to the underlying socket"""
        return getattr(self.sock, name)

Async/Await 구문으로 변환

다음으로, 서버 코드를 변환하여 async/await 구문을 사용해 보겠습니다. 이렇게 하면 코드를 더 읽기 쉽게 만들고 작업의 비동기적 특성을 명확하게 표현할 수 있습니다.

async def tcp_server(address, handler):
    """
    An asynchronous TCP server using async/await.
    """
    sock = GenSocket(socket(AF_INET, SOCK_STREAM))
    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sock.bind(address)
    sock.listen(5)
    while True:
        client, addr = await sock.accept()
        tasks.append(handler(client, addr))

async def echo_handler(client, address):
    """
    An asynchronous handler for echo clients.
    """
    print('Connection from', address)
    while True:
        data = await client.recv(1000)
        if not data:
            break
        await client.send(b'GOT:' + data)
    print('Connection closed')
    client.close()

yield fromawait로 대체되었고 함수가 이제 def 대신 async def로 정의되었음을 확인하십시오. 이 변경으로 코드가 더 직관적이고 이해하기 쉬워졌습니다.

변환 이해하기

yield from을 사용하는 제너레이터에서 async/await 구문으로의 전환은 단순한 구문 변경만은 아닙니다. 이는 비동기 프로그래밍에 대한 사고 방식의 변화를 나타냅니다.

  1. yield from 을 사용하는 제너레이터:

    • yield from을 사용하는 제너레이터를 사용할 때 작업이 준비되었음을 알리기 위해 명시적으로 제어를 양보합니다. 즉, 작업이 언제 계속될 수 있는지 수동으로 관리해야 합니다.
    • 또한 작업의 스케줄링을 수동으로 관리해야 합니다. 이는 특히 더 큰 프로그램에서 복잡할 수 있습니다.
    • 제어 흐름의 메커니즘에 초점을 맞추면 코드를 읽고 유지 관리하기 어려울 수 있습니다.
  2. Async/await 구문:

    • async/await 구문을 사용하면 await 지점에서 제어가 암시적으로 양보됩니다. 이렇게 하면 제어를 명시적으로 양보하는 것에 대해 걱정할 필요가 없으므로 코드가 더 간단해집니다.
    • 이벤트 루프가 작업의 스케줄링을 처리하므로 수동으로 관리할 필요가 없습니다.
    • 프로그램의 논리적 흐름에 초점을 맞추면 코드를 더 읽기 쉽고 유지 관리할 수 있습니다.

이 변환을 통해 더 읽기 쉽고 유지 관리 가능한 비동기 코드를 사용할 수 있으며, 이는 네트워크 서버와 같은 복잡한 응용 프로그램에 특히 중요합니다.

최신 비동기 프로그래밍

최신 Python 에서는 사용자 지정 이벤트 루프 대신 일반적으로 asyncio 모듈을 비동기 프로그래밍에 사용합니다. asyncio 모듈은 다음과 같은 많은 유용한 기능에 대한 기본 제공 지원을 제공합니다.

  • 여러 코루틴을 동시에 실행합니다. 이렇게 하면 프로그램이 여러 작업을 동시에 처리할 수 있습니다.
  • 네트워크 I/O 를 관리합니다. 네트워크를 통해 데이터를 보내고 받는 프로세스를 단순화합니다.
  • 동기화 기본 요소. 이는 동시 환경에서 공유 리소스에 대한 액세스를 관리하는 데 도움이 됩니다.
  • 작업 예약 및 취소. 특정 시간에 실행되도록 작업을 쉽게 예약하고 필요한 경우 취소할 수 있습니다.

다음은 asyncio를 사용하는 서버의 모습입니다.

import asyncio

async def handle_client(reader, writer):
    addr = writer.get_extra_info('peername')
    print(f'Connection from {addr}')

    while True:
        data = await reader.read(1000)
        if not data:
            break

        writer.write(b'GOT:' + data)
        await writer.drain()

    print('Connection closed')
    writer.close()
    await writer.wait_closed()

async def main():
    server = await asyncio.start_server(
        handle_client, 'localhost', 25000
    )

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()

if __name__ == '__main__':
    asyncio.run(main())

이 코드는 제너레이터 기반 서버와 동일한 기능을 수행하지만 더 강력하고 기능이 풍부한 표준 asyncio 라이브러리를 사용합니다.

결론

이 랩에서는 몇 가지 중요한 개념에 대해 배웠습니다.

  1. yield from 문과 다른 제너레이터로 위임하는 방법. 이는 제너레이터가 작동하는 방식을 이해하는 데 기본적인 개념입니다.
  2. 메시지 전달을 위해 코루틴과 함께 yield from을 사용하는 방법. 이를 통해 비동기 프로그램의 서로 다른 부분 간에 통신할 수 있습니다.
  3. 더 깔끔한 코드를 위해 제너레이터로 소켓 작업을 래핑하는 방법. 이렇게 하면 네트워크 관련 코드가 더 체계적이고 이해하기 쉬워집니다.
  4. 제너레이터에서 현대적인 async/await 구문으로의 전환. 이 전환을 이해하면 제너레이터를 직접 사용하든 현대적인 async/await 구문을 사용하든 Python 에서 더 읽기 쉽고 유지 관리 가능한 비동기 코드를 작성하는 데 도움이 됩니다.

요약

이 랩에서는 Python 에서 제너레이터 위임의 개념에 대해 배웠으며, yield from 문과 그 다양한 응용 프로그램에 중점을 두었습니다. yield from을 사용하여 다른 제너레이터에 위임하는 방법을 탐구하여 코드를 단순화하고 가독성을 향상시켰습니다. 또한 yield from을 사용하여 코루틴을 생성하여 메시지를 수신하고 유효성을 검사하는 방법과, 더 깔끔한 네트워크 코드를 위해 제너레이터를 사용하여 소켓 작업을 래핑하는 방법에 대해서도 배웠습니다.

이러한 개념은 Python 에서 비동기 프로그래밍을 이해하는 데 필수적입니다. 제너레이터에서 현대적인 async/await 구문으로의 전환은 비동기 작업을 처리하는 데 있어 중요한 발전입니다. 이러한 개념을 더 자세히 탐구하려면 asyncio 모듈을 연구하고, 인기 있는 프레임워크가 async/await를 사용하는 방식을 검토하고, 자신만의 비동기 라이브러리를 개발할 수 있습니다. 제너레이터 위임 및 yield from을 이해하면 Python 의 비동기 프로그래밍 접근 방식에 대한 더 깊은 통찰력을 얻을 수 있습니다.