Python 미드레벨 종합 기술면접
새 면접Q. Python에서 __slots__를 사용하는 이유와 내부 동작 원리를 설명하고, 일반 클래스와 비교했을 때 메모리 사용량과 성능에 어떤 차이가 있는지 설명해주세요. 또한 __slots__를 사용할 때 제약사항과 실무에서 사용하기 적합한 경우를 설명해주세요.
Python의 일반 클래스는 인스턴스 속성을 __dict__ 딕셔너리에 저장하여 동적으로 속성 추가가 가능하지만, 메모리 오버헤드가 큽니다. __slots__를 선언하면 __dict__ 대신 고정된 크기의 배열에 속성을 저장하여 메모리를 약 40-50% 절감하고 속성 접근 속도도 향상됩니다. 단, __slots__ 사용 시 동적 속성 추가가 불가능하고, 다중 상속 시 복잡해지며, __weakref__를 명시적으로 추가해야 약한 참조가 가능합니다. 대량의 인스턴스를 생성하는 데이터 클래스나 DTO 객체에서 메모리 최적화가 필요할 때 유용합니다.
- • __dict__ 대신 고정 크기 배열로 속성 저장하여 메모리 절감
- • 동적 속성 추가 불가능, 약한 참조 시 __weakref__ 명시 필요
- • 대량 인스턴스 생성 시나리오에서 메모리 최적화에 유용
dataclass와 __slots__를 함께 사용할 때 어떻게 구현하며, 성능상 이점이 있는지 설명해주세요.
Q. Python의 asyncio 이벤트 루프가 내부적으로 어떻게 동작하는지 설명하고, run_until_complete()와 run_forever()의 차이점을 설명해주세요. 또한 이벤트 루프가 실행 중일 때 동기 함수에서 비동기 함수를 호출하려고 하면 발생하는 문제와 이를 해결하는 방법을 설명해주세요.
asyncio 이벤트 루프는 단일 스레드에서 Task 큐를 관리하며, 각 코루틴이 await를 만나면 제어권을 반환하고 다음 Task를 실행하는 협력적 멀티태스킹 방식으로 동작합니다. run_until_complete()는 특정 코루틴이 완료될 때까지 블로킹하며 결과를 반환하지만, run_forever()는 명시적으로 stop()을 호출할 때까지 계속 실행됩니다. 이미 실행 중인 이벤트 루프에서 동기 함수가 비동기 함수를 호출하면 'RuntimeError: This event loop is already running' 에러가 발생하며, 이는 asyncio.create_task()로 Task를 스케줄링하거나, 별도 스레드에서 asyncio.run()을 실행하거나, nest_asyncio 라이브러리를 사용하여 해결할 수 있습니다.
- • 이벤트 루프는 협력적 멀티태스킹으로 Task 큐를 관리
- • run_until_complete()는 완료 대기, run_forever()는 명시적 중지 필요
- • 실행 중인 루프에서 비동기 호출 시 create_task() 또는 별도 스레드 활용
asyncio.run()이 내부적으로 새로운 이벤트 루프를 생성하고 정리하는 과정을 설명하고, 왜 여러 번 호출할 수 없는지 설명해주세요.
Q. 데이터베이스에서 N+1 쿼리 문제가 무엇인지 설명하고, 이로 인해 발생하는 성능 문제를 실무 예시와 함께 설명해주세요. 또한 Python ORM(Django, SQLAlchemy 등)에서 이 문제를 해결하기 위한 여러 방법과 각 방법의 장단점을 비교 설명해주세요.
N+1 쿼리 문제는 부모 레코드 N개를 조회한 후, 각 레코드의 연관 데이터를 가져오기 위해 N번의 추가 쿼리가 발생하는 현상으로, 예를 들어 100개의 게시글과 각 작성자 정보를 조회하면 1(게시글) + 100(작성자) = 101번의 쿼리가 실행됩니다. 해결 방법으로는 JOIN을 사용한 Eager Loading(select_related, joinedload), 별도 쿼리로 한 번에 가져오는 Subquery Loading(prefetch_related, selectinload), 또는 DataLoader 패턴을 사용할 수 있습니다. JOIN 방식은 쿼리 수를 1개로 줄이지만 중복 데이터 전송이 발생하고, Subquery 방식은 쿼리 2개로 중복 없이 처리하지만 관계가 복잡하면 비효율적일 수 있습니다.
- • N+1 문제는 연관 데이터 조회 시 N번의 추가 쿼리 발생
- • JOIN 기반 Eager Loading은 쿼리 1개지만 중복 데이터 전송
- • Subquery Loading은 쿼리 2개로 중복 없이 처리
GraphQL API에서 N+1 문제가 더 심각하게 발생하는 이유와 DataLoader 패턴으로 이를 해결하는 원리를 설명해주세요.
Q. Python에서 팩토리 패턴(Factory Pattern)과 추상 팩토리 패턴(Abstract Factory Pattern)의 차이를 설명하고, 각각의 사용 사례를 실무 예시와 함께 제시해주세요. 또한 Python의 동적 타이핑 특성 때문에 다른 언어와 달리 구현 시 달라지는 점이 있다면 설명해주세요.
팩토리 패턴은 단일 팩토리 메서드나 클래스가 파라미터에 따라 다른 타입의 객체를 생성하는 패턴으로, 예를 들어 파일 확장자에 따라 ImageParser, VideoParser를 반환하는 MediaParserFactory가 있습니다. 추상 팩토리 패턴은 연관된 객체군을 생성하는 여러 팩토리를 추상화한 것으로, UI 테마에 따라 WindowsButton/MacButton, WindowsCheckbox/MacCheckbox를 함께 생성하는 GUIFactory처럼 일관된 제품군을 보장합니다. Python에서는 덕 타이핑으로 인터페이스 구현이 유연하고, 클래스 자체가 일급 객체이므로 딕셔너리에 클래스를 저장하여 간단히 팩토리를 구현할 수 있으며, ABC 모듈로 명시적 추상화도 가능합니다.
- • 팩토리 패턴은 파라미터 기반 단일 객체 생성, 추상 팩토리는 연관 객체군 생성
- • Python은 클래스가 일급 객체라 딕셔너리 기반 팩토리 구현 가능
- • 덕 타이핑으로 유연하지만 ABC로 명시적 계약 정의 가능
Python에서 팩토리 패턴 대신 functools.singledispatch를 사용하여 타입 기반 객체 생성을 구현하는 방법과 장단점을 설명해주세요.
Q. Python 코드의 성능을 프로파일링할 때 cProfile, line_profiler, memory_profiler의 차이점과 각각의 사용 시나리오를 설명해주세요. 또한 실무에서 API 응답 속도가 느려졌을 때 병목 지점을 찾아내는 체계적인 접근 방법과 사용할 도구들을 단계별로 설명해주세요.
cProfile은 함수 단위로 호출 횟수와 누적 실행 시간을 측정하는 표준 프로파일러로 전체적인 핫스팟을 찾는 데 적합하며, line_profiler는 특정 함수의 각 라인별 실행 시간을 측정하여 함수 내부 병목을 정밀 분석할 때 사용하고, memory_profiler는 라인별 메모리 사용량을 추적하여 메모리 누수나 과도한 메모리 할당을 찾을 때 유용합니다. API 성능 문제 해결 시에는 먼저 APM 도구(New Relic, DataDog)로 전체 트랜잭션을 모니터링하고, 느린 엔드포인트를 식별한 후 cProfile로 함수별 시간을 측정하며, DB 쿼리는 django-debug-toolbar나 SQL 로깅으로 분석하고, 특정 함수는 line_profiler로 상세 분석하며, 외부 API 호출은 requests의 타임아웃과 응답 시간을 로깅하여 단계적으로 원인을 좁혀갑니다.
- • cProfile은 함수별 전체 시간, line_profiler는 라인별 상세, memory_profiler는 메모리 추적
- • APM → cProfile → 쿼리 분석 → line_profiler 순으로 단계적 접근
- • DB 쿼리, 외부 API, 알고리즘 복잡도 등 다각도로 병목 분석
Python에서 Py-Spy나 Austin 같은 샘플링 프로파일러가 cProfile과 어떻게 다르며, 프로덕션 환경에서 더 유용한 이유를 설명해주세요.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!