-
2.14 면접 리마인드Python 2025. 2. 16. 22:08
14일인 지난주 금요일에 파이썬을 사용하는 회사 면접을 봤다.
개인적으로 미흡했던 답변들이 생각나서 정리해보는 시간을 가졌다.
마크다운 잘 쓰는지에 대한 질문도 하셔서 이번 포스팅은 마크다운 형식으로 작성했다.
옵시디언에서는 예쁘게 보이는데 티스토리로 옮기니 조금 당황스럽다.여하튼 나의 장점인 다방면적인 관심사, 커뮤니케이션 역량, 비전공자의 한계를 극복하기 위한 꾸준한 공부, 글쓰기에 대한 애정 등 장점을 많이 봐주셔서 정말 감사했고, 아래 질문들 외에는 편안한 마음으로 잘 답변했던 것 같다.
주로 Java/Kotlin + Spring 기반의 웹서비스 회사를 타겟팅해서 이직을 준비하다보니 Python 관련 지식들이 벌써 많이 풍화된 느낌이다.
도메인의 비전이나 팀 분위기, 또 내 장점과 잘 맞았던 컬쳐핏 등이 참 좋았어서 더 잘 정리해서 면접봤으면 좋았을텐데 하는 아쉬움이 크지만 즐거운 면접이었다.1. Asyncio 동작 원리
- 싱글 스레드 이벤트 루프를 기반으로 I/O 작업을 비동기적으로 처리합니다.
- 일반적인 동기 코드는 I/O 작업 대기 중에 다른 작업을 할 수 없지만, Asyncio 비동기 코드는 I/O 작업 대기 중에 다른 코루틴을 실행하여 CPU 사용 효율을 극대화할 수 있습니다.
- 사용 개념
Event Loop
- 코루틴간의 콘텍스트 전환을 제어하고, 태스크 큐, I/O 폴링, 콜백 큐로 구성되어 비동기 작업을 관리합니다.
- 현재 코루틴에서
await
키워드를 만나면 I/O 작업을 OS에 맡기고 다음 코루틴을 실행합니다.
Task
- 코루틴을 이벤트 루프에 등록하고 실행 상태를 관리하는 객체입니다.
asyncio.create_task()
로 생성할 수 있습니다.
- 코루틴을 이벤트 루프에 등록하고 실행 상태를 관리하는 객체입니다.
await
await
는 비동기 작업의 완료를 기다리며, 현재 코루틴의 실행을 일시 중단하고 다른 코루틴을 실행해도 된다는 신호입니다.
coroutine
- 이벤트 루프에 의해
await
키워드 시점에 실행을 중단했다가 I/O 작업이 완료된 시점에 다시 재개되는 함수로,async def
로 정의합니다.
- 이벤트 루프에 의해
2. I/O bound 작업 기준 'Asyncio'와 '멀티스레딩' 처리 속도 비교
- 두 방식 모두 I/O 작업 시간 동안 CPU를 유휴 상태로 두지 않아 작업 시간만 비교했을 때는 큰 차이가 없습니다.
- Asyncio는 이벤트 루프 기반으로 I/O 작업 대기 중 다른 코루틴을 실행하여 비동기적으로 작업을 수행할 수 있습니다.
- 멀티스레딩은 I/O 작업 중 GIL(Global Interpreter Lock)이 해제되어 스레드들의 작업이 병렬로 실행됩니다.
- 아래 코드로 네트워크 I/O를 테스트해본 결과 Asyncio는 약 0.049초, 멀티스레딩은 약 0.057초로 큰 차이가 나지 않았습니다.
import asyncio import aiohttp import time import threading import requests async def fetch_async(): url = "https://scan.luniverse.io/" async with aiohttp.ClientSession() as session: tasks = [session.get(url) for _ in range(10)] start = time.time() await asyncio.gather(*tasks) print("Async 작업 시간:", time.time() - start) def fetch_thread(): url = "https://scan.luniverse.io/" start = time.time() threads = [threading.Thread(target=lambda: requests.get(url)) for _ in range(10)] for t in threads: t.start() for t in threads: t.join() print("멀티스레딩 작업 시간:", time.time() - start) asyncio.run(fetch_async()) fetch_thread()
- 하지만 전체적인 오버헤드 측면에서 Asyncio가 동시성 처리에 더 효율적입니다.
- 멀티스레딩은 OS 레벨에서 스레드 전환이 이루어지므로 컨텍스트 전환 비용이 발생하며, GIL 경합 관리 등 스레드 동기화 비용이 추가됩니다.
- 반면, Asyncio는 하나의 이벤트 루프 안에서 보다 적은 비용으로 코루틴 간 전환이 이루어져 더 효율적으로 동시성을 제공합니다.
3. 매직메소드
- 특정 동작을 자동으로 호출할 때 사용됩니다.
- 연산자 오버로딩, 객체 생성/소멸, 컨테이너 동작, 형변환 등을 정의할 수 있습니다.
- 사용 예시
class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): # '+ 연산자' 오버로딩 # 두 벡터의 x, y 좌표를 더해 새로운 Vector 객체 반환 return Vector(self.x + other.x, self.y + other.y) def __repr__(self): # Vector 객체를 "Vector(x, y)" 형태의 문자열로 반환 return f"Vector({self.x}, {self.y})" v1 = Vector(2, 3) v2 = Vector(4, 5) print(v1 + v2) >>> Vector(6, 8)
4. 데코레이터
- 기존 함수나 클래스의 기능을 변경하거나 확장할 수 있는 함수입니다.
@
키워드를 사용함으로써 적용되며, 로깅, 성능 측정, 접근 제어 등 공통 기능을 캡슐화할 때 사용됩니다.- 사용 예시
def my_decorator(func): def wrapper(*args, **kwargs): print("함수 실행 전") func(*args, **kwargs) print("함수 실행 후") return wrapper @my_decorator def greet(name: str): print(f"안녕하세요, {name}님!") greet("람다256") >>> 함수 실행 전 안녕하세요, 람다256님! 함수 실행 후
5. 컨텍스트 매니저
with
문과 함께 사용되며, 파일, 네트워크 소켓, 데이터베이스 연결과 같이 사용 후 반드시 해제해야 하는 리소스의 획득과 해제를 안전하고 명확하게 관리할 수 있습니다.- 내부적으로 __enter__()와 __exit__() 두 '매직 메소드'를 통해 동작하며, with문이 시작될 때 __enter__()가 실행되고, with문의 코드 블록이 실행을 마치면 __exit__()이 실행됩니다. __exit__()은 예외 처리를 위한 매개변수를 가질 수도 있습니다.
- 사용 예시
class FileManager: def __init__(self, filename, mode): self.filename = filename self.mode = mode def __enter__(self): self.file = open(self.filename, self.mode) return self.file def __exit__(self, exc_type, exc_value, traceback): self.file.close() with FileManager('test.txt', 'w') as f: f.write('컨텍스트 매니저 테스트')
6. 최소힙 자료구조
- ‘완전 이진 트리' 구조로, 항상 부모 노드가 자식 노드보다 작은 값을 가지며, 루트 노드가 최소값을 가집니다.
- 시간복잡도
삽입
- 평균: O(log n)
대부분의 경우 몇 단계의 교환이 발생 - 최선: O(1)
삽입된 노드가 부모보다 큰 경우 아무 교환 없이 삽입
- 평균: O(log n)
삭제(최소값 삭제)
- 평균: O(log n)
대부분의 경우 기존 루트 삭제 후 루트 재설정 후 아래로 내려가며 재정렬 - 최선: O(1)
노드가 1개이거나 재정렬이 필요 없는 경우
- 평균: O(log n)
접근(최소값 확인)
- 모든 경우: O(1)
최소값은 항상 루트에 위치하므로 상수 시간에 접근
- 모든 경우: O(1)
힙 생성
- 모든 경우: O(n)
하향식(heapify) 방법을 사용하면 입력 배열의 상태와 상관없이 선형 시간에 생성
- 모든 경우: O(n)
- 시간복잡도
7. Python과 Java/Kotlin 차이점
- 세 언어 모두 사용해봤을 때 Python과 JVM 기반 언어의 가장 큰 차이점은 타입 강제성이었습니다. 또한, 숙련도도 큰 영향을 미쳤겠지만 Python의 개발 속도가 확실히 빨랐습니다.
- Python
- 동적 타이핑 인터프리터 언어로, 변수의 자료형이 런타임에 결정되어 개발이 빠르고 유연합니다.
- 컴파일 단계가 내부적으로 존재하지만 개발자가 직접 관리하거나 최적화할 수 없고, GIL(Global Interpreter Lock)로 인해 CPU bound 멀티스레드 작업에는 제약이 있어 안정성 성능의 측면에서 다소 부족합니다.
- 타입 오류는 실행 시에야 발견되므로, 개발 과정에서 발생하는 오류를 사전에 잡기 어렵습니다.
- Java/Kotlin
- 정적 타이핑 컴파일 언어로, 코드 작성 시 자료형을 명시하고 컴파일 단계에서 오류를 확인할 수 있어 안정성과 신뢰성이 높습니다.
- JVM 위에서 실행되므로 플랫폼 독립성을 보장하며, 멀티스레드 환경에서 효율적이고 빠른 성능을 제공합니다.
- 다만, 엄격한 타입 시스템과 보일러플레이트 코드로 인해 개발 속도와 유연성은 Python에 비해 다소 떨어집니다.