인생 명언 연락은 짧아도, 마음은 오래 남는다.
velog
“좋은 인연은 자주 만나는 것보다, 잊지 않고 연락하는 데서 이어진다.”
Score: 54.4Confidence: 49%
View offerLoading the catalog…
THE AI OPPORTUNITY INDEX
Find your next AI tool. Explore free access, trials, and credits — all in one place.
velog
“좋은 인연은 자주 만나는 것보다, 잊지 않고 연락하는 데서 이어진다.”
Score: 54.4Confidence: 49%
View offervelog
🔎 문제 설명 AI 엔지니어인 현식이는 데이터를 분석하는 작업을 진행하고 있습니다. 데이터는 코드 번호(code), 제조일(date), 최대 수량(maximum), 현재 수량(remain)으로 구성되어 있으며 현식이는 이 데이터들 중 조건을 만족하는 데이터만 뽑아서 정렬하려 합니다. 예를 들어 다음과 같이 데이터가 주어진다면 data = [[1, 20300104, 100, 80], [2, 20300804, 847, 37], [3, 20300401, 10, 8]] 이 데이터는 다음 표처럼 나타낼 수 있습니다. code date maximum remain 1 20300104 100 80 2 20300804 847 37 3 20300401 10 8 주어진 데이터 중 제조일이 20300501 이전인 물건들을 현재 수량이 적은 순서로 정렬해야 한다면 조건에 맞게 가공된 데이터는 다음과 같습니다. data = [[3, 20300401, 10, 8], [1, 20300104, 100, 80]] 정렬한 데이터들이 담긴 이차원 정수 리스트 data와 어떤 정보를 기준으로 데이터를 뽑아낼지를 의미하는 문자열 ext, 뽑아낼 정보의 기준값을 나타내는 정수 val_ext, 정보를 정렬할 기준이 되는 문자열 sort_by가 주어집니다. data에서 ext 값이 val_ext보다 작은 데이터만 뽑은 후, sort_by에 해당하는 값을 기준으로 오름차순으로 정렬하여 return 하도록 solution 함수를 완성해 주세요. 단, 조건을 만족하는 데이터는 항상 한 개 이상 존재합니다. 제한사항 1 ≤ data의 길이 ≤ 500 data[i] 의 원소는 [코드 번호(code), 제조일(date), 최대 수량(maximum), 현재 수량(remain)] 형태입니다. 1 ≤ 코드 번호 ≤ 100,000 20000101 ≤ 제조일 ≤ 29991231 data[i][1] 은 yyyymmdd 형태의 값을 가지며, 올바른 날짜만 주어집니다. 1 ≤ 최대 수량 ≤ 10,000 1 ≤ 현재 수량 ≤ 최대 수량 ext 와 sort_by 의 값은 다음 중 하나를 가집니다. "code" "date" "maximum" "remain" 순서대로 코드 번호, 제조일, 최대 수량, 현재 수량을 의미합니다. val_ext 는 ext 에 따라 올바른 범위의 숫자로 주어집니다. 정렬 기준에 해당하는 값이 서로 같은 경우는 없습니다. 입출력 예 data = [[1, 20300104, 100, 80], [2, 20300804, 847, 37], [3, 20300401, 10, 8]] ext = "date" val_ext = 20300501 sort_by = "remain" result = [[3, 20300401, 10, 8], [1, 20300104, 100, 80]] 💡 코드 풀이 핵심 로직 1 문자열로 주어진 열 이름을 실제 인덱스로 바꾸기 2 ext 열의 값이 val_ext 보다 작은 행만 남기기 3 sort_by 열 기준으로 오름차순 정렬하기 🧐 정답 def solution(data, ext, val_ext, sort_by): index = { 'code' : 0, 'date' : 1, 'maximum' : 2, 'remain' : 3, } answer = [row for row in data if row[index[ext]] < val_ext] answer.sort(key=lambda row: row[index[sort_by]]) ✏️ 내가 놓친 것 문자열로 주어진 ext , sort_by 를 실제 데이터의 열 인덱스와 연결하는 방법을 떠올리지 못했다. 이 문제처럼 열 이름이 문자열로 주어지고, 각 열의 위치가 정해져 있는 경우에는 딕셔너리를 사용해 "열 이름": 인덱스 형태로 매핑하면 쉽게 처리할 수 있다. 다음에 비슷한 문제가 나오면 문자열 조건을 바로 비교하려 하지 말고, 먼저 딕셔너리를 이용해 실제 인덱스로 변환할 수 있는지 생각해보기.
Score: 54.4Confidence: 49%
View offervelog
Jupyter, Kernel, ipykernel, 가상환경과 Python Interpreter 이해하기 Python으로 데이터 분석이나 AI를 시작하면 생각보다 자주 마주치는 단어들이 존재함. .py .ipynb Jupyter Kernel ipykernel Python Interpreter 가상환경 라이브러리 Interactive Window 처음에는 전부 비슷해 보이지만 실제로는 각각 담당하는 역할이 다름 . 이번 글에서는 단순히 사용 방법만 외우는 것이 아니라 다음 질문을 하나씩 해결하면서 전체 구조를 이해하는 것이 목적임. "내가 VS Code에서 작성한 Python 코드는 도대체 누가 실행하는 것일까?" 1. Jupyter란 무엇일까? ❓ 질문 Jupyter Notebook에서 말하는 Jupyter 는 무슨 뜻일까? Jupyter의 의미 Jupyter는 대화형 컴퓨팅 환경을 제공하는 프로젝트임. 이름은 전통적으로 다음 세 언어에서 유래한 것으로 설명됨. Ju → Julia Py → Python R → R 즉, Jupyter가 처음부터 Python만을 위한 프로그램은 아니라는 의미 . Python뿐만 아니라 해당 언어를 실행할 수 있는 Kernel만 존재한다면 다양한 언어를 Notebook 환경에서 실행 가능 . 대표적인 예시는 다음과 같음. 언어 사용할 수 있는 Kernel 예시 Python ipykernel R IRkernel C++ xeus-cling JavaScript ijavascript Java IJava 왜 이 개념이 필요할까? .ipynb = Python 파일 이라고 생각하면 Kernel의 역할을 이해하기 어려워지기 때문임. 정확하게는 다음 구조에 가까움. .ipynb ↓ 선택한 Kernel ↓ 해당 언어 실행 따라서 .ipynb 는 특정 언어 그 자체가 아니라 코드와 실행 결과 등을 담을 수 있는 Notebook 문서 형식 으로 이해하는 것이 중요함. 2. 그렇다면 Kernel은 무엇일까? ❓ 질문 Notebook 화면이 있는데 왜 Kernel이라는 것이 또 필요할까? VS Code나 Jupyter Notebook은 우리가 코드를 작성하고 결과를 확인하는 화면 을 제공함. 하지만 화면 자체가 Python 코드를 실행하는 것은 아님. 실제 코드를 실행해 주는 별도의 실행 엔진이 필요함. 이것이 Kernel 임. Kernel 코드를 전달받아 실제로 실행하고 결과를 다시 Notebook에 전달하는 실행 프로세스 쉽게 구조화하면 다음과 같음. 사용자 ↓ VS Code / Jupyter Notebook ↓ Kernel ↓ Python 실행 ↓ 실행 결과 ↓ VS Code / Jupyter Notebook 🍳 주방으로 비유하기 전체 구조를 식당으로 생각하면 이해하기 쉬움. 개념 비유 사용자 손님 VS Code / Jupyter 주문을 전달하는 공간 .ipynb 메뉴와 주문 내용이 적힌 종이 Kernel 실제 요리를 수행하는 주방장 가상환경 주방장이 사용하는 전용 주방 라이브러리 주방 안에 준비된 식재료와 조리도구 손님이 메뉴판에 주문을 적었다고 해서 음식이 자동으로 만들어지는 것은 아님. 실제로 요리하는 주방장 , 즉 Kernel이 필요한 구조임. 3. ipykernel은 무엇일까? ❓ 질문 그러면 ipykernel 은 그냥 Python을 실행하는 Kernel인 걸까? 핵심적으로 맞는 이해임. ipykernel 이라는 이름을 나누어 보면 역할을 이해하기 쉬움. i + py + kernel i → Interactive, 대화형 py → Python kernel → 코드를 실행하는 Kernel 즉, Python 코드를 대화형으로 실행할 수 있도록 Jupyter 환경과 연결해 주는 Python Kernel 이라고 이해 가능함. ipykernel은 IPython 기반의 Jupyter용 Python Kernel임. 4. 그런데 왜 '대화형(Interactive)'이라고 부를까? ❓ 질문 Python을 실행하는 건 알겠는데 왜 굳이 '대화형'이라는 이름을 사용할까? 핵심은 코드를 한 번에 전부 실행하는 것이 아니라 조금씩 실행하고 바로 결과를 확인할 수 있기 때문 임. 예를 들어 다음 코드를 실행한다고 가정함. a = 10 Kernel이 살아 있는 상태에서 다음 셀을 실행함. b = 20 그리고 다시 다음 코드를 실행함. a + b 결과: 30 첫 번째 셀에서 만든 a 와 두 번째 셀에서 만든 b 가 Kernel의 메모리에 남아 있기 때문에 세 번째 셀에서 다시 사용 가능함. 카카오톡으로 비유하기 사람과 다음과 같은 대화를 했다고 가정함. 나: 어제 치킨 먹었어. 상대방: 오. 나: 그거 맛있더라. 상대방은 앞의 대화를 기억하기 때문에 그거 = 치킨 이라는 사실을 이해함. 대화형 실행도 비슷한 구조임. a = 10 라고 먼저 실행하면 Kernel이 a 를 기억함. 이후 print(a) 만 실행해도 이전 실행 상태를 기억하고 있기 때문에 다음 결과 출력이 가능함. 10 대화형이라는 이름이 필요한 이유 데이터 분석과 AI 실험에서는 다음과 같은 확인 작업의 반복이 많기 때문임. 데이터 불러오기 ↓ 잘 불러왔는지 확인 ↓ 전처리 ↓ 결과 확인 ↓ 그래프 생성 ↓ 결과 확인 ↓ 모델 학습 ↓ 성능 확인 즉, 코드 작성 → 실행 → 결과 확인 → 수정 → 다시 실행 이라는 반복적인 작업 구조에 적합한 방식임. 5. Python Interpreter는 무엇일까? ❓ 질문 그런데 Kernel 말고 Python Interpreter라는 것도 나오는데 이건 무엇일까? Python Interpreter는 Python 코드를 읽고 실행하는 Python 실행기 임. 컴퓨터에 Python을 설치하면 사용할 수 있게 되는 python 실행 프로그램이 대표적인 Python Interpreter임. 예를 들어 터미널에서 다음 명령을 실행하는 상황임. python main.py 이때 main.py 를 읽고 실행하는 주체가 Python Interpreter임. Interpreter가 하는 일 Python 코드를 위에서부터 실행하면서 필요한 연산을 수행하고 오류가 발생하면 해당 위치에서 오류를 알려주는 역할 수행. 예를 들어 다음 코드가 있다고 가정함. a = 10 b = 20 print(a + b) Python Interpreter가 이 코드를 읽고 실행하여 다음 결과 생성. 30 6. Python Interpreter와 ipykernel은 무엇이 다를까? ❓ 질문 둘 다 Python 코드를 실행한다면 Python Interpreter와 ipykernel은 같은 것 아닐까? 완전히 같은 개념은 아님. Python Interpreter가 Python 코드 실행의 기본 주체 라면, ipykernel은 이 Python 실행 환경을 Jupyter와 연결하여 대화형으로 사용할 수 있도록 하는 Kernel 에 가까움. 구조를 단순화하면 다음과 같음. .py 실행 .py ↓ Python Interpreter ↓ 실행 Notebook에서는 다음 구조로 이해 가능함. .ipynb ↓ Jupyter / VS Code ↓ ipykernel ↓ Python 실행 환경 ↓ 실행 결과 반환 즉, ipykernel이 Python 자체를 대체하는 것이 아니라 Python 실행 환경을 Jupyter의 대화형 구조와 연결하는 역할 이라는 이해가 중요함. 7. 그렇다면 .ipynb 는 왜 Kernel을 선택해야 할까? ❓ 질문 .ipynb 파일은 Kernel을 연결해야 실행할 수 있는 것일까? 맞음. Notebook은 코드가 적혀 있는 문서이므로 어떤 실행 환경으로 해당 코드를 실행할 것인지 결정해야 함 . Python Notebook이라면 보통 Python 환경을 사용하는 ipykernel 선택이 필요함. VS Code에서는 오른쪽 위의 다음 메뉴에서 확인 가능함. Select Kernel 여기에서 프로젝트의 Python 가상환경 등을 선택하여 Notebook 실행 환경 지정 가능. 중요한 포인트 반드시 사용자가 매번 jupyter kernelspec 명령으로 직접 등록해야 한다는 의미는 아님. VS Code에서는 설치된 Python 환경을 찾아 Kernel로 선택할 수 있는 경우도 많음. 따라서 핵심은 다음과 같음. .ipynb 실행에는 어떤 Kernel을 사용할 것인지 연결이 필요함 8. 등록된 Kernel은 어떻게 확인할까? ❓ 질문 내 컴퓨터에는 어떤 Jupyter Kernel이 등록되어 있을까? 터미널에서 다음 명령 사용. jupyter kernelspec list 등록된 Kernel 목록 확인 가능. ❓ 질문 예전에 만든 Kernel이 너무 많다면 어떻게 삭제할까? 다음 명령 사용. jupyter kernelspec uninstall 삭제할커널이름 여기서 중요한 점은 Kernel 등록 정보를 삭제하는 것과 실제 Python 가상환경 폴더를 삭제하는 것은 다른 작업 이라는 점임. 즉, 일반적으로 kernelspec uninstall 은 Jupyter에 등록된 Kernel 정보를 제거하는 작업임. 9. 그렇다면 .py 파일은 무엇이 다를까? ❓ 질문 .ipynb 가 Kernel을 이용한다면 .py 는 완전히 다른 것일까? .py 는 일반적인 Python Script 파일 임. 예를 들어 다음 파일 존재. main.py 터미널에서 다음과 같이 실행 가능. python main.py Python Interpreter가 파일의 코드를 실행함. 10. .py 와 .ipynb 비교 구분 .py Python Script .ipynb Jupyter Notebook 기본 실행 방식 스크립트 단위 실행 Cell 단위 대화형 실행 실행 상태 실행 프로세스 종료 시 메모리 해제 Kernel이 살아 있는 동안 상태 유지 주요 용도 프로그램, 자동화, 서버, 배포 데이터 분석, AI 실험, 시각화 실행 환경 Python Interpreter Jupyter Kernel(ipykernel 등) 부분 실행 기본 실행은 전체 Script Cell 단위 실행 결과 확인 터미널 등 Notebook Cell 아래 Git 관리 일반 텍스트라 비교 용이 JSON 구조와 출력값 등으로 Diff가 복잡할 수 있음 11. 영화로 비유하면? .py 는 완성된 영화 에 가까움. 처음 ↓ 장면 1 ↓ 장면 2 ↓ 장면 3 ↓ 끝 일반적인 Script 실행에서는 처음부터 마지막까지 프로그램 흐름에 따라 실행됨. 반면 .ipynb 는 촬영 현장 과 비슷함. Scene 1 촬영 ↓ 결과 확인 Scene 2 촬영 ↓ 결과 확인 Scene 2 수정 ↓ 다시 촬영 따라서 데이터를 탐색하거나 AI 모델을 실험하는 과정에서 Notebook 방식의 장점 발생. 12. .py 가 끝나면 메모리는 어떻게 될까? ❓ 질문 .py 파일은 실행이 끝나면 메모리가 전부 사라지고 출력값만 남는 걸까? 일반적인 Script 실행 프로세스를 기준으로 보면 프로그램 종료와 함께 해당 프로세스가 사용하던 메모리도 해제됨. 흐름은 다음과 같음. 1 실행 시작 python main.py Python 프로세스 시작. 2 데이터 생성 a = 10 변수 a 가 실행 중인 프로세스의 메모리에 존재. 3 출력 print(a) 터미널에 다음 내용 출력. 10 4 프로그램 종료 마지막 코드까지 실행 완료 후 Python 프로세스 종료. 해당 프로세스가 사용하던 변수와 객체의 메모리 해제. 따라서 프로그램이 끝난 뒤 이전 실행의 a 를 다음 실행에서 그대로 가져오는 것은 불가능함. 13. 그렇다면 출력값은 남는 것 아닐까? 터미널에 다음 내용이 보일 수 있음. 10 하지만 이것은 a 라는 변수가 계속 Python 메모리에 존재한다는 의미가 아님. 터미널에 출력된 실행 기록 이 화면에 남아 있는 것임. 따라서 다음 실행에서 다시 해당 값이 필요하다면 재계산하거나 파일 등에 저장 필요. 예를 들어 CSV 저장. df.to_csv("result.csv") 이미지 저장. plt.savefig("result.png") 모델 저장. torch.save(model.state_dict(), "model.pt") 왜 저장이 필요할까? RAM의 변수와 SSD/HDD의 파일은 서로 다른 저장 방식이기 때문임. 프로그램 실행 중 ↓ RAM에 변수 존재 ↓ 프로그램 종료 ↓ RAM의 실행 상태 해제 반면 파일로 저장하면 다음 구조가 됨. 프로그램 실행 ↓ 결과 생성 ↓ 파일 저장 ↓ 프로그램 종료 ↓ SSD/HDD에 결과 파일 유지 14. 그런데 .py 에서도 pandas 같은 라이브러리가 필요하지 않을까? ❓ 질문 .py 는 Kernel이 필요 없다고 해도 pandas, numpy 같은 라이브러리는 필요하지 않을까? 당연히 필요함. 여기서 가장 중요한 구분이 등장함. Kernel 등록과 라이브러리 설치는 서로 다른 개념 15. 라이브러리 설치와 Kernel 등록의 차이 라이브러리 설치 pip install pandas 현재 Python 환경에 pandas 설치. Kernel 연결 Notebook이 어떤 Python 환경을 사용해서 코드를 실행할지 연결하는 과정 . 둘을 방으로 비유하면 다음과 같음. 가상환경 = 하나의 전용 방 pandas / numpy / torch = 방 안에 있는 도구 ipykernel = 그 방의 Python을 Jupyter와 연결해주는 역할 따라서 .py 와 .ipynb 모두 pandas를 사용한다면 실제로 실행되는 Python 환경에 pandas가 설치되어 있어야 함 . 16. 가상환경은 왜 필요할까? ❓ 질문 그냥 컴퓨터에 pandas를 한 번 설치하면 되는데 왜 굳이 가상환경을 만들까? 프로젝트마다 필요한 Python과 라이브러리 버전이 다를 수 있기 때문임. 예를 들어 다음 두 프로젝트 존재. 프로젝트 A Python 3.11 pandas 2.x torch 2.x 프로젝트 B Python 3.9 pandas 1.x tensorflow 특정 버전 모든 라이브러리를 하나의 전역 환경에 설치하면 프로젝트 간 버전 충돌 가능성 증가. 따라서 프로젝트별 독립 환경 구성. Project A └── .venv ├── Python ├── pandas └── torch Project B └── .venv ├── Python ├── pandas └── tensorflow 주방 비유 가상환경은 프로젝트 전용 주방 과 같은 개념. 프로젝트 A 주방에는 한식 재료가 있고, 프로젝트 B 주방에는 양식 재료가 있는 형태. 각 프로젝트가 자기에게 필요한 도구와 라이브러리만 사용하는 구조. 17. .py 는 어떤 가상환경의 라이브러리를 사용할까? ❓ 질문 컴퓨터에 가상환경이 여러 개 있다면 .py 는 어디의 pandas를 가져오는 걸까? 결국 어떤 Python Interpreter로 해당 파일을 실행했느냐 가 중요함. 가상환경을 활성화한 터미널에서 다음과 같이 실행한다고 가정함. python test.py 현재 활성화된 가상환경의 Python이 실행되고, 해당 환경에 설치된 라이브러리 사용. 예를 들어 macOS/Linux에서는 상황에 따라 다음과 같이 가상환경 활성화 가능. source .venv/bin/activate Windows에서는 환경과 Shell에 따라 다음과 같은 방식 사용. .venv\Scripts\activate 활성화 이후: python test.py 해당 가상환경의 Python과 라이브러리 사용. 18. VS Code의 Select Interpreter 는 무엇일까? ❓ 질문 VS Code에서 자꾸 Python: Select Interpreter 가 나오는데 도대체 무엇을 선택하라는 것일까? VS Code에게 다음을 알려주는 과정임. "이 프로젝트의 Python 코드를 어떤 Python 환경을 기준으로 처리할 것인가?" 컴퓨터에 다음 Python들이 존재할 수 있음. 시스템 Python Python 3.11 Python 3.12 Project A .venv Project B .venv Conda Environment VS Code 입장에서는 어떤 Python을 사용해야 하는지 결정 필요. 따라서 Select Interpreter 를 통해 프로젝트에서 사용할 Python 환경 선택. 19. VS Code에서 Python Interpreter 선택하기 macOS 명령 팔레트 실행. Cmd + Shift + P Windows Ctrl + Shift + P 검색창에 다음 입력. Select Interpreter 다음 메뉴 선택. Python: Select Interpreter 이후 프로젝트 내부 가상환경 선택. 예: .venv 또는 macOS/Linux의 경우 다음과 유사한 경로 확인. ./.venv/bin/python Windows에서는 다음과 유사한 경로 사용. .venv\Scripts\python.exe 20. 왜 Interpreter를 제대로 선택해야 할까? 예를 들어 .venv 에는 pandas가 설치되어 있다고 가정함. .venv ├── Python ├── pandas ├── numpy └── ipykernel 하지만 VS Code가 시스템 Python을 사용하고 있다면 다음 코드에서 오류 발생 가능. import pandas as pd 대표적인 오류: ModuleNotFoundError: No module named 'pandas' pandas가 컴퓨터에 전혀 없는 것이 아니라, 현재 코드를 실행하고 있는 Python 환경에는 pandas가 없는 상황 일 수 있음. 따라서 Python 환경 문제를 확인할 때 중요한 질문은 다음과 같음. "pandas를 설치했는가?" 뿐만 아니라 "지금 실행 중인 Python과 pandas를 설치한 Python이 같은 환경인가?" 라는 확인 필요. 21. 그렇다면 .venv 를 터미널에서 활성화하면 Interpreter 선택은 안 해도 될까? ❓ 질문 터미널에 (.venv) 가 떠 있다면 VS Code에서 Python Interpreter를 따로 선택하지 않아도 될까? 실행 방식에 따라 구분 필요. Case 1. 활성화된 터미널에서 직접 실행 터미널에 다음처럼 가상환경이 활성화되어 있다고 가정함. (.venv) 그리고 직접 다음 명령 실행. python test.py 이 경우 Shell의 python 이 활성화된 .venv 의 Python을 가리키도록 설정되어 있으므로 해당 환경을 이용한 실행 가능. 활성화된 .venv ↓ python test.py ↓ .venv의 Python ↓ .venv의 pandas 사용 Case 2. VS Code의 실행 버튼 이용 VS Code에서 오른쪽 위의 ▶ 버튼 등을 사용하는 경우에는 VS Code가 선택하고 있는 Python 환경 설정도 중요함. 따라서 VS Code 작업에서는 프로젝트의 .venv 를 Interpreter로 선택해 두는 것이 안전함. Cmd/Ctrl + Shift + P ↓ Python: Select Interpreter ↓ 프로젝트 .venv 선택 추천 설정 프로젝트를 열었을 때 한 번 다음 설정 수행. Select Interpreter ↓ .venv 선택 이를 통해 코드 분석, 실행, 디버깅 등의 환경을 프로젝트 가상환경과 일치시키기 쉬워짐. 22. 그런데 .py 에서도 Notebook처럼 조금씩 실행할 수 없을까? ❓ 질문 .py 파일은 항상 처음부터 끝까지 실행해야 할까? 일반적인 Script 실행은 파일 단위 실행이지만 VS Code에서는 .py 파일을 Cell 형태로 나누어 Interactive Window에서 실행하는 기능 사용 가능. 이때 사용하는 것이 다음 주석임. # %% 23. # %% 란 무엇일까? .py 파일에 다음처럼 작성. # %% [1번 블록] 데이터 정의하기 import pandas as pd a = 10 b = 20 print("데이터 입력 완료!") 다음 Cell 작성. # %% [2번 블록] 계산하기 result = a + b print(f"결과는? {result}") VS Code가 # %% 를 기준으로 코드 영역을 Cell처럼 인식 가능. 화면 위에 다음과 같은 메뉴 표시 가능. Run Cell Run Below Debug Cell Run Cell 을 누르면 해당 영역만 실행 가능. 24. # %% 을 사용하면 왜 메모리가 유지될까? ❓ 질문 .py 인데 왜 첫 번째 블록에서 만든 a 를 두 번째 블록에서 사용할 수 있을까? python test.py 로 일반 Script 실행을 하는 것이 아니라 VS Code의 Interactive Window 를 통해 실행하고 있기 때문임
velog
PWM 이란 Pulse Width Modulation 정보나 제어값에 따라 Pulse 폭을 변화시켜 전달하는 방식 Pulse 폭은 한 주기에서 신호가 HIGH로 유지되는 시간을 의미함 한 주기에서 HIGH 시간이 차지하는 비율 을 듀티비(Duty Cycle) 라고 함 듀티비를 조절하여 부하에 공급되는 평균 파워 변화 가능 주로 LED 밝기 제어, DC 모터 속도 제어 등에 사용함 듀티비(%) = HIGH로 유지되는 시간 / 전체 주기 × 100 ex) 전체 주기가 4ms이고 HIGH 시간이 1ms라면 듀티비는 25% PWM 생성 과정 PWM 생성 시 사용하는 Register 4가지 레지스터 역할 PSC — Prescaler 카운터가 숫자를 세는 속도 결정 CNT — Counter 현재 세고 있는 숫자 ARR — Auto-Reload Register 카운터의 최대치 설정 CCR — Capture/Compare Register 출력이 HIGH에서 LOW로 바뀌는 기준값 CNT가 CCR 보다 낮을 경우 HIGH 그 외 LOW _CNT와 CCR 비교는 TIM이 수행 _
Score: 54.39Confidence: 49%
View offervelog
요즘 취업 준비하면서 드는 고민 IT 쪽으로 취업을 준비한 지 꽤 됐는데, 요즘은 정말 쉽지 않다는 생각이 계속 든다. 몇 년 전만 해도 개발 직군은 사람이 부족하다는 말이 많았는데 지금은 분위기가 완전히 달라졌다. 채용 공고 자체가 눈에 띄게 줄었고 신입을 뽑는 곳은 더 적어졌다.여기에 AI까지 빠르게 발전하면서 고민이 하나 더 생겼다. 간단한 코드는 AI가 금방 짜주는 시대에 신입 개발자에게 기업이 기대하는 게 뭘까. 지금 내가 준비하는 방향이 맞는 걸까. 이런 생각이 들 때마다 막막했다. 혼자 공고를 찾아보고, 자소서를 쓰고, 포트폴리오를 고치는 걸 반복했지만 잘하고 있는 건지 확인할 방법이 없었다. 제대로 된 피드백을 받을 곳이 없다는 게 제일 답답했다. 그러다 알게 된 게 제베(제로베이스)이다. 취업정보회사 제로베이스는 어떤 서비스일까 간단히 말하면 취업을 준비하는 사람들을 위한 취업 코칭 서비스다. 컨설턴트와 현직자 멘토가 함께 붙어서 서류부터 면접까지 전반적인 취업 준비를 도와준다. 혼자 준비할 때는 방향을 잡는 것부터가 어려웠는데, 여기서는 지금 내 상태를 먼저 점검하고 거기에 맞춰서 무엇을 준비해야 할지 같이 정해준다는 점이 좋았다. 컨설턴트 상담은 상담 전에 작성하는 사전 질문지를 바탕으로 진행된다. 처음엔 그냥 형식적인 설문이겠거니 했는데, 막상 채워보니 생각이 달라졌다. 지금 내가 어디쯤 와 있는지, 무엇이 고민인지, 상담에서 어떤 걸 얻고 싶은지를 차근차근 정리하게 만들어준다. 상담 전에 뭘 준비해야 할지 막막한 사람에게는 이 질문지가 좋은 지침서가 되어줄 것 같다. 덕분에 상담 시간도 헤매지 않고 핵심적인 이야기에 집중할 수 있었다. 부담 없이 질문할 수 있는 구조 솔직히 이런 서비스를 쓰기 전에 가장 걱정했던 건 "이런 것까지 물어봐도 되나?" 하는 부분이었다. 정해진 상담 시간에만 질문할 수 있으면 사소한 궁금증은 그냥 넘기게 되니까. 그런데 제베는 슬랙으로 소통하다 보니 궁금한 게 생길 때마다 바로 메시지를 남길 수 있었다. 컨설턴트님과 멘토님도 친절하게 답해주셔서 질문하는데 부담이 덜했다. 마무리 취업 시장이 어렵다는 건 여전히 사실이다. 그래도 혼자 막막하게 버티는 것과 방향을 같이 잡아주고 언제든 물어볼 수 있는 사람이 있는 건 정말 다르다는 걸 느꼈다. 나처럼 IT 취업 준비하면서 어디서부터 손대야 할지 막막한 사람이라면 한 번쯤 제베를 알아보는 걸 추천하고 싶다.
Score: 54.39Confidence: 49%
View offervelog
D-ask는 내가 처음으로 진행했던 프로젝트다. 1학년 때 처음 만들었던 프로젝트를 지금 다시 꺼내 v2라는 이름으로 처음부터 다시 만들고 있다. 왜 이미 한 번 만들었던 프로젝트를 다시 만들게 되었는지, 그리고 이번에는 무엇을 다르게 해보고 싶은지 정리해보려고 한다. 1. D-ask v1은 왜 시작했는가 학교생활을 하다 보면 학교 홈페이지에 올라온 문서를 찾아볼 일이 종종 있다. 하지만 필요한 정보가 여러 공지와 문서에 흩어져 있다 보니 학생들이 매번 직접 문서를 찾아 내용을 확인해야 했다. 그래서 이런 생각에서 D-ask를 시작했다. 학교 문서가 한곳에 모여 있고, 학생이 직접 문서를 찾는 대신 질문만 하면 필요한 정보를 알려줄 수 없을까? 이 문제를 해결하기 위해 학교 문서를 수집하고, 문서를 기반으로 학생의 질문에 답변하는 서비스를 만들어보기로 했다. 그게 D-ask v1의 시작이었다. 2. v1에서는 무엇을 만들었는가 D-ask v1에서는 RAG를 이용해 학교 문서를 기반으로 답변하는 Q&A 챗봇 을 만들었다. 사용자가 질문하면 관련된 학교 문서를 검색하고, 검색한 내용을 LLM에 전달해 답변을 생성하는 방식이었다. 당시에는 RAG를 직접 구현하고 실제 학교 문서를 이용해 답변이 생성되는 것만으로도 신기했다. 하지만 프로젝트를 진행하면서 여러 문제를 만나기 시작했다. 문서 안에 분명 답이 있는데 관련 내용을 제대로 검색하지 못하는 경우가 있었고, 검색 결과가 있어도 답변 생성 과정에서 문제가 발생하기도 했다. 그때의 나는 이런 문제가 발생하면 주로 한 가지를 생각했다. “어떻게 하면 일단 이 문제를 해결할 수 있을까?” D-ask v1 프로젝트 회고 3. 만들고 나서 어떤 한계와 의문을 느꼈는가 대표적인 사례가 임베딩 모델이었다. 검색이 제대로 되지 않는 문제가 발생했을 때, 당시 사용하던 임베딩 모델이 한국어 문서를 충분히 잘 처리하지 못한다고 판단했다. 그래서 한국어 성능이 더 좋다고 알려진 다른 임베딩 모델로 교체했다. 모델을 변경한 뒤 당시 문제가 발생했던 일부 질문에서는 이전보다 정상적으로 검색되는 것을 확인할 수 있었다. 그런데 지금 다시 생각해보면 여기에는 문제가 있었다. 왜 그 모델이어야 하는지 설명할 수 없었다. 모델 A보다 모델 B가 정말 전체적으로 검색을 더 잘하는지 측정하지 않았다. 어떤 종류의 질문에서 얼마나 좋아졌는지도 알 수 없었다. 결국 당시의 선택 과정은 이랬다. 문제 발생 ↓ 다른 모델로 교체 ↓ 잘 되는 것 같음 ↓ 채택 지금 생각하면 모델을 바꾸기 전에 먼저 이런 질문을 했어야 했다. 현재 검색 성능이 실제로 얼마나 나쁜가? 모델을 바꿨을 때 얼마나 좋아졌는가? 그 차이를 무엇으로 판단할 것인가? 하지만 v1에는 이 질문에 답할 수 있는 평가 기준이 없었다. 돌아가는 것과 운영할 수 있는 것은 달랐다 평가만의 문제도 아니었다. D-ask v1은 챗봇 자체는 동작했지만, 프로젝트를 완성하고 나니 한 번 동작하는 RAG를 만드는 것과 지속적으로 운영할 수 있는 서비스를 만드는 것은 다른 문제 라는 생각이 들었다. 새로운 학교 문서가 추가되거나 기존 문서가 수정되면 이를 어떻게 반영할 것인지에 대한 구조가 부족했다. 검색 방식을 변경했을 때 이전보다 정말 좋아졌는지 검증하기도 어려웠다. 오류가 발생했을 때도 원인이 문서 파싱인지, 검색인지, 임베딩인지, 답변 생성인지 명확하게 분리해서 판단하기 어려웠다. 무엇보다 프로젝트에서 했던 여러 기술 선택에 대해 “왜 이렇게 했나요?” 라는 질문을 받았을 때 충분한 근거를 제시하기 어려웠다. 처음 만드는 프로젝트였던 만큼 일단 기능을 완성하는 데 집중했고, 평가와 운영은 그다음 문제라고 생각했기 때문이다. 4. 왜 v1을 고치는 대신 v2를 시작했는가 처음에는 v1을 계속 수정하는 것도 생각했다. 하지만 다시 생각해보니 바꾸고 싶은 것은 임베딩 모델 하나나 특정 기능 하나가 아니었다. 문서를 어떻게 준비할 것인지부터 검색 결과를 어떻게 평가할 것인지, 모델을 어떤 기준으로 선택할 것인지, 문제가 발생하면 어떻게 찾아낼 것인지까지 프로젝트를 만드는 방식 자체를 바꿔보고 싶었다. 기존 프로젝트에 기능을 하나씩 덧붙이기보다 v1을 만들면서 생긴 의문들을 가지고 처음부터 다시 설계해보기로 했다. 그래서 D-ask v2를 개인 프로젝트로 다시 시작했다. 5. v2에서는 무엇을 다르게 해보고 싶은가 v2의 목표는 단순히 v1보다 기능이 많은 챗봇을 만드는 것이 아니다. 이번에는 내가 내린 기술적인 결정을 측정하고 설명할 수 있는 서비스를 만드는 것 을 목표로 잡았다. 예를 들어 임베딩 모델을 선택한다면 단순히 유명하거나 성능이 좋다고 알려진 모델 하나를 가져오는 방식으로 결정하지 않으려고 한다. 여러 후보를 선정하고 동일한 데이터와 평가 기준으로 비교한 뒤, 실제 측정 결과를 근거로 선택하려고 한다. Chunking, Retrieval, Reranking, LLM 같은 다른 요소도 같은 방식으로 접근하고 싶다. 후보 선정 ↓ 동일한 조건에서 실험 ↓ 성능 측정 ↓ Trade-off 비교 ↓ 기술 선택 ↓ 선택한 이유 기록 그래서 D-ask v2에서는 RAG 파이프라인부터 빠르게 구현하지 않았다. 먼저 Golden Dataset을 만들고, 평가 기준을 정의하고, 동일한 조건에서 여러 방법을 비교할 수 있는 Benchmark 환경을 만드는 작업 부터 시작했다. 이 과정은 다음 글에서 자세히 다뤄보려고 한다. 그리고 이번에는 개발에서 끝내지 않고 실제 서비스까지 만들어보는 것 도 목표로 하고 있다. v1처럼 “챗봇이 동작한다”에서 프로젝트를 끝내는 것이 아니라, 실제로 배포해 사용자가 사용할 수 있는 형태로 만들고 싶다. 6. D-ask v2를 통해 무엇을 배우고 검증하고 싶은가 D-ask v2를 통해 처음부터 완벽한 RAG 시스템을 만드는 것이 목표는 아니다. 오히려 실제로 서비스를 운영하면서 다음 과정을 경험해보고 싶다. 배포 ↓ 사용 ↓ 문제 발견 ↓ 원인 분석 ↓ 개선 ↓ 다시 측정 내가 선택한 임베딩 모델이 정말 적절했는지, Retrieval 방식을 변경했을 때 실제로 검색 성능이 좋아지는지, 검색 성능의 개선이 최종 답변 품질의 개선으로 이어지는지 직접 측정하고 검증해보고 싶다. 그리고 배포 이후에는 개발하면서 예상하지 못했던 문제도 실제 사용자와 데이터를 통해 발견해보고 싶다. 결국 이번 프로젝트에서 경험하고 싶은 것은 단순히 RAG를 구현하는 방법 만은 아니다. 문제를 발견하고, 원인을 찾고, 여러 해결 방법을 비교하고, 하나를 선택한 뒤, 그 선택이 실제로 효과가 있었는지 다시 측정하는 과정까지 경험해보는 것이 목표다. D-ask v1이 나에게 RAG 서비스를 처음 만들어본 프로젝트 였다면, D-ask v2는 RAG 서비스를 어떻게 평가하고, 기술을 선택하고, 실제로 운영할 것인지 고민하는 프로젝트 로 만들어보려고 한다. 다음 글에서는 그 첫 번째 과정으로, D-ask v2에서 RAG 파이프라인을 구현하기 전에 왜 평가 환경부터 만들기 시작했는지 정리해보려고 한다.
velog
[Outbreak Breaker] 인게임 일시정지 메뉴, 적 사운드 재생 구조, 그리고 해머 텔레포트 버그 오늘은 커밋 두 개를 올렸다. 하나는 어제 발견한 해머 텔레포트 버그를 잡은 거고, 다른 하나는 일시정지 메뉴 기본 구현이랑 적 사운드 재생 구조를 새로 짜넣은 거다. 1. 해머 패시브→선택 전환 시 무기 위치 텔레포트 버그 수정 어제 해머 패시브 이동 방식을 무기 액터 자체를 옮기는 방식으로 바꾸고 나서, 패시브랑 선택(액티브) 상태를 왔다갔다 전환할 때 무기가 갑자기 엉뚱한 위치로 순간이동하는 버그가 있었다. 원인은 상태 전환 시 해머 패시브 이동용 타임라인을 재생 여부와 상관없이 무조건 Stop + Set New Time으로 리셋하고 있던 것이었다. Set New Time 은 타임라인이 재생 중이 아니어도 지정한 시간 지점에서 Update를 한 번 동기적으로 발동시켜버리는데( SetPlaybackPosition(NewTime, bFireUpdate=true, bFireUpdateEventsInBetween=false) ), 그 시점의 커브 위치/회전 값이 즉시 액터에 적용되는 구조였다. 그러니까 타임라인이 idle 상태여도 이 호출 자체는 실행되면서, 무기가 커브상의 임의 좌표로 스냅되어 텔레포트하는 것처럼 보였던 거다. IsPlaying() 체크를 추가해서 실제로 재생 중일 때만 Stop + Set New Time을 호출하도록 가드를 걸어서 해결했다. 겸사겸사 정리도 좀 했다. 마그넷 마인 스폰용 액터를 Actor/Weapon 폴더에서 Weapon/Props 폴더로 이동 — 무기 본체랑 무기가 만들어내는 부속 오브젝트를 폴더 구조상 구분해두려고 나이아가라 이펙트가 일시적으로 비정상 표시되던 현상이 있었는데, 참조 텍스처를 재오픈하니 정상화됐다. 로직/데이터 문제가 아니라 에디터 텍스처 컴파일 캐시가 스테일해진 이슈로 추정하고 있고, 재발하면 패턴을 더 확인해볼 예정 2. 인게임 일시정지 메뉴 기본 구현 어제 만든 레이아웃 기준으로 일시정지 메뉴 위젯을 붙였다. 계속하기 / 설정 / 타이틀로 돌아가기 / 게임 종료 네 개 항목에 하단엔 "ESC 눌러 계속하기" 안내 텍스트를 넣었다. 지금은 계속하기(ESC)만 실제로 동작하고 나머지 세 개는 아직 로직을 안 붙인 상태. 지금 단계에서는 기본 UMG 구조로 단순하게 구현해뒀고, 나중에 CommonUI를 도입하게 되면 그 구조로 교체할 계획이다. 3. 적 사운드 재생 구조 추가 및 컨커런시 설정 어제 SK_Whisper 사운드 프롬프트 정리했던 걸 바탕으로, 적 사운드를 실제로 재생하는 구조를 짰다. FOBEnemySoundSet 구조체를 추가해서 Spawn/Attack/Death 사운드를 각각 배열로 관리한다. 뽑을 때 직전에 재생한 인덱스를 제외하고 범위를 하나 줄여서 뽑는 방식으로, 같은 사운드가 루프 없이 연속으로 중복 재생되는 걸 방지했다 AOBEnemy 에 GetRandomSpawnSound / GetRandomAttackSound / GetRandomDeathSound BlueprintCallable 래퍼 함수를 추가 근접/원거리 공용으로 쓸 수 있는 AnimNotify( OBAnimNotify_EnemyAttackSound )를 추가해서 공격 사운드 재생을 처리하도록 했다 적 스폰/공격/사망 사운드 각각에 Sound Concurrency를 구성했다: CC_EnemySpawn (Max 6, Stop Oldest), CC_EnemyAttack (Max 8, Stop Lowest Priority, Retrigger 0.1초), CC_EnemyDeath (Max 5, Stop Lowest Priority, Retrigger 0.05초) — 셋 다 Volume Scale Mode를 Priority로 둬서 겹칠수록 자연스럽게 감쇠되도록 처리했다 4. 원거리 공격 이펙트 적용 및 발사 방향 버그 수정 적 원거리 공격 히트 이펙트를 실제로 적용 완료했다. 그 과정에서 벽(WorldStatic)에 대한 오버랩 처리가 빠져있던 걸 발견해서 추가 — 기존엔 대상 판정에 WorldStatic이 안 들어가 있어서 벽에 맞아도 히트 이펙트가 안 터졌었다 원거리 공격 발사 방향도 손봤다. 기존엔 플레이어의 현재 위치를 직접 조준해서 쏘고 있었는데, 이게 부자연스러워서 적의 Forward Vector 방향으로 발사하도록 변경했다 정리하면서 안 쓰던 ShockWaves 이펙트도 제거 다음에 할 것 일시정지 메뉴는 지금은 구조/레이아웃만 미리 만들어둔 것이고, 설정/타이틀로 돌아가기/게임 종료 로직 연결은 CommonUI를 먼저 공부하고 적용해볼 예정이라 시기는 미정 내일은 무기 쪽 작업을 계속 이어갈 예정
Score: 54.39Confidence: 49%
View offervelog
문자열을 2글자씩 한 칸씩 이동하며 자른다. 두 문자 모두 영문자인 경우만 List에 저장. 다중집합이므로 중복을 제거하면 안 된다. 교집합 계산 시 매칭된 원소를 복사본에서 제거. 합집합 = A크기 + B크기 - 교집합. 자카드 = 교집합 / 합집합. int 나눗셈 주의 → double 형변환. 합집합이 0이면 65536. 핵심: 중복을 허용하는 집합에서 매칭 문제 → 한 번 매칭한 원소를 다시 사용하지 않는다.
Score: 54.39Confidence: 49%
View offervelog
파이썬 초보 탈출 - 코딩 면허시험 문제풀이 목차 Q1. 문자열 바꾸기 Q2. 딕셔너리 값 추출하기 Q3. 리스트 더하기와 extend() Q4. 리스트 총합 구하기 Q5. 피보나치 수열 Q6. 숫자의 총합 구하기 Q7. 한 줄 구구단 Q8. 파일 역순 저장 Q9. 총합과 평균 구하기 Q10. Calculator 클래스 Q11. 모듈 불러오기 Q12. 오류와 예외 처리 Q13. DashInsert Q16. 모스 부호 해독 Q17. 기초 메타 문자 Q18. 문자열 검색 Q19. 그루핑 Q20. 전방 탐색 Q1. 문자열 바꾸기 문자열 a:b:c:d 에서 : 을 기준으로 문자열을 나누고 다시 # 으로 연결한다. 풀이 a = "a:b:c:d" result = "#".join(a.split(":")) print(result) 실행 결과 a#b#c#d 동작 과정 먼저 a.split(":") 을 실행하면 ['a', 'b', 'c', 'd'] 가 된다. 그리고 "#".join(...) 을 사용하면 a#b#c#d 처럼 리스트의 요소 사이에 # 을 넣어 다시 문자열로 만든다. "a:b:c:d" ↓ split(":") ↓ ['a', 'b', 'c', 'd'] ↓ "#".join() ↓ "a#b#c#d" 핵심 split() → 문자열 나누기 join() → 문자열 연결하기 Q2. 딕셔너리 값 추출하기 딕셔너리에 존재하지 않는 Key를 a['C'] 처럼 조회하면 KeyError 가 발생한다. a = { 'A': 90, 'B': 80 } 이때 C 가 없다면 기본값 70 을 얻고 싶다. 풀이 get() 을 사용하면 간단하다. a = { 'A': 90, 'B': 80 } print(a.get('C', 70)) 실행 결과 70 get() 딕셔너리.get(Key, 기본값) 형태로 사용할 수 있다. a.get('A', 70) A 가 존재하므로 90 을 반환한다. 반면 a.get('C', 70) 에서는 C 가 없기 때문에 70 을 반환한다. 핵심 a['C'] → Key가 없으면 오류 a.get('C') → Key가 없으면 None a.get('C', 70) → Key가 없으면 70 Q3. 리스트 더하기와 extend() 다음 두 코드는 결과만 보면 동일하다. + a = [1, 2, 3] a = a + [4, 5] print(a) [1, 2, 3, 4, 5] extend() a = [1, 2, 3] a.extend([4, 5]) print(a) [1, 2, 3, 4, 5] 하지만 동작 방식에는 차이가 있다. + a = a + [4, 5] 은 기존 리스트를 수정하는 것이 아니라 새로운 리스트를 만들어 a 가 다시 가리키게 한다. a = [1, 2, 3] print(id(a)) a = a + [4, 5] print(id(a)) 두 id() 값이 달라진다. 기존 리스트 [1, 2, 3] + [4, 5] ↓ 새로운 리스트 [1, 2, 3, 4, 5] extend() a.extend([4, 5]) 는 기존 리스트 자체에 값을 추가한다. a = [1, 2, 3] print(id(a)) a.extend([4, 5]) print(id(a)) 객체의 id() 가 유지된다. 핵심 방법 동작 a + b 새로운 리스트 생성 a.extend(b) 기존 리스트 수정 Q4. 리스트 총합 구하기 점수 중에서 50점 이상인 값만 더한다. A = [20, 55, 67, 82, 45, 33, 90, 87, 100, 25] 풀이 A = [20, 55, 67, 82, 45, 33, 90, 87, 100, 25] result = 0 for score in A: if score >= 50: result += score print(result) 실행 결과 481 동작 과정 20 → X 55 → +55 67 → +67 82 → +82 45 → X 33 → X 90 → +90 87 → +87 100 → +100 25 → X 조금 더 간단하게 result = sum(score for score in A if score >= 50) 하지만 지금 단계에서는 for + if 방식의 흐름을 정확히 이해하는 것이 더 중요하다. Q5. 피보나치 수열 피보나치 수열은 앞의 두 값을 더해 다음 값을 만든다. 0, 1, 1, 2, 3, 5, 8, 13 ... 즉 0 + 1 = 1 1 + 1 = 2 1 + 2 = 3 2 + 3 = 5 ... 형태이다. n개의 피보나치 수 만들기 def fibonacci(n): result = [] a = 0 b = 1 for _ in range(n): result.append(a) a, b = b, a + b return result n = int(input("숫자 입력: ")) print(fibonacci(n)) 예를 들어 숫자 입력: 8 결과 [0, 1, 1, 2, 3, 5, 8, 13] 핵심 코드 a, b = b, a + b 예를 들어 a = 0 b = 1 이라면 a = 1 b = 1 이 되고 다음에는 a = 1 b = 2 가 된다. Q6. 숫자의 총합 구하기 사용자가 다음과 같이 입력한다고 해보자. 65,45,2,3,45,8 콤마를 기준으로 값을 나눈 후 숫자로 변환하여 합계를 구한다. 풀이 data = input("값을 입력하세요: ") numbers = map(int, data.split(",")) result = sum(numbers) print(result) 실행 결과 168 동작 과정 "65,45,2,3,45,8" ↓ split(",") ['65', '45', '2', '3', '45', '8'] ↓ map(int, ...) 65, 45, 2, 3, 45, 8 ↓ sum() 168 Q7. 한 줄 구구단 사용자로부터 2~9 중 하나를 입력받아 해당 구구단의 결과를 한 줄로 출력한다. 풀이 dan = int( input("구구단을 출력할 숫자를 입력하세요(2~9): ") ) for i in range(1, 10): print(dan * i, end=" ") 예를 들어 2 를 입력하면 2 4 6 8 10 12 14 16 18 이 출력된다. end=" " 기본적인 print() 는 출력이 끝나면 줄을 바꾼다. print(1) print(2) 1 2 하지만 print(1, end=" ") print(2, end=" ") 처럼 사용하면 1 2 한 줄로 출력할 수 있다. Q8. 파일 역순 저장 파일 내용이 다음과 같다고 해보자. AAA BBB CCC DDD EEE 이를 역순으로 변경한다. EEE DDD CCC BBB AAA 풀이 with open("abc.txt", "r", encoding="utf-8") as f: lines = f.readlines() lines.reverse() with open("abc.txt", "w", encoding="utf-8") as f: f.writelines(lines) readlines() f.readlines() 파일을 줄 단위로 읽어 리스트로 반환한다. 예를 들어 [ "AAA\n", "BBB\n", "CCC\n", "DDD\n", "EEE\n" ] 와 같은 형태이다. reverse() lines.reverse() 리스트의 순서를 실제로 뒤집는다. AAA EEE BBB DDD CCC → CCC DDD BBB EEE AAA 주의할 점 텍스트 파일의 마지막 줄에 \n 이 없다면 역순 저장 후 EEEDDD 처럼 두 줄이 붙는 문제가 생길 수 있다. 즉 파일에서 줄바꿈 문자 \n 도 데이터의 일부 라는 점을 기억하자. Q9. 총합과 평균 구하기 파일에 숫자가 한 줄에 하나씩 저장되어 있다고 해보자. 70 60 55 75 95 90 80 80 85 100 이 값을 읽어 총합 평균 을 계산하고 평균을 result.txt 에 저장한다. 풀이 with open( "sample.txt", "r", encoding="utf-8" ) as f: numbers = [ int(line.strip()) for line in f ] total = sum(numbers) average = total / len(numbers) print("총합:", total) print("평균:", average) with open( "result.txt", "w", encoding="utf-8" ) as f: f.write(str(average)) 실행 결과 총합: 790 평균: 79.0 result.txt 79.0 평균 공식 평균 = 전체 합계 / 데이터 개수 즉 790 / 10 = 79.0 이다. Q10. Calculator 클래스 숫자 리스트를 입력받아 sum() avg() 기능을 제공하는 클래스를 만든다. 풀이 class Calculator: def __init__(self, numbers): self.numbers = numbers def sum(self): return sum(self.numbers) def avg(self): return sum(self.numbers) / len(self.numbers) cal1 = Calculator([1, 2, 3, 4, 5]) print(cal1.sum()) print(cal1.avg()) cal2 = Calculator([6, 7, 8, 9, 10]) print(cal2.sum()) print(cal2.avg()) 실행 결과 15 3.0 40 8.0 왜 self.result 를 계속 누적하면 안 될까? 예를 들어 def sum(self): for i in self.numbers: self.result += i 를 실행한 뒤 다시 avg() 에서 self.result += i 를 하면 앞에서 계산했던 값이 남아 있어 중복으로 더해진다. 따라서 합계와 평균처럼 단순 계산은 매번 sum(self.numbers) 를 사용하는 것이 훨씬 깔끔하다. Q11. 모듈 불러오기 다음 위치에 C:\doit\mymod.py 모듈이 있다고 가정해 보자. 파이썬에서 import mymod 를 사용할 수 있도록 하는 대표적인 방법은 세 가지가 있다. 1. sys.path 추가 import sys sys.path.append(r"C:\doit") import mymod 파이썬이 모듈을 검색하는 경로에 C:\doit 을 추가한다. 2. PYTHONPATH Windows 명령 프롬프트에서 set PYTHONPATH=C:\doit 환경 변수를 설정한 후 python 을 실행하면 import mymod 를 사용할 수 있다. 3. 모듈이 있는 위치에서 실행 cd C:\doit python 이후 import mymod 를 실행한다. 핵심 파이썬은 모듈을 sys.path 에 등록된 경로에서 찾는다. 확인하려면 import sys print(sys.path) 를 사용할 수 있다. Q12. 오류와 예외 처리 다음 코드를 살펴보자. result = 0 try: [1, 2, 3][3] "a" + 1 4 / 0 except TypeError: result += 1 except ZeroDivisionError: result += 2 except IndexError: result += 3 finally: result += 4 print(result) 실행 과정 초기값 result = 0 먼저 [1, 2, 3][3] 이 실행된다. 리스트 인덱스는 0 1 2 ↓ ↓ ↓ 1 2 3 까지만 존재한다. 따라서 IndexError 가 발생한다. except 이동 except IndexError: result += 3 가 실행된다. 0 + 3 = 3 그리고 try 블록의 나머지 코드는 실행하지 않는다. 따라서 "a" + 1 과 4 / 0 은 실행되지 않는다. finally finally 는 오류 발생 여부와 관계없이 실행된다. finally: result += 4 따라서 3 + 4 = 7 최종 결과 7 이다. Q13. DashInsert 숫자로 이루어진 문자열에서 홀수 + 홀수 가 연속되면 - 를 넣고, 짝수 + 짝수 가 연속되면 * 을 넣는다. 예를 들어 4546793 을 처리하면 454*67-9-3 이 된다. 풀이 data = "4546793" numbers = list(map(int, data)) result = [] for i, num in enumerate(numbers): result.append(str(num)) if i == len(numbers) - 1: continue next_num = numbers[i + 1] if num % 2 == 1 and next_num % 2 == 1: result.append("-") elif num % 2 == 0 and next_num % 2 == 0: result.append("*") print("".join(result)) 실행 결과 454*67-9-3 enumerate() for i, num in enumerate(numbers): 을 사용하면 인덱스 + 값 을 동시에 가져올 수 있다. 예를 들어 numbers = [4, 5, 4] 이면 i=0, num=4 i=1, num=5 i=2, num=4 순서로 반복한다. 홀수 판별 num % 2 == 1 나머지가 1 이면 홀수이다. 짝수 판별 num % 2 == 0 나머지가 0 이면 짝수이다. Q14 / Q15 참고한 게시글에는 Q13 다음이 바로 Q16으로 이어져 있어 Q14와 Q15 문제 및 풀이가 포함되어 있지 않다. Q16. 모스 부호 해독 모스 부호를 영어 문자로 변경하려면 모스 부호 → 알파벳 형태의 딕셔너리를 만들 수 있다. 모스 부호 딕셔너리 morse_code = { '.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E', '..-.': 'F', '--.': 'G', '....': 'H', '..': 'I', '.---': 'J', '-.-': 'K', '.-..': 'L', '--': 'M', '-.': 'N', '---': 'O', '.--.': 'P', '--.-': 'Q', '.-.': 'R', '...': 'S', '-': 'T', '..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X', '-.--': 'Y', '--..': 'Z' } 해독 함수 def morse(src): result = [] words = src.split(" ") for word in words: chars = word.split() decoded = "" for char in chars: decoded += morse_code[char] result.append(decoded) return " ".join(result) 즉 모스 부호 ↓ 단어 단위 분리 ↓ 문자 단위 분리 ↓ 딕셔너리 조회 ↓ 영어 문자 변환 ↓ 다시 합치기 순서로 처리한다. Q17. 기초 메타 문자 정규식 a[.]{3,}b 를 해석해 보자. [.] 실제 점(.) 문자 를 의미한다. {3,} 앞의 패턴이 3번 이상 이라는 의미이다. 따라서 전체적으로 a + 점(.) 3개 이상 + b 이다. 보기 중 a....b 가 조건을 만족한다. 정답 2번 코드 확인 import re p = re.compile(r"a[.]{3,}b") print(p.match("acccb")) print(p.match("a....b")) print(p.match("aaab")) print(p.match("a.cccb")) a....b 만 정상적으로 매치된다. Q18. 문자열 검색 import re p = re.compile(r"[a-z]+") m = p.search("5 python") print(m.start() + m.end()) 패턴 분석 [a-z]+ 는 영문 소문자가 1개 이상 연속 이라는 의미이다. 따라서 5 python 에서 python 이 매치된다. 인덱스 문자 5 공백 p y t h o n 인덱스 0 1 2 3 4 5 6 7 따라서 m.start() 은 2 이고, m.end() 는 마지막 문자의 다음 위치이므로 8 이다. 따라서 2 + 8 = 10 정답 10 Q19. 그루핑 다음과 같은 데이터가 있다고 해보자. park 010-9999-9998 kim 010-9909-7789 lee 010-8789-7768 전화번호의 마지막 네 자리를 #### 로 변경한다. 정규식 작성 전화번호의 앞부분을 그룹으로 만든다. r"(\d{3}-\d{4})-\d{4}" 해석하면 ( 숫자 3개 - 숫자 4개 ) ↓ 그룹 1 - 숫자 4개 이다. 풀이 import re data = """ park 010-9999-9998 kim 010-9909-7789 lee 010-8789-7768 """ pattern = re.compile( r"(\d{3}-\d{4})-\d{4}" ) result = pattern.sub( r"\g<1>-####", data ) print(result) 결과 park 010-9999-#### kim 010-9909-#### lee 010-8789-#### \g<1> 첫 번째 그룹에서 매치된 문자열 을 의미한다. 따라서 010-9999 부분은 그대로 유지하고 9998 부분만 #### 로 변경한다. Q20. 전방 탐색 이메일 주소 중에서 마지막 도메인이 .com .net 인 경우만 매치하도록 만든다. 기본 패턴 .*[@].*[.].*$ 대략적으로 문자들 @ 문자들 . 문자들 형태의 이메일을 찾는다. 긍정형 전방 탐색 (?=...) 은 뒤에 특정 패턴이 있는지 확인하지만 해당 문자열 자체는 소비하지 않는다. 이를 이용하면 r".*[@].*[.](?=com$|net$).*$" 처럼 작성할 수 있다. 코드 import re pattern = re.compile( r".*[@].*[.](?=com$|net$).*$" ) print( pattern.match("test@gmail.com") ) print( pattern.match("test@daum.net") ) print( pattern.match("test@myhome.co.kr") ) 결과는 개념적으로 test@gmail.com → Match O test@daum.net → Match O test@myhome.co.kr → Match X 가 된다. 핵심 부분 (?=com$|net$) 을 해석하면 뒤에 com으로 끝나거나 또는 net으로 끝나야 한다. 라는 의미이다. (?=...) → 긍정형 전방 탐색 | → OR $ → 문자열 끝 📌 코딩 면허시험 핵심 정리 이번 문제들을 통해 지금까지 배운 파이썬 문법을 전체적으로 복습할 수 있다. 문제 핵심 개념 Q1 split() , join() Q2 딕셔너리 get() Q3 리스트 + , extend() Q4 for , if , 누적 Q5 함수, 피보나치 Q6 split() , map() , sum() Q7 range() , print(end=) Q8 파일 입출력, readlines() Q9 파일 입출력, 합계, 평균 Q10 클래스, 생성자, 메서드 Q11 모듈, sys.path Q12 try , except , finally Q13 enumerate() , 홀짝 판별 Q16 딕셔너리, 함수, 문자열 분리 Q17 정규식 [.] , {m,} Q18 search() , start() , end() Q19 그룹, sub() Q20 긍정형 전방 탐색 ⭐ 전체 학습 흐름 파이썬 기초 ↓ 자료형 ↓ 문자열 / 리스트 / 딕셔너리 ↓ if / while / for ↓ 함수 ↓ 파일 입출력 ↓ 클래스 ↓ 모듈 ↓ 예외 처리 ↓ 정규 표현식 ↓ 종합 문제 풀이 이번 문제에서 특히 다시 확인하면 좋은 부분은 split / join get extend map enumerate 파일 입출력 클래스 try-except-finally 정규 표현식 이다. 코딩 문제를 풀 때는 바로 정답 코드를 작성하려고 하기보다 입력값 → 필요한 처리 → 출력값 으로 문제를 먼저 나눈 뒤, 지금까지 배운 문법 중 무엇을 사용할지 생각하는 습관을 들이는 것이 중요하다.
velog
오늘 아침에 제 자동화 루틴이 몇 개인지 세어봤습니다. 서른여섯 개였습니다. 지난주 화요일 글에서 "하루 열일곱 개가 다섯 채널에 글을 쓴다"고 적었는데, 일주일 만에 두 배가 넘게 늘었더군요. 저는 그동안 뭘 하고 있었길래 숫자가 이렇게 뛴 걸까요? 세어본 김에 실제 운영 기록도 같이 뽑아봤습니다. 지난 7일 동안 이 서른여섯 개 루틴이 실행된 횟수는 630번, 쓴 토큰은 57억 4천만 개, 그중 97.4%가 새로 읽은 게 아니라 캐시에서 다시 읽은 값이었습니다. 실패는 630번 중 3번, 비율로 치면 0.48%입니다. 숫자만 보면 거의 완벽한 시스템처럼 보입니다. 그런데 저는 왜 이 숫자를 보고 마음이 편해지지 않았을까요? 루틴이 두 배로 늘어난 이유 지난주에는 자동화가 다섯 채널(네이버 블로그, Threads, velog, 인스타그램, 유튜브)에 글을 쓰는 걸 지켜보는 게 제 역할의 전부였습니다. 이번 주에 늘어난 건 그 다섯 채널의 후속 작업이었습니다. 스마트스토어 입점을 마치고 위탁판매 상품을 찾는 루틴, 로또 번호를 사고 복기하는 루틴, 크몽 문의에 즉시 답하는 루틴, AI 검색엔진에 제 콘텐츠가 얼마나 인용되는지 추적하는 루틴 같은 것들이 새로 켜졌습니다. 실제 구성을 표로 정리하면 이렇습니다. 트리거 종류 개수 비고 시간 기반(schedule) 35개 매일, 매주 정해진 시각에 실행 이벤트 기반(event) 1개 특정 조건이 오면 깨어남 소속 프로젝트 개수 없음(독립 실행) 8개 유튜브 6개 인스타그램 4개 Threads 4개 크몽 4개 네이버 블로그 3개 공모전 2개 그 외(아이디어, 인류오류노트, 오래된 질문, E:LAB 대시보드) 4개 velog 1개 이 글을 쓰는 velog 루틴도 이 서른여섯 개 중 하나입니다. 제가 지금 쓰고 있는 이 문장도 사실 그 서른여섯 개 중 하나가 만들고 있는 셈이죠. 이상하지 않나요? 저는 분명 제 블로그에 글을 쓰고 있다고 생각했는데, 정작 손가락을 움직이는 건 제가 아니었습니다. 숫자가 늘어난 속도도 곱씹어볼 만합니다. 채널 다섯 개를 돌리던 열일곱 개에서, 채널의 "다음 단계"를 처리하는 열아홉 개가 더 붙었습니다. 늘어난 절반 가까이가 유튜브(6개)에 몰려 있는데, 이건 제가 채널을 하나 더 만들 때마다(실험실 채널, 테크 가십 채널, 인류오류노트, 오래된 질문 순서로) 발행과 회고, 댓글고정까지 세트로 켜기 때문입니다. 채널 하나를 늘린다는 게 실제로는 루틴 서너 개를 늘린다는 뜻이었다는 걸, 표로 정리하고 나서야 알았습니다. 모델을 두 등급으로 나눈 이유 서른여섯 개 중 스물한 개는 무거운 모델(Opus), 열네 개는 가벼운 모델(Sonnet)을 씁니다. 관찰과 댓글처럼 빈도는 높지만 판단이 단순한 루틴은 가벼운 모델로, 제작과 고객 응대처럼 결과물의 질이 직접 매출에 연결되는 루틴은 무거운 모델로 갈라뒀습니다. 이 배분을 처음 할 때는 "다 비싼 모델로 돌리면 안 되나"라는 생각도 했습니다. 그런데 단가를 계산해보니 무거운 모델이 가벼운 모델보다 입력 기준 2.5배 비쌌습니다. 하루 여러 번 도는 관찰과 댓글류 루틴까지 전부 비싼 모델로 돌리면, 한 달 안에 제한에 걸려서 정작 중요한 제작 루틴이 못 도는 역설이 생깁니다. 단가를 조금 더 구체적으로 적으면, 무거운 모델은 입력 기준 100만 토큰당 5달러, 가벼운 모델은 2달러입니다. 출력은 각각 25달러와 10달러, 캐시를 다시 읽는 비용은 0.5달러와 0.2달러입니다. 정확히 2.5배 차이인데, 지난 7일 소비의 97.4%가 캐시를 다시 읽는 항목이었으니 이 2.5배가 그대로 누적됩니다. 그래서 이번 주에 실제로 한 일 중 하나가 velog 루틴 자체를 비싼 모델에서 가벼운 모델로 옮긴 것이었습니다. 이 글도 그 가벼운 모델이 쓰고 있습니다. 아이러니하게도, 제 역할을 "루틴이 뭘 써야 할지 정하는 사람"으로 좁혀갈수록 정작 제가 직접 결정한 건 "무엇을 쓸지"가 아니라 "무엇으로 쓸지"였습니다. 실패율 0.48%가 저를 더 불안하게 만든 이유 숫자로만 보면 이건 아주 잘 돌아가는 시스템입니다. 630번 중 3번만 실패했으니까요. 그런데 저는 이 숫자를 처음 봤을 때 오히려 등골이 서늘했습니다. 왜였을까요? 지난주 토요일 글에 자동화 실패 4건을 적은 적이 있습니다. 전부 "성공했다"는 메시지를 받고도 실제로는 실패한 경우였습니다. 로그에는 정상 종료로 남아 있는데 실제 산출물을 열어보면 아무것도 없거나, 중복으로 두 번 올라가 있거나, 엉뚱한 값이 저장돼 있었습니다. 이번 주 3건의 실패도 마찬가지 유형이 섞여 있었을 겁니다. 630번 중 3번이라는 숫자는 정확하지만, 그 3번 중 몇 번이 "실패라고 표시된 실패"이고 몇 번이 "성공이라고 표시된 실패"인지는 이 숫자만으로는 알 수 없습니다. 이 구분을 하려면 로그의 마지막 줄이 아니라 실제 산출물을 봐야 합니다. 채널에 실제로 올라간 글의 목록, 대시보드에 실제로 찍힌 행, 실제로 발행된 영상 링크 같은 것들이요. 멈춘 줄 알았던 루틴이 사실은 끝까지 실행돼서 결과물까지 남겨놓은 경우도 있었고, 반대로 로그에는 완료라고 찍혔는데 결과물이 하나도 없는 경우도 있었습니다. 로그를 믿는 것과 결과물을 믿는 것 사이에는 생각보다 큰 간극이 있습니다. 저는 여기서 제 역할이 뭔지 다시 생각하게 됐습니다. 처음에는 제가 "글을 쓰는 사람"이라고 생각했습니다. 그다음에는 "통과시킬지 막을지 정하는 사람"이라고 생각을 바꿨습니다. 그런데 지금은 그것도 아닌 것 같습니다. 저는 "성공이라고 적힌 걸 의심하는 사람"이 된 것 같습니다. 권한을 세 단계로 나눈 이유 루틴이 서른여섯 개가 되면서 제가 실제로 손댄 건 개별 루틴의 프롬프트가 아니라 권한 구조였습니다. 각 루틴은 읽기 전용(read-only), 확인 필요(guard), 전체 권한(full-access) 세 단계 중 하나로 실행됩니다. 읽기 전용은 데이터를 조회만 하고 아무것도 바꾸지 못합니다. 확인 필요는 돈이 나가거나 계약이 걸리는 작업 직전에 저한테 먼저 물어봅니다. 전체 권한은 발행이나 댓글처럼 이미 정해진 규칙 안에서는 혼자 결정해서 실행합니다. 인스타그램 팔로우 루틴은 하루 최대 5명, 네이버 이웃 댓글 루틴은 하루 최대 25개처럼 상한을 걸어둔 것도 전체 권한이 폭주하지 않게 하는 장치입니다. 숫자 상한을 정하는 기준이 뭐냐고 물으신다면, 솔직히 처음엔 감이었습니다. 팔로우 5명, 댓글 25개는 "이 정도면 하루치 활동처럼 보이겠다"는 감각으로 잡은 값입니다. 그런데 이 감각도 한 번 부러진 적이 있습니다. 인스타그램 팔로우 루틴이 상한을 넘겨서 실행된 날, 계정이 일시적으로 제한 경고를 받았습니다. 그날 이후로 상한은 "넉넉하게 잡은 목표치"가 아니라 "넘으면 진짜 문제가 생기는 선"으로 다시 정의했습니다. 이 구조를 만들면서 든 생각은, 제가 하는 일이 "자동화를 만드는 일"이 아니라 "자동화가 넘지 말아야 할 선을 긋는 일"에 가깝다는 것이었습니다. 코드 한 줄을 더 짜는 것보다 "이건 물어보고 가", "이건 상한 5", "이건 그냥 해"를 정하는 데 더 많은 시간을 씁니다. 이게 개발일까요, 아니면 다른 이름이 필요한 일일까요? 캐시 97.4%가 말해주는 것 토큰 57억 4천만 개 중 97.4%가 캐시에서 다시 읽은 값이라는 것도 곱씹어볼 만한 숫자입니다. 이 말은 루틴들이 매번 새로운 걸 생각해내는 게 아니라, 같은 맥락(이전 대화, 메모리 파일, 규칙 문서)을 계속 다시 불러다 쓰고 있다는 뜻입니다. 새로 만들어내는 부분은 2.6%뿐입니다. 이 숫자를 보고 처음 든 생각은 "낭비가 심하다"였습니다. 같은 걸 왜 이렇게 자주 다시 읽을까 싶었죠. 그런데 다시 생각해보면 이게 오히려 제가 원했던 그림이기도 합니다. 매번 처음부터 판단하는 루틴보다, 지난 실수와 규칙을 기억하고 그 위에서 판단하는 루틴이 더 낫습니다. 가운뎃점을 쓰지 말라는 규칙 하나를 서른네 개 루틴 프롬프트 맨 앞에 박아넣은 것도, DM을 유도하는 문구를 쓰지 말라는 규칙도, 전부 "한 번 정한 규칙을 계속 다시 읽게" 만든 결과입니다. 낭비처럼 보이는 97.4%가 사실은 제가 계속 지키고 싶은 규칙들의 무게였습니다. 규칙을 규칙 문서 한 곳에 몰아두지 않고 여러 층으로 나눈 것도 같은 이유입니다. 계정 전체에 적용되는 규칙, 채널마다 다른 규칙, 루틴 하나에만 적용되는 규칙을 구분해두지 않으면, 규칙을 고칠 때마다 서른여섯 개 프롬프트를 전부 열어봐야 합니다. 층을 나눠두면 계정 전체 규칙 하나를 고치는 것만으로 서른여섯 개가 동시에 업데이트됩니다. 이게 제가 이번 주에 한 일 중 "글쓰기"라고 부를 수 없는 또 하나의 작업이었습니다. 실제로 있었던 일: 로그는 멀쩡한데 결과물이 없던 날 말로만 하면 추상적이니 실제 사례 하나를 적습니다. 이번 주 초, 유튜브 채널 하나의 롱폼 발행 루틴이 "정상 종료"로 기록됐습니다. 세션 로그 마지막 줄도 평범했습니다. 그런데 실제 채널에 들어가보니 새 영상이 없었습니다. 원인을 찾으려고 로그가 아니라 세션 기록 테이블을 직접 열어봤습니다. 실행 시간과 턴 수를 보니, 렌더링 단계에서 멈춘 뒤 그 상태로 "완료"라고 잘못 표시된 경우였습니다. 메시지가 수십 개 쌓이고 토큰도 정상적으로 소비된 걸 보면 작업은 분명히 진행됐는데, 마지막에 결과를 업로드하는 단계에서 조용히 실패한 것이었습니다. 이런 실패는 로그 줄 수만 봐서는 절대 안 걸립니다. 메시지 4개짜리 빈 실행(시작하자마자 죽은 경우)과 메시지 수십 개짜리 실행(끝까지 갔다가 마지막에 죽은 경우)은 원인도 다르고 고치는 방법도 다릅니다. 이 사례를 겪고 나서 바꾼 게 하나 있습니다. 이제는 "루틴이 끝났다"는 말을 믿지 않고, 채널에 실제로 뭐가 올라갔는지를 먼저 확인합니다. 저한테는 이게 이번 주에 배운 가장 중요한 교훈이었습니다. 자동화를 늘리는 것보다, 자동화가 거짓으로 "성공"이라고 말하지 않는지 확인하는 절차를 만드는 게 더 오래 걸렸습니다. 다른 개발자들은 이 질문에 뭐라고 답했을까 이번 주 트렌딩 글 중에 비슷한 질문을 던진 글이 몇 개 있었습니다. SI 회사에서 4년을 일하다 두 번째 이직을 고민한 분의 글, 개발자 1년차가 "최강이 되고 싶었다"고 적은 글, "Jev가 뭐냐"는 제목으로 새로운 직무 이름 자체를 다시 묻는 글까지, 전부 "나는 지금 뭘 하는 사람인가"를 스스로에게 묻고 있었습니다. 공통점은 답을 명확하게 내린 글이 별로 없다는 것이었습니다. 대신 질문을 더 구체적으로 다듬어가는 과정 자체가 글이 됐습니다. 저도 이번 글을 쓰면서 비슷한 걸 느꼈습니다. "저는 개발자입니다"라고 딱 잘라 말하고 싶었는데, 서른여섯 개 루틴 중 제가 직접 코드를 짠 건 몇 개 안 됩니다. 대신 권한 구조를 설계하고, 상한을 정하고, 실패를 의심하는 일에 시간을 씁니다. 이게 새로운 직무라면 이름이 있어야 할 텐데, 아직 그 이름을 못 찾았습니다. 그래서 저는 뭘 하는 사람일까요 이 질문에 아직 답을 못 찾았습니다. 개발자라고 하기엔 제가 짜는 코드가 없는 날이 더 많습니다. 운영자라고 하기엔 실제 운영 판단(뭘 팔지, 뭘 그만둘지)은 여전히 제가 직접 합니다. 관리자라고 하면 제일 가깝긴 한데, 관리하는 대상이 사람이 아니라 서른여섯 개의 자동화라는 게 좀 낯섭니다. 확실한 건 하나입니다. 루틴이 열일곱 개였을 때는 각각을 다 기억할 수 있었는데, 서른여섯 개가 되니 표로 정리하지 않으면 전체 그림이 안 보입니다. 다음에 이 숫자를 다시 셀 때는 더 늘어 있을 것 같습니다. 그때는 또 어떤 이름으로 제 역할을 불러야 할지, 지금은 잘 모르겠습니다. 제 결론은 일단 이렇습니다. 저는 글을 쓰는 사람도 아니고, 코드를 짜는 사람도 아니고, 결정을 대신 내려주는 사람도 아닙니다. 서른여섯 개의 판단 기준을 정해두고, 그 기준이 실제로 지켜지고 있는지 매주 확인하는 사람에 가깝습니다. 이게 개발자의 새로운 형태인지, 아니면 완전히 다른 직무인지는 아직 판단이 안 섭니다. 혹시 여러분은 이런 걸 뭐라고 부르시나요? 저와 비슷한 구조를 돌리고 계신 분이 있다면, 권한을 어떻게 나누고 계신지, 실패를 어떻게 걸러내고 계신지 댓글로 들어보고 싶습니다. 긴 글 읽어주셔서 감사합니다.
velog
오늘 한 일 근무 오늘은... 남은 작업... 마무리 처리... 그리고나니 기존 작업에 대해 쌓인 작업을 처리해야하네.. 적용 적용.
Score: 54.39Confidence: 49%
View offervelog
09-2 AI와 함께 파이썬 공부하기 목차 1. AI를 파이썬 공부에 활용하기 2. AI가 도움이 되는 순간 3. 질문을 잘하는 방법 4. 오류 원인 찾기 5. 코드 수정하기 6. 코드 설명 받기 7. 연습문제 만들기 8. 코드 스타일 개선하기 9. AI 답변 검증하기 10. AI를 이용한 공부 방법 정리 1. AI를 파이썬 공부에 활용하기 파이썬을 공부하다 보면 책이나 강의를 보면서도 이해되지 않는 부분이 생긴다. 예를 들어 왜 오류가 발생하지? 왜 결과가 내가 생각한 것과 다르지? 이 코드는 왜 이렇게 작성하지? 이 개념을 더 쉽게 설명할 수 없을까? 와 같은 궁금증이 생긴다. 과거에는 이런 문제가 생기면 검색 ↓ 여러 글 확인 ↓ 내 상황과 비슷한 내용 찾기 ↓ 직접 해결 하는 과정이 필요했다. 하지만 AI를 활용하면 현재 작성하고 있는 코드와 오류 내용을 그대로 보여 주면서 질문할 수 있다. 즉 AI를 모르는 내용을 바로 질문할 수 있는 개인 학습 도우미 처럼 활용할 수 있다. 2. AI가 특히 도움이 되는 순간 파이썬을 처음 공부할 때 자주 막히는 상황은 크게 세 가지이다. 1. 오류 메시지를 이해하지 못할 때 IndexError TypeError KeyError NameError 등의 오류가 발생했지만 원인을 모르겠을 때이다. 2. 코드가 원하는 대로 동작하지 않을 때 코드는 실행되지만 내가 기대한 결과 ≠ 실제 결과 인 경우이다. 3. 개념을 이해하기 어려울 때 예를 들어 클래스 클로저 데코레이터 이터레이터 제너레이터 정규 표현식 같은 개념이 책의 설명만으로 잘 이해되지 않을 수 있다. 이때 AI에게 더 쉽게 설명해 줘 예제를 추가해서 설명해 줘 한 줄씩 설명해 줘 처럼 요청할 수 있다. 3. 질문을 잘하는 방법 AI에게 질문할 때 단순히 이거 왜 안 돼? 라고 물어보는 것보다 필요한 정보를 함께 제공하는 것이 좋다. 특히 다음 네 가지를 알려 주면 된다. 1. 내가 하고 싶은 것 2. 내가 작성한 코드 3. 실제 실행 결과 또는 오류 메시지 4. 내가 기대한 결과 질문 구조 예를 들어 다음과 같은 형태이다. 목표 ↓ 리스트의 모든 값을 더하고 싶습니다. 코드 ↓ numbers = [1, 2, 3] ... 실제 결과 ↓ IndexError가 발생합니다. 원하는 결과 ↓ 6이 출력되었으면 좋겠습니다. 이렇게 질문하면 AI도 무엇을 만들고 있는지 어디에서 문제가 발생했는지 어떤 결과를 원하는지 를 정확하게 파악할 수 있다. 4. 오류 원인 찾기 다음 코드를 실행했다고 해보자. numbers = [1, 2, 3] print(numbers[3]) 오류가 발생한다. IndexError: list index out of range 이럴 때 AI에게 코드와 오류를 함께 전달한다. 왜 오류가 발생할까? 리스트가 numbers = [1, 2, 3] 이라면 인덱스는 다음과 같다. 값 1 2 3 인덱스 0 1 2 따라서 numbers[3] 은 존재하지 않는 네 번째 위치를 요청하는 것이다. 그래서 IndexError 가 발생한다. 정상적인 마지막 값은 numbers[2] 이다. 오류를 질문할 때 중요한 점 오류 메시지가 길더라도 가능하면 중간 부분을 임의로 제거하지 않는 것이 좋다. 예를 들어 Traceback ... 파일 위치 오류가 발생한 줄 오류 종류 오류 메시지 에는 문제를 찾는 데 필요한 정보가 포함되어 있다. 따라서 코드 + 전체 오류 메시지 를 같이 전달하는 것이 좋다. 5. 코드 수정하기 다음 코드를 보자. numbers = [1, 2, 3] total = 0 for i in range(4): total += numbers[i] print(total) 목표는 1 + 2 + 3 = 6 을 출력하는 것이다. 하지만 오류가 발생한다. 문제 원인 range(4) 는 0 1 2 3 을 생성한다. 따라서 반복 과정은 numbers[0] → 1 numbers[1] → 2 numbers[2] → 3 numbers[3] → 오류 가 된다. numbers[3] 은 존재하지 않는다. 수정 방법 1 numbers = [1, 2, 3] total = 0 for i in range(3): total += numbers[i] print(total) 결과 6 수정 방법 2 리스트의 길이를 직접 사용하면 더 좋다. numbers = [1, 2, 3] total = 0 for i in range(len(numbers)): total += numbers[i] print(total) 이렇게 하면 리스트 길이가 바뀌어도 사용할 수 있다. 수정 방법 3 인덱스가 필요 없다면 값을 직접 반복할 수 있다. numbers = [1, 2, 3] total = 0 for number in numbers: total += number print(total) 결과 6 이 방법이 가장 읽기 쉽다. 6. AI에게 코드를 수정해 달라고 할 때 단순히 이 코드 고쳐 줘. 라고 요청하는 것보다 현재 결과 + 원하는 결과 를 알려 주는 것이 중요하다. 예를 들어 현재는 IndexError가 발생합니다. 리스트의 숫자를 모두 더해서 6이 출력되도록 수정해 주세요. 수정한 이유도 설명해 주세요. 처럼 질문할 수 있다. 그러면 단순히 정답 코드만 얻는 것이 아니라 왜 문제가 발생했는지도 함께 공부할 수 있다. 7. 코드 설명 받기 처음 보는 코드를 만났을 때 AI에게 한 줄씩 설명해 달라고 요청할 수도 있다. 예를 들어 for i in range(1, 6): if i % 2 == 0: print(i, "는 짝수입니다.") else: print(i, "는 홀수입니다.") 한 줄씩 살펴보기 range() range(1, 6) 은 1, 2, 3, 4, 5 를 만든다. for for i in range(1, 6): i 에 숫자가 하나씩 들어간다. i = 1 i = 2 i = 3 i = 4 i = 5 나머지 연산자 i % 2 숫자를 2로 나눈 나머지를 구한다. 짝수라면 나머지 = 0 이다. 따라서 if i % 2 == 0: 은 i가 짝수인가? 를 검사하는 코드이다. 전체 흐름 1부터 5까지 반복 ↓ 현재 숫자를 2로 나눔 ↓ 나머지가 0인가? ↙ ↘ Yes No ↓ ↓ 짝수 출력 홀수 출력 8. 이해 수준을 지정해서 질문하기 AI의 설명이 너무 어렵다면 질문할 때 이해 수준을 지정할 수 있다. 예를 들어 파이썬을 처음 배우는 사람 기준으로 설명해 줘. 중학생도 이해할 수 있게 설명해 줘. 코드를 한 줄씩 실행 순서대로 설명해 줘. 변수 값이 어떻게 바뀌는지 표로 보여 줘. 처럼 요청할 수 있다. 특히 코드를 공부할 때는 실행 순서 + 변수 변화 를 같이 보는 것이 이해에 도움이 된다. 9. 연습문제 만들기 AI는 현재 배운 범위에 맞는 문제를 만드는 데도 활용할 수 있다. 예를 들어 현재 if문 for문 함수 까지만 공부했다고 해보자. 이때 if문, for문, 함수까지만 사용해서 풀 수 있는 연습문제를 만들어 줘. 라고 요청할 수 있다. 난이도도 지정 가능 초급 문제 3개 만들어 줘. 중간 난이도로 만들어 줘. 한 문제씩 내 줘. 내가 답을 말하기 전까지 정답은 알려 주지 마. 틀렸을 때는 힌트만 줘. 처럼 공부 방식까지 지정할 수 있다. 10. 문제 풀이에 AI를 사용하는 좋은 방법 AI가 문제를 만들자마자 정답까지 보여 주면 직접 생각할 시간이 줄어든다. 따라서 다음과 같은 방식이 좋다. 문제 출제 ↓ 직접 풀이 ↓ 코드 실행 ↓ 결과 확인 ↓ 모르겠으면 힌트 요청 ↓ 다시 풀이 ↓ 마지막에 정답과 비교 AI를 답을 바로 알려 주는 도구 가 아니라 문제 출제자 + 힌트를 주는 선생님 처럼 사용할 수 있다. 11. 코드 스타일 개선하기 프로그램이 정상적으로 동작하더라도 코드가 읽기 어려울 수 있다. 예를 들어 a = [1, 2, 3, 4, 5] b = 0 for c in a: b += c print(b) 결과 자체는 정상이다. 15 하지만 변수 이름만 보면 각각 무엇을 의미하는지 알기 어렵다. 읽기 쉽게 수정 numbers = [1, 2, 3, 4, 5] total = 0 for number in numbers: total += number print(total) 기능은 동일하지만 훨씬 쉽게 읽을 수 있다. a → numbers b → total c → number 처럼 변수의 역할을 알 수 있기 때문이다. 12. 리팩터링 기존 프로그램의 기능은 유지하면서 코드 구조를 개선하는 것을 리팩터링(Refactoring) 이라고 한다. AI에게 다음과 같이 요청할 수 있다. 기능은 그대로 유지해 주세요. 코드를 더 읽기 쉽게 수정해 주세요. 변수 이름도 역할을 알 수 있게 바꿔 주세요. 수정 전후의 차이도 설명해 주세요. 단순히 짧은 코드가 항상 좋은 코드는 아니다. 중요한 것은 읽기 쉬운가? 의도를 알 수 있는가? 수정하기 쉬운가? 이다. 13. AI의 답변을 그대로 믿으면 안 되는 이유 AI는 매우 유용하지만 항상 정확한 답변만 하는 것은 아니다. 예를 들어 존재하지 않는 함수 잘못된 문법 논리적으로 틀린 코드 현재 버전에서는 동작하지 않는 코드 를 제시할 수도 있다. 따라서 AI가 코드를 만들어 줬다면 반드시 직접 실행 해야 한다. 14. 직접 실행하기 AI가 다음과 같은 코드를 알려 주었다고 해도 numbers = [1, 2, 3] print(sum(numbers)) 눈으로만 보고 맞겠지 라고 생각하는 것이 아니라 실제로 실행한다. 실행 결과 6 처럼 확인한다. 15. 입력값을 바꿔서 테스트하기 한 번 정상 동작했다고 해서 모든 상황에서 정상이라고 볼 수는 없다. 예를 들어 def divide(a, b): return a / b 다음 코드는 정상이다. divide(10, 2) 5.0 하지만 divide(10, 0) 을 실행하면 ZeroDivisionError 가 발생한다. 따라서 여러 입력값을 사용해 보는 것이 중요하다. 정상적인 값 경계값 예외적인 값 등을 테스트해 본다. 16. 테스트하는 습관 예를 들어 양수를 판별하는 함수를 만들었다고 해보자. def is_positive(number): return number > 0 한 가지 값만 확인하지 않는다. print(is_positive(10)) print(is_positive(-10)) print(is_positive(0)) 결과 True False False 처럼 여러 상황에서 확인한다. 17. 이해가 안 되면 다시 질문하기 AI가 설명했지만 여전히 이해되지 않는다면 그대로 넘어가지 않는다. 예를 들어 왜 이렇게 되는지 모르겠어. 더 쉽게 설명해 줘. 변수 값이 어떻게 변하는지 단계별로 보여 줘. 작은 숫자로 예시를 들어 줘. 그림처럼 흐름으로 보여 줘. 처럼 다시 질문하면 된다. 같은 개념도 설명 방법을 바꾸면 훨씬 쉽게 이해될 수 있다. 18. 초보자에게 좋은 질문 방식 처음부터 너무 많은 내용을 한 번에 질문하면 답변도 복잡해질 수 있다. 따라서 하나씩 질문하는 것이 좋다. 예를 들어 파이썬 함수, 클래스, 상속, 오버라이딩, 클로저, 데코레이터를 전부 설명해 줘. 보다는 클래스가 무엇인지 먼저 설명해 줘. 그리고 이해한 뒤 이번에는 상속을 설명해 줘. 처럼 진행한다. 19. 코드만 받지 않기 AI에게 코드를 요청하면 정답 코드를 빠르게 얻을 수 있다. 하지만 코드 복사 ↓ 실행 ↓ 끝 으로 공부하면 실력이 잘 늘지 않는다. 더 좋은 방법은 코드 확인 ↓ 왜 이렇게 작성했는지 이해 ↓ 직접 다시 작성 ↓ 값을 변경해서 테스트 하는 것이다. 20. 좋은 질문과 좋지 않은 질문 비교 좋지 않은 질문 이거 왜 안 돼? 문제 상황을 알기 어렵다. 더 좋은 질문 리스트 [1, 2, 3]의 합계를 구하려고 합니다. 현재 코드는 다음과 같습니다. numbers = [1, 2, 3] total = 0 for i in range(4): total += numbers[i] print(total) IndexError가 발생합니다. 원하는 결과는 6입니다. 왜 오류가 발생하는지 초보자 기준으로 설명해 주세요. AI가 문제 상황을 훨씬 정확하게 이해할 수 있다. 21. 질문 템플릿 파이썬 공부 중 오류가 발생했다면 다음 형식을 사용할 수 있다. [목표] 제가 만들고 싶은 것은 ______ 입니다. [현재 코드] 코드는 다음과 같습니다. ```python 코드 작성 [현재 결과] 실행하면 __ 결과가 나오거나 __ 오류가 발생합니다. [원하는 결과] 제가 원하는 결과는 __ 입니다. [질문] 왜 이런 문제가 발생하는지 파이썬 초보자 기준으로 설명해 주세요. 수정 코드뿐 아니라 수정하는 이유도 같이 설명해 주세요. --- # 22. 코드 공부용 질문 템플릿 코드를 이해하고 싶을 때는 다음처럼 사용할 수 있다. ```text 다음 파이썬 코드를 공부하고 있습니다. ```python 코드 작성 다음 순서로 설명해 주세요. 전체 코드의 목적 한 줄씩 설명 실행 순서 변수 값이 어떻게 변하는지 핵심 문법 초보자가 헷갈릴 만한 부분 23. 연습문제용 질문 템플릿 현재 파이썬에서 다음 내용을 공부했습니다. - 리스트 - if문 - for문 - 함수 이 범위 안에서만 풀 수 있는 연습문제 3개를 만들어 주세요. 조건 - 쉬운 문제부터 순서대로 - 정답은 바로 알려 주지 않기 - 한 문제씩 출제 - 틀리면 힌트만 제공 - 마지막에 전체 풀이 설명 이런 방식으로 AI를 개인 문제 출제 도구처럼 사용할 수 있다. 24. AI를 이용한 추천 학습 흐름 파이썬을 공부할 때 다음과 같은 흐름으로 활용할 수 있다. 책 / 강의 공부 ↓ 직접 코드 작성 ↓ 직접 실행 ↓ 이해됨? ↙ ↘ Yes No ↓ ↓ 다음 내용 AI에게 질문 ↓ 설명 확인 ↓ 코드 수정 ↓ 다시 직접 실행 ↓ 값 변경 테스트 ↓ 직접 다시 작성 핵심은 항상 AI 답변 ↓ 직접 확인 과정을 거치는 것이다. 25. AI에게 질문할 때 기억할 것 좋은 질문에는 보통 다음 정보가 포함된다. 무엇을 하려고 하는가? 현재 코드는 무엇인가? 현재 어떤 일이 발생하는가? 어떤 결과를 원하는가? 그리고 답변을 받은 뒤에는 직접 실행 다른 값으로 테스트 이해되지 않는 부분 재질문 과정을 거친다. 📌 핵심 정리 AI가 유용한 상황 오류 원인을 모를 때 코드가 원하는 대로 동작하지 않을 때 개념이 이해되지 않을 때 코드 설명이 필요할 때 연습문제가 필요할 때 코드를 더 읽기 쉽게 만들고 싶을 때 좋은 질문의 4가지 요소 1. 목표 2. 현재 코드 3. 실제 결과 / 오류 메시지 4. 기대하는 결과 오류 질문 코드 + 전체 오류 메시지 + 원하는 동작 을 함께 전달한다. 코드 설명 한 줄씩 설명해 줘 실행 순서대로 설명해 줘 변수 값이 어떻게 변하는지 보여 줘 처럼 구체적으로 요청한다. 연습문제 현재 배운 범위를 알려 주고 정답은 바로 말하지 않기 힌트만 제공 한 문제씩 진행 처럼 학습 방식까지 지정할 수 있다. 코드 개선 AI에게 단순히 코드를 짧게 만들어 달라고 하기보다 가독성 변수 이름 중복 코드 코드 구조 를 개선해 달라고 요청한다. AI 답변 검증 AI가 알려 준 답변은 반드시 직접 실행 ↓ 입력값 변경 ↓ 2~3번 추가 테스트 ↓ 이상하면 다시 확인 하는 과정이 필요하다. ⭐ 09-2 전체 흐름 파이썬 공부 ↓ 직접 생각하고 코드 작성 ↓ 문제 발생 ↓ AI에게 질문 ↓ ┌─────────────────┐ │ 목표 │ │ 코드 │ │ 실제 결과 │ │ 원하는 결과 │ └─────────────────┘ ↓ AI의 설명 확인 ↓ 직접 코드 수정 ↓ 직접 실행 ↓ 다른 값으로 테스트 ↓ 왜 동작하는지 이해 ↓ 직접 다시 작성 AI는 정답을 대신 작성해 주는 도구로 사용하기보다, 막힌 부분을 설명받고 스스로 이해할 수 있도록 돕는 학습 도구로 사용하는 것이 가장 좋다. 결국 중요한 것은 질문 → 답변 복사 → 끝 이 아니라 질문 → 원리 이해 → 직접 실행 → 테스트 → 직접 다시 작성 의 과정을 반복하는 것이다.
Score: 54.4Confidence: 49%
Score: 54.39Confidence: 49%
Score: 54.39Confidence: 49%
Score: 54.39Confidence: 49%
Score: 54.39Confidence: 49%