SW 마에스트로 16기 연수생 모집이 시작되었습니다!

 

저는 15기 연수생 출신으로, 합격 직후 당시에 적었던 글에

조금의 후기를 섞어 경험을 공유하고자 합니다.

16기와 그 이후를 위해서 + 마친 후 추가한 후기

제 이야기는 사실 소마를 준비하는 분들께 크게 도움이 되지 않을 것 같다고 생각합니다.

 

일반적으로 SW마에스트로는 컴퓨터 관련 전공 학부생들이 선택하는 활동인데,

 

저는 둘 다 해당되지 않는 비전공, 그것도 공대도 아닌 완전 문과에 22년도에 졸업한 취준생입니다.

 

SW 마에스트로를 염두에 두면서 나와 비슷한 처지에 있는 사람은 거의 없을 것이라 생각합니다.

그래서 합격까지의 저만의 경험을 풀어놓기 보다는,
저라는 반례를 통해 SoMa에선 어떤 사람을 뽑는지 더욱 명확히 파악해보고자 합니다!

 

SoMa는 정말 조건을 거의 안 봅니다.

 

저는 교육 경험을 제외하면, 소위 말하는 스펙 같은 게 거의 없습니다.

 

필자의 스펙을 나열하자면

  1. 지방 4년제 사립대 국어국문학과 졸업, 학점 : 3.2 / 4.5
  2. 졸업 직후 4개월 간 빅데이터/AI 관련 부트캠프 수강
  3. 삼성 청년 SW 아카데미(SSAFY) 8기 입과, 프로젝트 3회 중 우수상 2개 및 대외 공모전 상장 1개 획득
  4. SSAFY 9기 실습코치로 채용되어, 4개월 간 근무
  5. 어학 : 삼성 계열 입사 지원을 위한 OPIc 단 1개 (심지어 IL)
  6. 자격증 : 지원 당시 전무했음
  7. 백준 티어 : 골드 4

보통 지원서에 적히게 될 주요 항목들에 대한 스펙들입니다.

위의 사항들 중에 긍정적으로 작용할 항목은 2, 3, 4 밖에 없는 것이죠.

좀 더 상세히 풀어봅시다.

 

학력을 진짜 진짜 안 본다.

저는 지방 사립 대학의 국어국문학과 출신입니다.

 

부산이나 경남 지방 사람이 아닌 이상, 이름도 생소한 편인 그저 그런 대학교입니다.

 

그런 학교에다가 국어국문학과 출신에, 학점도 3.2 / 4.5입니다!

 

아마 이 정도면, 정말로 학력을 아예 안 본다고 봐도 말해도 무방하지 않을까요?

그럼에도 당연히 유의미한 활동과 프로젝트는 필요합니다.

대외 활동과 프로젝트는 매우 중요한 비중을 차지하는 것으로 보입니다.
애초에 대부분의 질문 자체가 프로젝트를 기반으로 진행됩니다.

 

프로젝트의 경우, 토이 프로젝트 한 개라도 진행해본 적 없는 사람은 정말 아무도 없는 느낌입니다.

 

대부분 1인 토이 프로젝트라 해도 무언가 결과물이 나왔다 싶은 프로젝트 한 개 이상은 있는 편입니다.

 

저는 SSAFY에서 진행한 프로젝트 3개 중, 1개에 대해 매우 깊게 설명하는 식이었습니다.

실제로 해당 프로젝트 아이템을 멘토님들과 공유할 때, "당장 사업 시작해도 괜찮겠는데?"라는 평가를 받기도 했습니다 후후

 

프로젝트가 기깔나면 당연히 그것만으로 충분하겠지만,

 

그 외에 프로그래밍과 관련된 활동과 경험들도 다들 여러개씩 있는 편이었습니다.

 

스터디, 공모전, 경진 대회 참여, 프로그래밍 관련 행사 참여, 오픈소스 기여, 근로, 봉사활동 등등...

 

매우 다양하고, 크고 작음에 상관없이 프로그래밍에 대한 경험, 관심과 연관지을 수만 있으면 될 것 같습니다.

 

저는 두 개의 교육과 수상 내역 정도를 적었는데, 면접에서 이와 관련해 공격(?)을 받았습니다.

 

왜 주도적으로 뭔가를 한 게 없냐고...

 

주도적인 토이 프로젝트랑 스터디를 다 적으면 포트폴리오가 터져버리기 때문에 적게 적은 것이긴 하지만....

 

작은 것들도 괜찮으니 적어줍시다!

 

알고리즘을 정말 잘하는 것도 방법입니다.

백준 다이아 정도 되면, 프로젝트와 활동이 비교적 부족한 편이라 해도 거의 붙지 않을까 싶습니다.

 

애초에 알고리즘을 그렇게 팠는데 개발에 관심이 없을리가 없죠....

 

면접에서도 그러한 분이 계셨기 때문에, 대부분의 시간을 알고리즘과 관련하여 질답을 진행했습니다.

 

전형에 대해서

 

서류는 떨어지는 사람을 못 본 것 같습니다.

칸만 채우면 일단 합격하는 모양입니다만, 그렇다고 대충 쓰는 사람은 없겠죠?

 

어쨌든 일단 코딩 테스트를 뚫어야 합니다.

SW 마에스트로의 1차, 2차 코딩 테스트는 알고리즘 4문제 + SQL 1문제 세트로 동일하게 출제된다.

 

코딩 테스트에 자체에 대해 궁금하다면,

 

본 포스팅 말고 다른 분들의 블로그를 확인해주십사 합니다.

 

위에서 밝혔듯, 저의 알고리즘 실력은 절대 높은 편이 아닙니다.

 

저는 특별히 준비한 것은 없었고, SQL 문제는 무조건 풀어야 합격!이라는 글을 봐서,

 

프로그래머스에서 SQL 문제를 조금 푼 게 끝이었습니다.

 

그냥 평소 실력대로 봤다.

 

즉, 백준 골딱이라 해도 운이 좋으면 합격할 수 있습니다!

 

1차 코딩 테스트

알고리즘 1번, 2번, 3번 + SQL 해결로 총 4문제 해결했습니다.

 

알고리즘 1 ~ 3번 문제의 난이도는 백준 기준 브론즈+실버 수준의 문제들이었습니다.

 

4번은 골드5, 4정도가 아니었나 싶습니다.

 

SQL은 매우 기본적인 수준의 문제였습니다.

 

나름 정보를 얻어보려 SoMa 준비 오카방에 들어가봤는데, 대부분이 3솔 이상이었다고 합니다.

 

다만, 코딩 테스트를 처음 쳐봐서 테스트 케이스만 맞으면 맞춘 거라 생각하는 분들이 많았던 것 같았습니다.

 

1차 코테의 합격 컷은 기억이 안 나지만, 최소 2솔 이상은 확실할 것 같습니다.

 

2차 코딩 테스트

꽤 어려웠습니다.

 

다른 블로그의 회고 글들이나, 오카방에서도 문제가 꽤 어려운 편이었다고 합니다

 

실제 투표에서도 2솔이 가장 많았던 것으로 기억합니다.

 

저는 알고리즘 1번과 3번, 총 2문제 해결했습니다.

 

나머지 문제는 사실상 손도 대지 못했고, SQL도 상당히 어려웠습니다...

 

3솔 이상은 사실상 무조건 합격한 것 같고,

 

2솔은 내부에서 어떻게 어떻게 점수를 매겨서 면접 대상자를 추리지 않았을까 싶습니다.

 

여기서 면접 대상자가 골라지는데, 그럼 무슨 기준이지?

기억상 2차 코딩 테스트는 최종 합격자의 3 ~ 4배수 정도가 치른 것으로 예상됩니다.

 

면접 대상자는 아마 300 ~ 330명 사이였을 것으로 보입니다.

 

15기 최종 합격자는 정확히 200명이었습니다.

 

2차 코테를 600~800명 정도는 응시를 한 셈이죠.

 

 

SoMa 선발은 서류 전형 발표 이후부터 매우매우 빠르게 진행됩니다.

 

1차 코테 결과가 영업일 기준 사흘 후에 나오고, 바로 그 주에 또 시험입니다.

 

2차 코테 결과 발표 후 면접까지는 시간이 좀 있으나,

 

2차 코테 시험과 결과 발표도 1차 코테와 마찬가지로 사흘만에 나옵니다.

 

코딩 테스트의 합격컷에 걸려있는 여러 사람들을 대상으로 서류 기반 정성평가를 할 수야 있겠지만,

사실상 그럴 일 없고, 반드시 수치화 가능한 정량평가로 판가름할 것이라 생각이 들었습니다.

 

아마 2솔한 사람들이 합격 컷에 걸렸으리라 생각하는데,

 

코드의 실행 시간이나 해결 및 제출까지 걸린 시간을 기반으로 점수를 매겼을 것으로 예상됩니다.

 

다행히 저는 어떻게든 합격 기준을 맞춘 것으로 보입니다.

 

당연하지만, 채점 기준은 알려주지 않습니다.

 

면접에서도 마찬가지입니다.

 

학력 사항이나 나이, 자격증, 수상 내역 같은, 소위 스펙은 크게 영향을 주지 않습니다.

 

정말로 긴장 덜하고 그냥 질문에 대답 잘 하는 분들이 붙습니다.

 

다만, 공공기관이나 은행권처럼 블라인드로 진행하진 않는 것 같습니다.

 

면접에서 위의 내용으로 지적을 받았다는 사람은 못 봤습니다. (물론 경우에 따라 분명 다를 수 있습니다.)

 

그러나 명확한 활동 없는 긴 공백 기간이 있다면, 그건 확실히 감점으로 작용할 것입니다.

 

쉬는 동안 뭘 했는지 잘 정리하는 것도 중요하다 봅니다.

 

어쨌든 면접에서 중요한 건,

  1. 내가 소마를 왜 하고 싶은지와
  2. 프로그래밍과 관련된 활동은 어떤 것을 했으며
  3. 프로젝트는 어떻게 진행을 했고
  4. 관련된 CS 지식은 얼마나 있는지
  5. 다른 일을 하다 왔다면, 대충 뭘 했었는지

입니다. 질문의 범위는 위의 범위를 거의 벗어나지 않습니다.

 

면접에서 오간 질문을 발설하는 것은 여러 문제를 야기하고,

 

애초에 면접관과 면접자마다 질문과 방식이 전혀 다를 수밖에 없기 때문에

 

상세한 질문은 말씀드리기 힘들 것 같습니다.

 

그저 평소에 프로그래밍과 개발에 진심으로 열정을 쏟고 있었으면 무조건 된다, 라고 말씀드리고 싶습니다.

 

그리고 면접관들은 여러분들을 떨어뜨리려고 하는 것이 아니라, 정말로 어떤 사람일지 궁금하기 때문에 질문하는 것입니다.

 

뛰어난 성과와 실력도 물론 중요하겠지만, "정말로 무언가 이뤄내고 싶어서 소마를 지원했구나"가 보여야 합니다.

 

후기

 

합격자들의 스펙을 보니 가슴이 지나치게 웅장해졌습니다.

 

내가 여기 끼어있어도 되는가 싶다는 생각도 많이 들었습니다.

 

그래도 너무 대단한 사람들이 많아서 내가 정말 많이 배울 수 있는 기회가 되겠다 싶었습니다.

 

다만, 솔직히 졸업 후 2년 동안 프로그래밍 교육과 공부를 해오고,

 

또 1년 거기서 연장하는 것에 걱정이 많았습니다.

 

결과가 나온 직후 3일 정도는 포기 각서를 쓸까 많이 고민했으나,

 

싸피 사람들과 컨설턴트님들과 진지하게 이야기를 주고 받았습니다.

 

그러다가 하나 둘 씩 올라오는 합격자들의 자기소개서를 보면서,

 

와, 이런 사람들도 이렇게 죽자살자 소마에 달려드는데
고작 내 수준 되는 사람이 취업하겠다고 나가는 게 말이되나? 싶었습니다.

 

어차피 취업도 다같이 안 되는데(흑흑),

 

소마에서 역량을 극한까지 끌어올리기로 결심했습니다.

 

진짜 쏘마 후기 (2025년 2월 1일 추가)

소마는 싸피의 코치 활동과 더불어, 제 인생에 가장 값진 경험 중 하나였습니다.

 

멋진 멘토님과 좋은 동료들을 만났고, 제 개발에 대한 지평이 훨씬 넓어진 경험이었습니다.

 

훌륭한 사람들이 정말 많은 곳입니다. 사무국분들도 연수생을 위해 불철주야 부단히 노력하십니다.

 

개발과 관련된 경험 중, 최고 수준의 경험을 쌓을 수 있는 곳입니다.

 

소마를 생각하고 계시다면, 무조건 지원해보시고,

 

소마에 입과하게 된다면 최대한 많은 것들을 얻어가셨으면 합니다!

 

https://www.youtube.com/watch?v=5waF5p-XS4Q&t=249s

여기에 저 나옵니다!

가장 존경하는 개발자는 누구예요?

개발자들 간의 대화에서 종종 나오는 질문 중 하나가

"가장 존경하는 개발자는 누구예요?"

입니다.

 

백엔드 개발자라면 로버트 C. 마틴(클린 코드), 마틴 파울러(리팩토링), 켄트 백(TDD)처럼

 

소프트웨어 개발 패러다임을 정립한 인물들을 꼽는 경우가 많습니다.

 

귀도 반 로섬(Python), 데니스 리치(C), 제임스 고슬링(Java)처럼 프로그래밍 언어를 창시한 인물들도 있으며,

 

컴퓨터 과학의 기초를 닦은 에츠허르 다익스트라,

 

오픈 소스 운동을 주도한 리처드 스톨먼과 리누스 토르발즈,

 

또한 혁신의 아이콘인 스티브 잡스를 존경하는 사람도 많습니다.

 

하지만 개인적으로 가장 존경하는 개발자는 브라이언 W. 커니핸(Brian W. Kernighan)입니다.

브라이언 W. 커니핸?

 

브라이언 커니핸은 1942년 캐나다에서 태어나, 토론토 대학에서 물리학 학사 학위를,

 

프린스턴 대학교에서 전기공학 박사 학위를 받았습니다.

 

말년(이자 현재 진행 중)에는 모교인 프린스턴 대학교에서 컴퓨터 과학과 교수로 재직 중입니다.

 

가장 두드러진 업적은 벨 연구소에서 근무하며 UNIX의 개발에 참여한 것입니다.

 

초기 Unix 커널 설계에는 어셈블리어가 사용되었는데, 플랫폼 종속성이라는 문제로 커널 설계 언어를 만들 필요가 있었습니다.

 

켄 톰슨(좌), 데니스 리치(우)

 

데니스 리치는 같은 팀의 켄 톰슨이 만든 B언어를 모태로 C언어를 만들었고,

 

커니핸은 C언어를 직접 설계하지는 않았지만, UNIX와 C언어의 대중화에 큰 역할을 했습니다.

 

특히 데니스 리치와 함께 집필한 "The C Programming Language"(K&R C)는 오늘날까지도 C언어 학습의 표준 교재로 활용되고 있습니다.

 

이외에도 "The Unix Programming Environment", "The Elements of Programming Style" 등의 저서를 통해 UNIX와 프로그래밍 스타일에 대해 널리 전파하기도 했습니다.

 

또한 커니핸-린 알고리즘(Kernighan–Lin algorithm), 린-커니핸 휴리스틱(Lin–Kernighan heuristic) 등도 만들었으며, AWK 언어와 AMPL의 개발에도 참여했습니다.

패러다임의 관찰자와 기록자

브라이언 커니핸은 UNIX와 C언어의 설계자가 아니라, 이를 기록하고 대중화한 인물입니다.

 

그는 새로운 패러다임을 창조하기보다는, 그것을 깊이 이해하고 체계적으로 설명하는 데 집중했습니다.

 

그의 저서들은 단순한 기술 설명을 넘어서, 독자가 스스로 사고하고 판단하도록 유도하는 편입니다.

 

단순히 "이 방법이 좋다"라고 설명하는 것이 아니라,

 

"이런 이유로 이 방법이 더 낫다"라는 방식으로 독자의 사고력을 확장시켜줍니다.

 

이는 그가 오랜 기간 교수로 활동하며 얻은 교육 철학이 반영된 것이 아닐까 생각합니다.

 

또한, 그는 UNIX와 C언어가 널리 퍼지는 데 중요한 역할을 했습니다.

 

새로운 기술이 아무리 뛰어나도 대중화되지 않으면 널리 사용될 수 없습니다.

 

커니핸은 UNIX와 C를 알리는 데 누구보다 적극적이었고, 이를 통해 소프트웨어 업계에 큰 영향을 미쳤다고 할 수 있겠습니다.

그래서 좋아하는 이유

브라이언 커니핸은 하나의 패러다임을 창조한 사람은 아닙니다.

 

그러나 새로운 패러다임을 깊이 이해하고, 이를 체계적으로 정리하여 전파하는 데 있어 독보적인 역할을 해냈습니다.

 

커니핸의 저서들은 단순한 프로그래밍 지침을 넘어서, 좋은 코드와 나쁜 코드의 차이를 이해하는 데 큰 도움을 줍니다.

 

추천 도서

 

한국에도 그의 다양한 저서가 번역되어 들어오고 있습니다.

 

물론 기술을 본격적으로 소개하는 저서들은 개발을 시작하는 비기너들이 읽기에는 조금 어려울 수 있습니다.

 

하지만 필체가 대단히 친절하고, 쉬우며, 다양한 케이스로 설명해주기 때문에

 

개발이 어느정도 익숙해진 초보자 정도라면 충분히 읽을 수 있습니다.

 

다음은 추천하는 저서들입니다.

 

https://product.kyobobook.co.kr/detail/S000001469800

 

프로그래밍 수련법 | 브라이언 W. 커니핸 - 교보문고

프로그래밍 수련법 |

product.kyobobook.co.kr

"프로그래밍 수련법(The Practice of Programming, 2008, 롭 파이크 공동저서)"

 

개발자라면 공통적으로 지키는 "관례""좋은 스타일"에 대해 매우 친절하게 알려줍니다.

 

난이도는 5점 만점에 2점 정도 된다고 생각합니다.

 

대부분의 코드가 C 기반이긴 하나, Python, Java 정도만 어느 정도 쓸 수 있어도 읽는 데에 전혀 문제는 없습니다.

 

 

https://product.kyobobook.co.kr/detail/S000001033125

 

1일 1로그 100일 완성 IT 지식 | 브라이언 W. 커니핸 - 교보문고

1일 1로그 100일 완성 IT 지식 | 복잡한 IT 세상을 선명하게 읽는 디지털 문해력 기르기 챌린지IT 지식은 분명 복잡하지만 인생처럼 혼잡하지는 않다. 필요한 지식을 습득하면 막연한 불안감에서 벗

product.kyobobook.co.kr

 

"1일 1로그 100일 완성 IT 지식(Understanding the Digital World, 2021)"

 

CS 지식과 개발과 관련된 일화들을 소개하는 (조금 난이도 있는) 교양 서적으로, 가볍게 읽기 좋습니다.

 

난이도는 5점 만점에 1.5점 정도 된다고 느꼈습니다.

 

https://product.kyobobook.co.kr/detail/S000001810293

 

유닉스의 탄생 | 브라이언 커니핸 - 교보문고

유닉스의 탄생 | 브라이언 커니핸이 들려주는 UNIX의 탄생과 발전 과정, 천재 개발자와 기여자들의 이야기이 책은 유닉스의 역사를 기록한 책이자 유닉스 개발 현장에 있던 이들의 회고록이다.

product.kyobobook.co.kr

 

"유닉스의 탄생(UNIX: A History and a Memoir, 2020)"

 

말 그대로 벨 연구소 시절 유닉스 개발 팀에서 겪었던 일들을 소개하는 책입니다.

 

UNIX에 대한 기본적인 이해는 물론, 이러한 "개발 비화"들을 좋아하는 사람들이 읽기 좋은 책입니다.

 

 

https://product.kyobobook.co.kr/detail/S000001533029

 

C언어 프로그래밍 | Brian W. Kernighan - 교보문고

C언어 프로그래밍 | ▶ 이 책은 C언어 프로그래밍에 대해 다룬 도서입니다. C언어 프로그래밍의 기초적이고 전반적인 내용을 학습할 수 있도록 구성했습니다.

product.kyobobook.co.kr

 

사실 K&R은 현대의 개발 입문서들에 비하면 상당히 어려운 편입니다.

 

기술을 직접적으로 알려주는 게 아니라, 이렇게 하면 안 된다. 이런 게 좋다. 식으로 설명하기 때문에,

 

입문서를 겸하는 고급 서적에 가깝다고 생각하는 편입니다.

'프로그래밍 트리비아' 카테고리의 다른 글

Python은 근본 웃음벨이다  (1) 2025.01.26
C언어는 Computer 언어가 아님  (1) 2025.01.26

세미콜론없다고ㅋㅋㅋㅋㅋㅋ



프로그래밍의 역사에서 가끔 천재들이 심심해서 나온 것들이 있습니다.

 

Nvidia를 향해 엿을 날리는 리누스 토발즈

 

이 아저씨가 만든 linuxGit이나 오늘 이야기할

프로그래밍 언어의 근본 웃음벨 Python 입니다.

진짜 파이썬 맞습니다

 

Python은 현재 2018년 은퇴를 번복하고 MS에서 Python 개발을 맡고 있는 귀도 반 로섬에 의해 창시되었습니다.

 

귀도 반 로섬(Guido van rossum, 1956~, 네덜란드)

 

대학시절부터 엘리트였던 귀도는 암스테르담 대학교에서 수학과 컴퓨터 과학을 공부하였으며

 

졸업 후, 네덜란드 국립수학정보학연구소(CWI, Centrum Wiskunde & Informatica)에서 근무했습니다.

 

귀도는 1989년 크리스마스 휴가를 보내던 중, 심심해서 Python을 만들기 시작했습니다.

 

그리고 2년 후인 1991년, Python 1.0이 탄생했습니다.

파이썬이 탄생하는 역사적 광경입니다

 

취미로 만든 언어다보니, 언어 자체에 많은 유머가 함축되어 있습니다.

 

그 중 가장 대표적인 건 Python이란 이름 그 자체입니다.

 

Python의 로고는 비단뱀(학명: Pythonidae, 영명 : Python)을 형상화해서 만들었습니다.

 

근데 얘가 비단뱀이 맞긴 한가?

 

대체 왜 프로그래밍 언어가 뱀으로부터 시작했지? 싶지만, 개발자가 이름 대충 짓는 건 예삿일이니 그런갑다 하겠죠.

 

물론 여느 프로그래밍 관련 이름들이 그렇듯, 비단뱀은 후에 짜맞춘 것일 뿐입니다.

 

진짜 이름의 유래는

 

 

영국의 6인조 코미디 그룹 몬티 파이선(발음 상 파에톤, 파이튼 등으로도 불립니다)으로부터 유래했습니다.

 

유명한 작품으로는 전설적인 코미디 영화 몬티 파이선의 성배가 있습니다.

 

진짜 50년 지난 지금 봐도 꿀잼입니다

 

즉, Python이라는 작명부터가 근본 웃음벨인 것이죠.

 

그렇기에 python 커뮤니티나 근-본을 따르는 예제에서는 몬티 파이선의 스케치에서 등장하는 소재들이 자주 등장합니다.

 

여타 대부분의 언어나 커뮤니티, 예제에서 foo, bar가 쓰이는 것과는 대조되게

 

spam, egg, Knights who say Ni(니라고 말하는 기사) 등이 쓰이는 것을 자주 볼 수 있습니다.

유명한 스캣 중 하나인 스팸

 

니!

 

(사실 foo, bar도 고치지 못할 정도로 망했다는 뜻인 FUBAR(F**ked Up Beyond All Recognition)에서 유래했다는 설이 있긴 합니다...)

왜 만들어졌을까?

사실 심심해서는 조금 과장된 얘기고 CWI에서 근무하던 시절,

 

ABC 언어 프로젝트에 참여하며, 확장성과 실용성에서 부족함을 느꼈다고 합니다.

 

이를 보완함과 동시에, 보다 쉽고 재미있는 프로그래밍 경험을 줄 수 있는 스크립트 언어를 개발하고자 마음먹은 것이죠.

 

당시에는 비슷한 목적을 가지고 개발된 Perl도 있었지만,

 

두 언어의 설계 철학을 보면 아무래도 귀도는 Perl의 방식이 그다지 맘에 들진 않았던 모양입니다.

진주 vs 비단뱀

한국은 물론이고 전 세계적으로도 사실상 Python이 압도적으로 많이 쓰이고 있으나,

 

둘 다 이것저것 다 할 수 있는 만능 스크립트 언어라는 점에서 항상 논쟁의 주제가 되곤 합니다.

 

앞서 귀도는 perl의 방식이 맘에 들지 않았다고 했었는데, 두 언어의 설계 철학을 먼저 봐야 합니다.

 

펄의 철학은 다음과 같습니다.

어떠한 일에는 여러 가지 방법이 존재한다.
There is more than one way to do it.

 

그에 반해 파이썬은 다음과 같은 원칙을 중요하게 생각합니다.

명확한, 그리고 가급적이면 유일하면서 명백한 방법이 있을 것이다.
There should be one-- and preferably only one --obvious way to do it.

 

여기서 "유일하면서 명백한" 방법이란, 무조건적인 하나의 답이 있으며 그 외에는 오답이다라는 의미는 아닙니다.

 

개발자의 의도가 분명하게 드러나며 파이썬 코드 컨벤션에 맞는(당연히 비효율적이지도 않은) 코드를 지향하라는 의미입니다.

 

물론, 앞서 언급했듯 귀도는 어디까지나 ABC 언어에서 Python을 만들겠다는 영감을 얻었습니다.

 

Perl은 그저 같은 시대에 태어난 범부일 뿐입니다.

 

더 자세하고 많은 Python 철학은 PEP 20 문서에서 찾을 수 있습니다.

PEP?

import this

위 코드를 통해서 Python의 설계 철학인 PEP 20 문서의 The Zen of Python을 출력시킬 수 있습니다.

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

 

이러한 PEP는 Python Enhancement Proposal의 약자로,

 

직역 그대로 Python에 대한 개선 제안서입니다.

 

python의 발전과 개선을 위해 새로운 기능을 추가하거나,

 

정보를 제공하고 표준을 정의하는 데에 쓰인 토론장 및 보고서라고 할 수 있습니다.

 

또한 공식 레퍼런스로도 쓰이죠.

 

앞서 설명한 The Zen of PythonPEP의 20번째 문서고,

 

VScode로 Python 코드를 작성하는 사람들에겐 자동으로 깔리는 autopep8이라는 포매터 익스텐션 역시

 

파이썬의 코드 스타일 가이드라인인 PEP-8을 기반으로 만들어진 것입니다.

 

PEP는 파이썬 생태계에 매우 중요한 역할입니다.

결국 쓰기 쉽고 재밌는 거

위의 The Zen of Python는 언뜻보면 대단한 의미를 가진 선언처럼 보이지만,

 

프로그래밍을 조금만 알아도 '당연한 말 아닌가?'싶은 부분들을 말하고 있습니다.

 

그러면서 지키기 어려운, 마치 클린코드나 초등학교 도덕과 같은 지침들을 말하고 있습니다.

 

이러한 지침은 상당히 모호해서 사실 시작할 때부터 반드시 지켜야지 하면서 지킬 수 있는 것은 아닙니다.

 

무엇보다 파이썬은 애초에 선수 지식이 필요 없는 개발 언어입니다.

 

국문과 출신인 저조차도 일주일동안 입문서 하나 읽고 로또 번호 생성기와 추첨기를 만들 정도였으니까요.

 

'재미' 그 자체를 추구했기에, 언어 자체도 근본 웃음벨이며, 이를 사용해 코딩하는 경험 또한 재미있습니다.

 

사실 파이썬만 하는 것은 쉽지만, 이외의 언어를 접하는 건 상당히 어려워집니다.

 

대부분 언어는 '모든 것이 객체는 아닌' 언어이며,

 

또한 '들여쓰기는 단지 가독성을 위한' 언어이고,

 

'중괄호와 세미콜론이 필수적인' 언어 이기 때문이죠.

 

이외에도 개발자의 편의를 위해 여타 언어들(심지어 같은 부모가 같은 C언어 기반 계열도)과 다른 문법이 매우 많습니다.

 

이 때문에 파이썬은 C계열, JAVA 개발자들에게 웃음벨로 통하곤 합니다.

 

제가 C++을 배우지 않는 이유입니다.

 

Python 개발자는 땀흘릴 일이 없기 때문이죠

 

그럼에도 파이썬은 현재 매우 사랑받은 언어임이 틀림없습니다.

 

작고(사실 겁나 큼) 소중한 우리의 언어, 파이썬이 꼭 사랑받았으면 합니다.

 

 

Computer 언어 라서 C 언어 아님?

 

보통 프로그래밍을 배운 적 없는 사람들(또는 이런 잡다한 것에 관심없는 사람들)은 흔히 위와 같이 생각합니다.

 

학위라곤 문학사 학위 밖에 없는 저 또한 처음엔 그렇게 생각했습니다.

 

그리고 이건 정말 프로그래밍을 꽤 배운 사람들도 모르는 경우가 많은데,

 

C언어는 최초의 고급 프로그래밍 언어 또한 아닙니다.

고급 언어는 저급 언어(사실 상 기계어와 어셈블리 둘 뿐)를 추상화하여 쓰고 읽기 쉽게 만든 언어입니다.

 

C언어는 1972년 발표되었고, 이보다 15년도 전에 사실상 최초의 고급 언어라고 불리는 Fortran이 발표되었습니다.

현대적 프로그래밍 언어와 문법적 괴리가 상당히 큽니다.

 

엄밀히 말하자면 Fortran 이전에도 개념이 제시되고 실제로 구현된 고급 언어가 있었지만,

 

통상적으론 Fortran이 최초의 고급 언어로 인정받고 있습니다.

 

최초의 고급 언어와 15년이란 간극이 있는만큼,

 

C언어 이전에도 많은 고급 언어가 있었습니다만,

 

사람들은 어째서 C를 최초의 고급 언어라고 무의식 중에 생각하게 되는 것일까요?

C언어는 중급 언어?

최초의 고급 언어라고 무의식 중에 생각하는 것과 더불어,

 

C언어에 대해서 어떤 사람들은

저급 언어, 고급 언어도 아닌 중급 언어다!

 

라고 주장하기도 합니다.

 

일반적으로 저급 언어는 하드웨어를 직접적으로, 즉 메모리와 레지스터를 직접적으로 제어할 수 있는 언어를 의미합니다.

 

이 경우 하드웨어의 종류나 사용하는 아키텍쳐에 따라 문법이 달라지며, 프로그래머는 그에 맞춰 코드를 작성해야 합니다.

 

그에 반해 고급 언어는 저급 언어를 추상화하여, 코드를 작성할 때는 하드웨어에 종속되지 않고 해당 언어의 문법에만 맞춰 작성하면 됩니다.

 

하드웨어와 아키텍쳐에 따라 자동으로 알맞은 저급 언어로 번역되어 작동하기 때문이죠.

 

그렇다면, C는 어째서 중급 언어라고 불리는 걸까요?

Unix 개발을 위한 초석

C언어는 현대적인 프로그래밍 언어 분류 상으론 고급 언어가 맞습니다.

 

다만 제작 목적성 자체가 상당히 독특하기 때문에 중급 언어라는 요상한 포지션을 가질 수 있었던 겁니다.

 

바로 C언어가 Unix의 커널 설계를 위해 만들어진 언어이기 때문이죠.

 

대부분의 언어는 특정 도메인의 문제 해결이나 프로그래밍 생산성 향상 또는 개발자가 심심해서(?) 만들어지는 경우가 많습니다.

 

따라서 그러한 고급 언어들은 제작 목적이나 방향성 부터가 하드웨어에 직접 접근해 메모리를 할당, 해제하는 작업에 맞춰져있지 않습니다.

 

가비지 컬렉션이 매우 강력한 Java와 Python 등 현대적인 프로그래밍 언어는 메모리를 코드 수준에서 직접 관리하는 일이 거의 없고,

 

그러한 언어들은 보통 일반 컴퓨터 사용자(비 개발자)를 위한 웹, 응용 프로그램 제작에 사용됩니다.

 

그에 반해 C는 Unix의 커널 설계를 위해 만들어진 만큼, 하드웨어를 직접적으로 조작하는 것을 염두로 만들어졌습니다.

 

그렇기에 언어 개발의 목적성 자체가 여타 고급 언어보단, 저급 언어와 어셈블리어에 조금 더 밀접한 것입니다.

C의 설계와 개발

Unix는 1960년대, 켄 톰슨과 데니스 리치, 브라이언 커니핸 등, 전설적인 프로그래머들이 벨 연구소에서 개발하고 있었습니다.

전설의 UNIX 개발 듀오, 켄 톰슨과 데니스 리치

 

초창기에는 어셈블리어로 모든 개발이 이루어지고 있었으나,

 

앞서 언급했듯 어셈블리어는 하드웨어마다 아키텍쳐가 달랐기에 유지보수와 이식에 있어 대단히 큰 한계가 존재했습니다.

 

이에 대한 대안으로 BCPL(Basic Combined Programming Language)를 사용했는데,

 

켄 톰슨(Ken Thompson)은 BCPL을 필수 기능만 남겨 경량화시킨 B언어를 만들었습니다.

 

그리고 나서 더욱 본격적으로 Unix를 개발하던 1970년대 초, B언어가 가진 자료 구조의 한계 때문에 다시 새로운 언어를 만들고자 합니다.

 

그리하여 C언어의 설계가 시작되었고, 이는 전설적인 프로그래머, 데니스 리치(Dennis Ritchie)의 주도 하에 이루어졌습니다.

그래서 중급 언어?

즉, 제작 목적 자체가 Unix 커널 설계였기 때문에, 목적성만 따지면 저급 언어에 가까운 언어입니다.

 

C언어의 창시자 데니스 리치와 Unix 커널 설계자 브라이언 커니핸의 전설적인 C언어 교범

공룡책을 넘어선 전설의 책입니다.

The C Programming Language1)에서는 서문에서 다음과 같이 말하고 있습니다.

C is a relatively "low level" language. This characterization is not pejorative; it simply means that C deals with the same sort of objects that most computers do, namely characters, numbers, and addresses. ......... Again, because the language reflects the capabilities of current computers, C programs tend to be efficient enough that there is no compulsion to write assembly language instead. ......... Although C matches the capabilities of many computers, it is independent of any particular machine architecture, and so with a little care it is easy to write "portable" programs...

 

요컨대, C언어는 저수준 언어에 가까우며 이식성있는 어셈블리어를 목표로 설계된 언어라는 것입니다.

 

현대적인 프로그래밍 언어의 분류로는 C는 분명 고급 언어가 맞지만,

 

저급 언어의 한 단계 위에서 작동하는, 플랫폼/하드웨어 종속성을 없앤 저급 언어라는 목적으로 개발되었기 때문에

 

중급 언어라는 묘한 포지션에 위치하는 것이죠.

C언어 이름의 유래

B 언어의 후속작이므로, 알파벳 순서 상에 따라 C언어가 되었습니다.(열린 결말이지만 이게 가장 유력합니다.)

 

프로그래머들이 가장 어려워하는 작업이 변수명 짓기인 것처럼, 과거의 천재 개발자들도 이름을 짓는 건 여간 어려운 게 아니었나봅니다.

 

아니면 그냥 공돌이답게 네이밍 센스가 바닥이던가

 

이에 대한 레퍼런스는 c언어의 탄생에 대해 데니스 리치가 직접 설명한 논문

 

The Development of the C Language2)에서 확인할 수 있습니다.

I decided to follow the single-letter style and called it C, leaving open the question whether the name represented a progression through the alphabet or through the letters in BCPL.

저는 단일 문자를 사용하는 스타일을 따르기로 결정했고, 그 이름을 C라고 지었습니다. 이 이름이 알파벳의 순서를 따른 것인지, 아니면 BCPL의 문자에서 따온 것인지는 열어둔 상태로 남겼습니다.

전설이 된 이유

그렇다면 어째서 Computer 언어를 줄여서 C언어인 것이라고 생각할 만큼 프로그래밍 언어의 대명사가 된 것일까요?

 

Unix는 Linux, MacOS와 같은 컴퓨터용 OS는 물론, Android, Ios같은 스마트폰의 OS의 모태이기도 합니다.

 

이러한 운영체제의 커널 역시 C언어와 C를 모태로 발전한 언어들이 사용되고 있습니다.

 

Windows 또한 Unix를 계승한 것은 아니지만 커널을 조작하는 데에 C언어를 사용하기 때문에,

 

Unix를 넘어서도 OS 커널 개발에 C언어가 사용되고 있는 것이죠.

 

즉, C는 프로그래밍 역사에서 가장 중요하며, 큰 파급력을 가진 언어 중 하나입니다.

 

그리고 시스템 소프트웨어 설계라는 목적성과 달리, 응용 프로그램 설계에도 충분히 활용할 수 있는 전천후 언어입니다.

 

프로그래밍을 처음 접하는 사람들에게 처음으로 배워야 할 언어로 C언어를 꼽으며, 대부분의 컴퓨터공학과에서도 1학년 1학기에 무조건 배우는 편이라고 하니, (이걸 처음부터 배웠다면 전 아마 개발을 관뒀을 겁니다) 개발을 잘 모르는 사람들게에도 인지도가 높을 수 밖에 없던 것이죠.


Ref)

  1. Kernighan, Brian W., and Dennis M. Ritchie. The C Programming Language. 1978.
  2. Ritchie, Dennis M. “The Development of the C Language.” Bell Labs, 1993, https://www.bell-labs.com/usr/dmr/www/chist.html.

Dictionary!

Dictionary는 파이썬 생태계에서 매우매우 중요한 자료구조입니다.

 

유연하고, 빠르며, 사용하기 쉽습니다.

 

그야말로 만능에 가까운 자료구조입니다.

 

1년 주기로 발표되는 Minor 버전마다 계속해서 성능의 향상이 이루어지고 있을 정도로

 

Python core 개발진과 커뮤니티가 모두 많은 관심을 보입니다.

 

주요 특징은 다음과 같습니다.

  1. 해시 테이블을 활용합니다.
  2. 대부분의 동작에서 평균적으로 O(1)의 시간복잡도를 가집니다.
  3. 불변 객체라면 모두 Key로 넣을 수 있습니다.
  4. 입력 순서를 보장합니다.

Java에도 유사한 역할을 하는 HashMap이라는 자료구조가 있으나,

 

위의 3, 4번 특징은 갖지 못하기에, 유연한 사용은 조금 힘든 편입니다.

 

또한 해시 테이블이 아닌 체이닝 방식으로, 세부적인 구조는 조금 다릅니다.

1. Hash Table 방식

딕셔너리는 해시 테이블 방식으로 구현되어 있습니다.

 

키로 들어온 값을 고유한 해시 값을 생성하여 인덱스로 활용합니다.

 

일반적으로 list, tuple은 list[1]처럼 정수형 인덱스로 값에 접근합니다.

 

name이라는 string 값을 key로 넣는다고 하면,

 

해시 함수를 활용해 5라는 정수 값으로 변환하고 이를 인덱스로 넣는 셈입니다.

 

내부적으로는 다른 시퀀스 자료형에 접근하는 것과 같이 정수형 인덱스로 접근한다는 것이죠.

 

그렇기 때문에 다른 시퀀스 자료형에 인덱스로 접근하는 것과 같이 평균적으로 O(1)이란 시간 복잡도를 가집니다.

 

다만, 해시 충돌이라는 문제를 고려해야 하기 때문에 평균적으로 O(1)이라 표현합니다.

해시 충돌?

두 개의 서로 다른 키 k1, k2가 해시 함수로 변환된 값이 서로 같을 때, 해시 충돌이 일어납니다.

 

충돌이 발생하면 해시 테이블의 시간 복잡도는 O(1)에서 O(n)으로 증가할 수 있습니다.

 

Python의 딕셔너리는 오픈 어드레싱(Open Addressing) 방식을 사용하여 충돌을 해결하며,

 

다음과 같은 방식으로 성능을 유지합니다.

  • 충돌이 발생할 경우, 인접한 빈 슬롯을 찾아 이동(프로빙)하여 데이터를 저장합니다.
  • 이는 충돌이 심하지 않다면 여전히 O(1)에 가까운 성능을 유지하도록 설계되었습니다.
  • 또한 입력되는 값의 수에 따라 동적으로 테이블의 크기를 조정합니다.

따라서, 많은 값이 입력되어도 평균적으로 O(1)에 가까운 성능을 내는 것이죠.

2. 대부분의 동작에서 평균 O(1)의 시간 복잡도

메서드/연산 설명 평균 시간 복잡도 최악 시간 복잡도
d[key] (조회) 키에 해당하는 값을 반환 O(1) O(n)
d[key] = value (삽입) 키-값 쌍을 삽입하거나 값을 업데이트 O(1) O(n)
del d[key] (삭제) 키-값 쌍을 삭제 O(1) O(n)
key in d 키가 딕셔너리에 있는지 확인 O(1) O(n)
len(d) 딕셔너리의 키-값 쌍 개수 반환 O(1) O(1)
d.clear() 모든 키-값 쌍 제거 O(n) O(n)
d.keys() 키 뷰 반환 O(1) O(n)
d.values() 값 뷰 반환 O(1) O(n)
d.items() 키-값 뷰 반환 O(1) O(n)
d.pop(key) 키-값 쌍을 삭제하고 값을 반환 O(1) O(n)
d.popitem() 마지막 키-값 쌍을 삭제하고 반환 (Python 3.7+) O(1) O(1)
d.get(key) 키에 해당하는 값을 반환 (없으면 기본값 반환) O(1) O(n)
d.update([other]) 다른 딕셔너리를 병합하거나 키-값 삽입 O(k) (k는 삽입 개수) O(k)

 

다만, 위에서 말했던 해시 충돌에 대한 대책 덕분에

 

실제로 O(n)이 걸릴 일은 사실상 없다고 봐도 무방합니다.

3. 불변 객체라면 모두 Key로 넣을 수 있습니다.

파이썬은 사실상 모든 객체가 1급 객체 취급이기 때문에 가능한 것이죠.

 

Key로 넣을 수 있는 값은 다음과 같습니다.

  1. int, str, float 등등, 수 많은 기본 자료형
  2. tuple
  3. 함수(메서드)
  4. 클래스에 대한 참조

ListDict 타입은 가변 객체이므로 넣을 수 없습니다!

 

ListDict 모두 1 버전에서 해시하여 dict의 키로 넣었을 때,

 

만약 해당 객체가 변형되어 2 버전이 되어버리면,

 

해당 객체를 해싱했을 때 같은 해시 값이 나오지 않아, 값을 찾을 수 없기 때문입니다.

 

4. 입력 순서를 보장합니다.

입력 순서 보장은, Python 3.6 버전에서 구현이 되었으나,

 

공식적으로 확정된 것은 3.7 버전 부터입니다!

 

애초에 해시 테이블이라 순차적 인덱스로 접근하는 것도 아닌데 이게 왜 필요한가 싶지만,

  1. 순차적 접근이 필요한 때, 별도의 정렬이 필요없게 됩니다.
  2. Json 형태로 파싱될 때, 입력순서가 보장된다면, 보다 가독성 높은 Json 파일로 변환할 수 있습니다.
  3. Print를 통해 값을 출력했을 때 가독성이 보장됩니다.

이러한 발전 덕에 기존에 사용되던 OrderedDict 타입을 굳이 쓸 필요가 없게 되었습니다!

 

정리하자면, 정렬 용이, 데이터 포맷 변환 용이, 디버깅 용이, 코드 간소화 등의 장점이 생긴 것이죠!

요약

Python의 dictionary유연하고 빠르며, 사용하기 쉬운 만능 자료구조로, Python 생태계에서 매우 중요한 역할을 합니다.

  1. 해시 테이블 기반:
    • 키를 해시하여 고유한 인덱스를 생성하고, 이를 통해 빠르게 값에 접근.
    • 평균 O(1)의 시간 복잡도를 가지며, 충돌이 발생할 경우에도 최적화된 방식(Open Addressing)으로 성능 유지.
  2. 대부분의 동작에서 평균 O(1):
    • 조회, 삽입, 삭제 등 대부분의 연산이 평균적으로 O(1)이며, 충돌이 거의 없으므로 O(n)은 사실상 발생하지 않음.
  3. 불변 객체만 키로 사용 가능:
    • int, str, float, tuple, 함수, 클래스 등 해시 가능한 객체를 키로 사용할 수 있음.
    • list, dict와 같은 가변 객체는 키로 사용할 수 없음(변경 시 해시 값 불일치 문제).
  4. 입력 순서 보장:
    • Python 3.7부터 입력 순서를 공식적으로 보장.
    • 정렬, JSON 변환, 디버깅 등에서 데이터 가독성과 사용성을 향상.

장점

  • 효율성: 평균 O(1)의 성능.
  • 유연성: 다양한 객체를 키로 사용 가능.
  • 가독성: 입력 순서 유지로 디버깅과 데이터 변환이 용이.
  • 코드 간소화: 추가 데이터 구조 사용 없이 순차 접근 지원.

Python의 dict는 계속해서 최적화되고 있으며, JSON 변환, 데이터 정렬, 디버깅 등 다방면에서 활용도가 매우 높은 자료구조입니다.

Python 3.13

Python의 정식 넘버링 3.13 버전의 LTS가 지난 10월 7일 릴리즈 되었습니다!

 

17개월의 개발 기간을 가진 이번 3.13 버전은 작년과 마찬가지로 2024년 10월 7일에 공개되었습니다.

 

이번 3.13 버전은 매우매우 실험적이고 중대한 기술의 도입이 이루어졌습니다.

 

1. JIT 컴파일러의 실험적 도입

 

2. GIL을 해제한 free-threded 환경 도입

 

3. 대화형 인터프리터 환경(REPL)의 사용성 증대

 

여기서도 가장 중요한 1번2번에 대해 조금 더 깊게 다뤄보도록 하겠습니다.

JIT 컴파일러의 실험적 도입

JIT(Just In Time) 컴파일러란, 인터프리터처럼 동작하지만 자주 사용되는 코드를 기계어로 컴파일하여 캐싱하는 방법을 통해 성능을 향상시킨 방식1)입니다.

 

인터프리터 방식은 파이썬이 다른 언어들에 비해 느린 여러 이유 중, 가장 큰 지분을 차지한다고 할 수 있습니다.

 

컴파일을 거치지 않으므로 프로그램의 수정과 실행 자체는 빠르게 할 수 있으나,

 

모든 동작에서 코드를 실시간으로 읽고 해석하기 때문입니다.

 

Java 또한 실질적으로 런타임 시에는 인터프리터 방식으로 동작하나,

 

자바 컴파일러가 바이트 코드를 만들어주고 이를 JIT 컴파일을 통해 기계어로 번역하여 실행하기 때문에

 

파이썬에 비해 훨씬 빠른 성능을 보여주는 것이죠.

 

이미 오래 전부터 JIT 컴파일을 통한 Python의 성능 향상은 시도된 바가 있었습니다만,

 

실험적으로라도 정식에 편입될 줄은 상상도 못했습니다.

 

이에 대해 가장 정확하고 자세한 정보를 확인하시려면

 

Python의 JIT 컴파일러에 대한 PEP-744 문서를 확인하시면 좋을 것 같습니다!

아직은 어디까지나 실험 단계

정식으로 편입되었다곤 하지만, 아직도 어디까지나 실험적 도입 단계임을 명심해야 합니다.

 

PEP-744 문서에서는, 프로덕션 환경에서는 절대 사용하지 마라고 못을 박아 두었습니다.

 

예기치 못한 문제가 발생할 수 있고, 다음 버전에서 다시 큰 변화가 생길 수 있기 때문입니다.

 

자세한 사용 방법은 저도 체험하고 다시 포스팅을 진행해보고자 합니다!

GIL 해제 실험적 도입

GIL(Global Interpreter Lock)은 멀티 코어(스레드) 환경에서도 하나의 스레드만 실행되도록 제한하는 상호 배제 락2)입니다.

 

파이썬이 느린 이유 2입니다.

 

GIL이 있어도 강제로 멀티 스레드 환경을 실행할 수는 있지만, 이는 병렬처리가 아닌 동시성 제어를 위한 기능입니다.

 

I/O가 발생했을 때, 스레드가 놀지 않고 다른 일을 하도록 만드는 것이죠.

 

즉, CPU 집약적 작업을 멀티 스레드로 처리할 수 없었습니다.

 

따라서 파이썬에서는 멀티 스레드가 아닌 멀티 프로세스 환경으로 병렬처리를 구현해야 합니다.

 

그러나 두 방식은 동작 원리나 구현 방식이 전혀 다릅니다.

 

또한 같은 수의 워커를 만드는 데에 멀티 프로세싱 환경이 더욱 많은 비용이 들기 때문에

 

보다 적은 리소스로 병렬 처리를 진행할 수 있는 멀티 스레딩 환경에 대한 아쉬움이 남아있던 것이죠.

 

Python의 초기 설계 철학으로 GIL이 존재하게 되었고,

 

시간이 지나며 코어와 GIL의 강결합으로 삭제가 매우 어려운 상태였으나,

 

많은 기여자들을 통해(무려 한국의 LINE 개발자 나동희 님에 대한 감사도 공식 문서에 있습니다!) 가능했다고 합니다.

 

앞으로는 GIL을 완전히 제거하고자 노력 중이라 합니다!

 

사용 방법은 똑같다!

 

앞서 말했듯, 원래 멀티 스레딩 자체는 기존 Python에서도 지원을 하고 있었기 때문에,

 

GIL을 해제한 버전의 Python 빌드를 사용하면, 진정한 멀티 스레딩 병렬 처리를 쓸 수 있게 됩니다!

요약하자면

GIL의 실험적 해제를 통한 병렬 처리 확장

 

JIT 컴파일의 실험적 도입을 통한 성능 확장

 

이 메인이 되는 매우 뜻 깊은 업데이트였습니다!

일급 객체?

컴퓨터 프로그래밍 언어 디자인에서, 일급 객체(first-class object)란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다. 보통 함수에 인자로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 한다. (출처 : 위키백과1))

사실 자주 쓰이는 개념은 아니고, 아직도 논의가 이루어지는 개념입니다.

 

앞선 위키백과의 설명문을 로빈 포플스톤이 정의한 세부적인 항목으로 나타내면 다음과 같습니다.

  1. 모든 요소는 함수의 실제 매개변수가 될 수 있다. (함수의 인자로 전달될 수 있다)
  2. 모든 요소는 함수의 반환 값이 될 수 있다.
  3. 모든 요소는 할당 명령문의 대상이 될 수 있다. (변수에 할당될 수 있다)
  4. 모든 요소는 동일 비교의 대상이 될 수 있다. (is)

다만, 아직도 일급 객체가 정확히 어떤 것인지 애매합니다.

 

그 이유는 현대적인 고급 언어에서 “객체”라는 개념 자체가 보편화되었기 때문입니다.

 

예를 들어, Python, JavaScript, Ruby 같은 언어에서는 함수도 하나의 객체로 취급되며, 자연스럽게 일급 객체처럼 사용됩니다.

 

이 때문에 “객체 = 일급 객체”라는 식으로 동일시되기도 합니다.


일반적으로 객체로 불릴 수 있는 것들을 나열해봅시다.

 

값, 기본 자료형, 복합 자료형, 클래스, 함수(메서드)...

 

이 보다 더 많을 수 있지만, 일단 이 다섯을 중점적으로 설명해봅시다.

 

만약, 어떤 언어에서 어떤 요소가 함수의 인자로 사용되고, 때론 함수의 반환값이 되며,



변수에 할당할 수 있고, 동일 비교의 대상이 된다면, 해당 요소는 일급 객체의 요건을 만족합니다.

Python 코드로 알아보기

모든 것은 객체로 이루어진 파이썬에서는, 사실 정말 거의 모든 것이 일급 객체입니다.

 

10이라는 숫자조차도 원시 타입이 아닌 int라는 클래스로 지정된 객체가 됩니다.

 

아주 한정적으로 원시 타입을 쓸 수 있기도 하지만, 사실상 파이썬의 모든 객체는 일급객체 입니다.

기본, 복합 자료형의 경우

기본 자료형은 매우 간단합니다.

 

굳이 설명이 필요하지 않을 것 같지만, 코드로 나타낸다면 다음과 같습니다.

# 할당 명령문의 대상이 됨
x1 = 10 # Python에서는 10이란 정수 값도 참조 클래스 기반 객체입니다.
l1 = [1, 2] # 복합 자료형 List

def function(x):
    if x == 10 or x == [1, 2]: # 동일 비교의 대상이 됨
        return x # 함수의 반환 값이 됨

x2 = function(x1) # 함수의 매개 변수가 됨
l2 = function(l1)

 

위의 코드는 매우 정상적으로 실행됩니다.

 

정수 자료형 x1과 복합 자료형 l1 모두 function 함수의 인자로 들어갈 수 있으며,

 

동일 비교(equals)의 대상이 되고,

 

함수의 반환 값으로 쓰일 수 있습니다.

함수(메서드)의 경우

Python에서 함수를 일급 객체 취급을 함으로써 가능한 것이 대표적으로 두 가지 있습니다.

  1. 데코레이터를 만들 때, 함수의 인자로 또다른 함수를 넣곤 합니다.
  2. 람다 표현식 자체가 함수를 일급 객체로 취급하기에 가능한 것이기도 합니다.
def deco(func):  # 함수의 매개 변수가 됨
    def wrapper():
        print("목표 함수 실행 이전")
        func()
        print("목표 함수 실행 이후")
    return wrapper # 함수의 반환 값이 됨

@deco
def hello():
    f = lambda x : print(f"{x} - hello world") # 변수에 람다 표현식(함수)를 할당 함
    f("cat") # 람다 함수에는 반드시 인자가 필요하기에 임시로 넣은 값입니다.

h = hello  # 데코레이터가 적용된 함수 자체를 새로운 변수에 할당
h()

print(h == hello)  # 함수 간에도 equals가 가능함

# 목표 함수 실행 이전
# cat - hello world
# 목표 함수 실행 이후
# True

 

함수 또한 다른 함수의 인자로 들어가거나, 반환값이 될 수 있고

 

함수 자체를 변수에 할당하거나 대소 비교가 가능합니다.

클래스의 경우

당연히 클래스도 가능합니다.

 

클래스 자체가 일반적으로 객체를 이르는 말이긴 합니다만,

 

클래스에 대한 참조도 객체로써 활용될 수 있습니다.

class Cat:
    def meow(self):
        print("Nyang")

Dog = Cat() # Cat의 인스턴스를 생성해 meow에 할당합니다.
Dog = Cat   # Cat 클래스에 대한 참조 자체를 할당합니다.

cat = Dog() # 이는 매우 정상적으로, Cat 클래스 인스턴스를 생성해 cat에 할당합니다.

cat.meow()  # Nyang

from typing import Type

def factory(Cls : Type[Cat] ): # Cat 클래스에 대한 참조를 넣습니다
    x = Cls()
    return x

cat2 = factory(Cat)

cat2.meow() # Nyang

 

del?

x = 10

del x

print(x) # NameError 발생

Python에서 직접적으로 변수의 네임스페이스 참조를 제거하고,
참조 카운트를 감소시키는 명령어
입니다.

 

어디에 쓰지?

엄밀히 말하면, 현대의 Python에서는 del 명령어를 굳이 쓸 이유가 별로 없습니다.

 

Python을 오래 써오신 분들도 del이란 명령어의 존재 자체는 알지만, 실제 개발에서 잘 사용하진 않습니다.

 

가비지 컬렉터(GC)가 알아서 다 해주기 때문입니다.

 

GC를 튜닝하거나 아예 끄지 않는 이상, 크게 필요가 없는 것이죠.

 

(GC를 끄고 개발할 수 있는 사람들은 그냥 C/C++을 쓰겠죠?)

 

복합 자료형에서 특정 객체를 삭제하는 데에 쓸 수도 있지만, 이미 여러 메서드가 이 역할도 수행하고 있습니다.

먼저 그나마 유용한 방법에 대해서

  1. 복합 자료형에서 특정 객체를 삭제
  2. (사실상) 모든 것을 삭제

여기서 1번은 코딩 테스트에서 꽤나 유용하게 쓰이나, 2번은 잘못 쓰면 매우 위험한 존재입니다.

복합 자료형에서 특정 객체 삭제

리스트

arr = [10, 20, 30]

del arr[0]

print(arr) # [20, 30]

 

이렇게, 해당 객체에 직접 접근해서 네임 스페이스를 해제하여, 객체 자체를 삭제할 수 있습니다.

 

pop처럼 값을 반환 받을 필요가 없고, 명시적으로 삭제를 행하고 싶다면 선택할법 합니다.

딕셔너리

딕셔너리의 경우, 특정 key로 접근한 객체의 value만 삭제하는 것이 아닌,

 

key-value모두 삭제합니다.

 

만약 키는 남겨두고 싶다면, dcit[key] = None 을 씁시다.

d = {10:3, 20:6, 30:9}

del d[10]

print(d) # {20:6, 30:9}

복합 자료형 자체를 삭제

arr = [10, 20, 30]

del arr

print(arr) # NameError 발생

 

이렇게 객체 자체를 삭제하는 것이 가능합니다.

 

그리고 클래스, 변수, 함수 모든 것들이 1급 객체로 관리되는 파이썬에서는...

(사실상) 모든 것을 삭제

예... 클래스와 함수, 변수 모두 삭제할 수 있습니다.

 

삭제할 수 있는 항목은 다음과 같습니다.

  1. 클래스 자체
  2. 클래스 속성, 인스턴스 속성
  3. 클래스 메서드
  4. 전역 함수
  5. 기본 모듈 및 함수 (sum, print 등등...)
  6. 모든 변수
# 클래스 삭제
class Spam:
    pass

del Spam  # 이제 Spam은 존재하지 않습니다.

# 전역 함수 삭제
def hello():
    print("안녕하세요!")

del hello  # hello 함수가 삭제되었습니다.

# 기본 함수 삭제
del print  # 이제 print를 사용할 수 없습니다.

 

그러나 이러한 반컴퓨터적 파괴행위가 딱히 개발 인생에 쓸모는 없을 것 같습니다...

 

이제 조금 심화된 메모리와 관련된 내용으로 넘어가도록 합시다!

 

GC와 메모리 관리에 관한 내용입니다.

짧은 Python의 GC 설명

Python의 GC는 Java와는 조금 다르게 작동합니다.

  1. 객체의 참조 카운트를 확인하고, 0이 될 경우 메모리를 해제시킵니다. 순환 참조 탐지도 이루어집니다.
  2. 3개의 세대로 나누어, 각각의 주기에 따라 객체를 체크하고 메모리를 수집합니다. (Java와 유사합니다.)

del 문은 엄밀히 말하면 메모리를 해제하는 것이 아니라 앞서 말씀드렸듯

 

네임 스페이스를 제거하고, 참조 카운트를 0으로 만듭니다.

 

다만 참조 카운트가 0이 될 경우, GC가 꺼져 있어도 메모리가 해제되긴 합니다.

 

그러나 순환 참조 탐지가 이루어지지 않아 메모리 누수 위험이 있습니다.

GC를 튜닝하거나, 아예 끄는 경우

Python의 GC는 분명 많은 최적화가 이루어졌지만, 특정 상황에선 튜닝하는 게 훨씬 좋은 성능을 내곤 합니다.

 

이 때, del은 의도적으로 특정 변수를 삭제하여 매우 세밀한 메모리 조정에 사용할 수 있습니다.

대용량 파일을 여는 경우

일반적으로 대용량 csv 파일이나 이미지 파일, 문서 파일을 여는 경우 with 블럭을 사용합니다.

with open('대용량.csv', 'r') as f:
    df = f.read()

이는 매우 강력히 권장되는 방법입니다.

with블럭을 쓰지 않을 경우, f라는 파일 자체에 대한 변수에 반드시 .close()를 해주어야 합니다.
그렇지 않을 경우, 원본 파일의 데이터가 소실, 변형되는 치명적 문제를 야기할 수도 있습니다.

 

그렇다면, fread()하여 값을 담은 df라는 변수는 with 블럭 밖에서 어떻게 될까요?

 

당연하지만 with 블럭 밖에서 여전히 값을 가지고 쓸 수 있는 변수입니다.

 

f라는 변수에 할당된 파일의 원본 데이터만이 with블럭을 통해 제어되는 것입니다.

 

그런데 만약, df에서 값 몇 개만 가져오기만 할 거라면?

 

100mb가 넘는 csv 파일이라면?

del df

 

데이터를 모두 가져와서 변수에 할당했다면, df를 통해 메모리 상에서 데이터를 명시적으로 삭제할 수 있습니다.

 

사실 알아서 GC가 메모리를 해제해주긴 합니다...

 

어디까지나 명시적인 코드를 위한 방안인 것이죠.

컴프리헨션(Comprehension)?

Python이 강력히 권고하는 복합 자료형(list, dict, set)의 생성 방식입니다.

Python Docs에서는 함수형 프로그래밍 언어 Haskell에서 빌린 표기법이라고 합니다. (빌린..?)

 

간단하게, 0부터 1억-1까지의 값을 순차적으로 넣은 리스트를 생성한다고 가정해봅시다.

lst = []

for i in range(100_000_000): # 천의 단위마다 끊어서 보기 좋게 표현합니다.
    lst.append(i)

 

이러한 방식을 쓰는 것에도 딱히 문제는 없지만, 생각보다 비효율적입니다.

초기 배열을 append로 생성하는 것은 느리다.

Python의 List는 기본적으로 동적 배열입니다.

 

초기에는 일정한 메모리를 할당하고 값의 추가로 인해 해당 메모리의 한계를 초과하면,

 

보다 큰 새로운 메모리 공간을 할당하여 기존 값을 복사해 넣습니다.

 

즉, 0의 길이로 시작한 리스트에 값이 하나씩 추가되면

 

할당된 메모리의 한계에 도달할 때마다, 새로운 메모리 공간을 할당하여 기존 값을 복사하는 과정이 일어납니다.

 

이 한계의 확장과 메모리 이동은 당연히 오버헤드를 발생시킵니다.

 

또한, 컴프리헨션은 하나의 표현식이므로, 바이트코드로 변환될 때 보다 최적화되어 빠른 성능을 보여줍니다.

 

처음부터 배열의 최대 크기를 알기 때문에 불필요한 새 리스트 생성 / 복사 과정이 일어나지 않는 것이죠.

 

문자열에서 특정 문자를 찾을 때 find 메서드를 사용하는 것과

 

for루프equals를 통해 구현하는 것이 상당한 성능차이가 나는 것도 이 부분이 어느 정도 관여합니다.

컴프리헨션을 쓴다면?

lst = [i for i in range(100_000_000)]

 

결과적으로 같은 lst를 만들지만, 코드도 간단하고 훨씬 직관적입니다.

 

또한, 리스트를 초기화하는 과정에서 몇 개의 요소를 넣을지 미리 알 수 있기 때문에

 

처음부터 해당 요소를 모두 넣을 수 있는 메모리를 할당합니다.

 

즉, 빈번한 메모리 확장(재할당) 및 복사가 이루어지지 않습니다.

import time

# append 방식

lst = []
start = time.time()

for i in range(100_000_000):
    lst.append(i)

end = time.time()
print(round(end-start,4)) # 2.860

# 컴프리헨서

start2 = time.time()

lst2 = [i for i in range(100_000_000)]

end2 = time.time()

print(round(end2-start2, 4)) # 1.282

 

보시다시비 시간이 약 45퍼센트 수준으로 단축된 걸 확인할 수 있습니다.

 

그러나 실제 백엔드 서버에서 이러한 코드를 짤 일은 별로 없죠...

 

혹시라도 있다 해도 그 때는 Numpy/Pandas가 압도적인 효율을 내줍니다.

 

기본 Python만 쓸 수 있는 코딩 테스트로 넘어가서,

 

0으로 초기화된 2차원 배열을 만들어 봅시다!

 

2차원 배열

start3 = time.time()
lst3 = []

for _ in range(10_000):
    tmp_lst = []
    for _ in range(10_000):
        tmp_lst.append(0)
    lst3.append(tmp_lst)

end3 = time.time()

print(round(end3-start3, 4)) # 3.047


start4 = time.time()

lst4 = [[0 for _ in range(10_000)] for _ in range(10_000)]

end4 = time.time()

print(round(end4-start4, 4)) # 1.555

 

역시 두 배 가까이 차이가 나는 것을 알 수 있습니다.

 

온몸 비틀기라도 필요한 시점이라면, 꽤나 유용하게 쓸 수 있을 겁니다!

 

숏코딩에도 당연히 필요합니다!

조건식이 달려도 여전합니다.

lst = []
start = time.time()

for i in range(100_000_000):
    if i//2 == 0:
        lst.append(i)

end = time.time()

print(round(end-start,4)) # 3.508


start2 = time.time()

lst2 = [i for i in range(100_000_000) if i//2 == 0]

end2 = time.time()

print(round(end2-start2, 4)) #2.645

 

물론, 이 경우에는 컴프리헨션도 최종 리스트의 길이를 알지 못하기 때문에, 메모리를 동적으로 늘려나가야 합니다.

 

다만 최대 길이는 알고 있으므로 보다 최적화된 동적 메모리 할당이 가능합니다.

 

그리고 표현식 자체가 여전히 최적화된 바이트코드로 변환 가능하므로, 빠릅니다.

 

가능하고, 의도에 맞다면 컴프리헨션을 적극적으로 사용하는 걸 추천드립니다.

 

다른 복합 자료형들도 가능합니다.

# dict
d = {i : 1 for i in range(100)}

# set
d = {s for s in range(100)}

 

혹시라도 소괄호()로 감싸면 튜플 컴프리헨션 아니냐? 하실 수 있겠으나, 이건

제네레이터 표현식

입니다!

 

리스트 컴프리헨션과 상당히 다른 표현식입니다.

 

모든 객체를 생성해서 메모리에 로드하는 것이 아니라,

 

객체에 접근하는 시점에 해당 인덱스의 표현식에 따라 값을 계산하는 지연 평가(lazy evaluation)을 사용합니다.

 

즉, 제네레이터 자체는 메모리를 적게 사용하나, 값을 읽는 속도는 비교적 느립니다.

 

코테 용도라면, 대부분의 경우 메모리보다 속도가 중요하므로(그리고 애초에 리스트에 표현식을 넣을 일이 없으므로)

 

리스트 컴프리헨션을 써야 합니다.

 

간단히 정리하자면 다음과 같습니다.

 

리스트 컴프리헨션 제너레이터 표현식
[]로 감싸서 작성됨 ()로 감싸서 작성됨
모든 요소를 한 번에 메모리에 로드 요소를 필요할 때마다 하나씩 생성
더 빠른 접근과 반복 작업 가능 메모리 사용이 적고 대용량 데이터 처리에 적합

 

Session.close()

sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30.00 (Background on this error at: https://sqlalche.me/e/20/3o7r)

연결 풀(QueuePool)의 크기 제한(5개)을 초과하여 오버플로(10개)가 발생했습니다. 연결이 제한 시간을 초과하여 타임아웃되었습니다. (제한 시간: 30초)

session.close()는 sqlalchemy에서 매우 중요한 명령어입니다.

기본적으로 Python에서 .close()는 매우 중요합니다.

Python에서 close()는 매우 중요한 명령어입니다.

 

open() 함수는 시스템 호출(system call)을 통해 파일을 엽니다. 따라서 여러 파일을 동시에 열어 사용할 때, close()를 통해 파일 점유를 해제하는 과정은 필수적입니다.

 

with 블록이나 .close() 메서드를 사용하여 자동적(컨텍스트 매니저를 통한) 또는 명시적으로 파일이 점유한 메모리를 반드시 해제해야 합니다.

 

Python의 가비지 컬렉터는 참조 카운트(reference count)가 0이 되면 메모리를 자동으로 해제하지만, 파일 처리의 경우 다음과 같은 문제가 발생할 수 있습니다:

 

  1. 파일의 버퍼에 담긴 데이터가 close() 호출 시점에 디스크에 저장되므로, close()가 명시적으로 이루어지지 않으면 데이터 유실이나 원치 않는 변경이 발생할 가능성이 있습니다.
  2. 열린 파일 핸들이 제대로 해제되지 않으면 리소스 누수가 생길 수 있습니다.

SQLAlchemy의 Session도 마찬가지입니다.

engine = create_engine(...)

Session = sessionmaker(bind=engine)

with Session() as db:
    # OR

db = Session()
db.add(...)
db.commit()
db.close()

 

세션 팩토리를 펑션 콜 하는 순간 Sessionopen()되고, with 블럭을 탈출하는 순간 session.close()가 이루어집니다.

 

또는 후자의 방식대로 명시적인 close()를 해줘야 합니다.

 

session(db)의 사용을 마친 뒤, 이를 닫지 않으면 문제가 발생하게 되는데요.

 

커넥션 풀링 방식을 쓰는 SQLAlchemy는 DB와의 커넥션을 한번 쓰고 삭제하지 않습니다.

 

커넥션은 유지하되, 요청이 들어올 때마다 session은 커넥션 풀에 존재하는 미점유 상태의 커넥션을 가져와 사용합니다.

 

원활한 쿼리를 위해서는 session사용이 끝난 뒤, 커넥션에 대한 점유를 해제하여 커넥션 풀로 빠르게 돌려보내야 하는 것이죠.

 

그렇지 않는다면 session은 요청이 끝난 뒤에도 계속 커넥션을 붙잡고 있기 때문에

 

새로운 요청이 들어왔을 때 사실상 놀고 있는(무의미하게 점유당한) 커넥션을 사용하지 못합니다.

 

그렇기 때문에,

Session을 닫지 않으면 커넥션을 계속 생성합니다.

커넥션에도 당연히 최대 개수가 존재하고, 이는 SQLAlchemy나 MySQL같은 DBMS에도 존재하죠.

 

MySQL의 경우, 151개로 상대적으로 넉넉한 편이지만,

 

별 다른 설정이 없는 경우 SQLAlchemy는 다음과 같은 설정을 가집니다.

pool_size=5,       # 기본 커넥션 수
max_overflow=10,   # 추가로 생성할 수 있는 임시 커넥션 수
pool_timeout=30    # 사용할 수 있는 커넥션이 없을 때, 기다리는 시간(초)
# 세션을 닫지 않은 채로 계속해서 DB에 쿼리를 보내는 요청을 보내고 그 결과를 프린팅하고 있습니다.
Pool size: 5  Connections in pool: 3 Current Overflow: 10 Current Checked out connections: 12
INFO:     127.0.0.1:50423 - "GET / HTTP/1.1" 200 OK
Pool size: 5  Connections in pool: 2 Current Overflow: 10 Current Checked out connections: 13
INFO:     127.0.0.1:50423 - "GET / HTTP/1.1" 200 OK
Pool size: 5  Connections in pool: 1 Current Overflow: 10 Current Checked out connections: 14
INFO:     127.0.0.1:50423 - "GET / HTTP/1.1" 200 OK
Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15

 

5개의 요청이 빠르게 들어오면 서버 쪽에서는 기본 커넥션 풀의 크기를 채워버립니다.

 

또한 추가로 생성하는 임시 커넥션도 10개 밖에 안 되므로, 매우 빠르게 임시 풀도 차버립니다.

 

총 15개의 세션이 모두 차버린 채로 새 요청을 받게 되면, 30초간 세션(커넥션 풀에서 놀고 있는 커넥션)을 기다립니다.

 

그리고 30초의 시간이 지날 때까지 세션을 가져오지 못할 경우

sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30.00 (Background on this error at: https://sqlalche.me/e/20/3o7r)

 

에러가 발생하는 것이죠.

 

당연하지만 커넥션을 닫지 않았기 때문에 연결된 DB에도 커넥션이 무의미하게 존재하고 있습니다.

반드시 with, finally를 통해서 close()를 시켜줍시다.

방법은 여러가지가 있지만, FastAPI가 공식 Docs에서 권장하는 방법이 매우 좋습니다.

 

FastAPI는 다음과 같은 연결 방식을 권장합니다.

from sqlalchemy import create_engine, Integer
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DB_URL = f'mysql+pymysql://{USER}:{PASSWORD}@{HOST}:{PORT}/{DATABASE}'
engine = create_engine(url=DB_URL)
session_maker = sessionmaker(bind=engine, autoflush=False)

async def get_db():
    db = session_maker()
    try:
        yield db
    finally:
        db.close()

app = FastAPI()

class Base(DeclarativeBase):
    pass

class Item(Base):
    __tablename__ == "items"
    item_id = Column(Integer, primary_key = True)

@app.get('/item')
async def get_item(n, db = Depends(get_db)):

    item = db.query(Item).get(n)

    return item

 

세션을 요청마다 주입받아 사용하고, 요청이 끝날 경우 강제로 close()를 호출하는 것이죠.

+ Recent posts