asyncio.gather (return_exception)
1. asyncio.gather는 어떤 역할을 하나요?
ocrs/enhance_img.py 파일의 이 코드를 보시죠.
asyncio.gather는 여러 개의 비동기 작업(코루틴)을 동시에 실행하고, 모든 작업이 끝날 때까지 기다리는 역할을 합니다.
- 동시 실행: HTML 문서 안에 이미지가 여러 개 있을 수 있습니다. 각 이미지에 대해 OCR(광학 문자 인식)을 요청하는 것은 네트워크 통신이 필요한 작업이라 시간이 걸립니다. 만약 이미지를 하나씩 순서대로 처리한다면, 첫 번째 이미지의 OCR이 끝날 때까지 기다렸다가 두 번째 이미지 처리를 시작해야 해서 총 시간이 매우 길어집니다.
asyncio.gather는 이 OCR 요청들을 동시에 보냅니다. 그리고 파이썬의 이벤트 루프가 각 요청의 응답을 효율적으로 관리하여, 여러 작업을 거의 동시에 처리하는 것처럼 만들어 줍니다. 이를 통해 전체 OCR 처리 시간을 크게 단축할 수 있습니다.
ocr_tasks:ocr_tasks는[task1, task2, task3]와 같은 리스트입니다. 는 이 리스트를 풀어서gather(task1, task2, task3)처럼 여러 개의 인자로 전달하는 역할을 합니다.
return_exceptions=True: 이 옵션이 매우 중요합니다. 만약 여러 OCR 작업 중 하나가 실패해서 예외(Exception)가 발생했을 때,False(기본값):gather는 즉시 실행을 중단하고 그 예외를 발생시킵니다. 성공한 다른 작업의 결과도 얻을 수 없습니다.True:gather는 중단하지 않고 모든 작업이 끝날 때까지 기다립니다. 그리고 실패한 작업의 결과는 예외 객체(Exception object) 자체로 돌려줍니다. 덕분에 아래 코드처럼 각 결과가 성공했는지, 실패했는지 확인하며 안정적으로 후처리를 할 수 있습니다.
요약: asyncio.gather는 여러 개의 시간이 걸리는 비동기 작업을 동시에 처리하여 프로그램의 전체 실행 속도를 높이는 역할을 합니다.
result에 예외를 넣는 이유: 안정성과 전체 작업 완료 보장
결론부터 말씀드리면, 여러 개의 작업을 동시에 처리할 때, 그중 일부가 실패하더라도 전체 프로그램이 중단되지 않고 나머지 성공한 작업들의 결과를 모두 얻기 위함입니다.
ocrs/enhance_img.py 파일의 이 코드를 다시 보겠습니다.
여기서 return_exceptions=True 옵션이 핵심입니다.
만약 return_exceptions=True가 없다면? (기본 동작)
- HTML 문서에 이미지가 5개 있다고 가정해 봅시다.
asyncio.gather는 5개의 OCR 작업을 동시에 시작합니다.
- 만약 3번째 이미지의 URL이 잘못되었거나 네트워크 오류가 발생하여 예외(Exception)가 발생하면,
gather는 그 즉시 실행을 중단하고 해당 예외를 발생시킵니다.
- 결과: 프로그램 전체가 멈추게 됩니다. 성공적으로 처리되었을 수도 있는 1, 2, 4, 5번째 이미지의 OCR 결과마저 모두 잃어버리게 됩니다. 단 하나의 실패 때문에 전체 작업이 실패로 돌아가는 것이죠.
return_exceptions=True를 사용하면?
- 똑같이 5개의 OCR 작업을 동시에 시작합니다.
- 3번째 이미지에서 예외가 발생하더라도,
gather는 실행을 중단하지 않습니다. 모든 작업(성공하든 실패하든)이 끝날 때까지 계속 기다립니다.
- 모든 작업이 완료되면,
gather는 각 작업의 결과를 리스트로 반환합니다. - 성공한 작업의 위치에는 결과값(OCR 딕셔셔리)이 들어갑니다.
- 실패한 작업의 위치에는 **예외 객체(Exception object)**가 들어갑니다.
- 결과:
ocr_results리스트는 다음과 같은 형태가 됩니다.
"없는게 실제 OCR 결과를 저장할 때는 더 유용한거 아니야?"에 대한 답변
아주 날카로운 지적이십니다. 최종적으로 저장하고 싶은 것은 성공한 OCR 결과뿐인 것이 맞습니다. 하지만 "실패한 결과를 버리는 과정" 을 안정적으로 수행하려면, 일단 성공과 실패를 모두 받아야 합니다.
"예외를 결과에 포함시키지 않는다"는 것은 "실패하면 그냥 무시한다"는 뜻이 아니라, "실패하면 프로그램 전체를 멈춘다"는 의미이기 때문입니다.
[ 1. Variable & Constant ]
Variable
- var <변수명> <type> = <초기값>
- 타입 추론 <변수명> := <값>
Constant
- iota : 0번 부터 시작하는 상수 열거형 at (enum을 만들 거나 할 때)
[ 2. Datatype ]
String
- backQuote(’’)
- DoubleQuote(’’)에서만 /n 과 같은 문자열 해석이 가능
compile error가 아닌 runtime error가 발생 ( 오류 체크 할 때 주의 필요! )
Pointer
- &k - 주소 할당
- *p - 해당 주소의 실제 내용
[ 3. Collection ]
Array
- zero-base 초기화가 자동
- var <변수명> [크기]<type>
< 응용 : Slice >
- python처럼 sub slice 이용
- slice[start idx : end idx +1 ]
< 응용 : append 활용 >
Map
- 기본형 map [key_type] value_type
- 키 값 확인 key_val, exists := map["value"]
[ 4. 조건문 / 반복문 ]
if / else if / else
- ( )는 안 쓰지만 { }는 필수
- else if/else를 쓸 때는 반드시 전 조건의 마지막 ‘}’ 와 같은 라인에 써준다.
- 다중 조건 ‘;’ 로 가능
for (no while)
- 반복문에서 while이 없음
- ( )는 사용하지 않음
< 응용 : range >
- python처럼 range 사용 가능 (단 index까지 반드시 포함)
- 기본적으로 break 사용가능
⇒ 즉 index와 리스트에서 뽑은 데이터 총 2개의 값을 for range에서 이용
< 응용 : 중첩 루프 탈출 : break[Label] >
- label을 이용하여 for문을 건너뛰고 반복문 탈출 가능
- label은 for문 앞에 선언되며, 여러 개의 for문을 한번에 탈출 가능