<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>pingu_122.log</title>
        <link>https://velog.io/</link>
        <description>성실하자:)</description>
        <lastBuildDate>Fri, 02 Oct 2026 03:21:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>pingu_122.log</title>
            <url>https://velog.velcdn.com/images/pingu_122/profile/291c4722-567c-461d-9fd3-32f0dd126a6c/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. pingu_122.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pingu_122" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[정보처리 실기 문제풀이 DAY4]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC-%EC%8B%A4%EA%B8%B0-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4-DAY4</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC-%EC%8B%A4%EA%B8%B0-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4-DAY4</guid>
            <pubDate>Fri, 02 Oct 2026 03:21:50 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-2020년-1회차">📌 2020년 1회차</h2>
<ul>
<li><strong>1회차 | 처음 문제 풀이</strong> → 점수 : (19/20)<ul>
<li>틀린 문제 : 19번</li>
</ul>
</li>
<li><span style="color:green"><strong>2회차 | 문제 재풀이</strong> → 점수 : (20/20)<ul>
<li>틀린 문제 : x</span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<h2 id="📌-2020년-2회차">📌 2020년 2회차</h2>
<ul>
<li><strong>1회차 | 처음 문제 풀이</strong> → 점수 :(16/20)<ul>
<li>틀린 문제 :2번, 6번, 11번, 15번</li>
</ul>
</li>
<li><span style="color:green"><strong>2회차 | 문제 재풀이</strong> → 점수 :(19/20)<ul>
<li>틀린 문제 : 12번 </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<h2 id="📌-2020년-3회차">📌 2020년 3회차</h2>
<ul>
<li><strong>1회차 | 처음 문제 풀이</strong> → 점수 : (12/20)<ul>
<li>틀린 문제 :3번, 7번, 8번, 9번, 10번, 15번, 17번, 20번</li>
</ul>
</li>
<li><span style="color:yellow"><strong>2회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<h2 id="📌-2020년-4회차">📌 2020년 4회차</h2>
<ul>
<li><strong>1회차 | 처음 문제 풀이</strong> → 점수 :(14/20)<ul>
<li>틀린 문제 :3번, 9번, 10번, 11번, 16번, 19번</li>
</ul>
</li>
<li><span style="color:"><strong>2회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<hr>
<hr>
<h2 id="📌-2021년-1회차">📌 2021년 1회차</h2>
<ul>
<li><span style="color:"><strong>1회차 | 처음 문제 풀이</strong> → 점수 : (10/20)<ul>
<li>틀린 문제 : 2, 3, 4, 8, 10, 12, 14, 15, 18, 19번</span></li>
</ul>
</li>
<li><span style="color:"><strong>2회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<h2 id="📌-2021년-2회차">📌 2021년 2회차</h2>
<ul>
<li><span style="color:"><strong>1회차 | 처음 문제 풀이</strong> → 점수 : (14/20)<ul>
<li>틀린 문제 : 6, 10, 11, 12, 13, 17번</span></li>
</ul>
</li>
<li><span style="color:"><strong>2회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<h2 id="📌-2021년-3회차">📌 2021년 3회차</h2>
<ul>
<li><span style="color:"><strong>1회차 | 처음 문제 풀이</strong> → 점수 : (11/20)<ul>
<li>틀린 문제 : 1, 2, 5, 6, 7, 8, 11, 13, 15</span></li>
</ul>
</li>
<li><span style="color:"><strong>2회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<hr>
<hr>
<h2 id="📌-2022년-1회차">📌 2022년 1회차</h2>
<ul>
<li><span style="color:"><strong>1회차 | 처음 문제 풀이</strong> → 점수 : <ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>2회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<h2 id="📌-2022년-2회차">📌 2022년 2회차</h2>
<ul>
<li><span style="color:"><strong>1회차 | 처음 문제 풀이</strong> → 점수 : <ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>2회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
<hr>
<h2 id="📌-2022년-3회차">📌 2022년 3회차</h2>
<ul>
<li><span style="color:"><strong>1회차 | 처음 문제 풀이</strong> → 점수 : <ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>2회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
<li><span style="color:"><strong>3회차 | 문제 재풀이</strong> → 점수 :<ul>
<li>틀린 문제 : </span></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[매일 1% 성장하기 DAY-31]]></title>
            <link>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-31</link>
            <guid>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-31</guid>
            <pubDate>Thu, 01 Oct 2026 18:00:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="2026년-목표">2026년 목표</h1>
</blockquote>
<ul>
<li>클라우드 환경 운영 경험(상세 내용은 추후 작성)</li>
<li>보안관제 자동화 경험</li>
<li>블로그 꾸준히 작성</li>
<li>자격증 : 정보처리기사(9월 시작), 정보보안기사, AWS-SAA,/// CISA 및 CISSP(정보 보안 관련 경력 5년 이상 필요 (4년 경력+보안 관련 학위 가능))</li>
<li>개발 공부</li>
<li>영어 공부</li>
<li>운동</li>
<li>리팩토링 과정 복습 및 벨로그에 새롭게 작성</li>
<li>클라우드 보안 복습 및 벨로그에 작성
&lt;내년 예정 - 석사 과정&gt;</li>
</ul>
<blockquote>
<h2 id="📅-공부-순서---version-10월">📅 공부 순서 - (Version 10월)</h2>
</blockquote>
<ol>
<li><span style="color:green"><strong>파이썬(점프 투 파이썬) 완강했음 - 지속적인 복습</strong></span></li>
<li><span style="color:yellow"><strong>정보처리기사 실기 - 10월 25일에 시험있음 합격 목표</strong></span></li>
<li><span style="color:yellow"><strong>모의해킹(노말틱 강의) - 10월 말까지 완료 목표</strong></span></li>
<li><span style="color:"><strong>영어 - 11월부터 시작!</strong></span></li>
<li><span style="color:green"><strong>혼자공부하는 네트워크(인프런) 완강했음 - 지속적인 복습</strong> </span></li>
<li><span style="color:"><strong>리펙토링 과정 복습 시작(실습 및 블로그 재작성)</strong></span></li>
<li><span style="color:"><strong>Docker(인프런_devsecops) 강의(모의해킹 끝나고 시작)</strong></span></li>
<li><span style="color:"><strong>AWS 보안 가이드(인프런 강의) - (Docker 인프런 끝나고 시작)</strong></span></li>
<li><span style="color:"><strong>JAVA - (파이썬 어느 정도 할 줄 알때 시작)</strong></span>
→ 1 + 2 + 3 + 5 → 1 + 4 + 6 + 7</li>
</ol>
<hr>
<blockquote>
<h3 id="📅-금주-목표---0928월1004일">📅 금주 목표 - (09/28(월)~10/04(일))</h3>
</blockquote>
<ul>
<li><span style="color:green"><strong>파이썬(점프 투 파이썬)</strong><ul>
<li>Youtube 강의 + Ebook 참고</li>
<li>목표 : 점프 투 파이썬 학습 계속 진행 및 진도율 향상</span></li>
</ul>
</li>
<li><strong>정보처리기사 실기</strong><ul>
<li>목표 : 2020년도 기출문제 2회차 풀이</li>
<li>범위 : 1회차 ~ 4회차</li>
<li>목표 : 2021년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
<li>목표 : 2022년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
</ul>
</li>
<li><strong>모의해킹</strong><ul>
<li>목표 : Day 1 ~ Day 2 완료</li>
<li>범위 : 1주차 ~ 2주차</li>
</ul>
</li>
</ul>
<p><span style="color:"></span></p>
<blockquote>
<h3 id="📅-오늘의-목표--span-stylecolorgreen완료span-or-span-stylecolorred미완료span-or-span-stylecoloryellow진행중span">📅 오늘의 목표 : <span style="color:green"><strong>완료</strong></span> OR <span style="color:red"><strong>미완료</strong></span> OR <span style="color:yellow"><strong>진행중</strong></span></h3>
</blockquote>
<ul>
<li><span style="color:"><strong>모의해킹(노말틱 강의)</strong><ul>
<li><strong>1주차 ~ 하는데 까지 + 블로그 정리</strong></span></li>
</ul>
</li>
<li><span style="color:"><strong>파이썬(nmap 기능 구현해보기)DAY-3 + 블로그 정리</strong></span></li>
<li><span style="color:"><strong>household account 작성(23:50분에 작성)</strong></span> </li>
<li><span style="color:yellow"><strong>정보처리기사 DAY3(2020년도 2회차 문제풀이 및 복습) + 2022년도 1회차 문제풀이</strong></span></li>
<li><span style="color:"><strong>방청소</strong></span></li>
<li><span style="color:green"><strong>분리수거</strong></span></li>
<li><span style="color:"><strong>혼자공부하는 네트워크 복습 DAY3</strong></span></li>
<li><span style="color:"><strong>파이썬 복습 DAY1</strong></span></li>
<li><span style="color:"><strong>빨래(Laundry) 개기</strong></span></li>
</ul>
<blockquote>
<h3 id="💻-오늘완료-한-일">💻 오늘(완료) 한 일</h3>
</blockquote>
<ul>
<li>분리수거</li>
<li></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[혼자공부하는 네트워크 복습 DAY2]]></title>
            <link>https://velog.io/@pingu_122/%ED%98%BC%EC%9E%90%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B5%EC%8A%B5-DAY2</link>
            <guid>https://velog.io/@pingu_122/%ED%98%BC%EC%9E%90%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B5%EC%8A%B5-DAY2</guid>
            <pubDate>Thu, 01 Oct 2026 17:56:30 GMT</pubDate>
            <description><![CDATA[<h1 id="📘-혼자-공부하는-네트워크--복습-day-2">📘 혼자 공부하는 네트워크 — 복습 DAY 2</h1>
<blockquote>
<p><strong>학습 범위:</strong> 네트워크 DAY 1 ~ DAY 11<br><strong>복습 방식:</strong> 20문제 풀이 및 해설<br><strong>학습 내용:</strong> HTTP, TCP/UDP, DNS, ARP, 서브넷, Wireshark, 이중화, 로드 밸런싱, 프록시, TLS, BGP</p>
</blockquote>
<hr>
<h2 id="q1-http의-stateless와-로그인-상태-유지">Q1. HTTP의 Stateless와 로그인 상태 유지</h2>
<p><strong>문제:</strong> HTTP는 Stateless 프로토콜인데, 웹사이트에서 로그인 상태를 계속 유지할 수 있는 이유는 무엇일까?</p>
<p><strong>정답:</strong> 쿠키와 세션 ID를 이용하기 때문이다.</p>
<p><strong>해설</strong></p>
<p>HTTP는 기본적으로 각 요청을 독립적으로 처리하며 이전 요청의 상태를 기억하지 않는다.</p>
<p>하지만 서버에 로그인 세션을 생성하고, 브라우저에 세션 ID를 전달하면 로그인 상태를 유지할 수 있다.</p>
<p><strong>동작 과정</strong></p>
<ol>
<li>사용자가 아이디와 비밀번호로 로그인한다.</li>
<li>서버가 인증 정보를 확인하고 세션을 생성한다.</li>
<li>서버가 세션 ID를 브라우저에 전달한다.</li>
<li>브라우저가 세션 ID를 보통 쿠키에 저장한다.</li>
<li>이후 요청에서 세션 ID를 서버에 전달한다.</li>
<li>서버는 세션 ID를 이용해 저장된 세션 정보를 조회한다.</li>
</ol>
<p><strong>핵심:</strong> HTTP는 Stateless지만, 쿠키와 세션을 활용하면 로그인 상태를 유지할 수 있다.</p>
<hr>
<h2 id="q2-tcp-3-way-handshake">Q2. TCP 3-Way Handshake</h2>
<p><strong>문제:</strong> TCP 연결을 설정할 때 메시지를 주고받는 순서는?</p>
<p><strong>정답</strong></p>
<pre><code class="language-text">클라이언트                         서버
    |                              |
    | ---------- SYN ------------&gt; |
    |                              |
    | &lt;------ SYN + ACK ----------- |
    |                              |
    | ---------- ACK ------------&gt; |
    |                              |
         TCP 연결 수립 완료</code></pre>
<p><strong>해설</strong></p>
<ul>
<li><strong>SYN:</strong> 클라이언트가 서버에 연결을 요청한다.</li>
<li><strong>SYN + ACK:</strong> 서버가 요청을 확인하고 자신도 연결을 요청한다.</li>
<li><strong>ACK:</strong> 클라이언트가 서버의 응답을 확인한다.</li>
</ul>
<p>3-Way Handshake를 통해 양쪽은 통신 준비 상태를 확인하고 초기 순서 번호를 교환한다.</p>
<p>참고로 TCP 연결을 정상적으로 종료할 때는 일반적으로 4-Way Handshake를 수행한다.</p>
<hr>
<h2 id="q3-active-active와-active-standby">Q3. Active-Active와 Active-Standby</h2>
<p><strong>문제:</strong> 주 서버가 서비스를 제공하고 대기 서버가 장애에 대비하는 이중화 방식은?</p>
<p><strong>정답:</strong> Active-Standby</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>Active-Active</th>
<th>Active-Standby</th>
</tr>
</thead>
<tbody><tr>
<td>평상시</td>
<td>두 서버 모두 서비스 제공</td>
<td>주 서버가 서비스 제공</td>
</tr>
<tr>
<td>장애 발생 시</td>
<td>정상 서버가 서비스 처리</td>
<td>대기 서버가 서비스 인계</td>
</tr>
<tr>
<td>특징</td>
<td>서버 자원을 함께 활용</td>
<td>장애에 대비한 대기 서버 운영</td>
</tr>
</tbody></table>
<p><strong>Failover(페일오버)</strong>는 주 서버에 장애가 발생했을 때 대기 서버가 서비스를 이어받는 과정을 의미한다.</p>
<p><strong>핵심:</strong> 이중화가 구성되어 있어도 자동 장애 감지 및 전환 기능이 없다면 서비스 중단이 발생할 수 있다.</p>
<hr>
<h2 id="q4-wireshark-디스플레이-필터">Q4. Wireshark 디스플레이 필터</h2>
<p><strong>문제:</strong> 출발지 IP가 <code>192.168.10.100</code>이고 TCP 목적지 포트가 <code>443</code>인 패킷만 표시하는 필터는?</p>
<p><strong>정답</strong></p>
<pre><code class="language-text">ip.src == 192.168.10.100 &amp;&amp; tcp.dstport == 443</code></pre>
<table>
<thead>
<tr>
<th>필터</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>ip.src</code></td>
<td>출발지 IP</td>
</tr>
<tr>
<td><code>ip.dst</code></td>
<td>목적지 IP</td>
</tr>
<tr>
<td><code>tcp.srcport</code></td>
<td>TCP 출발지 포트</td>
</tr>
<tr>
<td><code>tcp.dstport</code></td>
<td>TCP 목적지 포트</td>
</tr>
<tr>
<td><code>==</code></td>
<td>값이 같음</td>
</tr>
<tr>
<td><code>&amp;&amp;</code></td>
<td>AND 조건</td>
</tr>
<tr>
<td>`</td>
<td></td>
</tr>
</tbody></table>
<p><code>tcp.dstport</code> 조건은 TCP 프로토콜과 목적지 포트를 함께 지정한다.</p>
<p><strong>핵심:</strong> Wireshark의 디스플레이 필터는 <code>src_ip</code>가 아니라 <code>ip.src</code>와 같은 문법을 사용한다.</p>
<hr>
<h2 id="q5-http-put과-patch">Q5. HTTP PUT과 PATCH</h2>
<p><strong>문제:</strong> 기존 프로필에서 나이만 변경하려고 할 때 적합한 HTTP 메서드는?</p>
<p><strong>정답:</strong> PATCH</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>PUT</th>
<th>PATCH</th>
</tr>
</thead>
<tbody><tr>
<td>주요 용도</td>
<td>리소스 전체 대체</td>
<td>리소스의 일부 수정</td>
</tr>
<tr>
<td>요청 데이터</td>
<td>일반적으로 전체 리소스</td>
<td>변경할 항목만</td>
</tr>
<tr>
<td>멱등성</td>
<td>보장</td>
<td>반드시 보장하지는 않음</td>
</tr>
</tbody></table>
<p><strong>PATCH 요청 예시</strong></p>
<pre><code class="language-http">PATCH /users/1 HTTP/1.1
Content-Type: application/json

{
  &quot;age&quot;: 26
}</code></pre>
<p><strong>핵심:</strong> PUT은 전체 대체, PATCH는 부분 수정이다.</p>
<hr>
<h2 id="q6-tls와-대칭-키-암호화">Q6. TLS와 대칭 키 암호화</h2>
<p><strong>문제:</strong> TLS에서 실제 애플리케이션 데이터를 주고받을 때 대칭 키 암호화 방식을 사용하는 이유는?</p>
<p><strong>정답:</strong> 공개 키 암호화보다 일반적으로 처리 속도가 빠르기 때문이다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>대칭 키 암호화</th>
<th>공개 키 암호화</th>
</tr>
</thead>
<tbody><tr>
<td>사용하는 키</td>
<td>같은 키</td>
<td>공개 키와 개인 키</td>
</tr>
<tr>
<td>처리 속도</td>
<td>일반적으로 빠름</td>
<td>상대적으로 느림</td>
</tr>
<tr>
<td>TLS에서의 역할</td>
<td>실제 데이터 암호화</td>
<td>인증 및 키 교환 과정 등에 활용</td>
</tr>
</tbody></table>
<p>TLS는 핸드셰이크 과정에서 세션 키를 안전하게 마련하고, 실제 애플리케이션 데이터는 대칭 키 방식으로 암호화한다.</p>
<p>TLS 1.3에서는 일반적으로 (EC)DHE 키 교환으로 공유 비밀을 만들고 인증서와 전자 서명 등을 통해 서버를 인증한다.</p>
<p><strong>핵심:</strong> 공개 키 기술은 인증과 안전한 키 교환을 지원하고, 대칭 키는 효율적인 데이터 암호화에 사용된다.</p>
<hr>
<h2 id="q7-arp와-기본-게이트웨이">Q7. ARP와 기본 게이트웨이</h2>
<p><strong>문제:</strong> 서로 다른 네트워크에 있는 PC B로 패킷을 보내려면 PC A는 누구의 MAC 주소를 알아내야 할까?</p>
<p><strong>정답:</strong> 기본 게이트웨이의 MAC 주소</p>
<p><strong>네트워크 구성</strong></p>
<pre><code class="language-text">PC A
IP: 192.168.1.10
       |
       | ARP를 통해 게이트웨이 MAC 확인
       v
라우터
IP: 192.168.1.1
       |
       | 라우팅 후 다음 구간으로 전달
       v
PC B
IP: 192.168.2.10</code></pre>
<p><strong>동작 과정</strong></p>
<ol>
<li>PC A는 목적지 IP가 다른 네트워크에 있다는 것을 확인한다.</li>
<li>ARP를 이용해 기본 게이트웨이의 MAC 주소를 알아낸다.</li>
<li>목적지 IP는 PC B의 IP로 유지하고, 목적지 MAC 주소는 기본 게이트웨이의 MAC 주소로 지정한다.</li>
<li>라우터는 라우팅 테이블을 확인한 후 패킷을 다음 구간으로 전달한다.</li>
<li>PC B가 직접 연결된 네트워크에 있다면 라우터는 필요할 때 ARP로 PC B의 MAC 주소를 확인한다.</li>
</ol>
<p><strong>핵심:</strong> IP 주소는 최종 목적지를 나타내고, MAC 주소는 현재 구간에서 프레임을 전달할 대상을 나타낸다.</p>
<hr>
<h2 id="q8-l4와-l7-로드-밸런서">Q8. L4와 L7 로드 밸런서</h2>
<p><strong>문제:</strong> <code>/images</code> 요청은 이미지 서버로, <code>/api</code> 요청은 API 서버로 전달하려면 어떤 로드 밸런서를 사용해야 할까?</p>
<p><strong>정답:</strong> L7 로드 밸런서</p>
<table>
<thead>
<tr>
<th>계층</th>
<th>주요 정보</th>
</tr>
</thead>
<tbody><tr>
<td>L2</td>
<td>MAC 주소</td>
</tr>
<tr>
<td>L3</td>
<td>IP 주소</td>
</tr>
<tr>
<td>L4</td>
<td>IP 주소, TCP/UDP 포트</td>
</tr>
<tr>
<td>L7</td>
<td>HTTP URL, 헤더, 쿠키 등</td>
</tr>
</tbody></table>
<p><strong>L7 로드 밸런서 구성 예시</strong></p>
<pre><code class="language-text">                클라이언트
                    |
                    v
              L7 로드 밸런서
                    |
          +---------+---------+
          |                   |
          v                   v
       /images              /api
          |                   |
          v                   v
      이미지 서버          API 서버</code></pre>
<p>L4는 주로 IP 주소와 TCP/UDP 포트 등 전송 계층의 정보를 이용해 트래픽을 분산한다.</p>
<p>L7은 HTTP 요청의 URL 경로나 헤더 등 애플리케이션 계층의 정보를 분석해 트래픽을 분산할 수 있다.</p>
<p><strong>핵심:</strong> HTTP 요청의 내용에 따라 서버를 선택해야 한다면 L7 로드 밸런서를 활용할 수 있다.</p>
<hr>
<h2 id="q9-dns-조회-과정">Q9. DNS 조회 과정</h2>
<p><strong>문제:</strong> DNS 재귀 리졸버가 도메인의 IP 주소를 찾기 위해 조회하는 서버의 순서는?</p>
<p><strong>정답:</strong> 루트 네임 서버 → TLD 네임 서버 → 권한 있는 네임 서버</p>
<pre><code class="language-text">          DNS 재귀 리졸버
                 |
                 v
          루트 네임 서버
                 |
                 v
           TLD 네임 서버
              (.com)
                 |
                 v
        권한 있는 네임 서버
           (example.com)
                 |
                 v
             DNS 레코드
                 |
                 v
           IP 주소 확인</code></pre>
<p><strong>각 서버의 역할</strong></p>
<ul>
<li><strong>루트 네임 서버:</strong> 해당 TLD 네임 서버의 위치를 안내한다.</li>
<li><strong>TLD 네임 서버:</strong> 해당 도메인을 담당하는 권한 있는 네임 서버의 위치를 안내한다.</li>
<li><strong>권한 있는 네임 서버:</strong> 해당 도메인의 DNS 레코드를 제공한다.</li>
</ul>
<p>실제 조회에서는 DNS 캐시가 존재하면 일부 단계를 생략할 수 있다.</p>
<p><strong>핵심:</strong> 루트 → TLD → 권한 있는 네임 서버 순서로 기억한다.</p>
<hr>
<h2 id="q10-tcp와-udp">Q10. TCP와 UDP</h2>
<p><strong>문제:</strong> 실시간 음성 통화에서 일반적으로 UDP가 활용되는 이유는?</p>
<p><strong>정답:</strong> UDP는 별도의 연결 설정 과정이 없고 TCP처럼 기본적으로 재전송이나 순서 보장을 수행하지 않기 때문이다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>TCP</th>
<th>UDP</th>
</tr>
</thead>
<tbody><tr>
<td>연결 방식</td>
<td>연결 지향형</td>
<td>비연결형</td>
</tr>
<tr>
<td>연결 설정</td>
<td>3-Way Handshake</td>
<td>별도의 연결 설정 없음</td>
</tr>
<tr>
<td>전달 보장</td>
<td>제공</td>
<td>기본적으로 제공하지 않음</td>
</tr>
<tr>
<td>순서 보장</td>
<td>제공</td>
<td>제공하지 않음</td>
</tr>
<tr>
<td>활용</td>
<td>웹 통신, 파일 전송 등</td>
<td>실시간 음성·영상, DNS 등</td>
</tr>
</tbody></table>
<p>UDP는 TCP보다 오버헤드가 작고 지연을 줄이는 데 유리하지만 반드시 전송 속도가 더 빠른 것은 아니다.</p>
<p>실제 성능은 네트워크 상태와 애플리케이션 구현에 따라 달라진다.</p>
<p><strong>핵심:</strong> TCP는 신뢰성 있는 전송을 제공하고, UDP는 단순한 전송 방식으로 실시간 통신에 유리할 수 있다.</p>
<hr>
<h2 id="q11-http-502-bad-gateway">Q11. HTTP 502 Bad Gateway</h2>
<p><strong>문제:</strong> HTTP 502 Bad Gateway는 어떤 상황에서 발생할까?</p>
<p><strong>정답:</strong> 게이트웨이나 프록시 서버가 상위 서버로부터 유효하지 않은 응답을 받았을 때 발생한다.</p>
<pre><code class="language-text">클라이언트
    |
    v
리버스 프록시
    |
    | 상위 서버 응답 문제
    v
   WAS

=&gt; 클라이언트에 502 응답</code></pre>
<p><strong>실무 점검 항목</strong></p>
<ul>
<li>WAS 프로세스가 정상적으로 실행 중인지 확인</li>
<li>프록시에서 WAS의 IP와 포트로 접속할 수 있는지 확인</li>
<li>프록시의 상위 서버 설정 확인</li>
<li>프록시 및 WAS 오류 로그 확인</li>
</ul>
<p><strong>관련 상태 코드</strong></p>
<table>
<thead>
<tr>
<th>상태 코드</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>200</td>
<td>OK</td>
</tr>
<tr>
<td>401</td>
<td>Unauthorized</td>
</tr>
<tr>
<td>404</td>
<td>Not Found</td>
</tr>
<tr>
<td>502</td>
<td>Bad Gateway</td>
</tr>
<tr>
<td>504</td>
<td>Gateway Timeout</td>
</tr>
</tbody></table>
<p>502는 유효하지 않은 응답을 받았을 때 발생하며, 504는 제한 시간 내에 상위 서버의 응답을 받지 못했을 때 발생한다.</p>
<hr>
<h2 id="q12-http-캐시와-304-not-modified">Q12. HTTP 캐시와 304 Not Modified</h2>
<p><strong>문제:</strong> 브라우저가 <code>304 Not Modified</code> 응답을 받으면 어떻게 동작할까?</p>
<p><strong>정답:</strong> 기존에 저장된 캐시 데이터를 재사용한다.</p>
<p><strong>동작 과정</strong></p>
<ol>
<li>브라우저가 이전에 다운로드한 리소스를 캐시에 저장한다.</li>
<li>캐시가 만료되면 서버에 리소스의 변경 여부를 확인한다.</li>
<li>변경되지 않았다면 서버가 <code>304 Not Modified</code>를 응답한다.</li>
<li>브라우저가 기존 캐시 데이터를 재사용한다.</li>
</ol>
<p><strong>캐시의 장점</strong></p>
<ul>
<li>불필요한 네트워크 트래픽 감소</li>
<li>리소스 응답 속도 향상</li>
<li>서버의 데이터 전송 부담 감소</li>
</ul>
<p>캐시가 아직 유효하다면 서버에 재검증 요청을 보내지 않고 바로 사용할 수도 있다.</p>
<p><strong>핵심:</strong> 304는 리소스가 변경되지 않았다는 의미이며, 캐시를 재사용할 수 있다.</p>
<hr>
<h2 id="q13-단일-장애점spof">Q13. 단일 장애점(SPoF)</h2>
<p><strong>문제:</strong> WEB 서버 두 대를 이중화했지만 로드 밸런서는 한 대만 운영하는 경우, 로드 밸런서에 장애가 발생하면 어떻게 될까?</p>
<p><strong>정답:</strong> 서버 두 대가 모두 정상이더라도 클라이언트가 서비스에 접근하지 못할 수 있다.</p>
<p>이처럼 한 구성 요소의 장애가 전체 서비스에 영향을 줄 수 있는 지점을 <strong>단일 장애점(SPoF, Single Point of Failure)</strong>이라고 한다.</p>
<pre><code class="language-text">              클라이언트
                  |
                  v
            로드 밸런서
             (장애 발생)
                  X
                  |
          +-------+-------+
          |               |
          v               v
      WEB 서버 A       WEB 서버 B
        정상             정상

   서버는 정상이지만 요청이 전달되지 않음</code></pre>
<p><strong>대응 방안</strong></p>
<p>로드 밸런서도 이중화하고 장애 감지 및 자동 전환 기능을 구성할 수 있다.</p>
<p><strong>핵심:</strong> 일부 장비를 이중화했다고 해서 전체 시스템의 단일 장애점이 사라지는 것은 아니다.</p>
<hr>
<h2 id="q14-forward-proxy와-reverse-proxy">Q14. Forward Proxy와 Reverse Proxy</h2>
<p><strong>문제:</strong> 회사 직원의 인터넷 요청을 대신 외부 서버에 전달하고 접속 기록 및 접근을 관리하는 프록시는?</p>
<p><strong>정답:</strong> Forward Proxy</p>
<p><strong>① Forward Proxy</strong></p>
<p>클라이언트를 대신해 외부 서버에 요청을 전달한다.</p>
<pre><code class="language-text">클라이언트
    |
    v
Forward Proxy
    |
    v
외부 웹 서버</code></pre>
<p>주요 활용 사례는 인터넷 접근 제어, 접속 기록 관리, 캐싱 등이다.</p>
<p><strong>② Reverse Proxy</strong></p>
<p>외부에서 들어온 요청을 받아 내부 서버에 전달한다.</p>
<pre><code class="language-text">외부 클라이언트
       |
       v
 Reverse Proxy
       |
   +---+---+
   |       |
   v       v
서버 A   서버 B</code></pre>
<p>주요 활용 사례는 내부 서버 보호, 로드 밸런싱, 캐싱 등이다.</p>
<p><strong>핵심</strong></p>
<ul>
<li>Forward Proxy: 클라이언트 측에서 클라이언트를 대신한다.</li>
<li>Reverse Proxy: 서버 측에서 서버를 대신한다.</li>
</ul>
<hr>
<h2 id="q15-cidr과-서브넷-계산">Q15. CIDR과 서브넷 계산</h2>
<p><strong>문제:</strong> <code>192.168.10.130/26</code>의 네트워크 주소, 브로드캐스트 주소, 사용 가능한 호스트 범위는?</p>
<p><strong>정답</strong></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>IP 주소</td>
<td>192.168.10.130/26</td>
</tr>
<tr>
<td>서브넷 마스크</td>
<td>255.255.255.192</td>
</tr>
<tr>
<td>네트워크 주소</td>
<td>192.168.10.128</td>
</tr>
<tr>
<td>브로드캐스트 주소</td>
<td>192.168.10.191</td>
</tr>
<tr>
<td>호스트 범위</td>
<td>192.168.10.129 ~ 192.168.10.190</td>
</tr>
<tr>
<td>사용 가능한 호스트 수</td>
<td>62개</td>
</tr>
</tbody></table>
<p><strong>계산 과정</strong></p>
<p>IPv4는 총 32비트이고, <code>/26</code>은 네트워크 비트가 26비트라는 의미다.</p>
<pre><code class="language-text">호스트 비트 = 32 - 26 = 6비트

전체 IP 주소 수 = 2^6 = 64개

사용 가능한 호스트 수 = 64 - 2 = 62개</code></pre>
<p><strong>서브넷 구간</strong></p>
<pre><code class="language-text">192.168.10.0   ~ 192.168.10.63
192.168.10.64  ~ 192.168.10.127
192.168.10.128 ~ 192.168.10.191  ← 해당 구간
192.168.10.192 ~ 192.168.10.255</code></pre>
<p>일반적인 IPv4 서브넷에서는 네트워크 주소와 브로드캐스트 주소를 제외하고 호스트에 IP를 할당한다.</p>
<hr>
<h2 id="q16-tcp-흐름-제어와-혼잡-제어">Q16. TCP 흐름 제어와 혼잡 제어</h2>
<p><strong>문제:</strong> 수신자의 버퍼를 보호하는 제어와 네트워크의 혼잡을 방지하는 제어는 각각 무엇일까?</p>
<p><strong>정답:</strong> 흐름 제어와 혼잡 제어</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>흐름 제어</th>
<th>혼잡 제어</th>
</tr>
</thead>
<tbody><tr>
<td>영어</td>
<td>Flow Control</td>
<td>Congestion Control</td>
</tr>
<tr>
<td>목적</td>
<td>수신자의 처리 능력 초과 방지</td>
<td>네트워크 혼잡 방지</td>
</tr>
<tr>
<td>고려 대상</td>
<td>수신자의 버퍼</td>
<td>네트워크의 혼잡 상태</td>
</tr>
<tr>
<td>주요 메커니즘</td>
<td>수신 윈도우(rwnd)</td>
<td>혼잡 윈도우(cwnd)</td>
</tr>
</tbody></table>
<p><strong>① 흐름 제어</strong></p>
<p>송신자가 데이터를 너무 빠르게 보내면 수신자의 버퍼가 가득 찰 수 있다.</p>
<p>수신자가 처리할 수 있는 양을 고려해 송신량을 조절하는 것이 흐름 제어다.</p>
<p><strong>② 혼잡 제어</strong></p>
<p>네트워크에 패킷이 지나치게 많이 몰리면 지연과 패킷 손실이 발생할 수 있다.</p>
<p>네트워크의 혼잡 징후를 고려해 송신량을 조절하는 것이 혼잡 제어다.</p>
<p><strong>핵심</strong></p>
<ul>
<li>흐름 제어: 수신자가 감당할 수 있는가?</li>
<li>혼잡 제어: 네트워크가 감당할 수 있는가?</li>
</ul>
<hr>
<h2 id="q17-tcp-빠른-재전송">Q17. TCP 빠른 재전송</h2>
<p><strong>문제:</strong> 동일한 ACK 번호를 반복해서 받은 송신자가 재전송 타이머가 만료되기 전에 손실된 것으로 추정하는 세그먼트를 다시 보내는 기법은?</p>
<p><strong>정답:</strong> 빠른 재전송(Fast Retransmit)</p>
<p><strong>동작 예시</strong></p>
<pre><code class="language-text">송신자                         수신자
   |                              |
   | -------- 데이터 1 ---------&gt; |
   | -------- 데이터 2 ---------&gt; |
   | -------- 데이터 3 --- X      | 손실
   | -------- 데이터 4 ---------&gt; |
   | -------- 데이터 5 ---------&gt; |
   |                              |
   | &lt;------ 중복 ACK ----------- |
   | &lt;------ 중복 ACK ----------- |
   | &lt;------ 중복 ACK ----------- |
   |                              |
   | ---- 데이터 3 재전송 ------&gt; |</code></pre>
<p>TCP는 일반적으로 동일한 ACK 번호를 3번 중복해서 받으면 손실 가능성을 판단해 빠른 재전송을 수행한다.</p>
<p>다만 패킷의 도착 순서가 바뀌었을 때도 중복 ACK가 발생할 수 있다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>빠른 재전송</th>
<th>슬로 스타트</th>
</tr>
</thead>
<tbody><tr>
<td>영어</td>
<td>Fast Retransmit</td>
<td>Slow Start</td>
</tr>
<tr>
<td>목적</td>
<td>손실된 데이터 재전송</td>
<td>혼잡 윈도우 증가</td>
</tr>
<tr>
<td>관련 기능</td>
<td>손실 복구</td>
<td>혼잡 제어</td>
</tr>
</tbody></table>
<p><strong>핵심:</strong> 빠른 재전송은 손실 복구, 슬로 스타트는 혼잡 제어와 관련된 기능이다.</p>
<hr>
<h2 id="q18-디지털-인증서와-ca">Q18. 디지털 인증서와 CA</h2>
<p><strong>문제:</strong> CA(Certificate Authority)의 주요 역할은?</p>
<p><strong>정답:</strong> 디지털 인증서를 발급하고 전자 서명해 공개 키와 인증서에 기재된 신원 정보를 연결한다.</p>
<p><strong>HTTPS 인증서 검증 과정</strong></p>
<pre><code class="language-text">        웹 서버
           |
           | 디지털 인증서 전달
           v
       클라이언트
           |
           | 유효기간 및 도메인 확인
           v
      인증서 체인 검증
           |
           | 신뢰할 수 있는 CA 확인
           v
       전자 서명 검증
           |
           v
       서버 인증 확인</code></pre>
<p>CA는 서버의 신원을 검증할 수 있도록 신뢰 체계를 제공한다.</p>
<p>클라이언트는 인증서의 유효기간과 도메인 일치 여부, 인증서 체인, 전자 서명 등을 확인한다.</p>
<p>CA가 클라이언트와 서버 사이의 모든 HTTPS 데이터를 직접 암호화하는 것은 아니다.</p>
<p><strong>핵심:</strong> CA는 디지털 인증서의 신뢰성을 검증할 수 있도록 지원하는 인증 기관이다.</p>
<hr>
<h2 id="q19-mtu와-ipv4-단편화">Q19. MTU와 IPv4 단편화</h2>
<p><strong>문제:</strong> 크기가 2,000바이트인 IPv4 패킷이 MTU 1,500바이트인 구간을 통과해야 할 때, DF 비트가 0이면 어떻게 처리할까?</p>
<p><strong>정답:</strong> MTU에 맞게 패킷을 단편화할 수 있다.</p>
<p><strong>MTU(Maximum Transmission Unit)</strong>는 네트워크 구간에서 한 번에 전송할 수 있는 패킷의 최대 크기를 의미한다.</p>
<p><strong>단편화 개념도</strong></p>
<pre><code class="language-text">       원본 IPv4 패킷
         2,000바이트
              |
              v
       MTU 1,500바이트
              |
              v
           단편화
              |
        +-----+-----+
        |           |
        v           v
      단편 1      단편 2
    MTU 이하     MTU 이하</code></pre>
<p>단편화된 각 패킷에는 별도의 IPv4 헤더가 붙으며, 최종 목적지에서 재조립한다.</p>
<p><strong>DF(Don&#39;t Fragment) 비트</strong></p>
<table>
<thead>
<tr>
<th>값</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>DF = 0</td>
<td>단편화 허용</td>
</tr>
<tr>
<td>DF = 1</td>
<td>단편화 금지</td>
</tr>
</tbody></table>
<p>DF가 1인 패킷이 MTU보다 크면 라우터는 해당 패킷을 단편화하지 못하므로 폐기하고 일반적으로 ICMP Fragmentation Needed 메시지를 보낸다.</p>
<p>DF 비트는 경로 MTU 탐색(Path MTU Discovery)에도 활용된다.</p>
<p><strong>핵심:</strong> DF는 Don&#39;t Fragment, 즉 단편화하지 말라는 의미다.</p>
<hr>
<h2 id="q20-bgp와-as">Q20. BGP와 AS</h2>
<p><strong>문제:</strong> AS 65001과 AS 65002처럼 서로 다른 AS 사이에서 라우팅 정보를 교환할 때 사용하는 BGP는?</p>
<p><strong>정답:</strong> eBGP(External BGP)</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>iBGP</th>
<th>eBGP</th>
</tr>
</thead>
<tbody><tr>
<td>정식 명칭</td>
<td>Internal BGP</td>
<td>External BGP</td>
</tr>
<tr>
<td>통신 대상</td>
<td>같은 AS의 BGP 라우터</td>
<td>서로 다른 AS의 BGP 라우터</td>
</tr>
<tr>
<td>예시</td>
<td>AS 65001 ↔ AS 65001</td>
<td>AS 65001 ↔ AS 65002</td>
</tr>
</tbody></table>
<p><strong>AS(Autonomous System)</strong>는 공통된 라우팅 정책에 따라 관리되는 네트워크의 집합이다.</p>
<p>BGP는 AS 사이에서 라우팅 정보를 교환하는 데 사용하는 프로토콜이며, iBGP와 eBGP라는 동작 방식이 있다.</p>
<p><strong>핵심</strong></p>
<ul>
<li>iBGP: 같은 AS 내부</li>
<li>eBGP: 서로 다른 AS 사이</li>
</ul>
<hr>
<h1 id="📌-day-2--오답-및-헷갈렸던-개념">📌 DAY 2 — 오답 및 헷갈렸던 개념</h1>
<table>
<thead>
<tr>
<th>문제</th>
<th>개념</th>
<th>핵심 복습</th>
</tr>
</thead>
<tbody><tr>
<td>Q4</td>
<td>Wireshark</td>
<td><code>ip.src</code>, <code>tcp.dstport</code></td>
</tr>
<tr>
<td>Q5</td>
<td>HTTP 메서드</td>
<td>PUT은 전체 대체, PATCH는 부분 수정</td>
</tr>
<tr>
<td>Q9</td>
<td>DNS</td>
<td>루트 → TLD → 권한 있는 네임 서버</td>
</tr>
<tr>
<td>Q10</td>
<td>TCP/UDP</td>
<td>UDP가 반드시 더 빠른 것은 아님</td>
</tr>
<tr>
<td>Q13</td>
<td>SPoF</td>
<td>장비 하나의 장애가 전체 서비스에 영향을 줄 수 있음</td>
</tr>
<tr>
<td>Q14</td>
<td>프록시</td>
<td>Forward는 클라이언트 측, Reverse는 서버 측</td>
</tr>
<tr>
<td>Q16</td>
<td>TCP 제어</td>
<td>흐름 제어는 수신자, 혼잡 제어는 네트워크</td>
</tr>
<tr>
<td>Q17</td>
<td>TCP 재전송</td>
<td>중복 ACK와 빠른 재전송</td>
</tr>
</tbody></table>
<hr>
<h1 id="🎯-다음-복습-목표--day-3">🎯 다음 복습 목표 — DAY 3</h1>
<ul>
<li>Wireshark 디스플레이 필터 직접 작성하기</li>
<li>PUT과 PATCH의 차이 복습하기</li>
<li>TCP 흐름 제어와 혼잡 제어 구분하기</li>
<li>중복 ACK와 빠른 재전송 이해하기</li>
<li>Forward Proxy와 Reverse Proxy 구분하기</li>
<li>앞서 학습한 내용과 새로운 범위를 섞어 총 20문제 풀이하기</li>
</ul>
<hr>
<p><strong>DAY 2 복습 완료! ✅</strong></p>
<p>#네트워크 #혼자공부하는네트워크 #네트워크공부 #TCP #UDP #HTTP #Wireshark #DNS #정보보안 #복습</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python Network Security Scanner DAY2]]></title>
            <link>https://velog.io/@pingu_122/Python-Network-Security-Scanner-DAY2</link>
            <guid>https://velog.io/@pingu_122/Python-Network-Security-Scanner-DAY2</guid>
            <pubDate>Thu, 01 Oct 2026 15:31:32 GMT</pubDate>
            <description><![CDATA[<h1 id="🛡️-python-security-scanner---day-2">🛡️ Python Security Scanner - DAY 2</h1>
<blockquote>
<p>DAY 1에서는 Python의 <code>socket</code>과 <code>ThreadPoolExecutor</code>를 이용하여 여러 TCP Port를 빠르게 스캔하고 OPEN Port를 탐지했다.<br>DAY 2에서는 OPEN Port의 <strong>서비스 정보를 조회</strong>하고, 실제 HTTP 통신을 통해 <strong>HTTP Server Banner를 수집</strong>하는 기능을 추가한다.</p>
</blockquote>
<hr>
<h2 id="📌-day-2-목표">📌 DAY 2 목표</h2>
<p>DAY 1의 Scanner는 OPEN Port만 확인할 수 있었다.</p>
<pre><code class="language-text">[+] 127.0.0.1:135 OPEN
[+] 127.0.0.1:445 OPEN
[+] 127.0.0.1:8000 OPEN</code></pre>
<p>하지만 Port가 열려 있다는 사실만으로는 어떤 서비스가 동작하고 있는지 알기 어렵다.</p>
<p>DAY 2에서는 다음과 같이 정보를 추가하는 것을 목표로 한다.</p>
<pre><code class="language-text">[+] 127.0.0.1:445 OPEN
| Service: microsoft-ds
| Banner: HTTP Banner 없음

[+] 127.0.0.1:8000 OPEN
| Service: 등록된 서비스 정보가 없음
| Banner: SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>전체적인 흐름은 다음과 같다.</p>
<pre><code class="language-text">OPEN Port 탐지
      ↓
등록된 Service 이름 조회
      ↓
HTTP 요청 전송
      ↓
HTTP 응답 수신
      ↓
Server Header 분석
      ↓
HTTP Banner 출력</code></pre>
<hr>
<h1 id="1-port-번호로-service-이름-확인하기">1. Port 번호로 Service 이름 확인하기</h1>
<p>Python의 <code>socket</code> 모듈에는 Port 번호에 대응하는 서비스 이름을 조회할 수 있는 함수가 있다.</p>
<pre><code class="language-python">socket.getservbyport()</code></pre>
<p>간단하게 테스트해보았다.</p>
<pre><code class="language-python">import socket

print(socket.getservbyport(22))
print(socket.getservbyport(80))
print(socket.getservbyport(443))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">ssh
http
https</code></pre>
<p>즉,</p>
<pre><code class="language-text">22  → ssh
80  → http
443 → https</code></pre>
<p>와 같이 Port 번호에 등록된 서비스 이름을 확인할 수 있다.</p>
<h3 id="getservbyport의-특징">getservbyport()의 특징</h3>
<p>여기서 주의해야 할 점이 있다.</p>
<p><code>getservbyport()</code>는 실제 해당 Port에서 어떤 프로그램이 실행되고 있는지 확인하는 함수가 아니다.</p>
<p>예를 들어,</p>
<pre><code class="language-python">socket.getservbyport(80)</code></pre>
<p>을 실행하면</p>
<pre><code class="language-text">80번 Port
   ↓
등록된 서비스 정보 조회
   ↓
http</code></pre>
<p>와 같이 <strong>Port 번호에 등록된 서비스 이름을 조회</strong>한다.</p>
<p>따라서 실제 80번 Port에서 HTTP Server가 실행되고 있다는 것을 직접 확인한 것은 아니다.</p>
<hr>
<h1 id="2-등록된-서비스-정보가-없는-port-조회">2. 등록된 서비스 정보가 없는 Port 조회</h1>
<p>이번에는 <code>9999</code>번 Port를 조회해보았다.</p>
<pre><code class="language-python">import socket

print(socket.getservbyport(9999))</code></pre>
<p>실행 결과 오류가 발생했다.</p>
<pre><code class="language-text">OSError: port/proto not found</code></pre>
<p>여기서 중요한 점은 <strong>9999번 Port가 존재하지 않는다는 의미가 아니라는 것</strong>이다.</p>
<p>9999번 Port도 사용할 수 있지만 <code>getservbyport()</code>가 참조하는 서비스 정보에서 해당 Port에 대응하는 서비스 이름을 찾지 못한 것이다.</p>
<pre><code class="language-text">9999번 Port를 사용할 수 있는가?
        ↓
       YES

등록된 서비스 이름이 있는가?
        ↓
       NO
        ↓
     OSError</code></pre>
<hr>
<h1 id="3-try--except를-이용한-예외-처리">3. try / except를 이용한 예외 처리</h1>
<p>Scanner가 수많은 Port를 검사하다 보면 등록된 서비스 정보가 없는 Port를 만날 수 있다.</p>
<p>그때마다 프로그램이 종료되면 안 되기 때문에 <code>try / except</code>를 이용하여 예외를 처리한다.</p>
<pre><code class="language-python">import socket

try:
    service = socket.getservbyport(9999)
    print(service)

except OSError:
    print(&quot;등록된 서비스 정보가 없음&quot;)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">등록된 서비스 정보가 없음</code></pre>
<p>동작 과정은 다음과 같다.</p>
<pre><code class="language-text">try
 ↓
getservbyport(9999)
 ↓
OSError 발생
 ↓
except OSError
 ↓
등록된 서비스 정보가 없음</code></pre>
<p>이렇게 하면 오류가 발생해도 프로그램 전체가 종료되지 않는다.</p>
<hr>
<h1 id="4-service-조회-함수-만들기">4. Service 조회 함수 만들기</h1>
<p>서비스 조회 기능을 Scanner에서 계속 사용하기 위해 함수로 만들었다.</p>
<pre><code class="language-python">def get_service(port):
    try:
        service = socket.getservbyport(port)
        return service

    except OSError:
        return &quot;등록된 서비스 정보가 없음&quot;</code></pre>
<p>테스트</p>
<pre><code class="language-python">print(get_service(22))
print(get_service(80))
print(get_service(443))
print(get_service(9999))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">ssh
http
https
등록된 서비스 정보가 없음</code></pre>
<h3 id="return을-사용하는-이유">return을 사용하는 이유</h3>
<p>함수 안에서 <code>print()</code>를 사용하면 결과를 화면에 출력하는 것으로 끝난다.</p>
<p>하지만 <code>return</code>을 사용하면 함수에서 얻은 값을 다른 코드에서 다시 사용할 수 있다.</p>
<pre><code class="language-python">service = get_service(80)

print(service)</code></pre>
<p>동작 과정</p>
<pre><code class="language-text">get_service(80)
      ↓
port = 80
      ↓
socket.getservbyport(80)
      ↓
service = &quot;http&quot;
      ↓
return service
      ↓
service = &quot;http&quot;</code></pre>
<p>Scanner에서 Service 정보를 받아 출력해야 하기 때문에 <code>return</code>을 사용한다.</p>
<hr>
<h1 id="5-기존-port-scanner에-service-조회-기능-추가">5. 기존 Port Scanner에 Service 조회 기능 추가</h1>
<p>DAY 1에서 작성한 <code>scan_port()</code>에 <code>get_service()</code>를 연결했다.</p>
<pre><code class="language-python">if result == 0:
    service = get_service(target_port)

    print(f&quot;[+] {target_ip}:{target_port} OPEN | Service: {service}&quot;)</code></pre>
<p>이제 Scanner는 단순히 OPEN 여부만 출력하는 것이 아니라 등록된 Service 정보까지 출력할 수 있다.</p>
<p>실행 결과 예시</p>
<pre><code class="language-text">[+] 127.0.0.1:135 OPEN | Service: epmap
[+] 127.0.0.1:445 OPEN | Service: microsoft-ds
[+] 127.0.0.1:902 OPEN | Service: 등록된 서비스 정보가 없음</code></pre>
<p>하지만 여기서 새로운 문제가 생긴다.</p>
<p>직접 Python HTTP Server를 실행해보았다.</p>
<pre><code class="language-powershell">python -m http.server 8000</code></pre>
<p>분명히 8000번 Port에서 HTTP Server가 실행 중이다.</p>
<p>그런데 Scanner에서는 다음과 같이 출력되었다.</p>
<pre><code class="language-text">[+] 127.0.0.1:8000 OPEN | Service: 등록된 서비스 정보가 없음</code></pre>
<p>즉,</p>
<pre><code class="language-text">실제 서비스
python -m http.server 8000
        ↓
       HTTP

하지만

socket.getservbyport(8000)
        ↓
등록된 서비스 정보가 없음</code></pre>
<p>이라는 차이가 발생했다.</p>
<p>이를 통해 <code>getservbyport()</code>는 <strong>실제 서비스를 탐지하는 기능이 아니라 등록된 서비스 정보를 조회하는 기능</strong>이라는 것을 확인할 수 있었다.</p>
<p>그래서 실제 서버와 통신하여 정보를 받아오는 기능을 추가하기로 했다.</p>
<hr>
<h1 id="6-http-server와-직접-통신하기">6. HTTP Server와 직접 통신하기</h1>
<p>먼저 테스트를 위해 Python HTTP Server를 실행한다.</p>
<pre><code class="language-powershell">python -m http.server 8000</code></pre>
<p>8000번 Port에 TCP 연결을 시도한다.</p>
<pre><code class="language-python">import socket

target_ip = &quot;127.0.0.1&quot;
target_port = 8000

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.settimeout(2)

result = sock.connect_ex((target_ip, target_port))

print(result)</code></pre>
<p>서버가 정상적으로 실행 중이면</p>
<pre><code class="language-text">0</code></pre>
<p>이 출력된다.</p>
<p><code>connect_ex()</code>의 반환값이 <code>0</code>이라는 것은 TCP 연결에 성공했다는 의미이다.</p>
<hr>
<h1 id="7-http-요청-전송하기---sendall">7. HTTP 요청 전송하기 - sendall()</h1>
<p>TCP 연결에 성공했으므로 이번에는 서버에 실제 HTTP 요청을 보내본다.</p>
<p>HTTP 요청 문자열을 생성한다.</p>
<pre><code class="language-python">request = &quot;GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n&quot;</code></pre>
<p>사람이 보기 쉽게 표현하면 다음과 같다.</p>
<pre><code class="language-text">GET / HTTP/1.1
Host: 127.0.0.1
</code></pre>
<p>HTTP에서는 <code>\r\n</code>을 이용하여 줄을 구분한다.</p>
<p>마지막의</p>
<pre><code class="language-text">\r\n\r\n</code></pre>
<p>은 HTTP Header가 끝났다는 것을 의미한다.</p>
<hr>
<h2 id="encode">encode()</h2>
<p>Socket을 통해 데이터를 전송할 때 문자열을 그대로 전송할 수 없기 때문에 <code>bytes</code> 형태로 변환한다.</p>
<pre><code class="language-python">request.encode()</code></pre>
<p>변환 과정</p>
<pre><code class="language-text">str
 ↓
encode()
 ↓
bytes</code></pre>
<p>변환된 데이터를 <code>sendall()</code>을 이용하여 서버에 전송한다.</p>
<pre><code class="language-python">sock.sendall(request.encode())</code></pre>
<p>전체적인 흐름은 다음과 같다.</p>
<pre><code class="language-text">&quot;GET / HTTP/1.1...&quot;
        ↓
      str
        ↓
    encode()
        ↓
      bytes
        ↓
   sendall()
        ↓
   HTTP Server</code></pre>
<hr>
<h1 id="8-http-응답-받기---recv">8. HTTP 응답 받기 - recv()</h1>
<p>HTTP 요청을 보냈다면 서버의 응답도 받아야 한다.</p>
<pre><code class="language-python">response = sock.recv(4096)</code></pre>
<p><code>4096</code>은 Port 번호가 아니라 <strong>한 번에 최대 4096 bytes의 데이터를 수신하겠다는 의미</strong>이다.</p>
<p>처음에는 응답을 받지 않고 바로</p>
<pre><code class="language-python">sock.close()</code></pre>
<p>를 실행했더니 HTTP Server에서 다음과 같은 오류가 발생했다.</p>
<pre><code class="language-text">ConnectionAbortedError
[WinError 10053]</code></pre>
<p>동작 과정을 보면</p>
<pre><code class="language-text">Client
  │
  │ GET 요청
  ▼
Server
  │
  │ 200 OK 응답 준비
  ▼

Client가 먼저 연결 종료
  ↓
Server가 데이터를 보내지 못함</code></pre>
<p>과 같은 상황이었다.</p>
<p>따라서 <code>recv()</code>를 사용하여 Server의 응답을 받은 후 연결을 종료하도록 수정하였다.</p>
<pre><code class="language-python">sock.sendall(request.encode())

response = sock.recv(4096)

sock.close()</code></pre>
<hr>
<h1 id="9-bytes-데이터를-문자열로-변환---decode">9. bytes 데이터를 문자열로 변환 - decode()</h1>
<p><code>recv()</code>를 이용해 받은 데이터를 출력해보았다.</p>
<pre><code class="language-python">print(response)</code></pre>
<p>결과는 다음과 같은 형태였다.</p>
<pre><code class="language-text">b&#39;HTTP/1.0 200 OK\r\nServer: SimpleHTTP/0.6 Python/3.13.3...&#39;</code></pre>
<p>앞의 <code>b</code>는 해당 데이터가 <code>bytes</code>라는 의미이다.</p>
<p>사람이 읽기 편한 문자열로 변환하기 위해 <code>decode()</code>를 사용한다.</p>
<pre><code class="language-python">response = response.decode()</code></pre>
<p>변환 과정</p>
<pre><code class="language-text">서버
 ↓
recv()
 ↓
bytes
 ↓
decode()
 ↓
str</code></pre>
<p>결과</p>
<pre><code class="language-text">HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.3
Date: ...
Content-type: text/html; charset=utf-8
Content-Length: 355</code></pre>
<p>HTTP Server가 보낸 실제 응답을 확인할 수 있었다.</p>
<hr>
<h1 id="10-http-응답에서-server-정보-찾기">10. HTTP 응답에서 Server 정보 찾기</h1>
<p>HTTP 응답 전체가 필요한 것은 아니다.</p>
<p>이번 Scanner에서 확인하고 싶은 것은 다음 정보이다.</p>
<pre><code class="language-text">Server: SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>먼저 HTTP 응답을 줄 단위로 분리한다.</p>
<pre><code class="language-python">lines = response.split(&quot;\r\n&quot;)</code></pre>
<p>예를 들어 다음 문자열이</p>
<pre><code class="language-text">HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.13.3
Date: ...
Content-Type: text/html</code></pre>
<p><code>split(&quot;\r\n&quot;)</code>을 거치면 대략 다음과 같은 리스트가 된다.</p>
<pre><code class="language-python">[
    &quot;HTTP/1.0 200 OK&quot;,
    &quot;Server: SimpleHTTP/0.6 Python/3.13.3&quot;,
    &quot;Date: ...&quot;,
    &quot;Content-Type: text/html&quot;
]</code></pre>
<p>이제 <code>for</code>문으로 한 줄씩 확인한다.</p>
<pre><code class="language-python">for line in lines:
    print(line)</code></pre>
<hr>
<h1 id="11-startswith로-server-header-찾기">11. startswith()로 Server Header 찾기</h1>
<p><code>Server:</code>로 시작하는 문자열만 찾기 위해 <code>startswith()</code>를 사용한다.</p>
<pre><code class="language-python">for line in lines:
    if line.startswith(&quot;Server:&quot;):
        print(line)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">Server: SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p><code>startswith()</code>는 문자열이 특정 문자열로 시작하는지 검사한다.</p>
<p>예를 들어</p>
<pre><code class="language-python">text = &quot;Server: SimpleHTTP/0.6&quot;

print(text.startswith(&quot;Server:&quot;))</code></pre>
<p>결과</p>
<pre><code class="language-text">True</code></pre>
<p>가 된다.</p>
<hr>
<h1 id="12-server-문자열에서-banner만-추출하기">12. Server 문자열에서 Banner만 추출하기</h1>
<p>현재 결과는 다음과 같다.</p>
<pre><code class="language-text">Server: SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>여기에서 실제 Banner 정보만 필요하다.</p>
<pre><code class="language-text">SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>먼저 <code>:</code>을 기준으로 문자열을 분리한다.</p>
<pre><code class="language-python">line.split(&quot;:&quot;)</code></pre>
<p>결과</p>
<pre><code class="language-python">[
    &quot;Server&quot;,
    &quot; SimpleHTTP/0.6 Python/3.13.3&quot;
]</code></pre>
<p>두 번째 값이 필요하므로 <code>[1]</code>을 사용한다.</p>
<pre><code class="language-python">line.split(&quot;:&quot;)[1]</code></pre>
<p>하지만 문자열 앞에 공백이 남아 있다.</p>
<pre><code class="language-text"> SimpleHTTP/0.6 Python/3.13.3
↑
공백</code></pre>
<p>따라서 <code>strip()</code>을 사용하여 앞뒤 공백을 제거한다.</p>
<pre><code class="language-python">banner = line.split(&quot;:&quot;)[1].strip()</code></pre>
<p>최종 결과</p>
<pre><code class="language-text">SimpleHTTP/0.6 Python/3.13.3</code></pre>
<hr>
<h1 id="13-http-banner-조회-함수-만들기">13. HTTP Banner 조회 함수 만들기</h1>
<p>지금까지 작성한 HTTP 통신 코드를 하나의 함수로 만들었다.</p>
<pre><code class="language-python">def get_http_banner(target_ip, target_port):
    try:
        # HTTP Banner 확인을 위한 TCP 소켓 생성
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # 최대 2초 동안 응답 대기
        sock.settimeout(2)

        # 전달받은 IP와 Port에 TCP 연결
        result = sock.connect_ex((target_ip, target_port))

        if result == 0:

            # HTTP 요청 생성
            request = f&quot;GET / HTTP/1.1\r\nHost: {target_ip}\r\n\r\n&quot;

            # HTTP 요청 전송
            sock.sendall(request.encode())

            # HTTP 응답 수신
            response = sock.recv(4096)

            # bytes → str
            response = response.decode()

            # HTTP 응답을 한 줄씩 분리
            lines = response.split(&quot;\r\n&quot;)

            # Server Header 검색
            for line in lines:

                if line.startswith(&quot;Server:&quot;):
                    banner = line.split(&quot;:&quot;)[1].strip()
                    return banner

        return &quot;HTTP Banner 없음&quot;

    except (ConnectionResetError, TimeoutError):
        return &quot;HTTP Banner 없음&quot;</code></pre>
<hr>
<h1 id="14-http가-아닌-port에-http-요청을-보내면">14. HTTP가 아닌 Port에 HTTP 요청을 보내면?</h1>
<p>HTTP Banner 함수를 <code>445</code>번 Port에 테스트해보았다.</p>
<pre><code class="language-python">banner = get_http_banner(&quot;127.0.0.1&quot;, 445)

print(banner)</code></pre>
<p>TCP 연결 자체는 성공했지만 다음 오류가 발생했다.</p>
<pre><code class="language-text">ConnectionResetError
[WinError 10054]
현재 연결은 원격 호스트에 의해 강제로 끊겼습니다.</code></pre>
<p>동작 과정</p>
<pre><code class="language-text">Scanner
   │
   │ TCP 연결
   ▼
445 Port
   │
   └─ 연결 성공

Scanner
   │
   │ GET / HTTP/1.1
   ▼
445 Port
   │
   └─ HTTP 요청을 처리하는 서비스가 아님
          ↓
       연결 종료

Scanner
   │
   └─ recv()
        ↓
ConnectionResetError</code></pre>
<p>즉,</p>
<pre><code class="language-text">Port OPEN = HTTP Service</code></pre>
<p>가 아니라는 것을 알 수 있다.</p>
<p>OPEN은 단순히 <strong>해당 TCP Port에서 연결을 받아주는 서비스가 있다는 의미</strong>이다.</p>
<p>따라서 다음과 같이 예외 처리를 추가하였다.</p>
<pre><code class="language-python">except (ConnectionResetError, TimeoutError):
    return &quot;HTTP Banner 없음&quot;</code></pre>
<p><code>ConnectionResetError</code> 또는 <code>TimeoutError</code>가 발생하면 프로그램을 종료하지 않고</p>
<pre><code class="language-text">HTTP Banner 없음</code></pre>
<p>을 반환한다.</p>
<hr>
<h1 id="15-445와-8000-비교">15. 445와 8000 비교</h1>
<p>두 Port를 비교해보았다.</p>
<pre><code class="language-python">banner = get_http_banner(&quot;127.0.0.1&quot;, 445)
print(banner)

banner = get_http_banner(&quot;127.0.0.1&quot;, 8000)
print(banner)</code></pre>
<p>결과</p>
<pre><code class="language-text">HTTP Banner 없음
SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>즉,</p>
<pre><code class="language-text">445
 ↓
TCP OPEN
 ↓
HTTP 요청
 ↓
HTTP Banner 없음


8000
 ↓
TCP OPEN
 ↓
HTTP 요청
 ↓
HTTP 응답
 ↓
Server Header 발견
 ↓
SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>으로 동작한다.</p>
<hr>
<h1 id="16-day-1-scanner와-http-banner-기능-합치기">16. DAY 1 Scanner와 HTTP Banner 기능 합치기</h1>
<p>마지막으로 DAY 1에서 작성한 멀티스레드 Port Scanner와 오늘 만든 기능들을 합쳤다.</p>
<p>구조는 다음과 같다.</p>
<pre><code class="language-text">scan_port()
    │
    ├── Port OPEN 여부 확인
    │
    ├── get_service()
    │       ↓
    │   등록된 Service 이름 조회
    │
    └── get_http_banner()
            ↓
        실제 HTTP 응답 분석</code></pre>
<p>OPEN Port가 발견되면 다음 두 함수를 호출한다.</p>
<pre><code class="language-python">if result == 0:

    service = get_service(target_port)

    banner = get_http_banner(target_ip, target_port)

    print(
        f&quot;[+] {target_ip}:{target_port} OPEN &quot;
        f&quot;| Service: {service} &quot;
        f&quot;| Banner: {banner}&quot;
    )</code></pre>
<hr>
<h1 id="17-day-2-최종-코드">17. DAY 2 최종 코드</h1>
<pre><code class="language-python">import socket
from concurrent.futures import ThreadPoolExecutor


# 스캔할 대상 IP 입력
target_ip = input(&quot;스캔할 IP를 입력하세요: &quot;)


# Port 번호에 등록된 Service 이름을 조회하는 함수
def get_service(port):

    try:
        service = socket.getservbyport(port)

        return service

    except OSError:
        return &quot;등록된 서비스 정보가 없음&quot;


# HTTP 응답에서 Server Banner를 확인하는 함수
def get_http_banner(target_ip, target_port):

    try:
        # HTTP Banner 확인을 위한 IPv4 + TCP 소켓 생성
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # 최대 2초 동안 응답 대기
        sock.settimeout(2)

        # 전달받은 IP와 Port에 TCP 연결
        result = sock.connect_ex((target_ip, target_port))

        if result == 0:

            # HTTP GET 요청 생성
            request = f&quot;GET / HTTP/1.1\r\nHost: {target_ip}\r\n\r\n&quot;

            # 문자열을 bytes로 변환하여 HTTP 요청 전송
            sock.sendall(request.encode())

            # HTTP 응답을 최대 4096 bytes까지 수신
            response = sock.recv(4096)

            # bytes 데이터를 문자열로 변환
            response = response.decode()

            # HTTP 응답을 한 줄씩 분리
            lines = response.split(&quot;\r\n&quot;)

            # Server Header 검색
            for line in lines:

                if line.startswith(&quot;Server:&quot;):

                    # &quot;Server:&quot; 뒤의 Banner 정보만 추출
                    banner = line.split(&quot;:&quot;)[1].strip()

                    return banner

        return &quot;HTTP Banner 없음&quot;

    except (ConnectionResetError, TimeoutError):

        return &quot;HTTP Banner 없음&quot;


# 하나의 Port를 검사하는 함수
def scan_port(target_ip, target_port):

    # IPv4(AF_INET) + TCP(SOCK_STREAM) 소켓 생성
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # TCP 연결 시 최대 0.5초 동안 응답 대기
    sock.settimeout(0.5)

    # 대상 IP와 Port에 TCP 연결 시도
    result = sock.connect_ex((target_ip, target_port))

    # connect_ex() 반환값이 0이면 TCP 연결 성공
    if result == 0:

        # Port 번호에 등록된 Service 이름 조회
        service = get_service(target_port)

        # 실제 HTTP 통신을 통한 Server Banner 확인
        banner = get_http_banner(target_ip, target_port)

        # OPEN Port + Service + Banner 출력
        print(
            f&quot;[+] {target_ip}:{target_port} OPEN &quot;
            f&quot;| Service: {service} &quot;
            f&quot;| Banner: {banner}&quot;
        )

    # 현재 Port 검사 종료
    sock.close()


# 최대 50개의 작업자 스레드를 사용하는 Thread Pool 생성
with ThreadPoolExecutor(max_workers=50) as executor:

    # 1번부터 8000번 Port까지 반복
    for target_port in range(1, 8001):

        # scan_port() 작업을 Thread Pool에 제출
        executor.submit(scan_port, target_ip, target_port)</code></pre>
<hr>
<h1 id="18-최종-실행-결과">18. 최종 실행 결과</h1>
<p>테스트를 위해 별도의 Terminal에서 Python HTTP Server를 실행하였다.</p>
<pre><code class="language-powershell">python -m http.server 8000</code></pre>
<p>Scanner 실행 결과 일부</p>
<pre><code class="language-text">[+] 127.0.0.1:4441 OPEN | Service: 등록된 서비스 정보가 없음 | Banner: HTTP Banner 없음

[+] 127.0.0.1:5040 OPEN | Service: 등록된 서비스 정보가 없음 | Banner: HTTP Banner 없음

[+] 127.0.0.1:6463 OPEN | Service: 등록된 서비스 정보가 없음 | Banner: HTTP Banner 없음

[+] 127.0.0.1:7680 OPEN | Service: ms-do | Banner: HTTP Banner 없음

[+] 127.0.0.1:7895 OPEN | Service: 등록된 서비스 정보가 없음 | Banner: Microsoft-HTTPAPI/2.0

[+] 127.0.0.1:8000 OPEN | Service: 등록된 서비스 정보가 없음 | Banner: SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>특히 <code>8000</code>번 Port를 보면</p>
<pre><code class="language-text">Service: 등록된 서비스 정보가 없음</code></pre>
<p>이라고 나오지만 실제 HTTP 통신을 통해</p>
<pre><code class="language-text">Banner: SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>이라는 정보를 확인할 수 있었다.</p>
<p>또한 테스트 과정에서 <code>7895</code>번 Port에서는</p>
<pre><code class="language-text">Service: 등록된 서비스 정보가 없음
Banner: Microsoft-HTTPAPI/2.0</code></pre>
<p>이라는 결과도 확인할 수 있었다.</p>
<p>이를 통해 <strong>Port 번호에 등록된 서비스 이름을 조회하는 것과 실제 서비스의 응답을 분석하는 것은 서로 다른 과정</strong>이라는 것을 확인할 수 있었다.</p>
<hr>
<h1 id="📌-getservbyport와-banner-grabbing-차이">📌 getservbyport()와 Banner Grabbing 차이</h1>
<table>
<thead>
<tr>
<th>구분</th>
<th>getservbyport()</th>
<th>HTTP Banner 확인</th>
</tr>
</thead>
<tbody><tr>
<td>확인 방법</td>
<td>Port 번호 기반</td>
<td>실제 네트워크 통신</td>
</tr>
<tr>
<td>TCP 연결</td>
<td>필요 없음</td>
<td>필요</td>
</tr>
<tr>
<td>데이터 송수신</td>
<td>없음</td>
<td>있음</td>
</tr>
<tr>
<td>Service 정보</td>
<td>등록된 서비스 이름</td>
<td>실제 HTTP 응답 정보</td>
</tr>
<tr>
<td>예시</td>
<td>80 → http</td>
<td>Server: Apache</td>
</tr>
<tr>
<td>한계</td>
<td>실제 실행 서비스와 다를 수 있음</td>
<td>HTTP 응답을 제공하는 서비스 중심</td>
</tr>
</tbody></table>
<hr>
<h1 id="📌-오늘-사용한-주요-함수">📌 오늘 사용한 주요 함수</h1>
<table>
<thead>
<tr>
<th>함수</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>socket.getservbyport()</code></td>
<td>Port 번호에 등록된 서비스 이름 조회</td>
</tr>
<tr>
<td><code>socket.socket()</code></td>
<td>Socket 생성</td>
</tr>
<tr>
<td><code>connect_ex()</code></td>
<td>TCP 연결 시도</td>
</tr>
<tr>
<td><code>settimeout()</code></td>
<td>응답 대기 시간 설정</td>
</tr>
<tr>
<td><code>sendall()</code></td>
<td>서버에 데이터 전송</td>
</tr>
<tr>
<td><code>recv()</code></td>
<td>서버에서 데이터 수신</td>
</tr>
<tr>
<td><code>encode()</code></td>
<td>str → bytes 변환</td>
</tr>
<tr>
<td><code>decode()</code></td>
<td>bytes → str 변환</td>
</tr>
<tr>
<td><code>split()</code></td>
<td>문자열 분리</td>
</tr>
<tr>
<td><code>startswith()</code></td>
<td>특정 문자열로 시작하는지 확인</td>
</tr>
<tr>
<td><code>strip()</code></td>
<td>문자열 앞뒤 공백 제거</td>
</tr>
<tr>
<td><code>return</code></td>
<td>함수의 결과값 반환</td>
</tr>
<tr>
<td><code>try / except</code></td>
<td>예외 처리</td>
</tr>
</tbody></table>
<hr>
<h1 id="📌-encode--decode-정리">📌 encode() / decode() 정리</h1>
<p>Socket 통신에서는 문자열을 그대로 전송하는 것이 아니라 <code>bytes</code> 형태의 데이터를 주고받는다.</p>
<h3 id="데이터를-보낼-때">데이터를 보낼 때</h3>
<pre><code class="language-text">str
 ↓
encode()
 ↓
bytes
 ↓
sendall()
 ↓
Server</code></pre>
<h3 id="데이터를-받을-때">데이터를 받을 때</h3>
<pre><code class="language-text">Server
 ↓
recv()
 ↓
bytes
 ↓
decode()
 ↓
str</code></pre>
<hr>
<h1 id="📌-day-1-→-day-2-변화">📌 DAY 1 → DAY 2 변화</h1>
<h3 id="day-1">DAY 1</h3>
<pre><code class="language-text">Target IP
    ↓
Port Scan
    ↓
OPEN / CLOSED 확인</code></pre>
<p>출력</p>
<pre><code class="language-text">[+] 127.0.0.1:8000 OPEN</code></pre>
<h3 id="day-2">DAY 2</h3>
<pre><code class="language-text">Target IP
    ↓
Port Scan
    ↓
OPEN Port
    ↓
Service 조회
    ↓
HTTP 요청
    ↓
HTTP 응답 분석
    ↓
Server Banner 추출</code></pre>
<p>출력</p>
<pre><code class="language-text">[+] 127.0.0.1:8000 OPEN
| Service: 등록된 서비스 정보가 없음
| Banner: SimpleHTTP/0.6 Python/3.13.3</code></pre>
<hr>
<h1 id="📌-day-2에서-배운-점">📌 DAY 2에서 배운 점</h1>
<p>DAY 2에서는 단순히 새로운 기능만 추가한 것이 아니라 이전에 공부했던 Python 문법들이 실제 프로그램에서 어떻게 사용되는지 확인할 수 있었다.</p>
<pre><code class="language-text">함수
 ↓
매개변수
 ↓
return
 ↓
try / except
 ↓
for
 ↓
if
 ↓
f-string
 ↓
split()
 ↓
startswith()
 ↓
strip()</code></pre>
<p>또한 Network Programming과 관련하여</p>
<pre><code class="language-text">Socket 생성
 ↓
TCP 연결
 ↓
HTTP Request
 ↓
sendall()
 ↓
Server
 ↓
HTTP Response
 ↓
recv()
 ↓
Banner 분석</code></pre>
<p>과정을 직접 구현하였다.</p>
<p>특히 다음 차이를 이해하는 것이 중요했다.</p>
<pre><code class="language-text">Port가 OPEN이다.
≠
어떤 서비스인지 정확히 알고 있다.</code></pre>
<p>그리고</p>
<pre><code class="language-text">getservbyport()
→ Port 번호에 등록된 서비스 정보 조회

HTTP Banner 확인
→ 실제 Server와 통신하여 응답 정보 확인</code></pre>
<p>이라는 차이도 확인할 수 있었다.</p>
<hr>
<h1 id="🚀-day-2-완료">🚀 DAY 2 완료</h1>
<p>DAY 1에서 만들었던 단순 TCP Port Scanner에</p>
<pre><code class="language-text">✔ Service 이름 조회
✔ OSError 예외 처리
✔ HTTP Request 전송
✔ HTTP Response 수신
✔ bytes ↔ str 변환
✔ HTTP Server Header 분석
✔ Banner 추출
✔ ConnectionResetError / TimeoutError 처리
✔ 기존 Multi Thread Scanner와 결합</code></pre>
<p>기능을 추가하였다.</p>
<p>현재 Scanner의 구조는 다음과 같다.</p>
<pre><code class="language-text">Python Security Scanner
        │
        ├── ThreadPoolExecutor
        │
        └── scan_port()
                │
                ├── TCP Port Scan
                │
                ├── get_service()
                │       └── Service 이름 조회
                │
                └── get_http_banner()
                        ├── HTTP Request
                        ├── HTTP Response
                        └── Server Banner 추출</code></pre>
<p>DAY 1에서는 <strong>&quot;Port가 열려 있는가?&quot;</strong>를 확인했다면,</p>
<p>DAY 2에서는 한 단계 발전하여 <strong>&quot;열린 Port에서 어떤 서비스 정보를 얻을 수 있는가?&quot;</strong>까지 확인할 수 있게 되었다.</p>
<hr>
<h1 id="🔧-오늘-발견한-오류와-해결">🔧 오늘 발견한 오류와 해결</h1>
<h2 id="1-등록되지-않은-port에서-oserror-발생">1. 등록되지 않은 Port에서 OSError 발생</h2>
<p>처음 <code>socket.getservbyport()</code>를 테스트하면서 <code>9999</code>번 Port를 조회했더니 다음 오류가 발생했다.</p>
<pre><code class="language-text">OSError: port/proto not found</code></pre>
<h3 id="원인">원인</h3>
<p><code>getservbyport()</code>는 실제 Port가 열려 있는지 확인하는 함수가 아니라 <strong>Port 번호에 등록된 서비스 이름을 조회하는 함수</strong>이다.</p>
<p>따라서 해당 Port에 등록된 서비스 정보가 없으면 <code>OSError</code>가 발생한다.</p>
<h3 id="해결">해결</h3>
<p><code>try / except</code>를 사용하여 예외를 처리했다.</p>
<pre><code class="language-python">def get_service(port):
    try:
        service = socket.getservbyport(port)
        return service

    except OSError:
        return &quot;등록된 서비스 정보가 없음&quot;</code></pre>
<p>결과</p>
<pre><code class="language-text">9999 → 등록된 서비스 정보가 없음</code></pre>
<p>이 과정을 통해 Scanner는 등록되지 않은 Port를 만나더라도 종료되지 않고 계속 실행할 수 있게 되었다.</p>
<hr>
<h2 id="2-http-요청-후-연결을-바로-종료하여-오류-발생">2. HTTP 요청 후 연결을 바로 종료하여 오류 발생</h2>
<p>Python HTTP Server를 실행한 뒤 HTTP 요청을 전송했는데 Server Terminal에서 다음과 같은 오류가 발생했다.</p>
<pre><code class="language-text">ConnectionAbortedError
[WinError 10053]
현재 연결은 사용자의 호스트 시스템의 소프트웨어에 의해 중단되었습니다.</code></pre>
<h3 id="원인-1">원인</h3>
<p>처음 작성한 코드에서는 HTTP 요청을 보낸 후 Server의 응답을 제대로 받지 않고 Socket 연결을 종료했다.</p>
<pre><code class="language-text">Scanner
   │
   │ HTTP Request
   ▼
HTTP Server
   │
   │ HTTP Response 전송
   ▼
Scanner가 먼저 연결 종료</code></pre>
<p>Server 입장에서는 응답 데이터를 보내는 도중 연결이 종료된 것이다.</p>
<h3 id="해결-1">해결</h3>
<p><code>recv()</code>를 이용하여 Server의 응답을 받은 후 Socket을 종료하도록 수정했다.</p>
<pre><code class="language-python">sock.sendall(request.encode())

response = sock.recv(4096)

sock.close()</code></pre>
<p>이를 통해 HTTP의 기본적인 <strong>요청 → 응답 과정</strong>도 직접 확인할 수 있었다.</p>
<hr>
<h2 id="3-http가-아닌-port에서-connectionreseterror-발생">3. HTTP가 아닌 Port에서 ConnectionResetError 발생</h2>
<p>445번 Port가 OPEN 상태였기 때문에 HTTP Banner 함수를 테스트해보았다.</p>
<p>하지만 다음 오류가 발생했다.</p>
<pre><code class="language-text">ConnectionResetError
[WinError 10054]
현재 연결은 원격 호스트에 의해 강제로 끊겼습니다.</code></pre>
<h3 id="원인-2">원인</h3>
<p>445번 Port에 TCP 연결하는 것 자체는 성공했다.</p>
<pre><code class="language-text">connect_ex()
      ↓
result == 0
      ↓
TCP 연결 성공</code></pre>
<p>하지만 이후 Scanner가 HTTP 요청을 전송했다.</p>
<pre><code class="language-text">GET / HTTP/1.1</code></pre>
<p>445번에서 동작하는 서비스는 HTTP 요청을 처리하는 웹 서버가 아니기 때문에 연결이 종료되었고, 이후 <code>recv()</code> 과정에서 <code>ConnectionResetError</code>가 발생했다.</p>
<p>즉,</p>
<pre><code class="language-text">Port OPEN
   ≠
HTTP Service</code></pre>
<p>라는 것을 확인할 수 있었다.</p>
<h3 id="해결-2">해결</h3>
<p><code>try / except</code>를 이용하여 연결 종료와 Timeout을 처리했다.</p>
<pre><code class="language-python">except (ConnectionResetError, TimeoutError):
    return &quot;HTTP Banner 없음&quot;</code></pre>
<p>이제 HTTP가 아닌 서비스에 요청을 보내더라도 Scanner 전체가 종료되지 않는다.</p>
<pre><code class="language-text">[+] 127.0.0.1:445 OPEN
| Service: microsoft-ds
| Banner: HTTP Banner 없음</code></pre>
<hr>
<h2 id="4-getservbyport로는-8000번의-실제-서비스를-확인하지-못함">4. getservbyport()로는 8000번의 실제 서비스를 확인하지 못함</h2>
<p>테스트를 위해 직접 HTTP Server를 실행했다.</p>
<pre><code class="language-powershell">python -m http.server 8000</code></pre>
<p>하지만 <code>getservbyport()</code>의 결과는 다음과 같았다.</p>
<pre><code class="language-text">Service: 등록된 서비스 정보가 없음</code></pre>
<p>실제로는 HTTP Server가 실행되고 있는데 Service 정보를 확인하지 못한 것이다.</p>
<h3 id="원인-3">원인</h3>
<p><code>getservbyport()</code>는 실제 동작 중인 서비스를 분석하는 것이 아니라 <strong>Port 번호에 등록된 서비스 이름을 조회</strong>하기 때문이다.</p>
<h3 id="해결-3">해결</h3>
<p>실제 서비스에 HTTP Request를 전송하고 HTTP Response의 <code>Server:</code> Header를 분석하는 Banner 확인 기능을 직접 구현했다.</p>
<p>결과</p>
<pre><code class="language-text">[+] 127.0.0.1:8000 OPEN
| Service: 등록된 서비스 정보가 없음
| Banner: SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>이를 통해</p>
<pre><code class="language-text">Port 번호 기반 정보
        ↓
getservbyport()

실제 서비스 응답 기반 정보
        ↓
Banner Grabbing</code></pre>
<p>의 차이를 확인할 수 있었다.</p>
<hr>
<h1 id="💡-오늘의-핵심-정리">💡 오늘의 핵심 정리</h1>
<p>DAY 2에서 가장 중요하게 느낀 부분은 단순히 Port 번호만으로 실제 서비스를 판단할 수 없다는 점이다.</p>
<p>DAY 1에서는</p>
<pre><code class="language-text">8000 OPEN</code></pre>
<p>까지만 알 수 있었다.</p>
<p>DAY 2에서는</p>
<pre><code class="language-text">8000 OPEN
   ↓
등록된 Service 정보 확인
   ↓
실제 HTTP Request 전송
   ↓
HTTP Response 수신
   ↓
Server Header 분석
   ↓
SimpleHTTP/0.6 Python/3.13.3</code></pre>
<p>까지 확인할 수 있게 되었다.</p>
<p>또한 Python을 공부하면서 각각 따로 배웠던</p>
<pre><code class="language-text">함수
return
try / except
for
if
f-string
split()
startswith()
strip()</code></pre>
<p>등의 문법이 실제 프로그램 안에서 어떤 목적으로 사용되는지도 조금씩 이해할 수 있었다.</p>
<p>특히 이번 DAY 2에서는 <strong>예외 처리가 왜 필요한지</strong> 직접 경험할 수 있었다.</p>
<p>Scanner처럼 여러 Port와 통신하는 프로그램에서는 모든 Port가 예상한 대로 응답하지 않는다.</p>
<p>따라서</p>
<pre><code class="language-text">정상적인 상황만 처리하는 코드</code></pre>
<p>뿐만 아니라</p>
<pre><code class="language-text">응답이 없으면?
연결이 끊기면?
서비스 정보가 없으면?
HTTP 서비스가 아니면?</code></pre>
<p>같은 상황도 처리해야 안정적인 프로그램을 만들 수 있다는 것을 알게 되었다.</p>
<hr>
<h1 id="🎯-day-3-목표">🎯 DAY 3 목표</h1>
<p>현재 Scanner는 다음 단계까지 구현되어 있다.</p>
<pre><code class="language-text">Target IP 입력
      ↓
Multi Thread Port Scan
      ↓
OPEN Port 탐지
      ↓
Service 이름 조회
      ↓
HTTP Request 전송
      ↓
HTTP Banner 확인</code></pre>
<p>하지만 아직 개선해야 할 부분이 있다.</p>
<p>현재는 OPEN Port가 발견되면 HTTP 서비스인지 알 수 없는 상태에서도 <code>get_http_banner()</code>를 실행한다.</p>
<pre><code class="language-text">445 OPEN
 ↓
HTTP 요청 전송
 ↓
HTTP가 아님
 ↓
예외 처리

8000 OPEN
 ↓
HTTP 요청 전송
 ↓
HTTP Server
 ↓
Banner 획득</code></pre>
<p>또한 현재 결과는 Terminal에만 출력되고 프로그램이 종료되면 결과가 남지 않는다.</p>
<p>따라서 <strong>DAY 3에서는 Scanner의 결과를 조금 더 체계적으로 관리하는 방향으로 발전시켜 볼 예정이다.</strong></p>
<h3 id="day-3-예정">DAY 3 예정</h3>
<ul>
<li>Socket 자원 처리 방식 개선</li>
<li>HTTP Banner 탐지 로직 개선</li>
<li>스캔 결과를 List 또는 Dictionary에 저장</li>
<li>OPEN Port 개수 확인</li>
<li>스캔 시작/종료 시간 및 소요 시간 출력</li>
<li>결과를 파일로 저장하는 기능 추가</li>
<li>Scanner 결과를 보기 좋은 형태로 정리</li>
</ul>
<p>예를 들어 최종적으로 다음과 같은 결과를 만들어보는 것이 목표이다.</p>
<pre><code class="language-text">========================================
        Python Security Scanner
========================================

Target : 127.0.0.1

[+] 135 OPEN
    Service : epmap
    Banner  : -

[+] 445 OPEN
    Service : microsoft-ds
    Banner  : -

[+] 7895 OPEN
    Service : Unknown
    Banner  : Microsoft-HTTPAPI/2.0

[+] 8000 OPEN
    Service : Unknown
    Banner  : SimpleHTTP/0.6 Python/3.13.3

----------------------------------------
Open Ports : 4
Scan Time  : 3.21 sec
----------------------------------------</code></pre>
<p>그리고 이후에는 단순히 Port를 찾는 Scanner에서 끝나는 것이 아니라,</p>
<pre><code class="language-text">Port Scan
   ↓
Service Detection
   ↓
Banner Grabbing
   ↓
결과 저장
   ↓
결과 분석</code></pre>
<p>처럼 하나씩 기능을 확장해 볼 예정이다.</p>
<hr>
<h1 id="📝-day-2-한-줄-정리">📝 DAY 2 한 줄 정리</h1>
<blockquote>
<p><strong>DAY 1에서 &quot;어떤 Port가 열려 있는가?&quot;를 확인했다면, DAY 2에서는 실제 서비스와 통신하여 &quot;열린 Port에서 어떤 정보를 얻을 수 있는가?&quot;까지 확인해보았다.</strong></p>
</blockquote>
<p>다음은 <strong>DAY 3 - 스캔 결과 관리 및 Scanner 기능 개선</strong> 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 복습 DAY1]]></title>
            <link>https://velog.io/@pingu_122/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%B3%B5%EC%8A%B5-DAY1</link>
            <guid>https://velog.io/@pingu_122/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%B3%B5%EC%8A%B5-DAY1</guid>
            <pubDate>Thu, 01 Oct 2026 09:48:12 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[매일 1% 성장하기 DAY-30]]></title>
            <link>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-30</link>
            <guid>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-30</guid>
            <pubDate>Wed, 30 Sep 2026 17:36:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="2026년-목표">2026년 목표</h1>
</blockquote>
<ul>
<li>클라우드 환경 운영 경험(상세 내용은 추후 작성)</li>
<li>보안관제 자동화 경험</li>
<li>블로그 꾸준히 작성</li>
<li>자격증 : 정보처리기사(9월 시작), 정보보안기사, AWS-SAA,/// CISA 및 CISSP(정보 보안 관련 경력 5년 이상 필요 (4년 경력+보안 관련 학위 가능))</li>
<li>개발 공부</li>
<li>영어 공부</li>
<li>운동</li>
<li>리팩토링 과정 복습 및 벨로그에 새롭게 작성</li>
<li>클라우드 보안 복습 및 벨로그에 작성
&lt;내년 예정 - 석사 과정&gt;</li>
</ul>
<blockquote>
<h2 id="📅-공부-순서---version-10월">📅 공부 순서 - (Version 10월)</h2>
</blockquote>
<ol>
<li><span style="color:green"><strong>파이썬(점프 투 파이썬) 완강했음 - 지속적인 복습</strong></span></li>
<li><span style="color:yellow"><strong>정보처리기사 실기 - 10월 25일에 시험있음 합격 목표</strong></span></li>
<li><span style="color:yellow"><strong>모의해킹(노말틱 강의) - 10월 말까지 완료 목표</strong></span></li>
<li><span style="color:"><strong>영어 - 11월부터 시작!</strong></span></li>
<li><span style="color:green"><strong>혼자공부하는 네트워크(인프런) 완강했음 - 지속적인 복습</strong> </span></li>
<li><span style="color:"><strong>리펙토링 과정 복습 시작(실습 및 블로그 재작성)</strong></span></li>
<li><span style="color:"><strong>Docker(인프런_devsecops) 강의(모의해킹 끝나고 시작)</strong></span></li>
<li><span style="color:"><strong>AWS 보안 가이드(인프런 강의) - (Docker 인프런 끝나고 시작)</strong></span></li>
<li><span style="color:"><strong>JAVA - (파이썬 어느 정도 할 줄 알때 시작)</strong></span>
→ 1 + 2 + 3 + 5 → 1 + 4 + 6 + 7</li>
</ol>
<hr>
<blockquote>
<h3 id="📅-금주-목표---0928월1004일">📅 금주 목표 - (09/28(월)~10/04(일))</h3>
</blockquote>
<ul>
<li><span style="color:green"><strong>파이썬(점프 투 파이썬)</strong><ul>
<li>Youtube 강의 + Ebook 참고</li>
<li>목표 : 점프 투 파이썬 학습 계속 진행 및 진도율 향상</span></li>
</ul>
</li>
<li><strong>정보처리기사 실기</strong><ul>
<li>목표 : 2020년도 기출문제 2회차 풀이</li>
<li>범위 : 1회차 ~ 4회차</li>
<li>목표 : 2021년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
<li>목표 : 2022년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
</ul>
</li>
<li><strong>모의해킹</strong><ul>
<li>목표 : Day 1 ~ Day 2 완료</li>
<li>범위 : 1주차 ~ 2주차</li>
</ul>
</li>
</ul>
<p><span style="color:"></span></p>
<blockquote>
<h3 id="📅-오늘의-목표--span-stylecolorgreen완료span-or-span-stylecolorred미완료span-or-span-stylecoloryellow진행중span">📅 오늘의 목표 : <span style="color:green"><strong>완료</strong></span> OR <span style="color:red"><strong>미완료</strong></span> OR <span style="color:yellow"><strong>진행중</strong></span></h3>
</blockquote>
<ul>
<li><span style="color:RED"><strong>모의해킹(노말틱 강의)</strong><ul>
<li><strong>1주차 ~ 하는데 까지 + 블로그 정리</strong></span></li>
</ul>
</li>
<li><span style="color:green"><strong>파이썬(nmap 기능 구현해보기)DAY-2 + 블로그 정리</strong></span></li>
<li><span style="color:green"><strong>household account 작성(23:50분에 작성)</strong></span> </li>
<li><span style="color:green"><strong>정보처리기사 DAY3(2020년도 2회차 문제풀이 및 복습) + 2021년도 1회차 문제풀이 + 2022년도 1회차 문제풀이</strong></span></li>
<li><span style="color:RED"><strong>방청소</strong></span></li>
<li><span style="color:RED"><strong>분리수거</strong></span></li>
<li><span style="color:GREEN"><strong>혼자공부하는 네트워크 복습 DAY2</strong></span></li>
<li><span style="color:green"><strong>빨래(Laundry)</strong></span></li>
</ul>
<blockquote>
<h3 id="💻-오늘완료-한-일">💻 오늘(완료) 한 일</h3>
</blockquote>
<ul>
<li>정보처리기사 DAY3 : 2021년도 1회차 문제풀이</li>
<li>household account 작성(23:50분에 작성)</li>
<li>파이썬(nmap 기능 구현해보기)DAY-2 + 블로그 정리</li>
<li>빨래(Laundry)</li>
<li>혼자공부하는 네트워크 복습 DAY2</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[혼자공부하는 네트워크 복습 DAY1]]></title>
            <link>https://velog.io/@pingu_122/%ED%98%BC%EC%9E%90%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B5%EC%8A%B5-DAY1-tnr33lwa</link>
            <guid>https://velog.io/@pingu_122/%ED%98%BC%EC%9E%90%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B5%EC%8A%B5-DAY1-tnr33lwa</guid>
            <pubDate>Tue, 29 Sep 2026 21:07:47 GMT</pubDate>
            <description><![CDATA[<h1 id="혼자-공부하는-네트워크---복습-day-1">혼자 공부하는 네트워크 - 복습 DAY 1</h1>
<p>기존에 공부했던 네트워크 내용을 다시 복습해보자.</p>
<p>이번 복습은 하루에 20문제씩 풀면서 기억이 나지 않거나 헷갈리는 부분을 다시 정리하는 방식으로 진행한다.</p>
<hr>
<h2 id="q1-노드node란">Q1. 노드(Node)란?</h2>
<p>네트워크를 구성하는 기본 요소 중 <strong>노드(Node)</strong>란 무엇일까?</p>
<h3 id="풀이">풀이</h3>
<p>노드는 네트워크 장비 같은 것으로 이해하고 있었다.</p>
<p>예를 들어</p>
<ul>
<li>라우터</li>
<li>스위치</li>
</ul>
<p>등이 노드에 해당한다고 생각했다.</p>
<p>하지만 노드는 네트워크 장비만을 의미하는 것이 아니라 <strong>네트워크에 연결되어 정보를 주고받을 수 있는 장치</strong>를 의미한다.</p>
<p>따라서</p>
<ul>
<li>PC</li>
<li>서버</li>
<li>스위치</li>
<li>라우터</li>
</ul>
<p>등도 모두 노드가 될 수 있다.</p>
<blockquote>
<p><strong>노드(Node)</strong> : 네트워크에 연결되어 정보를 주고받을 수 있는 장치</p>
</blockquote>
<hr>
<h2 id="q2-lan과-wan">Q2. LAN과 WAN</h2>
<p>다음 중 LAN과 WAN에 대한 설명으로 올바른 것은?</p>
<pre><code class="language-text">① LAN은 국가 간 네트워크이고 WAN은 가정이나 회사 내부의 네트워크이다.
② LAN은 비교적 가까운 지역의 네트워크이고 WAN은 LAN보다 넓은 범위를 연결한다.
③ LAN과 WAN은 범위의 차이가 없고 사용하는 프로토콜만 다르다.
④ WAN은 반드시 무선으로 구성해야 한다.</code></pre>
<h3 id="풀이-1">풀이</h3>
<p>정답은 <strong>②번</strong></p>
<ul>
<li><strong>LAN(Local Area Network)</strong> : 비교적 가까운 범위에서 구성되는 네트워크</li>
<li><strong>WAN(Wide Area Network)</strong> : LAN보다 넓은 범위를 연결하는 네트워크</li>
</ul>
<hr>
<h2 id="q3-패킷-교환-방식">Q3. 패킷 교환 방식</h2>
<p><strong>패킷 교환 방식(Packet Switching)</strong>은 데이터를 어떤 방식으로 전송할까?</p>
<h3 id="풀이-2">풀이</h3>
<p>패킷 교환 방식과 회선 교환 방식이 있다는 것은 기억났지만, 처음에는 패킷 교환을 <strong>1:1로 패킷을 전달하는 방식</strong>이라고 생각했다.</p>
<p>하지만 1:1 통신은 패킷 교환의 핵심 개념이 아니다.</p>
<p>패킷 교환 방식은 <strong>전송하려는 데이터를 작은 패킷 단위로 나누어 전송하는 방식</strong>이다.</p>
<pre><code class="language-text">큰 데이터
   ↓
Packet 1
Packet 2
Packet 3
   ↓
전송</code></pre>
<p>반대로 <strong>회선 교환 방식</strong>은 통신하기 전에 두 장치 사이에서 사용할 통신 경로를 확보하여 사용하는 방식이다.</p>
<hr>
<h2 id="q4-브로드캐스트">Q4. 브로드캐스트</h2>
<p>다음 설명이 맞으면 O, 틀리면 X.</p>
<blockquote>
<p>브로드캐스트는 한 송신자가 특정한 하나의 수신자에게만 데이터를 전송하는 방식이다.</p>
</blockquote>
<h3 id="풀이-3">풀이</h3>
<p>정답은 <strong>X</strong></p>
<p>브로드캐스트는 <strong>한 송신자가 네트워크의 모든 대상에게 데이터를 전송하는 방식</strong>이다.</p>
<pre><code class="language-text">Unicast     → 1 : 1
Broadcast   → 1 : 모든 대상
Multicast   → 1 : 특정 그룹</code></pre>
<hr>
<h2 id="q5-캡슐화encapsulation">Q5. 캡슐화(Encapsulation)</h2>
<p>OSI 모델에서 데이터가 하위 계층으로 내려갈 때 왜 헤더 등의 정보를 계속 추가할까?</p>
<h3 id="풀이-4">풀이</h3>
<p>처음에는</p>
<blockquote>
<p>문제가 발생했을 때 어떤 계층에서 문제가 발생했는지 확인하기 위해서?</p>
</blockquote>
<p>라고 생각했다.</p>
<p>이것은 <strong>계층화의 장점</strong>과 관련된 내용이고, 캡슐화의 목적과는 조금 다르다.</p>
<p>캡슐화는 데이터가 각 계층을 지나면서 <strong>해당 계층의 통신에 필요한 정보를 추가하는 과정</strong>이다.</p>
<pre><code class="language-text">세그먼트
   ↓
패킷
   ↓
프레임</code></pre>
<p>수신 측에서는 반대로 각 계층의 정보를 확인하고 제거하는 <strong>역캡슐화(Decapsulation)</strong>가 이루어진다.</p>
<blockquote>
<p><strong>캡슐화</strong> : 각 계층에서 통신에 필요한 제어 정보를 데이터에 추가하는 과정</p>
</blockquote>
<hr>
<h2 id="q6-osi-3계층의-주소">Q6. OSI 3계층의 주소</h2>
<p>OSI 모델의 <strong>네트워크 계층(Layer 3)</strong>에서 사용하는 대표적인 주소는?</p>
<pre><code class="language-text">① MAC 주소
② IP 주소
③ Port 번호
④ URL</code></pre>
<h3 id="풀이-5">풀이</h3>
<p>정답은 <strong>② IP 주소</strong></p>
<p>L2 스위치는 MAC 주소를 이용하고 L3에서는 IP 주소를 사용한다고 기억하고 있었다.</p>
<pre><code class="language-text">L2 → MAC Address → Switch
L3 → IP Address  → Router
L4 → Port Number</code></pre>
<hr>
<h2 id="q7-ttl">Q7. TTL</h2>
<p>IP 패킷이 여러 라우터를 지나면서 <strong>TTL 값이 0</strong>이 되면 어떻게 될까?</p>
<h3 id="풀이-6">풀이</h3>
<p>TTL이 <strong>Time To Live</strong>라는 것과 라우터 하나를 지나는 것을 <strong>1 Hop</strong>이라고 한다는 것은 기억하고 있었다.</p>
<p>하지만 TTL이 0이 되었을 때 어떻게 되는지는 기억하지 못했다.</p>
<p>라우터를 거칠 때마다 TTL 값이 감소하고 <strong>TTL이 0이 되면 패킷은 폐기된다.</strong></p>
<p>TTL이 없다면 라우팅 문제로 패킷이 네트워크를 계속 돌아다니는 상황이 발생할 수 있다.</p>
<pre><code class="language-text">Router
TTL 3
  ↓
Router
TTL 2
  ↓
Router
TTL 1
  ↓
TTL 0
  ↓
패킷 폐기</code></pre>
<blockquote>
<p><strong>TTL</strong> : 패킷이 네트워크에서 무한히 돌아다니는 것을 방지하기 위한 값</p>
</blockquote>
<hr>
<h2 id="q8-mtu와-단편화">Q8. MTU와 단편화</h2>
<p>MTU보다 큰 IP 패킷을 전송해야 한다면 어떻게 될까?</p>
<h3 id="풀이-7">풀이</h3>
<p>MTU가 최대 전송 단위라는 것은 기억하고 있었다.</p>
<p>다만 약자를 처음에 <code>Maximum Transport Unit</code>으로 기억했는데 정확한 명칭은 다음과 같다.</p>
<blockquote>
<p><strong>MTU = Maximum Transmission Unit</strong></p>
</blockquote>
<p>MTU보다 큰 IP 패킷을 전송해야 하는 경우 패킷을 작은 크기로 나누는 것을 <strong>단편화(Fragmentation)</strong>라고 한다.</p>
<pre><code class="language-text">큰 IP Packet
     ↓
Fragmentation
     ↓
[조각 1] [조각 2] [조각 3]
     ↓
전송
     ↓
목적지에서 재조립</code></pre>
<hr>
<h2 id="q9-arp">Q9. ARP</h2>
<p>PC-A가 같은 네트워크의 PC-B에게 데이터를 보내려고 한다.</p>
<p>PC-B의 <strong>IP 주소는 알고 있지만 MAC 주소를 모른다면</strong> 어떻게 해야 할까?</p>
<h3 id="풀이-8">풀이</h3>
<p>이 경우 <strong>ARP(Address Resolution Protocol)</strong>를 사용한다.</p>
<p>ARP는 알고 있는 IP 주소를 이용하여 해당 장치의 MAC 주소를 알아내는 프로토콜이다.</p>
<p>처음에는 공유기의 ARP 테이블에서 목적지 MAC 주소를 알아낸다고 생각했다.</p>
<p>하지만 같은 네트워크에 있는 장치를 찾는 경우 통신하려는 장치가 직접 ARP를 이용한다.</p>
<pre><code class="language-text">PC-A

&quot;192.168.0.20 사용하는 장치 누구야?&quot;
            ↓
       ARP Request
            ↓
          PC-B

&quot;나야. 내 MAC 주소는 BB-BB-BB-BB-BB-BB&quot;
            ↓
        ARP Reply</code></pre>
<p>알아낸 정보는 ARP 테이블에 저장할 수 있다.</p>
<pre><code class="language-text">192.168.0.20
→ BB-BB-BB-BB-BB-BB</code></pre>
<hr>
<h2 id="q10-다른-네트워크와-arp">Q10. 다른 네트워크와 ARP</h2>
<p>이번에는 목적지가 <strong>다른 네트워크에 있는 서버</strong>이다.</p>
<p>PC가 ARP를 통해 알아내야 하는 MAC 주소는?</p>
<pre><code class="language-text">① 목적지 서버의 MAC 주소
② DNS 서버의 MAC 주소
③ Default Gateway의 MAC 주소
④ 자신의 MAC 주소</code></pre>
<h3 id="풀이-9">풀이</h3>
<p>정답은 <strong>③ Default Gateway의 MAC 주소</strong></p>
<p>다른 네트워크로 나가기 위해서는 우선 <strong>Default Gateway</strong>를 거쳐야 한다.</p>
<p>따라서 PC는 ARP를 이용해</p>
<pre><code class="language-text">Default Gateway IP
        ↓
       ARP
        ↓
Default Gateway MAC</code></pre>
<p>을 알아낸다.</p>
<p>여기서 중요한 점은 <strong>최종 목적지 IP 주소와 현재 전달할 프레임의 목적지 MAC 주소를 구분하는 것</strong>이다.</p>
<hr>
<h2 id="q11-cidr-계산">Q11. CIDR 계산</h2>
<p>다음 IP 주소의 네트워크 주소, 브로드캐스트 주소, 사용 가능한 호스트 범위를 구하자.</p>
<pre><code class="language-text">192.168.10.130/26</code></pre>
<h3 id="풀이-10">풀이</h3>
<p>IPv4는 총 32bit이다.</p>
<pre><code class="language-text">32 - 26 = 6bit</code></pre>
<p>호스트 영역이 6bit이므로</p>
<pre><code class="language-text">2^6 = 64</code></pre>
<p>64개 단위로 주소를 구분할 수 있다.</p>
<pre><code class="language-text">0   ~ 63
64  ~ 127
128 ~ 191
192 ~ 255</code></pre>
<p><code>130</code>은 <code>128 ~ 191</code> 범위에 포함된다.</p>
<p>따라서</p>
<pre><code class="language-text">네트워크 주소
→ 192.168.10.128

브로드캐스트 주소
→ 192.168.10.191

사용 가능한 호스트
→ 192.168.10.129 ~ 192.168.10.190</code></pre>
<p>사용 가능한 호스트 수는</p>
<pre><code class="language-text">2^6 - 2
= 62개</code></pre>
<p>이다.</p>
<hr>
<h2 id="q12-nat">Q12. NAT</h2>
<p>사설 IP를 사용하는 PC가 인터넷과 통신할 때 <strong>NAT</strong>는 어떤 역할을 할까?</p>
<h3 id="풀이-11">풀이</h3>
<p>사설 IP는 여러 내부 네트워크에서 중복해서 사용할 수 있다.</p>
<p>인터넷과 통신할 때는 공인 IP가 필요하며 이때 IP 주소를 변환하는 기술이 <strong>NAT(Network Address Translation)</strong>이다.</p>
<pre><code class="language-text">사설 IP
   ↓
  NAT
   ↓
공인 IP</code></pre>
<hr>
<h2 id="q13-port-forwarding">Q13. Port Forwarding</h2>
<p>공유기의 특정 포트로 들어온 요청을 내부의 특정 IP와 Port로 전달하도록 설정하는 기능은?</p>
<h3 id="풀이-12">풀이</h3>
<p>정답은 <strong>Port Forwarding</strong></p>
<p>예를 들어</p>
<pre><code class="language-text">공인 IP:5000
      ↓
공유기
      ↓
Port Forwarding
      ↓
192.168.0.100:8080</code></pre>
<p>처럼 외부에서 특정 포트로 들어온 요청을 내부의 지정된 장치와 포트로 전달할 수 있다.</p>
<hr>
<h2 id="q14-dhcp-dora">Q14. DHCP DORA</h2>
<p>DHCP를 통해 IP 주소를 할당받는 순서를 맞혀보자.</p>
<pre><code class="language-text">DHCP ACK
DHCP Discover
DHCP Request
DHCP Offer</code></pre>
<h3 id="풀이-13">풀이</h3>
<p>처음에는</p>
<pre><code class="language-text">Discover
→ Request
→ ACK
→ Offer</code></pre>
<p>라고 생각했다.</p>
<p>정확한 순서는 다음과 같다.</p>
<pre><code class="language-text">Discover
   ↓
Offer
   ↓
Request
   ↓
ACK</code></pre>
<p>앞글자를 따서 <strong>DORA</strong>라고 기억하면 된다.</p>
<h3 id="discover">Discover</h3>
<p>클라이언트</p>
<blockquote>
<p>DHCP 서버 있나요?</p>
</blockquote>
<h3 id="offer">Offer</h3>
<p>DHCP 서버</p>
<blockquote>
<p>이 IP를 사용하는 건 어때?</p>
</blockquote>
<h3 id="request">Request</h3>
<p>클라이언트</p>
<blockquote>
<p>그 IP를 사용하겠습니다.</p>
</blockquote>
<h3 id="ack">ACK</h3>
<p>DHCP 서버</p>
<blockquote>
<p>확인. 해당 IP를 사용해.</p>
</blockquote>
<hr>
<h2 id="q15-dhcp-임대-기간">Q15. DHCP 임대 기간</h2>
<p>DHCP의 임대 기간이 끝나면 무조건 IP 주소가 변경될까?</p>
<h3 id="풀이-14">풀이</h3>
<p>예전에 이 부분이 궁금해서 직접 PC의 DHCP 임대 기간을 확인했던 기억이 있다.</p>
<p>DHCP의 임대 기간이 끝난다고 해서 <strong>무조건 새로운 IP 주소로 변경되는 것은 아니다.</strong></p>
<p>클라이언트는 임대가 끝나기 전에 기존 IP 주소를 계속 사용하기 위해 <strong>갱신을 시도</strong>할 수 있다.</p>
<pre><code class="language-text">IP 할당
   ↓
임대 기간 사용
   ↓
갱신 시도
   ↓
갱신 성공
   ↓
기존 IP 계속 사용</code></pre>
<blockquote>
<p><strong>DHCP 임대 만료 = 무조건 IP 변경은 아니다.</strong></p>
</blockquote>
<hr>
<h2 id="q16-default-route">Q16. Default Route</h2>
<p>라우팅 테이블에 다음과 같은 경로가 있다.</p>
<pre><code class="language-text">0.0.0.0/0 → Gateway A</code></pre>
<p><code>0.0.0.0/0</code>은 무엇일까?</p>
<h3 id="풀이-15">풀이</h3>
<p>처음에는 <code>127.0.0.1</code>과 헷갈렸다.</p>
<pre><code class="language-text">127.0.0.1
→ Loopback
→ 자기 자신</code></pre>
<p>반면</p>
<pre><code class="language-text">0.0.0.0/0
→ Default Route</code></pre>
<p>이다.</p>
<p>목적지로 가는 더 구체적인 경로가 라우팅 테이블에 없다면 <strong>Default Route</strong>를 이용할 수 있다.</p>
<p>쉽게 생각하면</p>
<blockquote>
<p>어디로 보내야 할지 모르겠으면 일단 여기로 보내!</p>
</blockquote>
<p>라고 이해할 수 있다.</p>
<hr>
<h2 id="q17-정적-라우팅과-동적-라우팅">Q17. 정적 라우팅과 동적 라우팅</h2>
<p>라우터가 수십 대로 증가하고 네트워크 경로도 자주 변경된다면 어떤 라우팅 방식이 관리하기 좋을까?</p>
<h3 id="풀이-16">풀이</h3>
<p>정답은 <strong>동적 라우팅(Dynamic Routing)</strong></p>
<p>정적 라우팅은 관리자가 경로를 직접 등록한다.</p>
<p>라우터가 몇 대 없는 작은 네트워크에서는 관리할 수 있지만 네트워크 규모가 커지면 관리해야 하는 경로도 증가한다.</p>
<p>동적 라우팅에서는 라우터들이 <strong>라우팅 프로토콜을 이용해 경로 정보를 교환</strong>하고 네트워크 변화에 대응할 수 있다.</p>
<pre><code class="language-text">소규모 / 변화 적음
→ Static Routing

규모 증가 / 경로 변화 많음
→ Dynamic Routing</code></pre>
<hr>
<h2 id="q18-igp--rip--ospf--bgp">Q18. IGP / RIP / OSPF / BGP</h2>
<p>RIP와 OSPF는 어떤 종류의 라우팅 프로토콜이고 BGP는 어디에서 사용할까?</p>
<h3 id="풀이-17">풀이</h3>
<p>RIP가 내부 라우팅이고 <strong>Distance Vector</strong> 방식이라는 것은 기억하고 있었다.</p>
<p>RIP와 OSPF는 <strong>IGP(Interior Gateway Protocol)</strong>에 속한다.</p>
<pre><code class="language-text">AS 내부
   ↓
IGP
 ├─ RIP
 └─ OSPF</code></pre>
<p>반면 BGP는 <strong>AS와 AS 사이의 경로 교환</strong>에 사용된다.</p>
<hr>
<h2 id="q19-asautonomous-system">Q19. AS(Autonomous System)</h2>
<p>AS란 무엇일까?</p>
<h3 id="풀이-18">풀이</h3>
<p>용어 자체는 기억나지 않았지만 예전에 공부했던 그림은 기억났다.</p>
<p>동그란 영역 안에 여러 라우터가 있고 각각의 영역이 나누어져 있었던 그림이다.</p>
<p>AS는 <strong>하나의 관리 주체와 라우팅 정책 아래 관리되는 네트워크 영역</strong>으로 이해할 수 있다.</p>
<pre><code class="language-text">┌────── AS 100 ──────┐
│                    │
│ R1 ─── R2 ─── R3   │
│                    │
└────────────────────┘

          ↕

┌────── AS 200 ──────┐
│                    │
│ R4 ─── R5 ─── R6   │
│                    │
└────────────────────┘</code></pre>
<hr>
<h2 id="q20-egp--bgp--ebgp--ibgp">Q20. EGP / BGP / eBGP / iBGP</h2>
<p>EGP와 BGP는 같은 것일까?</p>
<p>그리고 eBGP는 무엇일까?</p>
<h3 id="풀이-19">풀이</h3>
<p>BGP가 <strong>AS와 AS 사이의 라우팅과 관련된 프로토콜</strong>이라는 것은 기억하고 있었다.</p>
<p>하지만 EGP와 eBGP의 관계는 헷갈렸다.</p>
<h3 id="추가로-공부한-내용">추가로 공부한 내용</h3>
<p><strong>EGP(Exterior Gateway Protocol)</strong>는 AS 간 외부 라우팅 프로토콜을 구분할 때 사용하는 개념이다.</p>
<p><strong>BGP(Border Gateway Protocol)</strong>는 AS 간 경로 정보를 교환하는 데 사용되는 대표적인 라우팅 프로토콜이다.</p>
<p>BGP는 사용되는 관계에 따라 다음과 같이 구분할 수 있다.</p>
<pre><code class="language-text">BGP
├─ eBGP
└─ iBGP</code></pre>
<h3 id="ebgp">eBGP</h3>
<p><strong>External BGP</strong></p>
<p>서로 다른 AS의 BGP 라우터 사이에서 BGP 경로 정보를 교환한다.</p>
<pre><code class="language-text">AS 100 ←── eBGP ──→ AS 200</code></pre>
<h3 id="ibgp">iBGP</h3>
<p><strong>Internal BGP</strong></p>
<p>같은 AS 내부의 BGP 라우터들이 BGP 경로 정보를 교환한다.</p>
<p>여기서 주의할 점은</p>
<pre><code class="language-text">iBGP ≠ IGP</code></pre>
<p>이다.</p>
<p>iBGP의 <code>Internal</code>은 같은 AS 내부에서 BGP 정보를 교환한다는 의미이며 RIP, OSPF와 같은 IGP와는 구분해야 한다.</p>
<hr>
<h1 id="오늘의-핵심-복습">오늘의 핵심 복습</h1>
<p>오늘 특히 헷갈렸던 내용만 다시 정리해보자.</p>
<h3 id="캡슐화">캡슐화</h3>
<pre><code class="language-text">각 계층에서 필요한 정보를 추가
→ Encapsulation</code></pre>
<h3 id="ttl">TTL</h3>
<pre><code class="language-text">Router를 지날 때 TTL 감소
→ TTL 0
→ Packet 폐기</code></pre>
<h3 id="arp">ARP</h3>
<pre><code class="language-text">IP 주소는 알고 있음
MAC 주소를 모름
        ↓
       ARP
        ↓
MAC 주소 확인</code></pre>
<p>다른 네트워크로 통신한다면 우선 <strong>Default Gateway의 MAC 주소</strong>가 필요하다.</p>
<h3 id="dhcp">DHCP</h3>
<pre><code class="language-text">Discover
→ Offer
→ Request
→ ACK

DORA</code></pre>
<h3 id="default-route">Default Route</h3>
<pre><code class="language-text">127.0.0.1
→ Loopback

0.0.0.0/0
→ Default Route</code></pre>
<h3 id="라우팅-프로토콜">라우팅 프로토콜</h3>
<pre><code class="language-text">                Routing Protocol
                       │
             ┌─────────┴─────────┐
             │                   │
            IGP                 EGP
             │                   │
        ┌────┴────┐             BGP
       RIP       OSPF         ┌──┴──┐
                             eBGP  iBGP</code></pre>
<h3 id="as-기준으로-다시-보기">AS 기준으로 다시 보기</h3>
<pre><code class="language-text">┌──────────── AS 100 ────────────┐
│                                │
│      RIP / OSPF 등 IGP         │
│                                │
│   Router ─ Router ─ Router     │
│                       │        │
└───────────────────────│────────┘
                        │
                       eBGP
                        │
┌───────────────────────│────────┐
│                       │        │
│   Router ─ Router ─ Router     │
│                                │
│           AS 200               │
└────────────────────────────────┘</code></pre>
<p>오늘은 기본적인 개념은 어느 정도 기억하고 있었지만 <strong>DHCP DORA, TTL, Default Route, AS와 BGP의 관계처럼 정확한 동작 과정이나 용어 관계에서 헷갈리는 부분</strong>이 있었다.</p>
<p>다음 복습에서는 오늘 헷갈렸던 개념들도 다시 문제로 만나면서 반복해서 익혀보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[매일 1% 성장하기 DAY-29]]></title>
            <link>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-29</link>
            <guid>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-29</guid>
            <pubDate>Tue, 29 Sep 2026 20:51:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="2026년-목표">2026년 목표</h1>
</blockquote>
<ul>
<li>클라우드 환경 운영 경험(상세 내용은 추후 작성)</li>
<li>보안관제 자동화 경험</li>
<li>블로그 꾸준히 작성</li>
<li>자격증 : 정보처리기사(9월 시작), 정보보안기사, AWS-SAA,/// CISA 및 CISSP(정보 보안 관련 경력 5년 이상 필요 (4년 경력+보안 관련 학위 가능))</li>
<li>개발 공부</li>
<li>영어 공부</li>
<li>운동</li>
<li>리팩토링 과정 복습 및 벨로그에 새롭게 작성</li>
<li>클라우드 보안 복습 및 벨로그에 작성
&lt;내년 예정 - 석사 과정&gt;</li>
</ul>
<blockquote>
<h2 id="📅-공부-순서---version-9-10월">📅 공부 순서 - (Version 9-10월)</h2>
</blockquote>
<ol>
<li><span style="color:green"><strong>파이썬(점프 투 파이썬) 완강했음</strong></span></li>
<li><span style="color:yellow"><strong>정보처리기사 실기 - 10월 25일에 시험있음 합격 목표</strong></span></li>
<li><span style="color:yellow"><strong>모의해킹(노말틱 강의) - 10월 말까지 완료 목표</strong></span></li>
<li><span style="color:"><strong>영어 - 11월부터 시작!</strong></span></li>
<li><span style="color:green"><strong>혼자공부하는 네트워크(인프런) 완강했음 - 10월에 지속적인 복습</strong> </span></li>
<li><span style="color:"><strong>리펙토링 과정 복습 시작(실습 및 블로그 재작성)</strong></span></li>
<li><span style="color:"><strong>Docker(인프런_devsecops) 강의(모의해킹 끝나고 시작)</strong></span></li>
<li><span style="color:"><strong>AWS 보안 가이드(인프런 강의) - (Docker 인프런 끝나고 시작)</strong></span></li>
<li><span style="color:yellow"><strong>JAVA - (파이썬 끝나고 시작)</strong></span></li>
</ol>
<hr>
<blockquote>
<h3 id="📅-금주-목표---0928월1004일">📅 금주 목표 - (09/28(월)~10/04(일))</h3>
</blockquote>
<ul>
<li><span style="color:green"><strong>파이썬(점프 투 파이썬)</strong><ul>
<li>Youtube 강의 + Ebook 참고</li>
<li>목표 : 점프 투 파이썬 학습 계속 진행 및 진도율 향상</span></li>
</ul>
</li>
<li><strong>정보처리기사 실기</strong><ul>
<li>목표 : 2020년도 기출문제 2회차 풀이</li>
<li>범위 : 1회차 ~ 4회차</li>
<li>목표 : 2021년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
<li>목표 : 2022년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
</ul>
</li>
<li><strong>모의해킹</strong><ul>
<li>목표 : Day 1 ~ Day 2 완료</li>
<li>범위 : 1주차 ~ 2주차</li>
</ul>
</li>
</ul>
<p><span style="color:"></span></p>
<blockquote>
<h3 id="📅-오늘의-목표--span-stylecolorgreen완료span-or-span-stylecolorred미완료span-or-span-stylecoloryellow진행중span">📅 오늘의 목표 : <span style="color:green"><strong>완료</strong></span> OR <span style="color:red"><strong>미완료</strong></span> OR <span style="color:yellow"><strong>진행중</strong></span></h3>
</blockquote>
<ul>
<li><span style="color:red"><strong>모의해킹(노말틱 강의)</strong><ul>
<li><strong>1주차 ~ 하는데 까지 + 블로그 정리</strong></span></li>
</ul>
</li>
<li><span style="color:green"><strong>파이썬(nmap 기능 구현해보기)DAY-1 + 블로그 정리</strong></span></li>
<li><span style="color:green"><strong>household account 작성(23:50분에 작성)</strong></span> </li>
<li><span style="color:yellow"><strong>정보처리기사 DAY3(2020년도 2회차 문제풀이 및 복습) + 2021년도 1회차 문제풀이 + 2022년도 1회차 문제풀이</strong></span></li>
<li><span style="color:red"><strong>방청소</strong></span></li>
<li><span style="color:red"><strong>JAVA 뭘로 공부할지 찾아보기</strong></span></li>
<li><span style="color:green"><strong>혼자공부하는 네트워크 복습 DAY1</strong></span></li>
</ul>
<blockquote>
<h3 id="💻-오늘완료-한-일">💻 오늘(완료) 한 일</h3>
</blockquote>
<ul>
<li>파이썬(nmap 기능 구현해보기)DAY-1 + 블로그 정리</li>
<li>혼자공부하는 네트워크 복습 DAY1 + 블로그 정리</li>
<li>household account 작성(23:50분에 작성)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python Network Security Scanner DAY1]]></title>
            <link>https://velog.io/@pingu_122/Python-Network-Security-Scanner-DAY1</link>
            <guid>https://velog.io/@pingu_122/Python-Network-Security-Scanner-DAY1</guid>
            <pubDate>Tue, 29 Sep 2026 19:38:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/pingu_122/post/a4878589-4011-499f-8215-6ee9f96a660f/image.png" alt=""></p>
<h1 id="🐍-python-security-scanner-프로젝트---day-1">🐍 Python Security Scanner 프로젝트 - Day 1</h1>
<blockquote>
<p><code>점프 투 파이썬</code> 학습을 마친 후 배운 내용을 실제로 활용하기 위해 시작한 개인 프로젝트이다.<br>Python의 <code>socket</code>을 이용해 <strong>TCP Port Scanner</strong>를 직접 구현하고, 순차 스캔의 속도 문제를 <code>ThreadPoolExecutor</code>를 이용한 멀티스레딩으로 개선해보았다.</p>
</blockquote>
<hr>
<h2 id="📌-day-1-목표">📌 Day 1 목표</h2>
<p>오늘의 목표는 다음과 같다.</p>
<ul>
<li>Python <code>socket</code> 모듈 이해</li>
<li>특정 IP / Port에 TCP 연결 시도</li>
<li>OPEN Port 판단</li>
<li>여러 Port를 자동으로 스캔</li>
<li>함수로 Port Scan 기능 분리</li>
<li><code>timeout</code> 설정</li>
<li><code>ThreadPoolExecutor</code>를 이용한 멀티스레딩</li>
<li>스캔 실행 시간 측정</li>
<li>직접 테스트 서버를 만들어 OPEN Port 탐지 검증</li>
</ul>
<blockquote>
<p>⚠️ 포트 스캔 실습은 본인의 PC 또는 직접 관리하는 테스트 환경을 대상으로 진행한다.</p>
</blockquote>
<hr>
<h1 id="1-프로젝트-생성">1. 프로젝트 생성</h1>
<p>프로젝트 폴더를 생성하고 <code>main.py</code> 파일부터 시작했다.</p>
<pre><code class="language-text">python-security-scanner/
└── main.py</code></pre>
<p>처음부터 복잡하게 파일을 나누기보다는 하나의 파일에서 기능을 구현하고, 프로젝트가 커지면 추후 모듈화할 예정이다.</p>
<hr>
<h1 id="2-ip와-port-입력받기">2. IP와 Port 입력받기</h1>
<p>가장 먼저 스캔할 대상의 IP와 Port를 사용자에게 입력받았다.</p>
<pre><code class="language-python">target_ip = input(&quot;스캔할 IP를 입력하세요: &quot;)
target_port = int(input(&quot;스캔할 Port를 입력하세요: &quot;))</code></pre>
<p>여기서 IP와 Port의 자료형이 다르다는 점이 중요하다.</p>
<pre><code class="language-text">IP
&quot;127.0.0.1&quot;
→ str

Port
80
→ int</code></pre>
<p>IP 주소는 <code>127.0.0.1</code>처럼 <code>.</code>이 포함되어 있기 때문에 정수로 변환할 수 없다.</p>
<p>따라서 IP는 문자열 그대로 입력받고, Port만 <code>int()</code>를 이용해 정수로 변환한다.</p>
<p>출력에는 f-string을 사용했다.</p>
<pre><code class="language-python">print(f&quot;[+] {target_ip}:{target_port}&quot;)</code></pre>
<p>실행 예시:</p>
<pre><code class="language-text">스캔할 IP를 입력하세요: 127.0.0.1
스캔할 Port를 입력하세요: 80

[+] 127.0.0.1:80</code></pre>
<hr>
<h1 id="3-tcp-socket-생성">3. TCP Socket 생성</h1>
<p>실제로 네트워크 연결을 시도하기 위해 Python의 <code>socket</code> 모듈을 사용했다.</p>
<pre><code class="language-python">import socket</code></pre>
<p>그리고 다음과 같이 Socket을 생성한다.</p>
<pre><code class="language-python">sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code></pre>
<p>각 옵션의 의미는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>코드</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>socket.socket()</code></td>
<td>Socket 생성</td>
</tr>
<tr>
<td><code>AF_INET</code></td>
<td>IPv4 사용</td>
</tr>
<tr>
<td><code>SOCK_STREAM</code></td>
<td>TCP 사용</td>
</tr>
</tbody></table>
<p>즉,</p>
<pre><code class="language-python">socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code></pre>
<p>은 쉽게 말하면</p>
<blockquote>
<p><strong>IPv4 주소를 사용하는 TCP Socket을 생성한다.</strong></p>
</blockquote>
<p>라는 의미이다.</p>
<hr>
<h1 id="4-connect_ex를-이용한-tcp-연결">4. connect_ex()를 이용한 TCP 연결</h1>
<p>생성한 Socket을 이용해 대상 IP와 Port에 TCP 연결을 시도한다.</p>
<pre><code class="language-python">result = sock.connect_ex((target_ip, target_port))</code></pre>
<p><code>connect_ex()</code>에는 IP와 Port를 하나의 튜플로 전달한다.</p>
<pre><code class="language-python">(&quot;127.0.0.1&quot;, 80)</code></pre>
<p>구조는 다음과 같다.</p>
<pre><code class="language-text">sock.connect_ex(
    (target_ip, target_port)
)</code></pre>
<p><code>connect_ex()</code>는 연결 결과를 숫자로 반환한다.</p>
<pre><code class="language-text">TCP 연결 성공
    ↓
result == 0

TCP 연결 실패
    ↓
result != 0</code></pre>
<p>따라서 <code>if</code>문을 이용해 Port의 상태를 판단할 수 있다.</p>
<pre><code class="language-python">if result == 0:
    print(f&quot;[+] {target_ip}:{target_port} OPEN&quot;)
else:
    print(f&quot;[-] {target_ip}:{target_port} CLOSED&quot;)</code></pre>
<p>단, 정확히 말하면 <code>result != 0</code>은 무조건 하나의 의미인 <code>CLOSED</code>만 나타내는 것은 아니다.</p>
<p>Connection Refused, Timeout 등 여러 이유로 TCP 연결이 성공하지 않을 수 있기 때문에 이후에는 <strong>OPEN 여부를 중심으로 판단</strong>하도록 구현했다.</p>
<hr>
<h1 id="5-socket-종료">5. Socket 종료</h1>
<p>생성한 Socket은 사용이 끝나면 종료해준다.</p>
<pre><code class="language-python">sock.close()</code></pre>
<p>기본적인 흐름은 다음과 같다.</p>
<pre><code class="language-text">Socket 생성
    ↓
TCP 연결 시도
    ↓
결과 확인
    ↓
Socket 종료</code></pre>
<hr>
<h1 id="6-여러-port-자동-스캔">6. 여러 Port 자동 스캔</h1>
<p>처음에는 하나의 Port만 입력받았다.</p>
<pre><code class="language-python">target_port = int(input(&quot;스캔할 Port를 입력하세요: &quot;))</code></pre>
<p>하지만 Port Scanner라면 여러 Port를 자동으로 검사할 수 있어야 한다.</p>
<p>따라서 <code>for</code>문과 <code>range()</code>를 이용했다.</p>
<pre><code class="language-python">for target_port in range(1, 101):</code></pre>
<p><code>range()</code>의 마지막 숫자는 포함되지 않기 때문에</p>
<pre><code class="language-python">range(1, 101)</code></pre>
<p>은 실제로</p>
<pre><code class="language-text">1
2
3
...
99
100</code></pre>
<p>까지 반복한다.</p>
<p>각 Port마다 새로운 TCP 연결을 시도해야 하기 때문에 Socket 역시 반복문 내부에서 생성하고 종료했다.</p>
<pre><code class="language-python">for target_port in range(1, 101):

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    result = sock.connect_ex((target_ip, target_port))

    if result == 0:
        print(f&quot;[+] {target_ip}:{target_port} OPEN&quot;)

    sock.close()</code></pre>
<p>CLOSED Port까지 모두 출력하면 결과가 너무 많아지기 때문에 이후에는 <strong>OPEN Port만 출력</strong>하도록 했다.</p>
<hr>
<h1 id="7-timeout-설정">7. Timeout 설정</h1>
<p>여러 Port를 검사해보니 스캔 속도가 매우 느린 문제가 발생했다.</p>
<p>TCP 연결을 시도했는데 상대방이 바로 응답하지 않으면 프로그램이 응답을 기다릴 수 있기 때문이다.</p>
<p>이를 방지하기 위해 Timeout을 설정했다.</p>
<pre><code class="language-python">sock.settimeout(0.5)</code></pre>
<p>의미는 다음과 같다.</p>
<pre><code class="language-text">TCP 연결 시도
    ↓
최대 0.5초 동안 기다림
    ↓
응답이 없으면 연결 시도를 계속 기다리지 않음</code></pre>
<p>따라서 Socket 생성 이후 다음과 같이 Timeout을 설정했다.</p>
<pre><code class="language-python">sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.settimeout(0.5)

result = sock.connect_ex((target_ip, target_port))</code></pre>
<p>Timeout 값을 너무 크게 설정하면 스캔 속도가 느려질 수 있고, 반대로 너무 작게 설정하면 응답이 느린 서비스의 연결 성공 여부를 놓칠 수 있다.</p>
<hr>
<h1 id="8-직접-8000번-port-열어보기">8. 직접 8000번 Port 열어보기</h1>
<p>Scanner가 실제로 OPEN Port를 탐지하는지 검증하기 위해 Python의 간단한 HTTP Server를 사용했다.</p>
<p>새로운 터미널에서 다음 명령어를 실행했다.</p>
<pre><code class="language-powershell">python -m http.server 8000</code></pre>
<p>실행하면 Python HTTP Server가 TCP 8000번 Port에서 연결을 기다린다.</p>
<pre><code class="language-text">Python HTTP Server
        ↓
TCP 8000
        ↓
OPEN</code></pre>
<p>브라우저에서도 다음 주소로 접근할 수 있다.</p>
<pre><code class="language-text">http://127.0.0.1:8000</code></pre>
<p>Scanner의 범위도 테스트를 위해 다음과 같이 변경했다.</p>
<pre><code class="language-python">for target_port in range(7995, 8006):</code></pre>
<p>그러면 다음 Port들을 검사한다.</p>
<pre><code class="language-text">7995
7996
7997
7998
7999
8000
8001
8002
8003
8004
8005</code></pre>
<p>실행 결과:</p>
<pre><code class="language-text">[+] 127.0.0.1:8000 OPEN</code></pre>
<p>정상적으로 8000번 Port가 탐지되었다.</p>
<p>HTTP Server를 <code>Ctrl + C</code>로 종료한 뒤 다시 Scanner를 실행하면 8000번 Port가 더 이상 OPEN으로 탐지되지 않는다.</p>
<p>이를 통해 직접 만든 Scanner가 실제 TCP 연결 상태를 기준으로 동작한다는 것을 확인했다.</p>
<hr>
<h1 id="9-port-scan-기능-함수화">9. Port Scan 기능 함수화</h1>
<p>기존에는 모든 코드를 <code>for</code>문 안에서 처리했다.</p>
<p>이후 멀티스레딩을 적용하기 위해 <strong>Port 하나를 검사하는 기능을 함수로 분리</strong>했다.</p>
<pre><code class="language-python">def scan_port(target_ip, target_port):

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    sock.settimeout(0.5)

    result = sock.connect_ex((target_ip, target_port))

    if result == 0:
        print(f&quot;[+] {target_ip}:{target_port} OPEN&quot;)

    sock.close()</code></pre>
<p>함수에 필요한 매개변수는 두 개이다.</p>
<pre><code class="language-python">def scan_port(target_ip, target_port):</code></pre>
<ul>
<li><code>target_ip</code> : 검사할 대상 IP</li>
<li><code>target_port</code> : 검사할 Port</li>
</ul>
<p>예를 들어</p>
<pre><code class="language-python">scan_port(&quot;127.0.0.1&quot;, 8000)</code></pre>
<p>을 실행하면</p>
<pre><code class="language-text">target_ip   = &quot;127.0.0.1&quot;
target_port = 8000</code></pre>
<p>이 함수 내부로 전달된다.</p>
<p>여기서는 <code>return</code>을 사용하지 않았다.</p>
<p>현재 함수의 목적은 결과값을 다른 곳에 반환하는 것이 아니라 OPEN Port가 발견되었을 때 직접 <code>print()</code>로 출력하는 것이기 때문이다.</p>
<hr>
<h1 id="10-함수-정의와-함수-호출">10. 함수 정의와 함수 호출</h1>
<p>함수를 사용하면서 다시 정리한 중요한 개념이다.</p>
<p>다음 코드는 <strong>함수를 정의</strong>하는 것이다.</p>
<pre><code class="language-python">def scan_port(target_ip, target_port):</code></pre>
<p>함수를 정의했다고 바로 실행되는 것은 아니다.</p>
<p>실제로 실행하려면 함수를 <strong>호출</strong>해야 한다.</p>
<pre><code class="language-python">scan_port(target_ip, target_port)</code></pre>
<p>따라서 반복문과 함수를 다음과 같이 분리할 수 있다.</p>
<pre><code class="language-python">def scan_port(target_ip, target_port):
    # Port 하나 검사
    ...


for target_port in range(1, 101):
    scan_port(target_ip, target_port)</code></pre>
<p>구조는 다음과 같다.</p>
<pre><code class="language-text">Port 1
 ↓
scan_port(IP, 1)

Port 2
 ↓
scan_port(IP, 2)

Port 3
 ↓
scan_port(IP, 3)

...</code></pre>
<p>즉,</p>
<blockquote>
<p><strong>함수는 한 번 정의하고, for문에서 필요한 만큼 호출한다.</strong></p>
</blockquote>
<hr>
<h1 id="11-순차-스캔의-문제">11. 순차 스캔의 문제</h1>
<p>함수화를 했지만 여전히 Port는 순서대로 검사한다.</p>
<pre><code class="language-text">Port 1 검사
    ↓
완료
    ↓
Port 2 검사
    ↓
완료
    ↓
Port 3 검사</code></pre>
<p>Timeout이 <code>0.5초</code>라고 가정하면 응답이 늦은 Port가 많을 경우 시간이 상당히 오래 걸릴 수 있다.</p>
<p>예를 들어 1000개의 Port가 각각 최대 0.5초를 기다린다면 이론적으로 상당한 시간이 필요할 수 있다.</p>
<p>따라서 여러 Port를 동시에 검사하도록 개선하기로 했다.</p>
<hr>
<h1 id="12-threadpoolexecutor">12. ThreadPoolExecutor</h1>
<p>Python의 <code>concurrent.futures</code> 모듈에서 <code>ThreadPoolExecutor</code>를 사용했다.</p>
<pre><code class="language-python">from concurrent.futures import ThreadPoolExecutor</code></pre>
<p><code>ThreadPoolExecutor</code>는 여러 Worker Thread가 작업을 나눠 처리할 수 있도록 해준다.</p>
<p>예를 들어</p>
<pre><code class="language-python">ThreadPoolExecutor(max_workers=10)</code></pre>
<p>은 최대 10개의 Worker Thread를 이용해 작업을 처리한다.</p>
<p>기존 방식:</p>
<pre><code class="language-text">Worker 1 → Port 1 → 완료
                    ↓
           Port 2 → 완료
                    ↓
           Port 3 → 완료</code></pre>
<p>멀티스레딩:</p>
<pre><code class="language-text">Worker 1  → Port 1
Worker 2  → Port 2
Worker 3  → Port 3
Worker 4  → Port 4
...
Worker 10 → Port 10</code></pre>
<p>네트워크 연결처럼 응답을 기다리는 시간이 많은 I/O 작업에서는 여러 연결을 동시에 진행할 수 있기 때문에 성능 개선 효과를 얻을 수 있다.</p>
<hr>
<h1 id="13-executorsubmit">13. executor.submit()</h1>
<p>Thread Pool에 작업을 전달하기 위해 <code>submit()</code>을 사용했다.</p>
<pre><code class="language-python">executor.submit(scan_port, target_ip, target_port)</code></pre>
<p>구조를 풀어보면 다음과 같다.</p>
<pre><code class="language-text">executor.submit(
    scan_port,      ← 실행할 함수
    target_ip,      ← 첫 번째 인수
    target_port     ← 두 번째 인수
)</code></pre>
<p>즉 Worker에게</p>
<pre><code class="language-python">scan_port(target_ip, target_port)</code></pre>
<p>작업을 실행하도록 요청하는 것이다.</p>
<hr>
<h1 id="14-멀티스레딩-port-scanner">14. 멀티스레딩 Port Scanner</h1>
<p><code>for</code>문과 <code>ThreadPoolExecutor</code>를 결합했다.</p>
<pre><code class="language-python">with ThreadPoolExecutor(max_workers=10) as executor:

    for target_port in range(1, 1001):

        executor.submit(scan_port, target_ip, target_port)</code></pre>
<p><code>with</code> 블록을 사용하면 Thread Pool을 사용한 뒤 필요한 정리를 처리할 수 있고, 블록을 빠져나올 때 제출된 작업들이 완료될 때까지 기다린다.</p>
<p>이제 하나의 Worker가 Port를 순서대로 검사하는 대신 여러 Worker가 Port Scan 작업을 나누어 처리한다.</p>
<hr>
<h1 id="15-스캔-시간-측정">15. 스캔 시간 측정</h1>
<p>멀티스레딩 적용 전후의 성능을 확인하기 위해 <code>time</code> 모듈을 사용했다.</p>
<pre><code class="language-python">import time</code></pre>
<p>스캔 직전에 시작 시간을 기록한다.</p>
<pre><code class="language-python">start_time = time.time()</code></pre>
<p>모든 작업이 완료된 이후 종료 시간을 기록한다.</p>
<pre><code class="language-python">end_time = time.time()</code></pre>
<p>그리고 두 값의 차이를 계산한다.</p>
<pre><code class="language-python">print(f&quot;스캔 소요 시간: {end_time - start_time}초&quot;)</code></pre>
<p>중요한 점은 <code>start_time</code>과 <code>end_time</code>의 위치이다.</p>
<pre><code class="language-text">start_time
    ↓

전체 Port Scan

    ↓
모든 Thread 작업 완료
    ↓

end_time</code></pre>
<p>각 Port를 검사할 때마다 시간을 측정하는 것이 아니라 <strong>전체 Port Scan 작업의 시작과 끝을 한 번씩 측정</strong>해야 한다.</p>
<hr>
<h1 id="16-멀티스레딩-성능-비교">16. 멀티스레딩 성능 비교</h1>
<p><code>127.0.0.1</code>의 1~1000번 Port를 대상으로 테스트했다.</p>
<p>Timeout:</p>
<pre><code class="language-python">sock.settimeout(0.5)</code></pre>
<h3 id="10-workers">10 Workers</h3>
<pre><code class="language-python">ThreadPoolExecutor(max_workers=10)</code></pre>
<p>실행 결과:</p>
<pre><code class="language-text">스캔 소요 시간: 약 51.03초</code></pre>
<h3 id="50-workers">50 Workers</h3>
<pre><code class="language-python">ThreadPoolExecutor(max_workers=50)</code></pre>
<p>실행 결과:</p>
<pre><code class="language-text">스캔 소요 시간: 약 10.22초</code></pre>
<p>결과를 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th align="center">Worker</th>
<th align="center">Port 범위</th>
<th align="center">Timeout</th>
<th align="center">소요 시간</th>
</tr>
</thead>
<tbody><tr>
<td align="center">10</td>
<td align="center">1~1000</td>
<td align="center">0.5초</td>
<td align="center">약 51.03초</td>
</tr>
<tr>
<td align="center">50</td>
<td align="center">1~1000</td>
<td align="center">0.5초</td>
<td align="center">약 10.22초</td>
</tr>
</tbody></table>
<p>50 Workers를 사용했을 때 10 Workers보다 약 <strong>5배 빠른 결과</strong>를 확인할 수 있었다.</p>
<p>대략적으로 생각하면 10 Workers의 경우:</p>
<pre><code class="language-text">1000개 Port ÷ 10 Workers
= Worker당 약 100개

100개 × 최대 0.5초
≈ 50초</code></pre>
<p>실제 결과는 약 <code>51초</code>였다.</p>
<p>50 Workers의 경우:</p>
<pre><code class="language-text">1000개 Port ÷ 50 Workers
= Worker당 약 20개

20개 × 최대 0.5초
≈ 10초</code></pre>
<p>실제 결과 역시 약 <code>10.22초</code>였다.</p>
<p>따라서 단순히 &quot;멀티스레딩이 빠르다&quot;가 아니라 실제 실행 시간을 측정하여 성능 차이를 확인할 수 있었다.</p>
<p>단, Worker 수를 무조건 크게 설정한다고 항상 좋은 것은 아니므로 이후 적절한 Worker 수와 Timeout 값도 테스트해볼 예정이다.</p>
<hr>
<h1 id="17-실제로-탐지된-open-port">17. 실제로 탐지된 OPEN Port</h1>
<p>1~1000번 Port를 스캔하면서 내 PC에서 다음과 같은 OPEN Port들이 탐지되었다.</p>
<pre><code class="language-text">[+] 127.0.0.1:135 OPEN
[+] 127.0.0.1:445 OPEN
[+] 127.0.0.1:902 OPEN
[+] 127.0.0.1:912 OPEN
[+] 127.0.0.1:945 OPEN</code></pre>
<p>현재 단계에서는 <strong>TCP 연결 성공 여부만 판단</strong>하고 있기 때문에 각 Port에서 실제로 어떤 서비스가 동작하고 있는지는 확인하지 않는다.</p>
<p>서비스 식별은 이후 기능으로 추가할 예정이다.</p>
<hr>
<h1 id="18-day-1-최종-코드">18. Day 1 최종 코드</h1>
<pre><code class="language-python">import time
import socket
from concurrent.futures import ThreadPoolExecutor

# 스캔할 대상 IP 입력
target_ip = input(&quot;스캔할 IP를 입력하세요: &quot;)


# 하나의 Port를 검사하는 함수
def scan_port(target_ip, target_port):

    # IPv4(AF_INET) + TCP(SOCK_STREAM) 소켓 생성
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # TCP 연결 시 최대 0.5초까지만 응답을 기다림
    sock.settimeout(0.5)

    # 대상 IP와 Port에 TCP 연결을 시도하고 결과 코드 반환
    result = sock.connect_ex((target_ip, target_port))

    # connect_ex()의 반환값이 0이면 TCP 연결 성공
    if result == 0:
        print(f&quot;[+] {target_ip}:{target_port} OPEN&quot;)

    # 현재 Port 검사가 끝났으므로 Socket 종료
    sock.close()


# 전체 Port Scan 시작 시간 기록
start_time = time.time()


# 최대 50개의 작업자 Thread를 사용하는 Thread Pool 생성
with ThreadPoolExecutor(max_workers=50) as executor:

    # 1번부터 1000번 Port까지 반복
    for target_port in range(1, 1001):

        # scan_port() 작업을 Thread Pool에 제출
        executor.submit(scan_port, target_ip, target_port)


# 모든 Port Scan 작업 완료 후 종료 시간 기록
end_time = time.time()


# 전체 Port Scan 소요 시간 출력
print(f&quot;스캔 소요 시간: {end_time - start_time}초&quot;)</code></pre>
<hr>
<h1 id="📊-day-1-프로그램-동작-흐름">📊 Day 1 프로그램 동작 흐름</h1>
<pre><code class="language-text">사용자에게 Target IP 입력
            ↓
      Port 1 ~ 1000
            ↓
   ThreadPoolExecutor
       (50 Workers)
            ↓
      scan_port()
            ↓
     TCP Socket 생성
            ↓
      Timeout 설정
            ↓
      connect_ex()
            ↓
       TCP 연결 시도
            ↓
      ┌─────┴─────┐
      ↓           ↓
 result == 0   result != 0
      ↓           ↓
    OPEN       출력하지 않음
      ↓
  Socket 종료
            ↓
    모든 작업 완료
            ↓
     실행 시간 출력</code></pre>
<hr>
<h1 id="💡-day-1에서-배운-내용">💡 Day 1에서 배운 내용</h1>
<p>이번 프로젝트를 진행하면서 기존에 공부했던 Python 문법이 실제 프로그램에서 어떻게 사용되는지 확인할 수 있었다.</p>
<table>
<thead>
<tr>
<th>Python 학습 내용</th>
<th>프로젝트 적용</th>
</tr>
</thead>
<tbody><tr>
<td><code>import</code></td>
<td>socket, time 모듈 사용</td>
</tr>
<tr>
<td>변수</td>
<td>IP, Port, 실행 시간 저장</td>
</tr>
<tr>
<td><code>input()</code></td>
<td>스캔 대상 IP 입력</td>
</tr>
<tr>
<td><code>if</code></td>
<td>TCP 연결 성공 여부 판단</td>
</tr>
<tr>
<td><code>for</code></td>
<td>여러 Port 반복</td>
</tr>
<tr>
<td><code>range()</code></td>
<td>Port Scan 범위 지정</td>
</tr>
<tr>
<td>함수 <code>def</code></td>
<td>Port Scan 기능 분리</td>
</tr>
<tr>
<td>매개변수</td>
<td>IP와 Port를 함수에 전달</td>
</tr>
<tr>
<td>f-string</td>
<td>Scan 결과 출력</td>
</tr>
<tr>
<td><code>with</code></td>
<td>Thread Pool 관리</td>
</tr>
</tbody></table>
<p>그리고 새롭게 다음 내용을 접했다.</p>
<ul>
<li>TCP Socket</li>
<li><code>AF_INET</code></li>
<li><code>SOCK_STREAM</code></li>
<li><code>connect_ex()</code></li>
<li>Timeout</li>
<li>Thread</li>
<li>Thread Pool</li>
<li><code>ThreadPoolExecutor</code></li>
<li><code>executor.submit()</code></li>
<li>프로그램 실행 시간 측정</li>
</ul>
<hr>
<h1 id="🔍-오늘-발생했던-문제와-해결">🔍 오늘 발생했던 문제와 해결</h1>
<h3 id="1-ip에-int를-사용함">1. IP에 <code>int()</code>를 사용함</h3>
<p>처음에는 다음과 같이 작성했다.</p>
<pre><code class="language-python">target_ip = int(input(...))</code></pre>
<p>하지만 IP는 <code>127.0.0.1</code>과 같은 문자열이므로 <code>int()</code>로 변환할 수 없다.</p>
<pre><code class="language-python">target_ip = input(...)</code></pre>
<p>로 수정했다.</p>
<h3 id="2-sockclose를-for문-밖에-작성">2. <code>sock.close()</code>를 for문 밖에 작성</h3>
<p>Port마다 새로운 Socket을 생성하면서 <code>close()</code>를 반복문 밖에 작성하면 마지막 Socket만 종료하게 된다.</p>
<p>따라서 각 Port의 검사가 끝날 때마다 Socket을 종료하도록 반복문 내부에 배치했다.</p>
<h3 id="3-함수-내부에-for문을-작성">3. 함수 내부에 for문을 작성</h3>
<p>처음 함수화할 때</p>
<pre><code class="language-python">def scan_port(target_ip, target_port):
    for target_port in range(...):</code></pre>
<p>형태로 작성했다.</p>
<p>하지만 <code>scan_port()</code>의 역할은 <strong>Port 하나를 검사하는 것</strong>으로 정하고 반복은 함수 외부에서 담당하도록 분리했다.</p>
<pre><code class="language-python">for target_port in range(...):
    scan_port(target_ip, target_port)</code></pre>
<h3 id="4-start_time을-함수-내부에-작성">4. <code>start_time</code>을 함수 내부에 작성</h3>
<p>함수 내부에서 시작 시간을 기록하면 Port마다 새로운 시작 시간이 만들어진다.</p>
<p>내가 측정하고 싶은 것은 <strong>전체 Port Scan 시간</strong>이므로 스캔 시작 직전에 한 번만 기록하도록 수정했다.</p>
<h3 id="5-end_time을-for문-내부에-작성">5. <code>end_time</code>을 for문 내부에 작성</h3>
<p><code>executor.submit()</code>은 작업을 Thread Pool에 제출하는 것이므로 작업 제출 직후가 전체 Scan 종료 시점은 아니다.</p>
<p>따라서 <code>with ThreadPoolExecutor(...)</code> 블록이 종료된 이후 <code>end_time</code>을 기록했다.</p>
<hr>
<h1 id="🚀-다음-목표---day-2">🚀 다음 목표 - Day 2</h1>
<p>현재 Scanner는</p>
<pre><code class="language-text">IP 입력
↓
1~1000 Port Scan
↓
OPEN Port 탐지</code></pre>
<p>까지 구현했다.</p>
<p>Day 2에서는 여기서 기능을 조금 더 발전시켜볼 예정이다.</p>
<pre><code class="language-text">OPEN Port
   ↓
어떤 서비스인가?
   ↓
22   → SSH
80   → HTTP
443  → HTTPS
...</code></pre>
<p>단순히</p>
<pre><code class="language-text">[+] 127.0.0.1:445 OPEN</code></pre>
<p>이라고 출력하는 것을 넘어</p>
<pre><code class="language-text">[+] 127.0.0.1:445 OPEN
    Service : ?</code></pre>
<p>처럼 <strong>열린 Port의 서비스 정보를 확인하는 기능</strong>을 만들어보는 것이 다음 목표다.</p>
<p>이후에는 결과 저장, 예외 처리, 입력값 검증 등을 하나씩 추가하면서 Scanner를 발전시켜볼 예정이다.</p>
<hr>
<h2 id="📝-day-1-한줄-정리">📝 Day 1 한줄 정리</h2>
<blockquote>
<p>Python <code>socket</code>으로 TCP Port Scanner를 구현하고, 순차 스캔에서 발생한 속도 문제를 <code>ThreadPoolExecutor</code> 기반 멀티스레딩으로 개선한 뒤 실제 실행 시간을 측정해 성능 차이를 확인했다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY17(AI와 함께 파이썬 공부하기)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17AI%EC%99%80-%ED%95%A8%EA%BB%98-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17AI%EC%99%80-%ED%95%A8%EA%BB%98-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 29 Sep 2026 14:02:49 GMT</pubDate>
            <description><![CDATA[<h1 id="09-2-ai와-함께-파이썬-공부하기">09-2 AI와 함께 파이썬 공부하기</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>1. AI를 파이썬 공부에 활용하기</strong></span></li>
<li><span style="color:slateblue"><strong>2. AI가 도움이 되는 순간</strong></span></li>
<li><span style="color:slateblue"><strong>3. 질문을 잘하는 방법</strong></span></li>
<li><span style="color:slateblue"><strong>4. 오류 원인 찾기</strong></span></li>
<li><span style="color:slateblue"><strong>5. 코드 수정하기</strong></span></li>
<li><span style="color:slateblue"><strong>6. 코드 설명 받기</strong></span></li>
<li><span style="color:slateblue"><strong>7. 연습문제 만들기</strong></span></li>
<li><span style="color:slateblue"><strong>8. 코드 스타일 개선하기</strong></span></li>
<li><span style="color:slateblue"><strong>9. AI 답변 검증하기</strong></span></li>
<li><span style="color:slateblue"><strong>10. AI를 이용한 공부 방법 정리</strong></span></li>
</ul>
<hr>
<h1 id="1-ai를-파이썬-공부에-활용하기">1. AI를 파이썬 공부에 활용하기</h1>
<p>파이썬을 공부하다 보면 책이나 강의를 보면서도 이해되지 않는 부분이 생긴다.</p>
<p>예를 들어</p>
<pre><code class="language-text">왜 오류가 발생하지?

왜 결과가 내가 생각한 것과 다르지?

이 코드는 왜 이렇게 작성하지?

이 개념을 더 쉽게 설명할 수 없을까?</code></pre>
<p>와 같은 궁금증이 생긴다.</p>
<p>과거에는 이런 문제가 생기면</p>
<pre><code class="language-text">검색
 ↓
여러 글 확인
 ↓
내 상황과 비슷한 내용 찾기
 ↓
직접 해결</code></pre>
<p>하는 과정이 필요했다.</p>
<p>하지만 AI를 활용하면 현재 작성하고 있는 코드와 오류 내용을 그대로 보여 주면서 질문할 수 있다.</p>
<p>즉 AI를</p>
<blockquote>
<p><strong>모르는 내용을 바로 질문할 수 있는 개인 학습 도우미</strong></p>
</blockquote>
<p>처럼 활용할 수 있다.</p>
<hr>
<h1 id="2-ai가-특히-도움이-되는-순간">2. AI가 특히 도움이 되는 순간</h1>
<p>파이썬을 처음 공부할 때 자주 막히는 상황은 크게 세 가지이다.</p>
<h3 id="1-오류-메시지를-이해하지-못할-때">1. 오류 메시지를 이해하지 못할 때</h3>
<pre><code class="language-text">IndexError
TypeError
KeyError
NameError</code></pre>
<p>등의 오류가 발생했지만 원인을 모르겠을 때이다.</p>
<hr>
<h3 id="2-코드가-원하는-대로-동작하지-않을-때">2. 코드가 원하는 대로 동작하지 않을 때</h3>
<p>코드는 실행되지만</p>
<pre><code class="language-text">내가 기대한 결과
≠
실제 결과</code></pre>
<p>인 경우이다.</p>
<hr>
<h3 id="3-개념을-이해하기-어려울-때">3. 개념을 이해하기 어려울 때</h3>
<p>예를 들어</p>
<pre><code class="language-text">클래스
클로저
데코레이터
이터레이터
제너레이터
정규 표현식</code></pre>
<p>같은 개념이 책의 설명만으로 잘 이해되지 않을 수 있다.</p>
<p>이때 AI에게</p>
<pre><code class="language-text">더 쉽게 설명해 줘

예제를 추가해서 설명해 줘

한 줄씩 설명해 줘</code></pre>
<p>처럼 요청할 수 있다.</p>
<hr>
<h1 id="3-질문을-잘하는-방법">3. 질문을 잘하는 방법</h1>
<p>AI에게 질문할 때 단순히</p>
<pre><code class="language-text">이거 왜 안 돼?</code></pre>
<p>라고 물어보는 것보다 필요한 정보를 함께 제공하는 것이 좋다.</p>
<p>특히 다음 네 가지를 알려 주면 된다.</p>
<pre><code class="language-text">1. 내가 하고 싶은 것

2. 내가 작성한 코드

3. 실제 실행 결과 또는 오류 메시지

4. 내가 기대한 결과</code></pre>
<hr>
<h2 id="질문-구조">질문 구조</h2>
<p>예를 들어 다음과 같은 형태이다.</p>
<pre><code class="language-text">목표
↓
리스트의 모든 값을 더하고 싶습니다.

코드
↓
numbers = [1, 2, 3]
...

실제 결과
↓
IndexError가 발생합니다.

원하는 결과
↓
6이 출력되었으면 좋겠습니다.</code></pre>
<p>이렇게 질문하면 AI도</p>
<pre><code class="language-text">무엇을 만들고 있는지

어디에서 문제가 발생했는지

어떤 결과를 원하는지</code></pre>
<p>를 정확하게 파악할 수 있다.</p>
<hr>
<h1 id="4-오류-원인-찾기">4. 오류 원인 찾기</h1>
<p>다음 코드를 실행했다고 해보자.</p>
<pre><code class="language-python">numbers = [1, 2, 3]

print(numbers[3])</code></pre>
<p>오류가 발생한다.</p>
<pre><code class="language-text">IndexError: list index out of range</code></pre>
<p>이럴 때 AI에게 코드와 오류를 함께 전달한다.</p>
<hr>
<h2 id="왜-오류가-발생할까">왜 오류가 발생할까?</h2>
<p>리스트가</p>
<pre><code class="language-python">numbers = [1, 2, 3]</code></pre>
<p>이라면 인덱스는 다음과 같다.</p>
<pre><code class="language-text">값       1   2   3
인덱스   0   1   2</code></pre>
<p>따라서</p>
<pre><code class="language-python">numbers[3]</code></pre>
<p>은 존재하지 않는 네 번째 위치를 요청하는 것이다.</p>
<p>그래서</p>
<pre><code class="language-text">IndexError</code></pre>
<p>가 발생한다.</p>
<p>정상적인 마지막 값은</p>
<pre><code class="language-python">numbers[2]</code></pre>
<p>이다.</p>
<hr>
<h2 id="오류를-질문할-때-중요한-점">오류를 질문할 때 중요한 점</h2>
<p>오류 메시지가 길더라도 가능하면 중간 부분을 임의로 제거하지 않는 것이 좋다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Traceback ...
파일 위치
오류가 발생한 줄
오류 종류
오류 메시지</code></pre>
<p>에는 문제를 찾는 데 필요한 정보가 포함되어 있다.</p>
<p>따라서</p>
<pre><code class="language-text">코드
+
전체 오류 메시지</code></pre>
<p>를 같이 전달하는 것이 좋다.</p>
<hr>
<h1 id="5-코드-수정하기">5. 코드 수정하기</h1>
<p>다음 코드를 보자.</p>
<pre><code class="language-python">numbers = [1, 2, 3]

total = 0

for i in range(4):
    total += numbers[i]

print(total)</code></pre>
<p>목표는</p>
<pre><code class="language-text">1 + 2 + 3
=
6</code></pre>
<p>을 출력하는 것이다.</p>
<p>하지만 오류가 발생한다.</p>
<hr>
<h2 id="문제-원인">문제 원인</h2>
<pre><code class="language-python">range(4)</code></pre>
<p>는</p>
<pre><code class="language-text">0
1
2
3</code></pre>
<p>을 생성한다.</p>
<p>따라서 반복 과정은</p>
<pre><code class="language-text">numbers[0] → 1
numbers[1] → 2
numbers[2] → 3
numbers[3] → 오류</code></pre>
<p>가 된다.</p>
<p><code>numbers[3]</code>은 존재하지 않는다.</p>
<hr>
<h2 id="수정-방법-1">수정 방법 1</h2>
<pre><code class="language-python">numbers = [1, 2, 3]

total = 0

for i in range(3):
    total += numbers[i]

print(total)</code></pre>
<p>결과</p>
<pre><code class="language-text">6</code></pre>
<hr>
<h2 id="수정-방법-2">수정 방법 2</h2>
<p>리스트의 길이를 직접 사용하면 더 좋다.</p>
<pre><code class="language-python">numbers = [1, 2, 3]

total = 0

for i in range(len(numbers)):
    total += numbers[i]

print(total)</code></pre>
<p>이렇게 하면 리스트 길이가 바뀌어도 사용할 수 있다.</p>
<hr>
<h2 id="수정-방법-3">수정 방법 3</h2>
<p>인덱스가 필요 없다면 값을 직접 반복할 수 있다.</p>
<pre><code class="language-python">numbers = [1, 2, 3]

total = 0

for number in numbers:
    total += number

print(total)</code></pre>
<p>결과</p>
<pre><code class="language-text">6</code></pre>
<p>이 방법이 가장 읽기 쉽다.</p>
<hr>
<h1 id="6-ai에게-코드를-수정해-달라고-할-때">6. AI에게 코드를 수정해 달라고 할 때</h1>
<p>단순히</p>
<pre><code class="language-text">이 코드 고쳐 줘.</code></pre>
<p>라고 요청하는 것보다</p>
<pre><code class="language-text">현재 결과
+
원하는 결과</code></pre>
<p>를 알려 주는 것이 중요하다.</p>
<p>예를 들어</p>
<pre><code class="language-text">현재는 IndexError가 발생합니다.

리스트의 숫자를 모두 더해서
6이 출력되도록 수정해 주세요.

수정한 이유도 설명해 주세요.</code></pre>
<p>처럼 질문할 수 있다.</p>
<p>그러면 단순히 정답 코드만 얻는 것이 아니라 <strong>왜 문제가 발생했는지도 함께 공부할 수 있다.</strong></p>
<hr>
<h1 id="7-코드-설명-받기">7. 코드 설명 받기</h1>
<p>처음 보는 코드를 만났을 때 AI에게 한 줄씩 설명해 달라고 요청할 수도 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python">for i in range(1, 6):
    if i % 2 == 0:
        print(i, &quot;는 짝수입니다.&quot;)
    else:
        print(i, &quot;는 홀수입니다.&quot;)</code></pre>
<hr>
<h2 id="한-줄씩-살펴보기">한 줄씩 살펴보기</h2>
<h3 id="range">range()</h3>
<pre><code class="language-python">range(1, 6)</code></pre>
<p>은</p>
<pre><code class="language-text">1, 2, 3, 4, 5</code></pre>
<p>를 만든다.</p>
<hr>
<h3 id="for">for</h3>
<pre><code class="language-python">for i in range(1, 6):</code></pre>
<p><code>i</code>에 숫자가 하나씩 들어간다.</p>
<pre><code class="language-text">i = 1
i = 2
i = 3
i = 4
i = 5</code></pre>
<hr>
<h3 id="나머지-연산자">나머지 연산자</h3>
<pre><code class="language-python">i % 2</code></pre>
<p>숫자를 2로 나눈 나머지를 구한다.</p>
<p>짝수라면</p>
<pre><code class="language-text">나머지 = 0</code></pre>
<p>이다.</p>
<p>따라서</p>
<pre><code class="language-python">if i % 2 == 0:</code></pre>
<p>은</p>
<pre><code class="language-text">i가 짝수인가?</code></pre>
<p>를 검사하는 코드이다.</p>
<hr>
<h2 id="전체-흐름">전체 흐름</h2>
<pre><code class="language-text">1부터 5까지 반복
       ↓
현재 숫자를 2로 나눔
       ↓
나머지가 0인가?
   ↙           ↘
 Yes           No
 ↓              ↓
짝수 출력      홀수 출력</code></pre>
<hr>
<h1 id="8-이해-수준을-지정해서-질문하기">8. 이해 수준을 지정해서 질문하기</h1>
<p>AI의 설명이 너무 어렵다면 질문할 때 이해 수준을 지정할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">파이썬을 처음 배우는 사람 기준으로 설명해 줘.

중학생도 이해할 수 있게 설명해 줘.

코드를 한 줄씩 실행 순서대로 설명해 줘.

변수 값이 어떻게 바뀌는지 표로 보여 줘.</code></pre>
<p>처럼 요청할 수 있다.</p>
<p>특히 코드를 공부할 때는</p>
<pre><code class="language-text">실행 순서
+
변수 변화</code></pre>
<p>를 같이 보는 것이 이해에 도움이 된다.</p>
<hr>
<h1 id="9-연습문제-만들기">9. 연습문제 만들기</h1>
<p>AI는 현재 배운 범위에 맞는 문제를 만드는 데도 활용할 수 있다.</p>
<p>예를 들어 현재</p>
<pre><code class="language-text">if문
for문
함수</code></pre>
<p>까지만 공부했다고 해보자.</p>
<p>이때</p>
<pre><code class="language-text">if문, for문, 함수까지만 사용해서
풀 수 있는 연습문제를 만들어 줘.</code></pre>
<p>라고 요청할 수 있다.</p>
<hr>
<h2 id="난이도도-지정-가능">난이도도 지정 가능</h2>
<pre><code class="language-text">초급 문제 3개 만들어 줘.

중간 난이도로 만들어 줘.

한 문제씩 내 줘.

내가 답을 말하기 전까지 정답은 알려 주지 마.

틀렸을 때는 힌트만 줘.</code></pre>
<p>처럼 공부 방식까지 지정할 수 있다.</p>
<hr>
<h1 id="10-문제-풀이에-ai를-사용하는-좋은-방법">10. 문제 풀이에 AI를 사용하는 좋은 방법</h1>
<p>AI가 문제를 만들자마자 정답까지 보여 주면 직접 생각할 시간이 줄어든다.</p>
<p>따라서 다음과 같은 방식이 좋다.</p>
<pre><code class="language-text">문제 출제
   ↓
직접 풀이
   ↓
코드 실행
   ↓
결과 확인
   ↓
모르겠으면 힌트 요청
   ↓
다시 풀이
   ↓
마지막에 정답과 비교</code></pre>
<p>AI를 <strong>답을 바로 알려 주는 도구</strong>가 아니라</p>
<pre><code class="language-text">문제 출제자
+
힌트를 주는 선생님</code></pre>
<p>처럼 사용할 수 있다.</p>
<hr>
<h1 id="11-코드-스타일-개선하기">11. 코드 스타일 개선하기</h1>
<p>프로그램이 정상적으로 동작하더라도 코드가 읽기 어려울 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python">a = [1, 2, 3, 4, 5]

b = 0

for c in a:
    b += c

print(b)</code></pre>
<p>결과 자체는 정상이다.</p>
<pre><code class="language-text">15</code></pre>
<p>하지만 변수 이름만 보면 각각 무엇을 의미하는지 알기 어렵다.</p>
<hr>
<h2 id="읽기-쉽게-수정">읽기 쉽게 수정</h2>
<pre><code class="language-python">numbers = [1, 2, 3, 4, 5]

total = 0

for number in numbers:
    total += number

print(total)</code></pre>
<p>기능은 동일하지만 훨씬 쉽게 읽을 수 있다.</p>
<pre><code class="language-text">a → numbers
b → total
c → number</code></pre>
<p>처럼 변수의 역할을 알 수 있기 때문이다.</p>
<hr>
<h1 id="12-리팩터링">12. 리팩터링</h1>
<p>기존 프로그램의 기능은 유지하면서 코드 구조를 개선하는 것을 <strong>리팩터링(Refactoring)</strong> 이라고 한다.</p>
<p>AI에게 다음과 같이 요청할 수 있다.</p>
<pre><code class="language-text">기능은 그대로 유지해 주세요.

코드를 더 읽기 쉽게 수정해 주세요.

변수 이름도 역할을 알 수 있게 바꿔 주세요.

수정 전후의 차이도 설명해 주세요.</code></pre>
<p>단순히 짧은 코드가 항상 좋은 코드는 아니다.</p>
<p>중요한 것은</p>
<pre><code class="language-text">읽기 쉬운가?

의도를 알 수 있는가?

수정하기 쉬운가?</code></pre>
<p>이다.</p>
<hr>
<h1 id="13-ai의-답변을-그대로-믿으면-안-되는-이유">13. AI의 답변을 그대로 믿으면 안 되는 이유</h1>
<p>AI는 매우 유용하지만 항상 정확한 답변만 하는 것은 아니다.</p>
<p>예를 들어</p>
<pre><code class="language-text">존재하지 않는 함수

잘못된 문법

논리적으로 틀린 코드

현재 버전에서는 동작하지 않는 코드</code></pre>
<p>를 제시할 수도 있다.</p>
<p>따라서 AI가 코드를 만들어 줬다면 반드시 <strong>직접 실행</strong>해야 한다.</p>
<hr>
<h1 id="14-직접-실행하기">14. 직접 실행하기</h1>
<p>AI가 다음과 같은 코드를 알려 주었다고 해도</p>
<pre><code class="language-python">numbers = [1, 2, 3]

print(sum(numbers))</code></pre>
<p>눈으로만 보고</p>
<pre><code class="language-text">맞겠지</code></pre>
<p>라고 생각하는 것이 아니라 실제로 실행한다.</p>
<pre><code class="language-text">실행 결과

6</code></pre>
<p>처럼 확인한다.</p>
<hr>
<h1 id="15-입력값을-바꿔서-테스트하기">15. 입력값을 바꿔서 테스트하기</h1>
<p>한 번 정상 동작했다고 해서 모든 상황에서 정상이라고 볼 수는 없다.</p>
<p>예를 들어</p>
<pre><code class="language-python">def divide(a, b):
    return a / b</code></pre>
<p>다음 코드는 정상이다.</p>
<pre><code class="language-python">divide(10, 2)</code></pre>
<pre><code class="language-text">5.0</code></pre>
<p>하지만</p>
<pre><code class="language-python">divide(10, 0)</code></pre>
<p>을 실행하면</p>
<pre><code class="language-text">ZeroDivisionError</code></pre>
<p>가 발생한다.</p>
<p>따라서 여러 입력값을 사용해 보는 것이 중요하다.</p>
<pre><code class="language-text">정상적인 값
경계값
예외적인 값</code></pre>
<p>등을 테스트해 본다.</p>
<hr>
<h1 id="16-테스트하는-습관">16. 테스트하는 습관</h1>
<p>예를 들어 양수를 판별하는 함수를 만들었다고 해보자.</p>
<pre><code class="language-python">def is_positive(number):
    return number &gt; 0</code></pre>
<p>한 가지 값만 확인하지 않는다.</p>
<pre><code class="language-python">print(is_positive(10))
print(is_positive(-10))
print(is_positive(0))</code></pre>
<p>결과</p>
<pre><code class="language-text">True
False
False</code></pre>
<p>처럼 여러 상황에서 확인한다.</p>
<hr>
<h1 id="17-이해가-안-되면-다시-질문하기">17. 이해가 안 되면 다시 질문하기</h1>
<p>AI가 설명했지만 여전히 이해되지 않는다면 그대로 넘어가지 않는다.</p>
<p>예를 들어</p>
<pre><code class="language-text">왜 이렇게 되는지 모르겠어.

더 쉽게 설명해 줘.

변수 값이 어떻게 변하는지 단계별로 보여 줘.

작은 숫자로 예시를 들어 줘.

그림처럼 흐름으로 보여 줘.</code></pre>
<p>처럼 다시 질문하면 된다.</p>
<p>같은 개념도 설명 방법을 바꾸면 훨씬 쉽게 이해될 수 있다.</p>
<hr>
<h1 id="18-초보자에게-좋은-질문-방식">18. 초보자에게 좋은 질문 방식</h1>
<p>처음부터 너무 많은 내용을 한 번에 질문하면 답변도 복잡해질 수 있다.</p>
<p>따라서 하나씩 질문하는 것이 좋다.</p>
<p>예를 들어</p>
<pre><code class="language-text">파이썬 함수, 클래스, 상속, 오버라이딩,
클로저, 데코레이터를 전부 설명해 줘.</code></pre>
<p>보다는</p>
<pre><code class="language-text">클래스가 무엇인지 먼저 설명해 줘.</code></pre>
<p>그리고 이해한 뒤</p>
<pre><code class="language-text">이번에는 상속을 설명해 줘.</code></pre>
<p>처럼 진행한다.</p>
<hr>
<h1 id="19-코드만-받지-않기">19. 코드만 받지 않기</h1>
<p>AI에게 코드를 요청하면 정답 코드를 빠르게 얻을 수 있다.</p>
<p>하지만</p>
<pre><code class="language-text">코드 복사
 ↓
실행
 ↓
끝</code></pre>
<p>으로 공부하면 실력이 잘 늘지 않는다.</p>
<p>더 좋은 방법은</p>
<pre><code class="language-text">코드 확인
 ↓
왜 이렇게 작성했는지 이해
 ↓
직접 다시 작성
 ↓
값을 변경해서 테스트</code></pre>
<p>하는 것이다.</p>
<hr>
<h1 id="20-좋은-질문과-좋지-않은-질문-비교">20. 좋은 질문과 좋지 않은 질문 비교</h1>
<h3 id="좋지-않은-질문">좋지 않은 질문</h3>
<pre><code class="language-text">이거 왜 안 돼?</code></pre>
<p>문제 상황을 알기 어렵다.</p>
<hr>
<h3 id="더-좋은-질문">더 좋은 질문</h3>
<pre><code class="language-text">리스트 [1, 2, 3]의 합계를 구하려고 합니다.

현재 코드는 다음과 같습니다.

numbers = [1, 2, 3]
total = 0

for i in range(4):
    total += numbers[i]

print(total)

IndexError가 발생합니다.

원하는 결과는 6입니다.

왜 오류가 발생하는지
초보자 기준으로 설명해 주세요.</code></pre>
<p>AI가 문제 상황을 훨씬 정확하게 이해할 수 있다.</p>
<hr>
<h1 id="21-질문-템플릿">21. 질문 템플릿</h1>
<p>파이썬 공부 중 오류가 발생했다면 다음 형식을 사용할 수 있다.</p>
<pre><code class="language-text">[목표]

제가 만들고 싶은 것은 ______ 입니다.


[현재 코드]

코드는 다음과 같습니다.

```python
코드 작성</code></pre>
<p>[현재 결과]</p>
<p>실행하면 <strong>__</strong> 결과가 나오거나
<strong>__</strong> 오류가 발생합니다.</p>
<p>[원하는 결과]</p>
<p>제가 원하는 결과는 <strong>__</strong> 입니다.</p>
<p>[질문]</p>
<p>왜 이런 문제가 발생하는지
파이썬 초보자 기준으로 설명해 주세요.</p>
<p>수정 코드뿐 아니라
수정하는 이유도 같이 설명해 주세요.</p>
<pre><code>
---

# 22. 코드 공부용 질문 템플릿

코드를 이해하고 싶을 때는 다음처럼 사용할 수 있다.

```text
다음 파이썬 코드를 공부하고 있습니다.

```python
코드 작성</code></pre><p>다음 순서로 설명해 주세요.</p>
<ol>
<li>전체 코드의 목적</li>
<li>한 줄씩 설명</li>
<li>실행 순서</li>
<li>변수 값이 어떻게 변하는지</li>
<li>핵심 문법</li>
<li>초보자가 헷갈릴 만한 부분<pre><code></code></pre></li>
</ol>
<hr>
<h1 id="23-연습문제용-질문-템플릿">23. 연습문제용 질문 템플릿</h1>
<pre><code class="language-text">현재 파이썬에서 다음 내용을 공부했습니다.

- 리스트
- if문
- for문
- 함수

이 범위 안에서만 풀 수 있는
연습문제 3개를 만들어 주세요.

조건

- 쉬운 문제부터 순서대로
- 정답은 바로 알려 주지 않기
- 한 문제씩 출제
- 틀리면 힌트만 제공
- 마지막에 전체 풀이 설명</code></pre>
<p>이런 방식으로 AI를 개인 문제 출제 도구처럼 사용할 수 있다.</p>
<hr>
<h1 id="24-ai를-이용한-추천-학습-흐름">24. AI를 이용한 추천 학습 흐름</h1>
<p>파이썬을 공부할 때 다음과 같은 흐름으로 활용할 수 있다.</p>
<pre><code class="language-text">책 / 강의 공부
      ↓
직접 코드 작성
      ↓
직접 실행
      ↓
이해됨?
 ↙          ↘
Yes          No
 ↓            ↓
다음 내용     AI에게 질문
              ↓
          설명 확인
              ↓
          코드 수정
              ↓
          다시 직접 실행
              ↓
          값 변경 테스트
              ↓
          직접 다시 작성</code></pre>
<p>핵심은 항상</p>
<pre><code class="language-text">AI 답변
 ↓
직접 확인</code></pre>
<p>과정을 거치는 것이다.</p>
<hr>
<h1 id="25-ai에게-질문할-때-기억할-것">25. AI에게 질문할 때 기억할 것</h1>
<p>좋은 질문에는 보통 다음 정보가 포함된다.</p>
<pre><code class="language-text">무엇을 하려고 하는가?

현재 코드는 무엇인가?

현재 어떤 일이 발생하는가?

어떤 결과를 원하는가?</code></pre>
<p>그리고 답변을 받은 뒤에는</p>
<pre><code class="language-text">직접 실행

다른 값으로 테스트

이해되지 않는 부분 재질문</code></pre>
<p>과정을 거친다.</p>
<hr>
<h1 id="📌-핵심-정리">📌 핵심 정리</h1>
<h2 id="ai가-유용한-상황">AI가 유용한 상황</h2>
<pre><code class="language-text">오류 원인을 모를 때

코드가 원하는 대로 동작하지 않을 때

개념이 이해되지 않을 때

코드 설명이 필요할 때

연습문제가 필요할 때

코드를 더 읽기 쉽게 만들고 싶을 때</code></pre>
<hr>
<h2 id="좋은-질문의-4가지-요소">좋은 질문의 4가지 요소</h2>
<pre><code class="language-text">1. 목표

2. 현재 코드

3. 실제 결과 / 오류 메시지

4. 기대하는 결과</code></pre>
<hr>
<h2 id="오류-질문">오류 질문</h2>
<pre><code class="language-text">코드
+
전체 오류 메시지
+
원하는 동작</code></pre>
<p>을 함께 전달한다.</p>
<hr>
<h2 id="코드-설명">코드 설명</h2>
<pre><code class="language-text">한 줄씩 설명해 줘

실행 순서대로 설명해 줘

변수 값이 어떻게 변하는지 보여 줘</code></pre>
<p>처럼 구체적으로 요청한다.</p>
<hr>
<h2 id="연습문제">연습문제</h2>
<p>현재 배운 범위를 알려 주고</p>
<pre><code class="language-text">정답은 바로 말하지 않기

힌트만 제공

한 문제씩 진행</code></pre>
<p>처럼 학습 방식까지 지정할 수 있다.</p>
<hr>
<h2 id="코드-개선">코드 개선</h2>
<p>AI에게 단순히 코드를 짧게 만들어 달라고 하기보다</p>
<pre><code class="language-text">가독성

변수 이름

중복 코드

코드 구조</code></pre>
<p>를 개선해 달라고 요청한다.</p>
<hr>
<h2 id="ai-답변-검증">AI 답변 검증</h2>
<p>AI가 알려 준 답변은 반드시</p>
<pre><code class="language-text">직접 실행
   ↓
입력값 변경
   ↓
2~3번 추가 테스트
   ↓
이상하면 다시 확인</code></pre>
<p>하는 과정이 필요하다.</p>
<hr>
<h1 id="⭐-09-2-전체-흐름">⭐ 09-2 전체 흐름</h1>
<pre><code class="language-text">파이썬 공부
    ↓
직접 생각하고 코드 작성
    ↓
문제 발생
    ↓
AI에게 질문
    ↓
┌─────────────────┐
│ 목표             │
│ 코드             │
│ 실제 결과        │
│ 원하는 결과      │
└─────────────────┘
    ↓
AI의 설명 확인
    ↓
직접 코드 수정
    ↓
직접 실행
    ↓
다른 값으로 테스트
    ↓
왜 동작하는지 이해
    ↓
직접 다시 작성</code></pre>
<blockquote>
<p><strong>AI는 정답을 대신 작성해 주는 도구로 사용하기보다, 막힌 부분을 설명받고 스스로 이해할 수 있도록 돕는 학습 도구로 사용하는 것이 가장 좋다.</strong></p>
</blockquote>
<p>결국 중요한 것은</p>
<pre><code class="language-text">질문
→ 답변 복사
→ 끝</code></pre>
<p>이 아니라</p>
<pre><code class="language-text">질문
→ 원리 이해
→ 직접 실행
→ 테스트
→ 직접 다시 작성</code></pre>
<p>의 과정을 반복하는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY17(파이썬 초보 탈출 - 코딩 면허시험 문제풀이)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%B4%88%EB%B3%B4-%ED%83%88%EC%B6%9C-%EC%BD%94%EB%94%A9-%EB%A9%B4%ED%97%88%EC%8B%9C%ED%97%98-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%B4%88%EB%B3%B4-%ED%83%88%EC%B6%9C-%EC%BD%94%EB%94%A9-%EB%A9%B4%ED%97%88%EC%8B%9C%ED%97%98-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4</guid>
            <pubDate>Tue, 29 Sep 2026 14:00:22 GMT</pubDate>
            <description><![CDATA[<h1 id="파이썬-초보-탈출---코딩-면허시험-문제풀이">파이썬 초보 탈출 - 코딩 면허시험 문제풀이</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>Q1. 문자열 바꾸기</strong></span></li>
<li><span style="color:slateblue"><strong>Q2. 딕셔너리 값 추출하기</strong></span></li>
<li><span style="color:slateblue"><strong>Q3. 리스트 더하기와 extend()</strong></span></li>
<li><span style="color:slateblue"><strong>Q4. 리스트 총합 구하기</strong></span></li>
<li><span style="color:slateblue"><strong>Q5. 피보나치 수열</strong></span></li>
<li><span style="color:slateblue"><strong>Q6. 숫자의 총합 구하기</strong></span></li>
<li><span style="color:slateblue"><strong>Q7. 한 줄 구구단</strong></span></li>
<li><span style="color:slateblue"><strong>Q8. 파일 역순 저장</strong></span></li>
<li><span style="color:slateblue"><strong>Q9. 총합과 평균 구하기</strong></span></li>
<li><span style="color:slateblue"><strong>Q10. Calculator 클래스</strong></span></li>
<li><span style="color:slateblue"><strong>Q11. 모듈 불러오기</strong></span></li>
<li><span style="color:slateblue"><strong>Q12. 오류와 예외 처리</strong></span></li>
<li><span style="color:slateblue"><strong>Q13. DashInsert</strong></span></li>
<li><span style="color:slateblue"><strong>Q16. 모스 부호 해독</strong></span></li>
<li><span style="color:slateblue"><strong>Q17. 기초 메타 문자</strong></span></li>
<li><span style="color:slateblue"><strong>Q18. 문자열 검색</strong></span></li>
<li><span style="color:slateblue"><strong>Q19. 그루핑</strong></span></li>
<li><span style="color:slateblue"><strong>Q20. 전방 탐색</strong></span></li>
</ul>
<hr>
<h1 id="q1-문자열-바꾸기">Q1. 문자열 바꾸기</h1>
<p>문자열</p>
<pre><code class="language-text">a:b:c:d</code></pre>
<p>에서 <code>:</code>을 기준으로 문자열을 나누고 다시 <code>#</code>으로 연결한다.</p>
<h2 id="풀이">풀이</h2>
<pre><code class="language-python">a = &quot;a:b:c:d&quot;

result = &quot;#&quot;.join(a.split(&quot;:&quot;))

print(result)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">a#b#c#d</code></pre>
<hr>
<h2 id="동작-과정">동작 과정</h2>
<p>먼저</p>
<pre><code class="language-python">a.split(&quot;:&quot;)</code></pre>
<p>을 실행하면</p>
<pre><code class="language-python">[&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;]</code></pre>
<p>가 된다.</p>
<p>그리고</p>
<pre><code class="language-python">&quot;#&quot;.join(...)</code></pre>
<p>을 사용하면</p>
<pre><code class="language-text">a#b#c#d</code></pre>
<p>처럼 리스트의 요소 사이에 <code>#</code>을 넣어 다시 문자열로 만든다.</p>
<pre><code class="language-text">&quot;a:b:c:d&quot;
     ↓
 split(&quot;:&quot;)
     ↓
[&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;]
     ↓
&quot;#&quot;.join()
     ↓
&quot;a#b#c#d&quot;</code></pre>
<h3 id="핵심">핵심</h3>
<pre><code class="language-python">split()</code></pre>
<p>→ 문자열 나누기</p>
<pre><code class="language-python">join()</code></pre>
<p>→ 문자열 연결하기</p>
<hr>
<h1 id="q2-딕셔너리-값-추출하기">Q2. 딕셔너리 값 추출하기</h1>
<p>딕셔너리에 존재하지 않는 Key를</p>
<pre><code class="language-python">a[&#39;C&#39;]</code></pre>
<p>처럼 조회하면 <code>KeyError</code>가 발생한다.</p>
<pre><code class="language-python">a = {
    &#39;A&#39;: 90,
    &#39;B&#39;: 80
}</code></pre>
<p>이때 <code>C</code>가 없다면 기본값 <code>70</code>을 얻고 싶다.</p>
<hr>
<h2 id="풀이-1">풀이</h2>
<p><code>get()</code>을 사용하면 간단하다.</p>
<pre><code class="language-python">a = {
    &#39;A&#39;: 90,
    &#39;B&#39;: 80
}

print(a.get(&#39;C&#39;, 70))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">70</code></pre>
<hr>
<h2 id="get">get()</h2>
<pre><code class="language-python">딕셔너리.get(Key, 기본값)</code></pre>
<p>형태로 사용할 수 있다.</p>
<pre><code class="language-python">a.get(&#39;A&#39;, 70)</code></pre>
<p><code>A</code>가 존재하므로</p>
<pre><code class="language-text">90</code></pre>
<p>을 반환한다.</p>
<p>반면</p>
<pre><code class="language-python">a.get(&#39;C&#39;, 70)</code></pre>
<p>에서는 <code>C</code>가 없기 때문에</p>
<pre><code class="language-text">70</code></pre>
<p>을 반환한다.</p>
<h3 id="핵심-1">핵심</h3>
<pre><code class="language-text">a[&#39;C&#39;]
→ Key가 없으면 오류

a.get(&#39;C&#39;)
→ Key가 없으면 None

a.get(&#39;C&#39;, 70)
→ Key가 없으면 70</code></pre>
<hr>
<h1 id="q3-리스트-더하기와-extend">Q3. 리스트 더하기와 extend()</h1>
<p>다음 두 코드는 결과만 보면 동일하다.</p>
<h3 id=""><code>+</code></h3>
<pre><code class="language-python">a = [1, 2, 3]

a = a + [4, 5]

print(a)</code></pre>
<pre><code class="language-text">[1, 2, 3, 4, 5]</code></pre>
<h3 id="extend"><code>extend()</code></h3>
<pre><code class="language-python">a = [1, 2, 3]

a.extend([4, 5])

print(a)</code></pre>
<pre><code class="language-text">[1, 2, 3, 4, 5]</code></pre>
<p>하지만 동작 방식에는 차이가 있다.</p>
<hr>
<h2 id="-1"><code>+</code></h2>
<pre><code class="language-python">a = a + [4, 5]</code></pre>
<p>은 기존 리스트를 수정하는 것이 아니라 <strong>새로운 리스트를 만들어 <code>a</code>가 다시 가리키게 한다.</strong></p>
<pre><code class="language-python">a = [1, 2, 3]

print(id(a))

a = a + [4, 5]

print(id(a))</code></pre>
<p>두 <code>id()</code> 값이 달라진다.</p>
<pre><code class="language-text">기존 리스트
[1, 2, 3]

      +

[4, 5]

      ↓

새로운 리스트
[1, 2, 3, 4, 5]</code></pre>
<hr>
<h2 id="extend-1"><code>extend()</code></h2>
<pre><code class="language-python">a.extend([4, 5])</code></pre>
<p>는 기존 리스트 자체에 값을 추가한다.</p>
<pre><code class="language-python">a = [1, 2, 3]

print(id(a))

a.extend([4, 5])

print(id(a))</code></pre>
<p>객체의 <code>id()</code>가 유지된다.</p>
<h3 id="핵심-2">핵심</h3>
<table>
<thead>
<tr>
<th align="center">방법</th>
<th align="left">동작</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>a + b</code></td>
<td align="left">새로운 리스트 생성</td>
</tr>
<tr>
<td align="center"><code>a.extend(b)</code></td>
<td align="left">기존 리스트 수정</td>
</tr>
</tbody></table>
<hr>
<h1 id="q4-리스트-총합-구하기">Q4. 리스트 총합 구하기</h1>
<p>점수 중에서 <strong>50점 이상인 값만 더한다.</strong></p>
<pre><code class="language-python">A = [20, 55, 67, 82, 45, 33, 90, 87, 100, 25]</code></pre>
<hr>
<h2 id="풀이-2">풀이</h2>
<pre><code class="language-python">A = [20, 55, 67, 82, 45, 33, 90, 87, 100, 25]

result = 0

for score in A:
    if score &gt;= 50:
        result += score

print(result)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">481</code></pre>
<hr>
<h2 id="동작-과정-1">동작 과정</h2>
<pre><code class="language-text">20  → X
55  → +55
67  → +67
82  → +82
45  → X
33  → X
90  → +90
87  → +87
100 → +100
25  → X</code></pre>
<hr>
<h2 id="조금-더-간단하게">조금 더 간단하게</h2>
<pre><code class="language-python">result = sum(score for score in A if score &gt;= 50)</code></pre>
<p>하지만 지금 단계에서는 <code>for + if</code> 방식의 흐름을 정확히 이해하는 것이 더 중요하다.</p>
<hr>
<h1 id="q5-피보나치-수열">Q5. 피보나치 수열</h1>
<p>피보나치 수열은 앞의 두 값을 더해 다음 값을 만든다.</p>
<pre><code class="language-text">0, 1, 1, 2, 3, 5, 8, 13 ...</code></pre>
<p>즉</p>
<pre><code class="language-text">0 + 1 = 1
1 + 1 = 2
1 + 2 = 3
2 + 3 = 5
...</code></pre>
<p>형태이다.</p>
<hr>
<h2 id="n개의-피보나치-수-만들기">n개의 피보나치 수 만들기</h2>
<pre><code class="language-python">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(&quot;숫자 입력: &quot;))

print(fibonacci(n))</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">숫자 입력: 8</code></pre>
<p>결과</p>
<pre><code class="language-text">[0, 1, 1, 2, 3, 5, 8, 13]</code></pre>
<hr>
<h2 id="핵심-코드">핵심 코드</h2>
<pre><code class="language-python">a, b = b, a + b</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">a = 0
b = 1</code></pre>
<p>이라면</p>
<pre><code class="language-text">a = 1
b = 1</code></pre>
<p>이 되고 다음에는</p>
<pre><code class="language-text">a = 1
b = 2</code></pre>
<p>가 된다.</p>
<hr>
<h1 id="q6-숫자의-총합-구하기">Q6. 숫자의 총합 구하기</h1>
<p>사용자가 다음과 같이 입력한다고 해보자.</p>
<pre><code class="language-text">65,45,2,3,45,8</code></pre>
<p>콤마를 기준으로 값을 나눈 후 숫자로 변환하여 합계를 구한다.</p>
<hr>
<h2 id="풀이-3">풀이</h2>
<pre><code class="language-python">data = input(&quot;값을 입력하세요: &quot;)

numbers = map(int, data.split(&quot;,&quot;))

result = sum(numbers)

print(result)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">168</code></pre>
<hr>
<h2 id="동작-과정-2">동작 과정</h2>
<pre><code class="language-text">&quot;65,45,2,3,45,8&quot;

        ↓ split(&quot;,&quot;)

[&#39;65&#39;, &#39;45&#39;, &#39;2&#39;, &#39;3&#39;, &#39;45&#39;, &#39;8&#39;]

        ↓ map(int, ...)

65, 45, 2, 3, 45, 8

        ↓ sum()

168</code></pre>
<hr>
<h1 id="q7-한-줄-구구단">Q7. 한 줄 구구단</h1>
<p>사용자로부터 <code>2~9</code> 중 하나를 입력받아 해당 구구단의 결과를 한 줄로 출력한다.</p>
<hr>
<h2 id="풀이-4">풀이</h2>
<pre><code class="language-python">dan = int(
    input(&quot;구구단을 출력할 숫자를 입력하세요(2~9): &quot;)
)

for i in range(1, 10):
    print(dan * i, end=&quot; &quot;)</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">2</code></pre>
<p>를 입력하면</p>
<pre><code class="language-text">2 4 6 8 10 12 14 16 18</code></pre>
<p>이 출력된다.</p>
<hr>
<h2 id="end"><code>end=&quot; &quot;</code></h2>
<p>기본적인 <code>print()</code>는 출력이 끝나면 줄을 바꾼다.</p>
<pre><code class="language-python">print(1)
print(2)</code></pre>
<pre><code class="language-text">1
2</code></pre>
<p>하지만</p>
<pre><code class="language-python">print(1, end=&quot; &quot;)
print(2, end=&quot; &quot;)</code></pre>
<p>처럼 사용하면</p>
<pre><code class="language-text">1 2</code></pre>
<p>한 줄로 출력할 수 있다.</p>
<hr>
<h1 id="q8-파일-역순-저장">Q8. 파일 역순 저장</h1>
<p>파일 내용이 다음과 같다고 해보자.</p>
<pre><code class="language-text">AAA
BBB
CCC
DDD
EEE</code></pre>
<p>이를 역순으로 변경한다.</p>
<pre><code class="language-text">EEE
DDD
CCC
BBB
AAA</code></pre>
<hr>
<h2 id="풀이-5">풀이</h2>
<pre><code class="language-python">with open(&quot;abc.txt&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    lines = f.readlines()

lines.reverse()

with open(&quot;abc.txt&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    f.writelines(lines)</code></pre>
<hr>
<h2 id="readlines">readlines()</h2>
<pre><code class="language-python">f.readlines()</code></pre>
<p>파일을 줄 단위로 읽어 리스트로 반환한다.</p>
<p>예를 들어</p>
<pre><code class="language-python">[
    &quot;AAA\n&quot;,
    &quot;BBB\n&quot;,
    &quot;CCC\n&quot;,
    &quot;DDD\n&quot;,
    &quot;EEE\n&quot;
]</code></pre>
<p>와 같은 형태이다.</p>
<hr>
<h2 id="reverse">reverse()</h2>
<pre><code class="language-python">lines.reverse()</code></pre>
<p>리스트의 순서를 실제로 뒤집는다.</p>
<pre><code class="language-text">AAA       EEE
BBB       DDD
CCC  →    CCC
DDD       BBB
EEE       AAA</code></pre>
<hr>
<h2 id="주의할-점">주의할 점</h2>
<p>텍스트 파일의 마지막 줄에 <code>\n</code>이 없다면 역순 저장 후</p>
<pre><code class="language-text">EEEDDD</code></pre>
<p>처럼 두 줄이 붙는 문제가 생길 수 있다.</p>
<p>즉 파일에서 <strong>줄바꿈 문자 <code>\n</code>도 데이터의 일부</strong>라는 점을 기억하자.</p>
<hr>
<h1 id="q9-총합과-평균-구하기">Q9. 총합과 평균 구하기</h1>
<p>파일에 숫자가 한 줄에 하나씩 저장되어 있다고 해보자.</p>
<pre><code class="language-text">70
60
55
75
95
90
80
80
85
100</code></pre>
<p>이 값을 읽어</p>
<ul>
<li>총합</li>
<li>평균</li>
</ul>
<p>을 계산하고 평균을 <code>result.txt</code>에 저장한다.</p>
<hr>
<h2 id="풀이-6">풀이</h2>
<pre><code class="language-python">with open(
    &quot;sample.txt&quot;,
    &quot;r&quot;,
    encoding=&quot;utf-8&quot;
) as f:
    numbers = [
        int(line.strip())
        for line in f
    ]

total = sum(numbers)
average = total / len(numbers)

print(&quot;총합:&quot;, total)
print(&quot;평균:&quot;, average)

with open(
    &quot;result.txt&quot;,
    &quot;w&quot;,
    encoding=&quot;utf-8&quot;
) as f:
    f.write(str(average))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">총합: 790
평균: 79.0</code></pre>
<p><code>result.txt</code></p>
<pre><code class="language-text">79.0</code></pre>
<hr>
<h2 id="평균-공식">평균 공식</h2>
<pre><code class="language-text">평균
=
전체 합계 / 데이터 개수</code></pre>
<p>즉</p>
<pre><code class="language-text">790 / 10
=
79.0</code></pre>
<p>이다.</p>
<hr>
<h1 id="q10-calculator-클래스">Q10. Calculator 클래스</h1>
<p>숫자 리스트를 입력받아</p>
<pre><code class="language-python">sum()
avg()</code></pre>
<p>기능을 제공하는 클래스를 만든다.</p>
<hr>
<h2 id="풀이-7">풀이</h2>
<pre><code class="language-python">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())</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">15
3.0
40
8.0</code></pre>
<hr>
<h2 id="왜-selfresult를-계속-누적하면-안-될까">왜 <code>self.result</code>를 계속 누적하면 안 될까?</h2>
<p>예를 들어</p>
<pre><code class="language-python">def sum(self):
    for i in self.numbers:
        self.result += i</code></pre>
<p>를 실행한 뒤 다시 <code>avg()</code>에서</p>
<pre><code class="language-python">self.result += i</code></pre>
<p>를 하면 앞에서 계산했던 값이 남아 있어 <strong>중복으로 더해진다.</strong></p>
<p>따라서 합계와 평균처럼 단순 계산은 매번</p>
<pre><code class="language-python">sum(self.numbers)</code></pre>
<p>를 사용하는 것이 훨씬 깔끔하다.</p>
<hr>
<h1 id="q11-모듈-불러오기">Q11. 모듈 불러오기</h1>
<p>다음 위치에</p>
<pre><code class="language-text">C:\doit\mymod.py</code></pre>
<p>모듈이 있다고 가정해 보자.</p>
<p>파이썬에서</p>
<pre><code class="language-python">import mymod</code></pre>
<p>를 사용할 수 있도록 하는 대표적인 방법은 세 가지가 있다.</p>
<hr>
<h2 id="1-syspath-추가">1. sys.path 추가</h2>
<pre><code class="language-python">import sys

sys.path.append(r&quot;C:\doit&quot;)

import mymod</code></pre>
<p>파이썬이 모듈을 검색하는 경로에 <code>C:\doit</code>을 추가한다.</p>
<hr>
<h2 id="2-pythonpath">2. PYTHONPATH</h2>
<p>Windows 명령 프롬프트에서</p>
<pre><code class="language-text">set PYTHONPATH=C:\doit</code></pre>
<p>환경 변수를 설정한 후</p>
<pre><code class="language-text">python</code></pre>
<p>을 실행하면</p>
<pre><code class="language-python">import mymod</code></pre>
<p>를 사용할 수 있다.</p>
<hr>
<h2 id="3-모듈이-있는-위치에서-실행">3. 모듈이 있는 위치에서 실행</h2>
<pre><code class="language-text">cd C:\doit
python</code></pre>
<p>이후</p>
<pre><code class="language-python">import mymod</code></pre>
<p>를 실행한다.</p>
<hr>
<h2 id="핵심-3">핵심</h2>
<p>파이썬은 모듈을</p>
<pre><code class="language-python">sys.path</code></pre>
<p>에 등록된 경로에서 찾는다.</p>
<p>확인하려면</p>
<pre><code class="language-python">import sys

print(sys.path)</code></pre>
<p>를 사용할 수 있다.</p>
<hr>
<h1 id="q12-오류와-예외-처리">Q12. 오류와 예외 처리</h1>
<p>다음 코드를 살펴보자.</p>
<pre><code class="language-python">result = 0

try:
    [1, 2, 3][3]
    &quot;a&quot; + 1
    4 / 0

except TypeError:
    result += 1

except ZeroDivisionError:
    result += 2

except IndexError:
    result += 3

finally:
    result += 4

print(result)</code></pre>
<hr>
<h2 id="실행-과정">실행 과정</h2>
<p>초기값</p>
<pre><code class="language-text">result = 0</code></pre>
<p>먼저</p>
<pre><code class="language-python">[1, 2, 3][3]</code></pre>
<p>이 실행된다.</p>
<p>리스트 인덱스는</p>
<pre><code class="language-text">0  1  2
↓  ↓  ↓
1  2  3</code></pre>
<p>까지만 존재한다.</p>
<p>따라서</p>
<pre><code class="language-text">IndexError</code></pre>
<p>가 발생한다.</p>
<hr>
<h2 id="except-이동">except 이동</h2>
<pre><code class="language-python">except IndexError:
    result += 3</code></pre>
<p>가 실행된다.</p>
<pre><code class="language-text">0 + 3 = 3</code></pre>
<p>그리고 <code>try</code> 블록의 나머지 코드는 실행하지 않는다.</p>
<p>따라서</p>
<pre><code class="language-python">&quot;a&quot; + 1</code></pre>
<p>과</p>
<pre><code class="language-python">4 / 0</code></pre>
<p>은 실행되지 않는다.</p>
<hr>
<h2 id="finally">finally</h2>
<p><code>finally</code>는 오류 발생 여부와 관계없이 실행된다.</p>
<pre><code class="language-python">finally:
    result += 4</code></pre>
<p>따라서</p>
<pre><code class="language-text">3 + 4 = 7</code></pre>
<p>최종 결과</p>
<pre><code class="language-text">7</code></pre>
<p>이다.</p>
<hr>
<h1 id="q13-dashinsert">Q13. DashInsert</h1>
<p>숫자로 이루어진 문자열에서</p>
<pre><code class="language-text">홀수 + 홀수</code></pre>
<p>가 연속되면</p>
<pre><code class="language-text">-</code></pre>
<p>를 넣고,</p>
<pre><code class="language-text">짝수 + 짝수</code></pre>
<p>가 연속되면</p>
<pre><code class="language-text">*</code></pre>
<p>을 넣는다.</p>
<p>예를 들어</p>
<pre><code class="language-text">4546793</code></pre>
<p>을 처리하면</p>
<pre><code class="language-text">454*67-9-3</code></pre>
<p>이 된다.</p>
<hr>
<h2 id="풀이-8">풀이</h2>
<pre><code class="language-python">data = &quot;4546793&quot;

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(&quot;-&quot;)

    elif num % 2 == 0 and next_num % 2 == 0:
        result.append(&quot;*&quot;)

print(&quot;&quot;.join(result))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">454*67-9-3</code></pre>
<hr>
<h2 id="enumerate">enumerate()</h2>
<pre><code class="language-python">for i, num in enumerate(numbers):</code></pre>
<p>을 사용하면</p>
<pre><code class="language-text">인덱스 + 값</code></pre>
<p>을 동시에 가져올 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python">numbers = [4, 5, 4]</code></pre>
<p>이면</p>
<pre><code class="language-text">i=0, num=4
i=1, num=5
i=2, num=4</code></pre>
<p>순서로 반복한다.</p>
<hr>
<h2 id="홀수-판별">홀수 판별</h2>
<pre><code class="language-python">num % 2 == 1</code></pre>
<p>나머지가 <code>1</code>이면 홀수이다.</p>
<h2 id="짝수-판별">짝수 판별</h2>
<pre><code class="language-python">num % 2 == 0</code></pre>
<p>나머지가 <code>0</code>이면 짝수이다.</p>
<hr>
<h1 id="q14--q15">Q14 / Q15</h1>
<blockquote>
<p>참고한 게시글에는 <strong>Q13 다음이 바로 Q16으로 이어져 있어 Q14와 Q15 문제 및 풀이가 포함되어 있지 않다.</strong></p>
</blockquote>
<hr>
<h1 id="q16-모스-부호-해독">Q16. 모스 부호 해독</h1>
<p>모스 부호를 영어 문자로 변경하려면</p>
<pre><code class="language-text">모스 부호 → 알파벳</code></pre>
<p>형태의 딕셔너리를 만들 수 있다.</p>
<hr>
<h2 id="모스-부호-딕셔너리">모스 부호 딕셔너리</h2>
<pre><code class="language-python">morse_code = {
    &#39;.-&#39;: &#39;A&#39;,
    &#39;-...&#39;: &#39;B&#39;,
    &#39;-.-.&#39;: &#39;C&#39;,
    &#39;-..&#39;: &#39;D&#39;,
    &#39;.&#39;: &#39;E&#39;,
    &#39;..-.&#39;: &#39;F&#39;,
    &#39;--.&#39;: &#39;G&#39;,
    &#39;....&#39;: &#39;H&#39;,
    &#39;..&#39;: &#39;I&#39;,
    &#39;.---&#39;: &#39;J&#39;,
    &#39;-.-&#39;: &#39;K&#39;,
    &#39;.-..&#39;: &#39;L&#39;,
    &#39;--&#39;: &#39;M&#39;,
    &#39;-.&#39;: &#39;N&#39;,
    &#39;---&#39;: &#39;O&#39;,
    &#39;.--.&#39;: &#39;P&#39;,
    &#39;--.-&#39;: &#39;Q&#39;,
    &#39;.-.&#39;: &#39;R&#39;,
    &#39;...&#39;: &#39;S&#39;,
    &#39;-&#39;: &#39;T&#39;,
    &#39;..-&#39;: &#39;U&#39;,
    &#39;...-&#39;: &#39;V&#39;,
    &#39;.--&#39;: &#39;W&#39;,
    &#39;-..-&#39;: &#39;X&#39;,
    &#39;-.--&#39;: &#39;Y&#39;,
    &#39;--..&#39;: &#39;Z&#39;
}</code></pre>
<hr>
<h2 id="해독-함수">해독 함수</h2>
<pre><code class="language-python">def morse(src):
    result = []

    words = src.split(&quot;  &quot;)

    for word in words:
        chars = word.split()

        decoded = &quot;&quot;

        for char in chars:
            decoded += morse_code[char]

        result.append(decoded)

    return &quot; &quot;.join(result)</code></pre>
<p>즉</p>
<pre><code class="language-text">모스 부호
    ↓
단어 단위 분리
    ↓
문자 단위 분리
    ↓
딕셔너리 조회
    ↓
영어 문자 변환
    ↓
다시 합치기</code></pre>
<p>순서로 처리한다.</p>
<hr>
<h1 id="q17-기초-메타-문자">Q17. 기초 메타 문자</h1>
<p>정규식</p>
<pre><code class="language-text">a[.]{3,}b</code></pre>
<p>를 해석해 보자.</p>
<hr>
<h2 id="-2"><code>[.]</code></h2>
<pre><code class="language-text">실제 점(.) 문자</code></pre>
<p>를 의미한다.</p>
<h2 id="3"><code>{3,}</code></h2>
<pre><code class="language-text">앞의 패턴이 3번 이상</code></pre>
<p>이라는 의미이다.</p>
<p>따라서 전체적으로</p>
<pre><code class="language-text">a
+
점(.) 3개 이상
+
b</code></pre>
<p>이다.</p>
<p>보기 중</p>
<pre><code class="language-text">a....b</code></pre>
<p>가 조건을 만족한다.</p>
<h3 id="정답">정답</h3>
<pre><code class="language-text">2번</code></pre>
<hr>
<h2 id="코드-확인">코드 확인</h2>
<pre><code class="language-python">import re

p = re.compile(r&quot;a[.]{3,}b&quot;)

print(p.match(&quot;acccb&quot;))
print(p.match(&quot;a....b&quot;))
print(p.match(&quot;aaab&quot;))
print(p.match(&quot;a.cccb&quot;))</code></pre>
<p><code>a....b</code>만 정상적으로 매치된다.</p>
<hr>
<h1 id="q18-문자열-검색">Q18. 문자열 검색</h1>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)

m = p.search(&quot;5 python&quot;)

print(m.start() + m.end())</code></pre>
<hr>
<h2 id="패턴-분석">패턴 분석</h2>
<pre><code class="language-text">[a-z]+</code></pre>
<p>는</p>
<pre><code class="language-text">영문 소문자가
1개 이상 연속</code></pre>
<p>이라는 의미이다.</p>
<p>따라서</p>
<pre><code class="language-text">5 python</code></pre>
<p>에서</p>
<pre><code class="language-text">python</code></pre>
<p>이 매치된다.</p>
<hr>
<h2 id="인덱스">인덱스</h2>
<pre><code class="language-text">문자     5   공백   p   y   t   h   o   n
인덱스   0    1    2   3   4   5   6   7</code></pre>
<p>따라서</p>
<pre><code class="language-python">m.start()</code></pre>
<p>은</p>
<pre><code class="language-text">2</code></pre>
<p>이고,</p>
<pre><code class="language-python">m.end()</code></pre>
<p>는 마지막 문자의 다음 위치이므로</p>
<pre><code class="language-text">8</code></pre>
<p>이다.</p>
<p>따라서</p>
<pre><code class="language-text">2 + 8
=
10</code></pre>
<h3 id="정답-1">정답</h3>
<pre><code class="language-text">10</code></pre>
<hr>
<h1 id="q19-그루핑">Q19. 그루핑</h1>
<p>다음과 같은 데이터가 있다고 해보자.</p>
<pre><code class="language-text">park 010-9999-9998
kim 010-9909-7789
lee 010-8789-7768</code></pre>
<p>전화번호의 마지막 네 자리를</p>
<pre><code class="language-text">####</code></pre>
<p>로 변경한다.</p>
<hr>
<h2 id="정규식-작성">정규식 작성</h2>
<p>전화번호의 앞부분을 그룹으로 만든다.</p>
<pre><code class="language-python">r&quot;(\d{3}-\d{4})-\d{4}&quot;</code></pre>
<p>해석하면</p>
<pre><code class="language-text">(
  숫자 3개
  -
  숫자 4개
)
      ↓
그룹 1

-

숫자 4개</code></pre>
<p>이다.</p>
<hr>
<h2 id="풀이-9">풀이</h2>
<pre><code class="language-python">import re

data = &quot;&quot;&quot;
park 010-9999-9998
kim 010-9909-7789
lee 010-8789-7768
&quot;&quot;&quot;

pattern = re.compile(
    r&quot;(\d{3}-\d{4})-\d{4}&quot;
)

result = pattern.sub(
    r&quot;\g&lt;1&gt;-####&quot;,
    data
)

print(result)</code></pre>
<p>결과</p>
<pre><code class="language-text">park 010-9999-####
kim 010-9909-####
lee 010-8789-####</code></pre>
<hr>
<h2 id="g1"><code>\g&lt;1&gt;</code></h2>
<pre><code class="language-text">첫 번째 그룹에서
매치된 문자열</code></pre>
<p>을 의미한다.</p>
<p>따라서</p>
<pre><code class="language-text">010-9999</code></pre>
<p>부분은 그대로 유지하고</p>
<pre><code class="language-text">9998</code></pre>
<p>부분만</p>
<pre><code class="language-text">####</code></pre>
<p>로 변경한다.</p>
<hr>
<h1 id="q20-전방-탐색">Q20. 전방 탐색</h1>
<p>이메일 주소 중에서 마지막 도메인이</p>
<pre><code class="language-text">.com
.net</code></pre>
<p>인 경우만 매치하도록 만든다.</p>
<hr>
<h2 id="기본-패턴">기본 패턴</h2>
<pre><code class="language-text">.*[@].*[.].*$</code></pre>
<p>대략적으로</p>
<pre><code class="language-text">문자들
@
문자들
.
문자들</code></pre>
<p>형태의 이메일을 찾는다.</p>
<hr>
<h2 id="긍정형-전방-탐색">긍정형 전방 탐색</h2>
<pre><code class="language-text">(?=...)</code></pre>
<p>은</p>
<blockquote>
<p>뒤에 특정 패턴이 있는지 확인하지만 해당 문자열 자체는 소비하지 않는다.</p>
</blockquote>
<p>이를 이용하면</p>
<pre><code class="language-python">r&quot;.*[@].*[.](?=com$|net$).*$&quot;</code></pre>
<p>처럼 작성할 수 있다.</p>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-python">import re

pattern = re.compile(
    r&quot;.*[@].*[.](?=com$|net$).*$&quot;
)

print(
    pattern.match(&quot;test@gmail.com&quot;)
)

print(
    pattern.match(&quot;test@daum.net&quot;)
)

print(
    pattern.match(&quot;test@myhome.co.kr&quot;)
)</code></pre>
<p>결과는 개념적으로</p>
<pre><code class="language-text">test@gmail.com
→ Match O

test@daum.net
→ Match O

test@myhome.co.kr
→ Match X</code></pre>
<p>가 된다.</p>
<hr>
<h2 id="핵심-부분">핵심 부분</h2>
<pre><code class="language-text">(?=com$|net$)</code></pre>
<p>을 해석하면</p>
<pre><code class="language-text">뒤에

com으로 끝나거나
또는
net으로 끝나야 한다.</code></pre>
<p>라는 의미이다.</p>
<pre><code class="language-text">(?=...)
→ 긍정형 전방 탐색

|
→ OR

$
→ 문자열 끝</code></pre>
<hr>
<h1 id="📌-코딩-면허시험-핵심-정리">📌 코딩 면허시험 핵심 정리</h1>
<p>이번 문제들을 통해 지금까지 배운 파이썬 문법을 전체적으로 복습할 수 있다.</p>
<table>
<thead>
<tr>
<th align="center">문제</th>
<th align="left">핵심 개념</th>
</tr>
</thead>
<tbody><tr>
<td align="center">Q1</td>
<td align="left"><code>split()</code>, <code>join()</code></td>
</tr>
<tr>
<td align="center">Q2</td>
<td align="left">딕셔너리 <code>get()</code></td>
</tr>
<tr>
<td align="center">Q3</td>
<td align="left">리스트 <code>+</code>, <code>extend()</code></td>
</tr>
<tr>
<td align="center">Q4</td>
<td align="left"><code>for</code>, <code>if</code>, 누적</td>
</tr>
<tr>
<td align="center">Q5</td>
<td align="left">함수, 피보나치</td>
</tr>
<tr>
<td align="center">Q6</td>
<td align="left"><code>split()</code>, <code>map()</code>, <code>sum()</code></td>
</tr>
<tr>
<td align="center">Q7</td>
<td align="left"><code>range()</code>, <code>print(end=)</code></td>
</tr>
<tr>
<td align="center">Q8</td>
<td align="left">파일 입출력, <code>readlines()</code></td>
</tr>
<tr>
<td align="center">Q9</td>
<td align="left">파일 입출력, 합계, 평균</td>
</tr>
<tr>
<td align="center">Q10</td>
<td align="left">클래스, 생성자, 메서드</td>
</tr>
<tr>
<td align="center">Q11</td>
<td align="left">모듈, <code>sys.path</code></td>
</tr>
<tr>
<td align="center">Q12</td>
<td align="left"><code>try</code>, <code>except</code>, <code>finally</code></td>
</tr>
<tr>
<td align="center">Q13</td>
<td align="left"><code>enumerate()</code>, 홀짝 판별</td>
</tr>
<tr>
<td align="center">Q16</td>
<td align="left">딕셔너리, 함수, 문자열 분리</td>
</tr>
<tr>
<td align="center">Q17</td>
<td align="left">정규식 <code>[.]</code>, <code>{m,}</code></td>
</tr>
<tr>
<td align="center">Q18</td>
<td align="left"><code>search()</code>, <code>start()</code>, <code>end()</code></td>
</tr>
<tr>
<td align="center">Q19</td>
<td align="left">그룹, <code>sub()</code></td>
</tr>
<tr>
<td align="center">Q20</td>
<td align="left">긍정형 전방 탐색</td>
</tr>
</tbody></table>
<hr>
<h1 id="⭐-전체-학습-흐름">⭐ 전체 학습 흐름</h1>
<pre><code class="language-text">파이썬 기초
   ↓
자료형
   ↓
문자열 / 리스트 / 딕셔너리
   ↓
if / while / for
   ↓
함수
   ↓
파일 입출력
   ↓
클래스
   ↓
모듈
   ↓
예외 처리
   ↓
정규 표현식
   ↓
종합 문제 풀이</code></pre>
<p>이번 문제에서 특히 다시 확인하면 좋은 부분은</p>
<pre><code class="language-text">split / join
get
extend
map
enumerate
파일 입출력
클래스
try-except-finally
정규 표현식</code></pre>
<p>이다.</p>
<blockquote>
<p><strong>코딩 문제를 풀 때는 바로 정답 코드를 작성하려고 하기보다 <code>입력값 → 필요한 처리 → 출력값</code>으로 문제를 먼저 나눈 뒤, 지금까지 배운 문법 중 무엇을 사용할지 생각하는 습관을 들이는 것이 중요하다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY17(강력한 정규 표현식의 세계로)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17%EA%B0%95%EB%A0%A5%ED%95%9C-%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D%EC%9D%98-%EC%84%B8%EA%B3%84%EB%A1%9C</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17%EA%B0%95%EB%A0%A5%ED%95%9C-%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D%EC%9D%98-%EC%84%B8%EA%B3%84%EB%A1%9C</guid>
            <pubDate>Tue, 29 Sep 2026 13:48:25 GMT</pubDate>
            <description><![CDATA[<h1 id="08-3-강력한-정규-표현식의-세계로">08-3 강력한 정규 표현식의 세계로</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>1. 문자열 소비가 없는 메타 문자</strong></span></li>
<li><span style="color:slateblue"><strong>2. <code>|</code>, <code>^</code>, <code>$</code></strong></span></li>
<li><span style="color:slateblue"><strong>3. <code>\A</code>, <code>\Z</code>, <code>\b</code>, <code>\B</code></strong></span></li>
<li><span style="color:slateblue"><strong>4. 그루핑 <code>()</code></strong></span></li>
<li><span style="color:slateblue"><strong>5. 그룹 재참조</strong></span></li>
<li><span style="color:slateblue"><strong>6. 이름 있는 그룹</strong></span></li>
<li><span style="color:slateblue"><strong>7. 전방 탐색</strong></span></li>
<li><span style="color:slateblue"><strong>8. <code>sub()</code>를 이용한 문자열 변경</strong></span></li>
<li><span style="color:slateblue"><strong>9. Greedy와 Non-Greedy</strong></span></li>
</ul>
<hr>
<h1 id="1-문자열-소비가-없는-메타-문자">1. 문자열 소비가 없는 메타 문자</h1>
<p>앞에서 배운</p>
<pre><code class="language-text">+
*
[]
{}</code></pre>
<p>같은 메타 문자는 문자열과 매치되면 해당 문자열을 <strong>실제로 소비</strong>한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">aac</code></pre>
<p>에서</p>
<pre><code class="language-text">a+</code></pre>
<p>를 찾으면</p>
<pre><code class="language-text">aa</code></pre>
<p>가 매치된다.</p>
<p>그리고 문자열을 읽고 있는 현재 위치도 <code>aa</code> 뒤쪽으로 이동한다.</p>
<pre><code class="language-text">aac
^^
매치

  ↑
  다음 위치</code></pre>
<p>하지만 이번에 배우는 일부 메타 문자는</p>
<blockquote>
<p><strong>특정 조건을 확인만 하고 실제 문자를 소비하지 않는다.</strong></p>
</blockquote>
<p>이러한 개념은 뒤에서 배우는 <strong>전방 탐색</strong>을 이해할 때 특히 중요하다.</p>
<hr>
<h1 id="2----or">2. <code>|</code> - OR</h1>
<pre><code class="language-text">|</code></pre>
<p>는</p>
<blockquote>
<p><strong>또는(OR)</strong></p>
</blockquote>
<p>이라는 의미이다.</p>
<p>기본 구조는</p>
<pre><code class="language-text">A|B</code></pre>
<p>이고</p>
<pre><code class="language-text">A 또는 B</code></pre>
<p>를 의미한다.</p>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;cat|dog&quot;)</code></pre>
<p>은</p>
<pre><code class="language-text">cat 또는 dog</code></pre>
<p>를 찾는다.</p>
<pre><code class="language-python">print(p.search(&quot;I have a dog&quot;))</code></pre>
<p><code>dog</code>가 있으므로 매치된다.</p>
<hr>
<h2 id="여러-개의-조건">여러 개의 조건</h2>
<pre><code class="language-text">cat|dog|bird</code></pre>
<p>라면</p>
<pre><code class="language-text">cat
또는
dog
또는
bird</code></pre>
<p>중 하나와 매치된다.</p>
<hr>
<h1 id="3----문자열의-시작">3. <code>^</code> - 문자열의 시작</h1>
<pre><code class="language-text">^</code></pre>
<p>는</p>
<blockquote>
<p><strong>문자열의 시작</strong></p>
</blockquote>
<p>을 의미한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">^Life</code></pre>
<p>는</p>
<pre><code class="language-text">Life로 시작하는 문자열</code></pre>
<p>을 의미한다.</p>
<pre><code class="language-python">import re

print(re.search(r&quot;^Life&quot;, &quot;Life is too short&quot;))</code></pre>
<p>매치된다.</p>
<p>하지만</p>
<pre><code class="language-python">print(re.search(r&quot;^Life&quot;, &quot;My Life&quot;))</code></pre>
<p>에서는 <code>Life</code>가 존재하더라도 문자열 처음에 있지 않기 때문에 매치되지 않는다.</p>
<hr>
<h2 id="쉽게-이해하기">쉽게 이해하기</h2>
<pre><code class="language-text">Life is too short
^^^^
O</code></pre>
<p>하지만</p>
<pre><code class="language-text">My Life
   ^^^^</code></pre>
<p><code>Life</code>가 문자열 중간에 있으므로</p>
<pre><code class="language-text">^Life</code></pre>
<p>와는 매치되지 않는다.</p>
<hr>
<h1 id="4----문자열의-끝">4. <code>$</code> - 문자열의 끝</h1>
<pre><code class="language-text">$</code></pre>
<p>는 <code>^</code>와 반대로</p>
<blockquote>
<p><strong>문자열의 끝</strong></p>
</blockquote>
<p>을 의미한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">short$</code></pre>
<p>는</p>
<pre><code class="language-text">short로 끝나는 문자열</code></pre>
<p>을 의미한다.</p>
<pre><code class="language-python">import re

print(re.search(r&quot;short$&quot;, &quot;Life is too short&quot;))</code></pre>
<p>매치된다.</p>
<p>하지만</p>
<pre><code class="language-python">print(re.search(
    r&quot;short$&quot;,
    &quot;Life is too short, you need python&quot;
))</code></pre>
<p>에서는 <code>short</code> 뒤에 문자열이 더 있으므로 매치되지 않는다.</p>
<hr>
<h2 id="--정리"><code>^</code>, <code>$</code> 정리</h2>
<pre><code class="language-text">^python</code></pre>
<pre><code class="language-text">python으로 시작</code></pre>
<pre><code class="language-text">python$</code></pre>
<pre><code class="language-text">python으로 끝남</code></pre>
<p>둘을 같이 사용하면</p>
<pre><code class="language-text">^python$</code></pre>
<pre><code class="language-text">문자열 전체가 정확히 python</code></pre>
<p>이라는 의미로 사용할 수 있다.</p>
<hr>
<h1 id="5-a">5. <code>\A</code></h1>
<pre><code class="language-text">\A</code></pre>
<p>도 문자열의 시작을 의미한다.</p>
<pre><code class="language-text">\Apython</code></pre>
<p>은</p>
<pre><code class="language-text">전체 문자열이 python으로 시작</code></pre>
<p>하는지 확인한다.</p>
<p>기본적으로 <code>^</code>와 비슷하지만 <code>re.MULTILINE</code> 옵션을 사용할 때 차이가 생긴다.</p>
<hr>
<h2 id="와-a-차이"><code>^</code>와 <code>\A</code> 차이</h2>
<p>여러 줄 문자열에서 <code>re.MULTILINE</code>을 사용하면</p>
<pre><code class="language-text">^</code></pre>
<p>는 <strong>각 줄의 시작</strong>을 의미한다.</p>
<p>반면</p>
<pre><code class="language-text">\A</code></pre>
<p>는 항상 <strong>전체 문자열의 시작 한 곳만</strong> 의미한다.</p>
<pre><code class="language-text">^
→ 각 줄의 처음 가능

\A
→ 전체 문자열의 처음만</code></pre>
<hr>
<h1 id="6-z">6. <code>\Z</code></h1>
<pre><code class="language-text">\Z</code></pre>
<p>는</p>
<blockquote>
<p><strong>전체 문자열의 끝</strong></p>
</blockquote>
<p>을 의미한다.</p>
<p><code>$</code>와 비슷하지만 <code>MULTILINE</code> 환경에서도 전체 문자열의 끝을 기준으로 한다.</p>
<p>정리하면</p>
<pre><code class="language-text">^  → 문자열 시작
$  → 문자열 끝

\A → 전체 문자열 시작
\Z → 전체 문자열 끝</code></pre>
<p>이라고 생각하면 된다.</p>
<hr>
<h1 id="7-b---단어-경계">7. <code>\b</code> - 단어 경계</h1>
<pre><code class="language-text">\b</code></pre>
<p>는 <strong>Word Boundary</strong>, 즉</p>
<blockquote>
<p><strong>단어와 단어가 아닌 부분의 경계</strong></p>
</blockquote>
<p>를 의미한다.</p>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;\bclass\b&quot;)</code></pre>
<p>이 정규식은 <code>class</code>라는 <strong>하나의 독립된 단어</strong>를 찾는다.</p>
<pre><code class="language-python">print(p.search(&quot;no class at all&quot;))</code></pre>
<p>에서는</p>
<pre><code class="language-text">class</code></pre>
<p>가 독립된 단어이므로 매치된다.</p>
<hr>
<h2 id="문자열-안에-class가-있어도-매치되지-않을-수-있다">문자열 안에 class가 있어도 매치되지 않을 수 있다</h2>
<pre><code class="language-python">print(p.search(&quot;declassified&quot;))</code></pre>
<p><code>declassified</code> 안에</p>
<pre><code class="language-text">class</code></pre>
<p>문자열은 포함되어 있다.</p>
<p>하지만 독립된 단어가 아니므로 매치되지 않는다.</p>
<p>마찬가지로</p>
<pre><code class="language-text">subclass</code></pre>
<p>에서도 <code>class</code>가 하나의 독립된 단어가 아니므로</p>
<pre><code class="language-text">\bclass\b</code></pre>
<p>와 매치되지 않는다.</p>
<hr>
<h2 id="왜-raw-string을-사용할까">왜 Raw String을 사용할까?</h2>
<p>파이썬에서</p>
<pre><code class="language-text">\b</code></pre>
<p>는 문자열 문법상 백스페이스를 의미할 수도 있다.</p>
<p>따라서 정규식의 단어 경계를 의미하도록</p>
<pre><code class="language-python">r&quot;\bclass\b&quot;</code></pre>
<p>처럼 <strong>Raw String</strong>으로 작성하는 습관이 중요하다.</p>
<hr>
<h1 id="8-b">8. <code>\B</code></h1>
<pre><code class="language-text">\B</code></pre>
<p>는 <code>\b</code>의 반대이다.</p>
<p>즉</p>
<blockquote>
<p><strong>단어 경계가 아닌 위치</strong></p>
</blockquote>
<p>를 의미한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">declassified</code></pre>
<p>에서는 <code>class</code> 앞과 뒤에 다른 단어 문자가 연결되어 있다.</p>
<p>따라서</p>
<pre><code class="language-python">r&quot;\Bclass\B&quot;</code></pre>
<p>와 매치될 수 있다.</p>
<hr>
<h2 id="b와-b"><code>\b</code>와 <code>\B</code></h2>
<pre><code class="language-text">\b
→ 단어의 경계</code></pre>
<pre><code class="language-text">\B
→ 단어의 경계가 아닌 곳</code></pre>
<p>이라고 기억하면 된다.</p>
<hr>
<h1 id="9-그루핑-">9. 그루핑 <code>()</code></h1>
<p>정규식에서 매우 중요한 기능 중 하나가 <strong>그루핑(Grouping)</strong> 이다.</p>
<p>그룹은</p>
<pre><code class="language-text">()</code></pre>
<p>를 사용한다.</p>
<p>그루핑을 사용하는 대표적인 이유는 두 가지이다.</p>
<ul>
<li>여러 문자를 하나의 단위로 묶기</li>
<li>매치된 문자열 중 원하는 부분만 추출하기</li>
</ul>
<hr>
<h1 id="10-여러-문자를-하나로-묶기">10. 여러 문자를 하나로 묶기</h1>
<p>예를 들어</p>
<pre><code class="language-text">ABC</code></pre>
<p>라는 문자열이 반복되는 형태를 찾고 싶다고 해보자.</p>
<p>다음처럼 그룹으로 묶는다.</p>
<pre><code class="language-text">(ABC)+</code></pre>
<p>의미는</p>
<pre><code class="language-text">ABC라는 묶음이
1번 이상 반복</code></pre>
<p>이다.</p>
<p>따라서</p>
<pre><code class="language-text">ABC
ABCABC
ABCABCABC</code></pre>
<p>등과 매치된다.</p>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-python">import re

p = re.compile(r&quot;(ABC)+&quot;)

m = p.search(&quot;ABCABCABC OK?&quot;)

print(m.group())</code></pre>
<p>결과</p>
<pre><code class="language-text">ABCABCABC</code></pre>
<p>이다.</p>
<hr>
<h1 id="11-그룹으로-원하는-부분만-추출하기">11. 그룹으로 원하는 부분만 추출하기</h1>
<p>다음 문자열이 있다고 해보자.</p>
<pre><code class="language-text">park 010-1234-1234</code></pre>
<p>구조는</p>
<pre><code class="language-text">이름
+
공백
+
전화번호</code></pre>
<p>이다.</p>
<p>정규식으로 표현하면 다음과 같은 형태가 된다.</p>
<pre><code class="language-python">r&quot;\w+\s+\d+[-]\d+[-]\d+&quot;</code></pre>
<p>하지만 여기서 <strong>이름만 가져오고 싶다면?</strong></p>
<p>이름 부분을 그룹으로 묶는다.</p>
<pre><code class="language-python">r&quot;(\w+)\s+\d+[-]\d+[-]\d+&quot;</code></pre>
<hr>
<h2 id="group1">group(1)</h2>
<pre><code class="language-python">import re

p = re.compile(
    r&quot;(\w+)\s+\d+[-]\d+[-]\d+&quot;
)

m = p.search(
    &quot;park 010-1234-1234&quot;
)

print(m.group(1))</code></pre>
<p>결과</p>
<pre><code class="language-text">park</code></pre>
<p>이다.</p>
<hr>
<h1 id="12-group-번호-이해하기">12. group() 번호 이해하기</h1>
<p>그룹은 만들어진 순서에 따라 번호가 붙는다.</p>
<table>
<thead>
<tr>
<th align="center">코드</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>group()</code></td>
<td align="left">전체 매치</td>
</tr>
<tr>
<td align="center"><code>group(0)</code></td>
<td align="left">전체 매치</td>
</tr>
<tr>
<td align="center"><code>group(1)</code></td>
<td align="left">첫 번째 그룹</td>
</tr>
<tr>
<td align="center"><code>group(2)</code></td>
<td align="left">두 번째 그룹</td>
</tr>
<tr>
<td align="center"><code>group(3)</code></td>
<td align="left">세 번째 그룹</td>
</tr>
</tbody></table>
<p>예를 들어</p>
<pre><code class="language-python">r&quot;(\w+)\s+(\d+[-]\d+[-]\d+)&quot;</code></pre>
<p>에는 그룹이 두 개 있다.</p>
<pre><code class="language-text">(\w+)
 ↑
그룹 1</code></pre>
<pre><code class="language-text">(\d+[-]\d+[-]\d+)
 ↑
그룹 2</code></pre>
<p>따라서</p>
<pre><code class="language-python">m.group(1)</code></pre>
<p>은 이름,</p>
<pre><code class="language-python">m.group(2)</code></pre>
<p>는 전화번호를 반환한다.</p>
<hr>
<h1 id="13-그룹-안에-그룹-만들기">13. 그룹 안에 그룹 만들기</h1>
<p>그룹을 중첩할 수도 있다.</p>
<pre><code class="language-python">r&quot;(\w+)\s+((\d+)[-]\d+[-]\d+)&quot;</code></pre>
<p>구조를 보면</p>
<pre><code class="language-text">(\w+)
→ 그룹 1 : 이름</code></pre>
<pre><code class="language-text">((\d+)-...)
→ 그룹 2 : 전체 전화번호</code></pre>
<p>그리고 그룹 2 내부의</p>
<pre><code class="language-text">(\d+)</code></pre>
<p>는</p>
<pre><code class="language-text">그룹 3 : 010</code></pre>
<p>이 된다.</p>
<p>따라서</p>
<pre><code class="language-python">m.group(3)</code></pre>
<p>을 사용하면</p>
<pre><code class="language-text">010</code></pre>
<p>만 가져올 수 있다.</p>
<hr>
<h1 id="14-그룹-재참조">14. 그룹 재참조</h1>
<p>이미 매치된 그룹의 내용을 다시 사용할 수 있다.</p>
<p>이것을 <strong>재참조(Backreference)</strong> 라고 한다.</p>
<p>예를 들어 다음 문장에서 중복된 단어를 찾아보자.</p>
<pre><code class="language-text">Paris in the the spring</code></pre>
<p><code>the</code>가 두 번 연속 사용되었다.</p>
<p>정규식은</p>
<pre><code class="language-python">r&quot;(\b\w+)\s+\1&quot;</code></pre>
<p>처럼 작성할 수 있다.</p>
<hr>
<h2 id="해석">해석</h2>
<p>먼저</p>
<pre><code class="language-text">(\b\w+)</code></pre>
<p>는 단어 하나를 그룹 1에 저장한다.</p>
<pre><code class="language-text">\s+</code></pre>
<p>는 하나 이상의 공백이다.</p>
<p>그리고</p>
<pre><code class="language-text">\1</code></pre>
<p>은</p>
<blockquote>
<p><strong>첫 번째 그룹에서 매치된 것과 동일한 문자열</strong></p>
</blockquote>
<p>을 의미한다.</p>
<p>따라서</p>
<pre><code class="language-text">the the</code></pre>
<p>처럼 같은 단어가 연속으로 있는 부분을 찾을 수 있다.</p>
<hr>
<h2 id="코드-1">코드</h2>
<pre><code class="language-python">import re

p = re.compile(
    r&quot;(\b\w+)\s+\1&quot;
)

m = p.search(
    &quot;Paris in the the spring&quot;
)

print(m.group())</code></pre>
<p>결과</p>
<pre><code class="language-text">the the</code></pre>
<p>이다.</p>
<hr>
<h1 id="15-재참조-번호">15. 재참조 번호</h1>
<pre><code class="language-text">\1</code></pre>
<p>은 첫 번째 그룹을 의미한다.</p>
<pre><code class="language-text">\2</code></pre>
<p>는 두 번째 그룹,</p>
<pre><code class="language-text">\3</code></pre>
<p>은 세 번째 그룹을 의미한다.</p>
<p>즉</p>
<pre><code class="language-text">그룹 1 → \1
그룹 2 → \2
그룹 3 → \3</code></pre>
<p>이다.</p>
<hr>
<h1 id="16-이름-있는-그룹">16. 이름 있는 그룹</h1>
<p>그룹이 많아지면</p>
<pre><code class="language-text">group(1)
group(2)
group(3)
...</code></pre>
<p>만으로는 어떤 그룹인지 알아보기 어려워진다.</p>
<p>그래서 그룹에 <strong>이름을 붙일 수 있다.</strong></p>
<p>기본 구조는</p>
<pre><code class="language-text">(?P&lt;그룹이름&gt;정규식)</code></pre>
<p>이다.</p>
<p>예를 들어</p>
<pre><code class="language-python">r&quot;(?P&lt;name&gt;\w+)&quot;</code></pre>
<p>은</p>
<pre><code class="language-text">\w+</code></pre>
<p>그룹의 이름을</p>
<pre><code class="language-text">name</code></pre>
<p>으로 지정한 것이다.</p>
<hr>
<h1 id="17-이름-있는-그룹-사용하기">17. 이름 있는 그룹 사용하기</h1>
<pre><code class="language-python">import re

p = re.compile(
    r&quot;(?P&lt;name&gt;\w+)\s+&quot;
    r&quot;(?P&lt;phone&gt;\d+[-]\d+[-]\d+)&quot;
)

m = p.search(
    &quot;park 010-1234-1234&quot;
)</code></pre>
<p>이제 숫자가 아니라 이름으로 가져올 수 있다.</p>
<pre><code class="language-python">print(m.group(&quot;name&quot;))</code></pre>
<p>결과</p>
<pre><code class="language-text">park</code></pre>
<p>이다.</p>
<p>전화번호는</p>
<pre><code class="language-python">print(m.group(&quot;phone&quot;))</code></pre>
<p>으로 가져올 수 있다.</p>
<hr>
<h1 id="18-이름-있는-그룹-재참조">18. 이름 있는 그룹 재참조</h1>
<p>이름 있는 그룹도 정규식 안에서 다시 참조할 수 있다.</p>
<p>구조는</p>
<pre><code class="language-text">(?P=그룹이름)</code></pre>
<p>이다.</p>
<p>예를 들어</p>
<pre><code class="language-python">r&quot;(?P&lt;word&gt;\b\w+)\s+(?P=word)&quot;</code></pre>
<p>은</p>
<pre><code class="language-text">단어 하나
+
공백
+
앞에서 나온 동일한 단어</code></pre>
<p>를 찾는다.</p>
<p>따라서</p>
<pre><code class="language-text">the the</code></pre>
<p>와 매치된다.</p>
<hr>
<h1 id="19-전방-탐색">19. 전방 탐색</h1>
<p><strong>전방 탐색(Lookahead)</strong> 은 정규식에서 매우 유용한 기능이다.</p>
<p>쉽게 설명하면</p>
<blockquote>
<p><strong>뒤에 특정 패턴이 존재하는지 확인은 하지만 그 문자열 자체는 결과에 포함하지 않는 기능</strong></p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="20-기본-예제">20. 기본 예제</h1>
<p>다음 문자열이 있다고 해보자.</p>
<pre><code class="language-text">http://google.com</code></pre>
<p>그리고</p>
<pre><code class="language-python">r&quot;.+:&quot;</code></pre>
<p>로 검색한다.</p>
<pre><code class="language-text">.+
→ 아무 문자 1개 이상

:
→ 콜론</code></pre>
<p>따라서 결과는</p>
<pre><code class="language-text">http:</code></pre>
<p>가 된다.</p>
<p>그런데 우리가 원하는 결과가</p>
<pre><code class="language-text">http</code></pre>
<p>이고</p>
<pre><code class="language-text">:</code></pre>
<p>은 <strong>존재 여부만 확인하고 결과에서는 제외</strong>하고 싶다면 전방 탐색을 사용한다.</p>
<hr>
<h1 id="21-긍정형-전방-탐색-">21. 긍정형 전방 탐색 <code>(?=...)</code></h1>
<p>긍정형 전방 탐색의 구조는</p>
<pre><code class="language-text">(?=...)</code></pre>
<p>이다.</p>
<p>뜻은</p>
<blockquote>
<p><strong>뒤에 ...가 존재하는 경우</strong></p>
</blockquote>
<p>이다.</p>
<p>예를 들어</p>
<pre><code class="language-python">r&quot;.+(?=:)&quot;</code></pre>
<p>를 사용한다.</p>
<p>해석하면</p>
<pre><code class="language-text">.+
→ 문자 1개 이상

(?=:)
→ 뒤에 :가 있는지 확인
   단, :는 매치 결과에 포함하지 않음</code></pre>
<p>이다.</p>
<hr>
<h2 id="결과">결과</h2>
<pre><code class="language-python">import re

p = re.compile(r&quot;.+(?=:)&quot;)

m = p.search(
    &quot;http://google.com&quot;
)

print(m.group())</code></pre>
<p>결과</p>
<pre><code class="language-text">http</code></pre>
<p>이다.</p>
<p><code>:</code>이 있는지는 확인했지만 실제 결과에는 포함시키지 않았다.</p>
<hr>
<h1 id="22-긍정형-전방-탐색-쉽게-이해하기">22. 긍정형 전방 탐색 쉽게 이해하기</h1>
<pre><code class="language-text">http://google.com
^^^^
결과

    :
    ↑
    존재 여부만 확인</code></pre>
<p>즉</p>
<pre><code class="language-text">&quot;뒤에 :가 오는 문자열을 찾아줘.
하지만 :는 결과에 넣지 마.&quot;</code></pre>
<p>라고 이해하면 된다.</p>
<hr>
<h1 id="23-부정형-전방-탐색-">23. 부정형 전방 탐색 <code>(?!...)</code></h1>
<p>부정형 전방 탐색은</p>
<pre><code class="language-text">(?!...)</code></pre>
<p>을 사용한다.</p>
<p>뜻은</p>
<blockquote>
<p><strong>뒤에 ...가 오지 않는 경우</strong></p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="24-특정-확장자-제외하기">24. 특정 확장자 제외하기</h1>
<p>예를 들어 파일 이름에서</p>
<pre><code class="language-text">.bat</code></pre>
<p>확장자를 제외하고 싶다고 해보자.</p>
<p>기본적인 파일 이름 형태를</p>
<pre><code class="language-text">.*[.].*$</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>하지만 <code>.bat</code> 파일을 제외하고 싶다면</p>
<pre><code class="language-text">.*[.](?!bat$).*$</code></pre>
<p>처럼 부정형 전방 탐색을 사용할 수 있다.</p>
<hr>
<h2 id="bat-해석"><code>(?!bat$)</code> 해석</h2>
<pre><code class="language-text">(?!bat$)</code></pre>
<p>은</p>
<pre><code class="language-text">뒤에 bat가 오지 않는 경우</code></pre>
<p>라는 의미이다.</p>
<p>따라서</p>
<pre><code class="language-text">test.txt   → O
test.jpg   → O
test.bat   → X</code></pre>
<p>처럼 사용할 수 있다.</p>
<hr>
<h1 id="25-여러-확장자-제외하기">25. 여러 확장자 제외하기</h1>
<p>이번에는</p>
<pre><code class="language-text">.bat
.exe</code></pre>
<p>둘 다 제외하고 싶다고 해보자.</p>
<pre><code class="language-text">.*[.](?!bat$|exe$).*$</code></pre>
<p>처럼 작성할 수 있다.</p>
<p><code>|</code>는 OR이므로</p>
<pre><code class="language-text">bat 또는 exe</code></pre>
<p>가 뒤에 오는 경우를 제외한다.</p>
<hr>
<h1 id="26-전방-탐색-정리">26. 전방 탐색 정리</h1>
<h3 id="긍정형">긍정형</h3>
<pre><code class="language-text">(?=...)</code></pre>
<pre><code class="language-text">뒤에 ...가 있어야 함
하지만 결과에는 포함하지 않음</code></pre>
<h3 id="부정형">부정형</h3>
<pre><code class="language-text">(?!...)</code></pre>
<pre><code class="language-text">뒤에 ...가 없어야 함</code></pre>
<p>둘 다 중요한 특징은</p>
<blockquote>
<p><strong>조건은 확인하지만 해당 문자를 직접 소비하지 않는다.</strong></p>
</blockquote>
<p>는 것이다.</p>
<hr>
<h1 id="27-sub---문자열-바꾸기">27. <code>sub()</code> - 문자열 바꾸기</h1>
<p>정규식에 일치하는 문자열을 다른 문자열로 바꿀 때</p>
<pre><code class="language-python">sub()</code></pre>
<p>를 사용할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(
    r&quot;blue|white|red&quot;
)

result = p.sub(
    &quot;colour&quot;,
    &quot;blue socks and red shoes&quot;
)

print(result)</code></pre>
<p>결과</p>
<pre><code class="language-text">colour socks and colour shoes</code></pre>
<p>가 된다.</p>
<hr>
<h1 id="28-sub-기본-구조">28. sub() 기본 구조</h1>
<pre><code class="language-python">패턴.sub(
    &quot;바꿀 문자열&quot;,
    &quot;대상 문자열&quot;
)</code></pre>
<p>이다.</p>
<p>즉</p>
<pre><code class="language-text">정규식과 일치하는 부분 찾기
          ↓
지정한 문자열로 교체</code></pre>
<p>한다.</p>
<hr>
<h1 id="29-변경-횟수-제한하기">29. 변경 횟수 제한하기</h1>
<p>모든 문자열을 변경하는 것이 아니라 처음 한 번만 변경하고 싶을 수도 있다.</p>
<p>이때</p>
<pre><code class="language-python">count=</code></pre>
<p>를 사용한다.</p>
<pre><code class="language-python">p.sub(
    &quot;colour&quot;,
    &quot;blue socks and red shoes&quot;,
    count=1
)</code></pre>
<p>결과</p>
<pre><code class="language-text">colour socks and red shoes</code></pre>
<p>이다.</p>
<p>첫 번째 <code>blue</code>만 변경된다.</p>
<hr>
<h1 id="30-subn">30. <code>subn()</code></h1>
<p><code>subn()</code>도 <code>sub()</code>와 비슷하게 문자열을 변경한다.</p>
<p>차이점은 결과를 <strong>튜플</strong>로 반환한다는 것이다.</p>
<pre><code class="language-python">result = p.subn(
    &quot;colour&quot;,
    &quot;blue socks and red shoes&quot;
)</code></pre>
<p>형태는</p>
<pre><code class="language-text">(
    변경된 문자열,
    변경 횟수
)</code></pre>
<p>이다.</p>
<p>즉</p>
<pre><code class="language-text">(&#39;colour socks and colour shoes&#39;, 2)</code></pre>
<p>와 같은 형태를 얻을 수 있다.</p>
<hr>
<h1 id="31-sub에서-그룹-참조하기">31. sub()에서 그룹 참조하기</h1>
<p><code>sub()</code>에서도 앞에서 만든 그룹을 사용할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">park 010-1234-1234</code></pre>
<p>을</p>
<pre><code class="language-text">010-1234-1234 park</code></pre>
<p>처럼 순서를 바꾸고 싶다고 해보자.</p>
<p>먼저 그룹을 만든다.</p>
<pre><code class="language-python">p = re.compile(
    r&quot;(?P&lt;name&gt;\w+)\s+&quot;
    r&quot;(?P&lt;phone&gt;\d+[-]\d+[-]\d+)&quot;
)</code></pre>
<p>그리고</p>
<pre><code class="language-python">p.sub(
    r&quot;\g&lt;phone&gt; \g&lt;name&gt;&quot;,
    &quot;park 010-1234-1234&quot;
)</code></pre>
<p>처럼 작성할 수 있다.</p>
<hr>
<h2 id="결과-1">결과</h2>
<pre><code class="language-text">010-1234-1234 park</code></pre>
<p>이다.</p>
<p>즉</p>
<pre><code class="language-text">\g&lt;phone&gt;</code></pre>
<p>으로 <code>phone</code> 그룹을,</p>
<pre><code class="language-text">\g&lt;name&gt;</code></pre>
<p>으로 <code>name</code> 그룹을 참조한다.</p>
<hr>
<h1 id="32-그룹-번호로도-참조-가능">32. 그룹 번호로도 참조 가능</h1>
<p>이름 대신 번호를 사용할 수도 있다.</p>
<pre><code class="language-text">\g&lt;1&gt;
\g&lt;2&gt;</code></pre>
<p>예를 들어</p>
<pre><code class="language-python">p.sub(
    r&quot;\g&lt;2&gt; \g&lt;1&gt;&quot;,
    &quot;park 010-1234-1234&quot;
)</code></pre>
<p>처럼 사용할 수 있다.</p>
<hr>
<h1 id="33-sub에-함수-전달하기">33. sub()에 함수 전달하기</h1>
<p><code>sub()</code>의 첫 번째 인수에는 문자열뿐 아니라 <strong>함수도 넣을 수 있다.</strong></p>
<p>예를 들어 문자열 안의 10진수를 16진수로 변경해 보자.</p>
<pre><code class="language-python">import re

def hexrepl(match):
    value = int(match.group())
    return hex(value)


p = re.compile(r&quot;\d+&quot;)

result = p.sub(
    hexrepl,
    &quot;Call 65490 for printing, &quot;
    &quot;49152 for user code.&quot;
)

print(result)</code></pre>
<p>매치되는 숫자가 발견될 때마다 <code>hexrepl()</code> 함수가 실행된다.</p>
<hr>
<h1 id="34-동작-과정">34. 동작 과정</h1>
<p>첫 번째 숫자</p>
<pre><code class="language-text">65490</code></pre>
<p>가 발견된다.</p>
<pre><code class="language-text">\d+
 ↓
65490 매치
 ↓
hexrepl(match)
 ↓
int(&quot;65490&quot;)
 ↓
hex(...)
 ↓
16진수 문자열 반환
 ↓
원래 숫자 교체</code></pre>
<p>다음 숫자도 같은 방법으로 처리된다.</p>
<p>즉 <code>sub()</code>에 함수를 전달하면</p>
<blockquote>
<p><strong>각 매치 결과를 원하는 로직으로 가공한 뒤 교체</strong></p>
</blockquote>
<p>할 수 있다.</p>
<hr>
<h1 id="35-greedy란">35. Greedy란?</h1>
<p>정규식을 배우다 보면 <strong>Greedy</strong>, 즉 탐욕적이라는 표현이 나온다.</p>
<p>다음 문자열을 살펴보자.</p>
<pre><code class="language-python">s = &quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;Title&lt;/title&gt;&quot;</code></pre>
<p>그리고</p>
<pre><code class="language-text">&lt;.*&gt;</code></pre>
<p>정규식을 사용한다고 해보자.</p>
<p>우리는 처음의</p>
<pre><code class="language-text">&lt;html&gt;</code></pre>
<p>만 매치될 것이라고 생각할 수 있다.</p>
<p>하지만 실제로는 훨씬 긴 문자열이 매치된다.</p>
<hr>
<h1 id="36-왜-전체가-매치될까">36. 왜 전체가 매치될까?</h1>
<pre><code class="language-text">.*</code></pre>
<p>에서</p>
<pre><code class="language-text">*</code></pre>
<p>은 가능한 한 많이 반복하려고 한다.</p>
<p>즉</p>
<pre><code class="language-text">&lt;.*&gt;</code></pre>
<p>은</p>
<pre><code class="language-text">&lt;
+
가능한 많은 문자
+
&gt;</code></pre>
<p>를 찾는다.</p>
<p>따라서 가능한 마지막 <code>&gt;</code>까지 가져간다.</p>
<pre><code class="language-text">&lt;html&gt;&lt;head&gt;&lt;title&gt;Title&lt;/title&gt;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</code></pre>
<p>이것을</p>
<blockquote>
<p><strong>Greedy Matching</strong></p>
</blockquote>
<p>이라고 한다.</p>
<hr>
<h1 id="37-non-greedy">37. Non-Greedy</h1>
<p>가능한 최소한만 매치하도록 만들고 싶다면</p>
<pre><code class="language-text">?</code></pre>
<p>를 반복 메타 문자 뒤에 붙인다.</p>
<pre><code class="language-text">&lt;.*?&gt;</code></pre>
<p>로 변경하면 된다.</p>
<hr>
<h2 id="결과-비교">결과 비교</h2>
<h3 id="greedy">Greedy</h3>
<pre><code class="language-text">&lt;.*&gt;</code></pre>
<p>결과</p>
<pre><code class="language-text">&lt;html&gt;&lt;head&gt;&lt;title&gt;Title&lt;/title&gt;</code></pre>
<h3 id="non-greedy">Non-Greedy</h3>
<pre><code class="language-text">&lt;.*?&gt;</code></pre>
<p>결과</p>
<pre><code class="language-text">&lt;html&gt;</code></pre>
<p>이다.</p>
<hr>
<h1 id="38-non-greedy-표현">38. Non-Greedy 표현</h1>
<p>다음과 같이 사용할 수 있다.</p>
<table>
<thead>
<tr>
<th align="center">Greedy</th>
<th align="center">Non-Greedy</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>*</code></td>
<td align="center"><code>*?</code></td>
</tr>
<tr>
<td align="center"><code>+</code></td>
<td align="center"><code>+?</code></td>
</tr>
<tr>
<td align="center"><code>?</code></td>
<td align="center"><code>??</code></td>
</tr>
<tr>
<td align="center"><code>{m,n}</code></td>
<td align="center"><code>{m,n}?</code></td>
</tr>
</tbody></table>
<p>Non-Greedy는</p>
<blockquote>
<p><strong>조건을 만족하는 범위에서 가능한 최소한으로 매치</strong></p>
</blockquote>
<p>한다.</p>
<hr>
<h1 id="39-의-두-가지-역할에-주의">39. <code>?</code>의 두 가지 역할에 주의</h1>
<p>앞에서 배운</p>
<pre><code class="language-text">a?</code></pre>
<p>는</p>
<pre><code class="language-text">a가 0번 또는 1번</code></pre>
<p>이라는 의미이다.</p>
<p>하지만</p>
<pre><code class="language-text">.*?</code></pre>
<p>에서 <code>?</code>는</p>
<pre><code class="language-text">*를 Non-Greedy 방식으로 변경</code></pre>
<p>한다.</p>
<p>즉 같은 <code>?</code>라도 위치에 따라 의미가 다르다.</p>
<hr>
<h2 id="비교">비교</h2>
<pre><code class="language-text">a?</code></pre>
<pre><code class="language-text">a가 0~1회 등장</code></pre>
<p>반면</p>
<pre><code class="language-text">a*?</code></pre>
<pre><code class="language-text">a를 0회 이상 반복하되
가능한 최소한으로 매치</code></pre>
<p>이다.</p>
<hr>
<h1 id="40-이번-장-메타-문자-정리">40. 이번 장 메타 문자 정리</h1>
<table>
<thead>
<tr>
<th align="center">표현</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="center">`A</td>
<td align="left">B`</td>
</tr>
<tr>
<td align="center"><code>^A</code></td>
<td align="left">A로 시작</td>
</tr>
<tr>
<td align="center"><code>A$</code></td>
<td align="left">A로 끝</td>
</tr>
<tr>
<td align="center"><code>\A</code></td>
<td align="left">전체 문자열의 시작</td>
</tr>
<tr>
<td align="center"><code>\Z</code></td>
<td align="left">전체 문자열의 끝</td>
</tr>
<tr>
<td align="center"><code>\b</code></td>
<td align="left">단어 경계</td>
</tr>
<tr>
<td align="center"><code>\B</code></td>
<td align="left">단어 경계가 아닌 위치</td>
</tr>
<tr>
<td align="center"><code>(ABC)</code></td>
<td align="left">ABC를 하나의 그룹으로 지정</td>
</tr>
<tr>
<td align="center"><code>\1</code></td>
<td align="left">첫 번째 그룹 재참조</td>
</tr>
<tr>
<td align="center"><code>(?P&lt;name&gt;...)</code></td>
<td align="left">이름 있는 그룹</td>
</tr>
<tr>
<td align="center"><code>(?P=name)</code></td>
<td align="left">이름 있는 그룹 재참조</td>
</tr>
<tr>
<td align="center"><code>(?=...)</code></td>
<td align="left">긍정형 전방 탐색</td>
</tr>
<tr>
<td align="center"><code>(?!...)</code></td>
<td align="left">부정형 전방 탐색</td>
</tr>
<tr>
<td align="center"><code>*?</code>, <code>+?</code></td>
<td align="left">최소한으로 매치</td>
</tr>
</tbody></table>
<hr>
<h1 id="41-그룹-관련-문법-정리">41. 그룹 관련 문법 정리</h1>
<h3 id="그룹-만들기">그룹 만들기</h3>
<pre><code class="language-text">(...)</code></pre>
<h3 id="첫-번째-그룹-가져오기">첫 번째 그룹 가져오기</h3>
<pre><code class="language-python">m.group(1)</code></pre>
<h3 id="이름-있는-그룹">이름 있는 그룹</h3>
<pre><code class="language-text">(?P&lt;name&gt;...)</code></pre>
<h3 id="이름으로-값-가져오기">이름으로 값 가져오기</h3>
<pre><code class="language-python">m.group(&quot;name&quot;)</code></pre>
<h3 id="그룹-번호-재참조">그룹 번호 재참조</h3>
<pre><code class="language-text">\1</code></pre>
<h3 id="이름-있는-그룹-재참조">이름 있는 그룹 재참조</h3>
<pre><code class="language-text">(?P=name)</code></pre>
<hr>
<h1 id="42-전방-탐색-정리">42. 전방 탐색 정리</h1>
<pre><code class="language-text">(?=ABC)</code></pre>
<pre><code class="language-text">뒤에 ABC가 있어야 함
하지만 ABC는 결과에 포함하지 않음</code></pre>
<pre><code class="language-text">(?!ABC)</code></pre>
<pre><code class="language-text">뒤에 ABC가 오면 안 됨</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">.+(?=:)</code></pre>
<p>은</p>
<pre><code class="language-text">뒤에 :가 있는 문자열을 찾되
:는 결과에서 제외</code></pre>
<p>한다.</p>
<hr>
<h1 id="43-문자열-변경-정리">43. 문자열 변경 정리</h1>
<pre><code class="language-python">pattern.sub(
    &quot;변경값&quot;,
    &quot;문자열&quot;
)</code></pre>
<p>패턴과 일치하는 내용을 변경한다.</p>
<pre><code class="language-python">pattern.sub(
    &quot;변경값&quot;,
    &quot;문자열&quot;,
    count=1
)</code></pre>
<p>처럼 횟수를 제한할 수 있다.</p>
<p><code>subn()</code>을 사용하면</p>
<pre><code class="language-text">변경된 문자열
+
변경 횟수</code></pre>
<p>를 함께 얻을 수 있다.</p>
<hr>
<h1 id="44-보안-관점에서-보면">44. 보안 관점에서 보면</h1>
<p>이번 장에서 배우는 기능은 보안 룰이나 로그 분석에서도 자주 볼 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">^GET</code></pre>
<p>이라면</p>
<pre><code class="language-text">GET으로 시작</code></pre>
<p>하는 문자열을 찾는다.</p>
<pre><code class="language-text">\.exe$</code></pre>
<p>라면</p>
<pre><code class="language-text">.exe로 끝남</code></pre>
<p>을 의미한다.</p>
<pre><code class="language-text">(cmd|powershell)</code></pre>
<p>은</p>
<pre><code class="language-text">cmd 또는 powershell</code></pre>
<p>을 찾는다.</p>
<pre><code class="language-text">\badmin\b</code></pre>
<p>은 다른 문자열 안에 포함된 <code>admin</code>이 아니라 <strong>독립된 admin 단어</strong>를 찾을 때 사용할 수 있다.</p>
<p>따라서 Snort나 PCRE 룰을 볼 때도 이번 장에서 배운 개념이 많이 등장한다.</p>
<hr>
<h1 id="📌-핵심-정리">📌 핵심 정리</h1>
<h2 id="or">OR</h2>
<pre><code class="language-text">A|B</code></pre>
<pre><code class="language-text">A 또는 B</code></pre>
<hr>
<h2 id="문자열-시작과-끝">문자열 시작과 끝</h2>
<pre><code class="language-text">^abc
→ abc로 시작</code></pre>
<pre><code class="language-text">abc$
→ abc로 끝</code></pre>
<hr>
<h2 id="단어-경계">단어 경계</h2>
<pre><code class="language-text">\bclass\b</code></pre>
<p><code>class</code>라는 독립된 단어를 찾는다.</p>
<hr>
<h2 id="그룹">그룹</h2>
<pre><code class="language-text">(\w+)</code></pre>
<p>매치된 부분을 하나의 그룹으로 저장한다.</p>
<pre><code class="language-python">m.group(1)</code></pre>
<p>으로 가져올 수 있다.</p>
<hr>
<h2 id="재참조">재참조</h2>
<pre><code class="language-text">(\b\w+)\s+\1</code></pre>
<p>앞에서 찾은 단어와 같은 단어가 다시 등장하는지 검사한다.</p>
<hr>
<h2 id="이름-있는-그룹-1">이름 있는 그룹</h2>
<pre><code class="language-text">(?P&lt;name&gt;\w+)</code></pre>
<p>그룹에 이름을 붙인다.</p>
<pre><code class="language-python">m.group(&quot;name&quot;)</code></pre>
<p>으로 값을 가져온다.</p>
<hr>
<h2 id="긍정형-전방-탐색">긍정형 전방 탐색</h2>
<pre><code class="language-text">(?=...)</code></pre>
<pre><code class="language-text">해당 내용이 뒤에 있어야 한다.
하지만 결과에는 포함하지 않는다.</code></pre>
<hr>
<h2 id="부정형-전방-탐색">부정형 전방 탐색</h2>
<pre><code class="language-text">(?!...)</code></pre>
<pre><code class="language-text">해당 내용이 뒤에 없어야 한다.</code></pre>
<hr>
<h2 id="sub">sub()</h2>
<pre><code class="language-python">p.sub(
    &quot;바꿀 값&quot;,
    &quot;대상 문자열&quot;
)</code></pre>
<p>정규식과 매치되는 문자열을 변경한다.</p>
<hr>
<h2 id="greedy-1">Greedy</h2>
<pre><code class="language-text">.*</code></pre>
<p>가능한 범위에서 <strong>최대한 많이</strong> 가져온다.</p>
<hr>
<h2 id="non-greedy-1">Non-Greedy</h2>
<pre><code class="language-text">.*?</code></pre>
<p>가능한 범위에서 <strong>최소한만</strong> 가져온다.</p>
<hr>
<h1 id="⭐-08-3-전체-흐름">⭐ 08-3 전체 흐름</h1>
<pre><code class="language-text">정규식 고급 기능
      ↓
 ┌──────────────┐
 │ 위치 / 조건   │
 └──────────────┘
      ↓
|  ^  $
\A \Z
\b \B

      ↓

 ┌──────────────┐
 │ 그룹          │
 └──────────────┘
      ↓
(...)
group()
\1
(?P&lt;name&gt;...)

      ↓

 ┌──────────────┐
 │ 전방 탐색     │
 └──────────────┘
      ↓
(?=...)
(?!...)

      ↓

 ┌──────────────┐
 │ 문자열 변경   │
 └──────────────┘
      ↓
sub()
subn()

      ↓

 ┌──────────────┐
 │ 반복 범위     │
 └──────────────┘
      ↓
Greedy
.*
      ↕

Non-Greedy
.*?</code></pre>
<blockquote>
<p><strong>08-3의 핵심은 단순히 문자를 찾는 것을 넘어, 원하는 부분을 그룹으로 저장하고 다시 참조하거나, 특정 조건을 확인하면서 실제 결과에는 포함하지 않는 등 더 정교하게 문자열을 제어하는 방법을 이해하는 것이다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY17(정규 표현식 시작하기)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 29 Sep 2026 13:34:25 GMT</pubDate>
            <description><![CDATA[<h1 id="08-2-정규-표현식-시작하기">08-2 정규 표현식 시작하기</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>1. 메타 문자란?</strong></span></li>
<li><span style="color:slateblue"><strong>2. 문자 클래스 <code>[ ]</code></strong></span></li>
<li><span style="color:slateblue"><strong>3. 자주 사용하는 문자 클래스 <code>\d</code>, <code>\s</code>, <code>\w</code></strong></span></li>
<li><span style="color:slateblue"><strong>4. Dot <code>.</code></strong></span></li>
<li><span style="color:slateblue"><strong>5. 반복 <code>*</code>, <code>+</code>, <code>{}</code>, <code>?</code></strong></span></li>
<li><span style="color:slateblue"><strong>6. 파이썬 <code>re</code> 모듈</strong></span></li>
<li><span style="color:slateblue"><strong>7. <code>match()</code>와 <code>search()</code></strong></span></li>
<li><span style="color:slateblue"><strong>8. <code>findall()</code>과 <code>finditer()</code></strong></span></li>
<li><span style="color:slateblue"><strong>9. Match 객체</strong></span></li>
<li><span style="color:slateblue"><strong>10. 정규식 컴파일 옵션</strong></span></li>
<li><span style="color:slateblue"><strong>11. 역슬래시와 Raw String</strong></span></li>
</ul>
<hr>
<h1 id="1-메타-문자란">1. 메타 문자란?</h1>
<p>정규 표현식에서는 일반적인 문자 외에도 <strong>특별한 의미를 가진 문자</strong>를 사용한다.</p>
<p>이러한 문자를 <strong>메타 문자(Meta Character)</strong> 라고 한다.</p>
<p>대표적인 메타 문자는 다음과 같다.</p>
<pre><code class="language-text">. ^ $ * + ? { } [ ] \ | ( )</code></pre>
<p>예를 들어 일반 문자열에서</p>
<pre><code class="language-text">*</code></pre>
<p>은 단순한 별표 문자이지만 정규식에서는</p>
<pre><code class="language-text">앞의 문자가 0번 이상 반복</code></pre>
<p>이라는 특별한 의미를 가진다.</p>
<p>따라서 정규식을 공부할 때는</p>
<blockquote>
<p><strong>각 메타 문자가 어떤 규칙을 표현하는지 이해하는 것이 핵심이다.</strong></p>
</blockquote>
<hr>
<h1 id="2-매치match란">2. 매치(Match)란?</h1>
<p>정규식에서는 <strong>매치된다</strong>라는 표현을 많이 사용한다.</p>
<p>매치란</p>
<blockquote>
<p><strong>문자열이 정규식에서 지정한 패턴과 일치한다</strong></p>
</blockquote>
<p>라는 의미이다.</p>
<p>예를 들어 정규식이</p>
<pre><code class="language-text">abc</code></pre>
<p>이고 문자열도</p>
<pre><code class="language-text">abc</code></pre>
<p>라면</p>
<pre><code class="language-text">정규식 : abc
문자열 : abc

→ Match O</code></pre>
<p>라고 할 수 있다.</p>
<hr>
<h1 id="3-문자-클래스--">3. 문자 클래스 <code>[ ]</code></h1>
<p>가장 먼저 알아볼 메타 문자는</p>
<pre><code class="language-text">[ ]</code></pre>
<p>이다.</p>
<p><code>[]</code>는</p>
<blockquote>
<p><strong>대괄호 안에 있는 문자 중 하나</strong></p>
</blockquote>
<p>와 매치한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">[abc]</code></pre>
<p>는</p>
<pre><code class="language-text">a 또는 b 또는 c</code></pre>
<p>중 하나를 의미한다.</p>
<hr>
<h2 id="abc-예제"><code>[abc]</code> 예제</h2>
<pre><code class="language-text">정규식 : [abc]</code></pre>
<p>문자열을 비교해 보자.</p>
<pre><code class="language-text">&quot;a&quot;
→ a가 존재
→ Match O

&quot;banana&quot;
→ b 또는 a가 존재
→ Match O

&quot;dog&quot;
→ a, b, c가 없음
→ Match X</code></pre>
<p>여기서 중요한 점은</p>
<pre><code class="language-text">[abc]</code></pre>
<p>가 문자열 <code>&quot;abc&quot;</code> 전체를 의미하는 것이 아니라는 것이다.</p>
<pre><code class="language-text">[abc]

= a 또는 b 또는 c 중 한 문자</code></pre>
<p>이다.</p>
<hr>
<h1 id="4--에서-범위-지정하기">4. <code>[ ]</code>에서 범위 지정하기</h1>
<p>문자를 하나씩 모두 적어야 한다면 불편할 수 있다.</p>
<p>예를 들어 모든 숫자를 표현하려면</p>
<pre><code class="language-text">[0123456789]</code></pre>
<p>라고 작성할 수도 있지만 다음처럼 간단하게 표현할 수 있다.</p>
<pre><code class="language-text">[0-9]</code></pre>
<p><code>-</code>를 사용하면 <strong>범위</strong>를 지정할 수 있다.</p>
<hr>
<h2 id="문자-범위">문자 범위</h2>
<pre><code class="language-text">[a-z]</code></pre>
<pre><code class="language-text">a부터 z까지의 소문자</code></pre>
<pre><code class="language-text">[A-Z]</code></pre>
<pre><code class="language-text">A부터 Z까지의 대문자</code></pre>
<pre><code class="language-text">[0-9]</code></pre>
<pre><code class="language-text">0부터 9까지의 숫자</code></pre>
<hr>
<h2 id="대소문자-모두-표현하기">대소문자 모두 표현하기</h2>
<pre><code class="language-text">[a-zA-Z]</code></pre>
<p>는</p>
<pre><code class="language-text">영문 소문자 또는 영문 대문자</code></pre>
<p>를 의미한다.</p>
<hr>
<h2 id="한글-범위">한글 범위</h2>
<p>한글 문자를 대상으로 다음과 같은 패턴도 볼 수 있다.</p>
<pre><code class="language-text">[가-힣]</code></pre>
<p>즉 한글 음절 범위를 표현할 때 사용할 수 있다.</p>
<hr>
<h1 id="5-문자-클래스-안의-">5. 문자 클래스 안의 <code>^</code></h1>
<p>문자 클래스 <code>[]</code>의 <strong>맨 앞</strong>에서 <code>^</code>를 사용하면 의미가 반대가 된다.</p>
<pre><code class="language-text">[^0-9]</code></pre>
<p>는</p>
<pre><code class="language-text">숫자가 아닌 문자</code></pre>
<p>를 의미한다.</p>
<p>즉</p>
<pre><code class="language-text">[0-9]
→ 숫자

[^0-9]
→ 숫자가 아닌 것</code></pre>
<p>이다.</p>
<hr>
<h2 id="예제">예제</h2>
<pre><code class="language-text">[^abc]</code></pre>
<p>는</p>
<pre><code class="language-text">a, b, c가 아닌 문자</code></pre>
<p>를 의미한다.</p>
<pre><code class="language-text">[^A-Z]</code></pre>
<p>는</p>
<pre><code class="language-text">영문 대문자가 아닌 문자</code></pre>
<p>를 의미한다.</p>
<hr>
<h1 id="6-자주-사용하는-문자-클래스">6. 자주 사용하는 문자 클래스</h1>
<p>정규식에서는 자주 사용하는 패턴을 더 짧게 표현할 수 있다.</p>
<table>
<thead>
<tr>
<th align="center">정규식</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>\d</code></td>
<td align="left">숫자</td>
</tr>
<tr>
<td align="center"><code>\D</code></td>
<td align="left">숫자가 아닌 문자</td>
</tr>
<tr>
<td align="center"><code>\s</code></td>
<td align="left">공백 계열 문자</td>
</tr>
<tr>
<td align="center"><code>\S</code></td>
<td align="left">공백 계열이 아닌 문자</td>
</tr>
<tr>
<td align="center"><code>\w</code></td>
<td align="left">단어를 구성하는 문자</td>
</tr>
<tr>
<td align="center"><code>\W</code></td>
<td align="left">단어를 구성하는 문자가 아닌 것</td>
</tr>
</tbody></table>
<p>대문자로 바뀌면 대부분 <strong>반대 의미</strong>라고 생각하면 이해하기 쉽다.</p>
<hr>
<h1 id="7-d">7. <code>\d</code></h1>
<p><code>\d</code>는 숫자를 의미한다.</p>
<pre><code class="language-text">\d</code></pre>
<p>일반적으로 숫자 한 글자를 찾는 데 사용할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;\d&quot;)

print(p.findall(&quot;abc123&quot;))</code></pre>
<p>결과</p>
<pre><code class="language-text">[&#39;1&#39;, &#39;2&#39;, &#39;3&#39;]</code></pre>
<p>이다.</p>
<hr>
<h2 id="여러-숫자를-한-번에-찾으려면">여러 숫자를 한 번에 찾으려면?</h2>
<p>현재</p>
<pre><code class="language-text">\d</code></pre>
<p>는 숫자 <strong>한 글자씩</strong> 찾는다.</p>
<p>숫자가 여러 개 연속된 부분을 찾고 싶다면 뒤에서 배우는 <code>+</code>를 붙인다.</p>
<pre><code class="language-text">\d+</code></pre>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;\d+&quot;)

print(p.findall(&quot;age=25 score=100&quot;))</code></pre>
<p>결과</p>
<pre><code class="language-text">[&#39;25&#39;, &#39;100&#39;]</code></pre>
<p>이다.</p>
<hr>
<h1 id="8-d">8. <code>\D</code></h1>
<p>대문자인</p>
<pre><code class="language-text">\D</code></pre>
<p>는 <code>\d</code>의 반대이다.</p>
<p>즉</p>
<pre><code class="language-text">숫자가 아닌 문자</code></pre>
<p>를 의미한다.</p>
<pre><code class="language-text">\d  → 숫자
\D  → 숫자가 아닌 것</code></pre>
<hr>
<h1 id="9-s">9. <code>\s</code></h1>
<pre><code class="language-text">\s</code></pre>
<p>는 <strong>Whitespace 문자</strong>와 매치한다.</p>
<p>대표적으로</p>
<pre><code class="language-text">공백
탭
줄바꿈</code></pre>
<p>등이 포함된다.</p>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;\s&quot;)

print(p.findall(&quot;hello python&quot;))</code></pre>
<p>문자열 중간의 공백이 매치된다.</p>
<hr>
<h1 id="10-s">10. <code>\S</code></h1>
<pre><code class="language-text">\S</code></pre>
<p>는 <code>\s</code>의 반대이다.</p>
<p>즉</p>
<pre><code class="language-text">공백 문자가 아닌 것</code></pre>
<p>을 의미한다.</p>
<hr>
<h1 id="11-w">11. <code>\w</code></h1>
<pre><code class="language-text">\w</code></pre>
<p>는 일반적으로 <strong>단어를 구성하는 문자</strong>를 찾을 때 사용한다.</p>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;\w+&quot;)

print(p.findall(&quot;python_2026&quot;))</code></pre>
<p>결과</p>
<pre><code class="language-text">[&#39;python_2026&#39;]</code></pre>
<p>처럼 사용할 수 있다.</p>
<blockquote>
<p>Python 3의 <code>re</code>에서는 기본적으로 유니코드를 사용하기 때문에 <code>\w</code>가 영문자와 숫자뿐 아니라 한글과 같은 유니코드 문자에도 매치될 수 있다.</p>
</blockquote>
<hr>
<h1 id="12-w">12. <code>\W</code></h1>
<pre><code class="language-text">\W</code></pre>
<p>는 <code>\w</code>의 반대이다.</p>
<p>즉</p>
<pre><code class="language-text">단어를 구성하지 않는 문자</code></pre>
<p>를 의미한다.</p>
<p>예를 들면 공백이나 일부 특수문자를 찾는 데 활용할 수 있다.</p>
<hr>
<h1 id="13--dot-메타-문자">13. <code>.</code> Dot 메타 문자</h1>
<p>정규식에서</p>
<pre><code class="language-text">.</code></pre>
<p>은</p>
<blockquote>
<p><strong>줄바꿈 <code>\n</code>을 제외한 거의 모든 문자 한 개</strong></p>
</blockquote>
<p>를 의미한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">a.b</code></pre>
<p>라는 정규식이 있다고 해보자.</p>
<p>구조는</p>
<pre><code class="language-text">a
+
아무 문자 1개
+
b</code></pre>
<p>이다.</p>
<hr>
<h2 id="예제-1">예제</h2>
<pre><code class="language-text">aab</code></pre>
<pre><code class="language-text">a + a + b

→ Match O</code></pre>
<pre><code class="language-text">a7b</code></pre>
<pre><code class="language-text">a + 7 + b

→ Match O</code></pre>
<pre><code class="language-text">a-b</code></pre>
<pre><code class="language-text">a + - + b

→ Match O</code></pre>
<p>하지만</p>
<pre><code class="language-text">ab</code></pre>
<p>는 중간에 문자가 없으므로 매치되지 않는다.</p>
<hr>
<h1 id="14--자체를-찾고-싶다면">14. <code>.</code> 자체를 찾고 싶다면?</h1>
<p>다음 두 정규식은 의미가 다르다.</p>
<pre><code class="language-text">a.b</code></pre>
<pre><code class="language-text">a + 아무 문자 + b</code></pre>
<p>반면</p>
<pre><code class="language-text">a[.]b</code></pre>
<p>는</p>
<pre><code class="language-text">a + 실제 점(.) + b</code></pre>
<p>를 의미한다.</p>
<p>따라서</p>
<pre><code class="language-text">a.b</code></pre>
<p>문자열 자체를 찾을 때 사용할 수 있다.</p>
<hr>
<h1 id="15-반복-메타-문자-">15. 반복 메타 문자 <code>*</code></h1>
<pre><code class="language-text">*</code></pre>
<p>는</p>
<blockquote>
<p><strong>바로 앞의 문자가 0번 이상 반복</strong></p>
</blockquote>
<p>된다는 의미이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">ca*t</code></pre>
<p>를 살펴보자.</p>
<p>여기서 <code>*</code>는 바로 앞의 <code>a</code>에 적용된다.</p>
<pre><code class="language-text">c
+
a가 0번 이상
+
t</code></pre>
<p>라는 의미이다.</p>
<hr>
<h2 id="매치-예제">매치 예제</h2>
<table>
<thead>
<tr>
<th align="center">문자열</th>
<th align="center">결과</th>
<th align="left">이유</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>ct</code></td>
<td align="center">O</td>
<td align="left">a가 0번</td>
</tr>
<tr>
<td align="center"><code>cat</code></td>
<td align="center">O</td>
<td align="left">a가 1번</td>
</tr>
<tr>
<td align="center"><code>caat</code></td>
<td align="center">O</td>
<td align="left">a가 2번</td>
</tr>
<tr>
<td align="center"><code>caaaaat</code></td>
<td align="center">O</td>
<td align="left">a가 여러 번</td>
</tr>
</tbody></table>
<p>가장 중요한 부분은</p>
<pre><code class="language-text">0번도 가능</code></pre>
<p>하다는 것이다.</p>
<p>따라서</p>
<pre><code class="language-text">ct</code></pre>
<p>도 매치된다.</p>
<hr>
<h1 id="16-반복-메타-문자-">16. 반복 메타 문자 <code>+</code></h1>
<pre><code class="language-text">+</code></pre>
<p>는</p>
<blockquote>
<p><strong>바로 앞의 문자가 1번 이상 반복</strong></p>
</blockquote>
<p>된다는 의미이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">ca+t</code></pre>
<p>는</p>
<pre><code class="language-text">c
+
a가 1번 이상
+
t</code></pre>
<p>이다.</p>
<hr>
<h2 id="와-비교"><code>*</code>와 비교</h2>
<table>
<thead>
<tr>
<th align="center">정규식</th>
<th align="center"><code>ct</code></th>
<th align="center"><code>cat</code></th>
<th align="center"><code>caaat</code></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>ca*t</code></td>
<td align="center">O</td>
<td align="center">O</td>
<td align="center">O</td>
</tr>
<tr>
<td align="center"><code>ca+t</code></td>
<td align="center">X</td>
<td align="center">O</td>
<td align="center">O</td>
</tr>
</tbody></table>
<p>차이는</p>
<pre><code class="language-text">*

0번 이상</code></pre>
<pre><code class="language-text">+

1번 이상</code></pre>
<p>이다.</p>
<p>이 차이를 꼭 기억하자.</p>
<hr>
<h1 id="17-m">17. <code>{m}</code></h1>
<p>반복 횟수를 정확하게 지정하고 싶을 때</p>
<pre><code class="language-text">{}</code></pre>
<p>를 사용할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">a{3}</code></pre>
<p>은</p>
<pre><code class="language-text">a가 정확히 3번</code></pre>
<p>이라는 의미이다.</p>
<hr>
<h2 id="예제-2">예제</h2>
<pre><code class="language-text">ca{2}t</code></pre>
<p>는</p>
<pre><code class="language-text">c
+
a 두 번
+
t</code></pre>
<p>이므로</p>
<pre><code class="language-text">caat</code></pre>
<p>과 매치된다.</p>
<p>하지만</p>
<pre><code class="language-text">cat</code></pre>
<p>은 <code>a</code>가 한 번이므로 매치되지 않는다.</p>
<hr>
<h1 id="18-mn">18. <code>{m,n}</code></h1>
<p>반복 횟수의 범위를 지정할 수도 있다.</p>
<pre><code class="language-text">{m,n}</code></pre>
<p>은</p>
<pre><code class="language-text">최소 m번
~
최대 n번</code></pre>
<p>이라는 의미이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">a{2,4}</code></pre>
<p>는</p>
<pre><code class="language-text">aa
aaa
aaaa</code></pre>
<p>처럼 <code>a</code>가 2~4번 반복되는 경우를 의미한다.</p>
<hr>
<h2 id="한쪽을-생략할-수도-있다">한쪽을 생략할 수도 있다</h2>
<pre><code class="language-text">a{3,}</code></pre>
<pre><code class="language-text">a가 3번 이상</code></pre>
<pre><code class="language-text">a{,3}</code></pre>
<pre><code class="language-text">a가 3번 이하</code></pre>
<p>라고 이해할 수 있다.</p>
<hr>
<h1 id="19-">19. <code>?</code></h1>
<pre><code class="language-text">?</code></pre>
<p>는</p>
<blockquote>
<p><strong>바로 앞의 문자가 0번 또는 1번</strong></p>
</blockquote>
<p>나타난다는 의미이다.</p>
<p>즉</p>
<pre><code class="language-text">있어도 되고
없어도 된다</code></pre>
<p>라고 생각하면 쉽다.</p>
<p>예를 들어</p>
<pre><code class="language-text">colou?r</code></pre>
<p>에서 <code>u?</code>는</p>
<pre><code class="language-text">u가 없어도 되고
u가 한 번 있어도 됨</code></pre>
<p>이라는 의미이다.</p>
<p>따라서</p>
<pre><code class="language-text">color</code></pre>
<p>와</p>
<pre><code class="language-text">colour</code></pre>
<p>둘 다 매치할 수 있다.</p>
<hr>
<h1 id="20-반복-메타-문자-한-번에-정리">20. 반복 메타 문자 한 번에 정리</h1>
<table>
<thead>
<tr>
<th align="center">메타 문자</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>*</code></td>
<td align="left">0번 이상</td>
</tr>
<tr>
<td align="center"><code>+</code></td>
<td align="left">1번 이상</td>
</tr>
<tr>
<td align="center"><code>?</code></td>
<td align="left">0번 또는 1번</td>
</tr>
<tr>
<td align="center"><code>{m}</code></td>
<td align="left">정확히 m번</td>
</tr>
<tr>
<td align="center"><code>{m,n}</code></td>
<td align="left">m~n번</td>
</tr>
<tr>
<td align="center"><code>{m,}</code></td>
<td align="left">m번 이상</td>
</tr>
<tr>
<td align="center"><code>{,n}</code></td>
<td align="left">n번 이하</td>
</tr>
</tbody></table>
<p>특히</p>
<pre><code class="language-text">*
+
?</code></pre>
<p>세 개를 많이 사용한다.</p>
<hr>
<h1 id="21----관계">21. <code>*</code>, <code>+</code>, <code>?</code> 관계</h1>
<p>다음처럼 생각할 수도 있다.</p>
<pre><code class="language-text">*

{0,}</code></pre>
<pre><code class="language-text">+

{1,}</code></pre>
<pre><code class="language-text">?

{0,1}</code></pre>
<p>즉 간단하게 자주 사용하는 반복 표현을</p>
<pre><code class="language-text">*
+
?</code></pre>
<p>로 줄여 놓았다고 생각하면 된다.</p>
<hr>
<h1 id="22-파이썬-re-모듈">22. 파이썬 <code>re</code> 모듈</h1>
<p>파이썬에서 정규식을 사용하려면</p>
<pre><code class="language-python">import re</code></pre>
<p>를 사용한다.</p>
<p><code>re</code>는 <strong>Regular Expression</strong>의 약자이다.</p>
<p>파이썬 표준 라이브러리이므로 별도의 설치는 필요 없다.</p>
<hr>
<h1 id="23-recompile">23. <code>re.compile()</code></h1>
<p>정규 표현식을 사용할 때 다음과 같이 패턴을 만들 수 있다.</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)</code></pre>
<p>여기서</p>
<pre><code class="language-python">re.compile()</code></pre>
<p>은 작성한 정규식을 파이썬에서 사용할 수 있는 <strong>패턴 객체</strong>로 만들어 준다.</p>
<p>흐름은 다음과 같다.</p>
<pre><code class="language-text">정규 표현식

[a-z]+
   ↓
re.compile()
   ↓
Pattern 객체
   ↓
검색 수행</code></pre>
<hr>
<h1 id="24-a-z-해석하기">24. <code>[a-z]+</code> 해석하기</h1>
<p>앞에서 배운 문법으로 다음 정규식을 해석해 보자.</p>
<pre><code class="language-text">[a-z]+</code></pre>
<p>먼저</p>
<pre><code class="language-text">[a-z]</code></pre>
<p>는</p>
<pre><code class="language-text">영문 소문자 한 글자</code></pre>
<p>이다.</p>
<p>뒤에</p>
<pre><code class="language-text">+</code></pre>
<p>가 붙었으므로</p>
<pre><code class="language-text">한 번 이상 반복</code></pre>
<p>이다.</p>
<p>따라서 최종적으로</p>
<pre><code class="language-text">[a-z]+

= 영문 소문자가 1글자 이상 연속된 문자열</code></pre>
<p>이라는 의미이다.</p>
<hr>
<h1 id="25-정규식을-이용한-문자열-검색">25. 정규식을 이용한 문자열 검색</h1>
<p>컴파일한 패턴 객체에서는 대표적으로 다음 메서드를 사용할 수 있다.</p>
<table>
<thead>
<tr>
<th align="center">메서드</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>match()</code></td>
<td align="left">문자열 처음부터 검사</td>
</tr>
<tr>
<td align="center"><code>search()</code></td>
<td align="left">문자열 전체에서 검색</td>
</tr>
<tr>
<td align="center"><code>findall()</code></td>
<td align="left">매치되는 모든 문자열을 리스트로 반환</td>
</tr>
<tr>
<td align="center"><code>finditer()</code></td>
<td align="left">매치되는 결과를 이터레이터로 반환</td>
</tr>
</tbody></table>
<p>이 네 개의 차이는 꼭 기억하자.</p>
<hr>
<h1 id="26-match">26. <code>match()</code></h1>
<p><code>match()</code>는</p>
<blockquote>
<p><strong>문자열의 처음부터 패턴과 일치하는지 검사</strong></p>
</blockquote>
<p>한다.</p>
<p>먼저 패턴을 만든다.</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)</code></pre>
<p>그리고</p>
<pre><code class="language-python">m = p.match(&quot;python&quot;)</code></pre>
<p>을 실행한다.</p>
<p><code>python</code>은 처음부터 영문 소문자로 이루어져 있으므로 매치된다.</p>
<hr>
<h2 id="매치되지-않는-경우">매치되지 않는 경우</h2>
<pre><code class="language-python">m = p.match(&quot;3 python&quot;)

print(m)</code></pre>
<p>결과</p>
<pre><code class="language-text">None</code></pre>
<p>이 된다.</p>
<p>왜냐하면 문자열의 첫 번째 글자가</p>
<pre><code class="language-text">3</code></pre>
<p>이기 때문이다.</p>
<p>현재 패턴은</p>
<pre><code class="language-text">[a-z]+</code></pre>
<p>이므로 첫 번째 위치에서 바로 실패한다.</p>
<hr>
<h1 id="27-match의-핵심">27. match()의 핵심</h1>
<pre><code class="language-text">정규식

[a-z]+


문자열

python
↑
처음부터 확인

→ O</code></pre>
<p>하지만</p>
<pre><code class="language-text">3 python
↑
처음 문자가 숫자

→ X</code></pre>
<p>이다.</p>
<p>즉</p>
<blockquote>
<p><strong>match()는 문자열 시작 부분이 패턴과 일치해야 한다.</strong></p>
</blockquote>
<hr>
<h1 id="28-match-결과-활용하기">28. match() 결과 활용하기</h1>
<p><code>match()</code>는 성공하면 Match 객체를 반환하고 실패하면 <code>None</code>을 반환한다.</p>
<p>따라서 다음과 같이 사용할 수 있다.</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)

m = p.match(&quot;python&quot;)

if m:
    print(&quot;매치되었습니다.&quot;)
else:
    print(&quot;매치되지 않았습니다.&quot;)</code></pre>
<hr>
<h1 id="29-search">29. <code>search()</code></h1>
<p><code>search()</code>는 <code>match()</code>와 다르게</p>
<blockquote>
<p><strong>문자열 전체를 검색</strong></p>
</blockquote>
<p>한다.</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)

m = p.search(&quot;3 python&quot;)

print(m)</code></pre>
<p>이번에는 <code>python</code>을 찾을 수 있다.</p>
<hr>
<h1 id="30-match와-search-비교">30. match()와 search() 비교</h1>
<p>이 둘은 매우 중요하다.</p>
<pre><code class="language-python">p.match(&quot;3 python&quot;)</code></pre>
<p>결과</p>
<pre><code class="language-text">None</code></pre>
<p>하지만</p>
<pre><code class="language-python">p.search(&quot;3 python&quot;)</code></pre>
<p>에서는</p>
<pre><code class="language-text">python</code></pre>
<p>부분을 찾는다.</p>
<p>정리하면</p>
<pre><code class="language-text">match()

문자열 시작부터 검사</code></pre>
<pre><code class="language-text">search()

문자열 전체에서 검색</code></pre>
<p>이다.</p>
<hr>
<h2 id="그림으로-보면">그림으로 보면</h2>
<pre><code class="language-text">문자열

3 python
01234567</code></pre>
<h3 id="match">match()</h3>
<pre><code class="language-text">↓
3 python

첫 위치부터 검사
3이 [a-z]가 아니므로 실패</code></pre>
<h3 id="search">search()</h3>
<pre><code class="language-text">3 python
  ↑↑↑↑↑↑

문자열 전체를 검색
python 발견</code></pre>
<hr>
<h1 id="31-findall">31. <code>findall()</code></h1>
<p><code>findall()</code>은</p>
<blockquote>
<p><strong>정규식과 일치하는 모든 문자열을 찾아 리스트로 반환</strong></p>
</blockquote>
<p>한다.</p>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)

result = p.findall(&quot;python is very easy&quot;)

print(result)</code></pre>
<p>결과</p>
<pre><code class="language-text">[&#39;python&#39;, &#39;is&#39;, &#39;very&#39;, &#39;easy&#39;]</code></pre>
<p>이다.</p>
<hr>
<h2 id="findall의-특징">findall의 특징</h2>
<p><code>search()</code>는 일치하는 위치 하나를 찾는 데 주로 사용하는 반면</p>
<pre><code class="language-python">findall()</code></pre>
<p>은 <strong>모든 결과</strong>가 필요할 때 유용하다.</p>
<pre><code class="language-text">문자열

python is very easy

↓ findall()

python
is
very
easy

↓ 리스트

[&#39;python&#39;, &#39;is&#39;, &#39;very&#39;, &#39;easy&#39;]</code></pre>
<hr>
<h1 id="32-숫자를-모두-추출하기">32. 숫자를 모두 추출하기</h1>
<p>실제 사용에서는 다음처럼 활용할 수 있다.</p>
<pre><code class="language-python">import re

data = &quot;WEB01=80 WAS01=8080 DB01=3306&quot;

result = re.findall(r&quot;\d+&quot;, data)

print(result)</code></pre>
<p>결과</p>
<pre><code class="language-text">[&#39;01&#39;, &#39;80&#39;, &#39;01&#39;, &#39;8080&#39;, &#39;01&#39;, &#39;3306&#39;]</code></pre>
<p>처럼 숫자 패턴을 모두 추출할 수 있다.</p>
<hr>
<h1 id="33-finditer">33. <code>finditer()</code></h1>
<p><code>finditer()</code>도 매치되는 결과를 모두 찾는다.</p>
<p>하지만 <code>findall()</code>과 차이가 있다.</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)

result = p.finditer(&quot;python is easy&quot;)

for m in result:
    print(m)</code></pre>
<p>각 결과가 <strong>Match 객체</strong> 형태로 반환된다.</p>
<hr>
<h1 id="34-findall과-finditer-비교">34. findall()과 finditer() 비교</h1>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left"><code>findall()</code></th>
<th align="left"><code>finditer()</code></th>
</tr>
</thead>
<tbody><tr>
<td align="left">모든 결과 검색</td>
<td align="left">O</td>
<td align="left">O</td>
</tr>
<tr>
<td align="left">반환</td>
<td align="left">리스트</td>
<td align="left">이터레이터</td>
</tr>
<tr>
<td align="left">각 값</td>
<td align="left">문자열 중심</td>
<td align="left">Match 객체</td>
</tr>
<tr>
<td align="left">위치 정보 사용</td>
<td align="left">직접적이지 않음</td>
<td align="left">가능</td>
</tr>
</tbody></table>
<p>특히 위치까지 확인해야 한다면</p>
<pre><code class="language-python">finditer()</code></pre>
<p>가 유용하다.</p>
<hr>
<h1 id="35-match-객체">35. Match 객체</h1>
<p><code>match()</code>, <code>search()</code>, <code>finditer()</code> 등을 사용하면 <strong>Match 객체</strong>를 얻게 된다.</p>
<p>Match 객체에서는 매치 결과에 대한 정보를 확인할 수 있다.</p>
<p>대표적인 메서드는 다음과 같다.</p>
<table>
<thead>
<tr>
<th align="center">메서드</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>group()</code></td>
<td align="left">매치된 문자열</td>
</tr>
<tr>
<td align="center"><code>start()</code></td>
<td align="left">시작 위치</td>
</tr>
<tr>
<td align="center"><code>end()</code></td>
<td align="left">끝 위치</td>
</tr>
<tr>
<td align="center"><code>span()</code></td>
<td align="left"><code>(시작, 끝)</code></td>
</tr>
</tbody></table>
<hr>
<h1 id="36-group">36. <code>group()</code></h1>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)

m = p.search(&quot;3 python&quot;)

print(m.group())</code></pre>
<p>결과</p>
<pre><code class="language-text">python</code></pre>
<p>즉</p>
<pre><code class="language-python">group()</code></pre>
<p>은 <strong>실제로 매치된 문자열</strong>을 반환한다.</p>
<hr>
<h1 id="37-start">37. <code>start()</code></h1>
<pre><code class="language-python">print(m.start())</code></pre>
<p>매치가 시작된 위치를 반환한다.</p>
<p>문자열이</p>
<pre><code class="language-text">3 python</code></pre>
<p>이라면 인덱스는</p>
<pre><code class="language-text">3   p y t h o n
0 1 2 3 4 5 6 7</code></pre>
<p>이므로 <code>python</code>의 시작 위치는</p>
<pre><code class="language-text">2</code></pre>
<p>이다.</p>
<hr>
<h1 id="38-end">38. <code>end()</code></h1>
<pre><code class="language-python">print(m.end())</code></pre>
<p>매치가 끝나는 위치를 반환한다.</p>
<p>여기서 주의할 점은 <strong>마지막 문자의 인덱스가 아니라 그 다음 위치</strong>라는 것이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">python</code></pre>
<p>이 인덱스 <code>2~7</code>에 있다면</p>
<pre><code class="language-text">start() → 2
end()   → 8</code></pre>
<p>이 된다.</p>
<p>이 방식은 파이썬 슬라이싱과 같다.</p>
<pre><code class="language-python">text[2:8]</code></pre>
<hr>
<h1 id="39-span">39. <code>span()</code></h1>
<pre><code class="language-python">print(m.span())</code></pre>
<p>결과는</p>
<pre><code class="language-text">(2, 8)</code></pre>
<p>처럼 나온다.</p>
<p>즉</p>
<pre><code class="language-text">(start(), end())</code></pre>
<p>를 한 번에 튜플로 반환한다.</p>
<hr>
<h1 id="40-match-객체-한-번에-보기">40. Match 객체 한 번에 보기</h1>
<pre><code class="language-python">import re

p = re.compile(r&quot;[a-z]+&quot;)

m = p.search(&quot;3 python&quot;)

print(m.group())
print(m.start())
print(m.end())
print(m.span())</code></pre>
<p>결과</p>
<pre><code class="language-text">python
2
8
(2, 8)</code></pre>
<p>따라서</p>
<pre><code class="language-text">group() → 무엇을 찾았는가?
start() → 어디서 시작했는가?
end()   → 어디까지인가?
span()  → 시작과 끝을 한 번에</code></pre>
<p>라고 기억하면 된다.</p>
<hr>
<h1 id="41-recompile-없이-사용하는-방법">41. <code>re.compile()</code> 없이 사용하는 방법</h1>
<p>지금까지는</p>
<pre><code class="language-python">p = re.compile(r&quot;[a-z]+&quot;)

m = p.match(&quot;python&quot;)</code></pre>
<p>처럼 사용했다.</p>
<p>하지만 다음처럼 한 번에 작성할 수도 있다.</p>
<pre><code class="language-python">m = re.match(r&quot;[a-z]+&quot;, &quot;python&quot;)</code></pre>
<p>즉</p>
<pre><code class="language-text">re.compile()
+
match()

↓

re.match()</code></pre>
<p>형태로 사용할 수 있다.</p>
<hr>
<h2 id="언제-compile을-사용할까">언제 compile을 사용할까?</h2>
<p>같은 정규식을 여러 번 사용할 예정이라면</p>
<pre><code class="language-python">p = re.compile(...)</code></pre>
<p>처럼 패턴 객체를 만들어 두면 코드가 깔끔하다.</p>
<p>간단하게 한 번 검색하는 경우라면</p>
<pre><code class="language-python">re.search(...)
re.findall(...)</code></pre>
<p>처럼 직접 사용할 수도 있다.</p>
<hr>
<h1 id="42-컴파일-옵션">42. 컴파일 옵션</h1>
<p>정규식을 사용할 때 동작 방식을 변경하기 위한 옵션이 있다.</p>
<p>대표적인 옵션은 다음과 같다.</p>
<table>
<thead>
<tr>
<th align="center">옵션</th>
<th align="center">축약</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>re.DOTALL</code></td>
<td align="center"><code>re.S</code></td>
<td align="left"><code>.</code>이 줄바꿈까지 포함</td>
</tr>
<tr>
<td align="center"><code>re.IGNORECASE</code></td>
<td align="center"><code>re.I</code></td>
<td align="left">대소문자 무시</td>
</tr>
<tr>
<td align="center"><code>re.MULTILINE</code></td>
<td align="center"><code>re.M</code></td>
<td align="left">여러 줄에서 <code>^</code>, <code>$</code> 적용</td>
</tr>
<tr>
<td align="center"><code>re.VERBOSE</code></td>
<td align="center"><code>re.X</code></td>
<td align="left">정규식을 여러 줄과 주석으로 작성</td>
</tr>
</tbody></table>
<hr>
<h1 id="43-dotall">43. DOTALL</h1>
<p>기본적으로</p>
<pre><code class="language-text">.</code></pre>
<p>은 줄바꿈</p>
<pre><code class="language-text">\n</code></pre>
<p>과 매치되지 않는다.</p>
<p>예를 들어</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;a.b&quot;)

print(p.match(&quot;a\nb&quot;))</code></pre>
<p>에서는 매치되지 않는다.</p>
<hr>
<h2 id="redotall">re.DOTALL</h2>
<p>다음처럼 옵션을 사용한다.</p>
<pre><code class="language-python">p = re.compile(r&quot;a.b&quot;, re.DOTALL)</code></pre>
<p>또는</p>
<pre><code class="language-python">p = re.compile(r&quot;a.b&quot;, re.S)</code></pre>
<p>그러면</p>
<pre><code class="language-text">.</code></pre>
<p>이 줄바꿈까지 포함해 매치할 수 있다.</p>
<pre><code class="language-text">a
\n
b</code></pre>
<p>에서도 매치할 수 있게 된다.</p>
<hr>
<h1 id="44-ignorecase">44. IGNORECASE</h1>
<p>기본적으로 정규식에서는 대소문자를 구분한다.</p>
<pre><code class="language-text">python
Python
PYTHON</code></pre>
<p>은 서로 다른 문자열이다.</p>
<p>대소문자를 구분하고 싶지 않다면</p>
<pre><code class="language-python">re.IGNORECASE</code></pre>
<p>또는</p>
<pre><code class="language-python">re.I</code></pre>
<p>를 사용한다.</p>
<pre><code class="language-python">import re

p = re.compile(r&quot;python&quot;, re.I)

print(p.match(&quot;python&quot;))
print(p.match(&quot;Python&quot;))
print(p.match(&quot;PYTHON&quot;))</code></pre>
<p>모두 매치할 수 있다.</p>
<hr>
<h1 id="45-multiline">45. MULTILINE</h1>
<p>다음 메타 문자도 자주 사용한다.</p>
<pre><code class="language-text">^</code></pre>
<pre><code class="language-text">문자열의 시작</code></pre>
<p>그리고</p>
<pre><code class="language-text">$</code></pre>
<p>는</p>
<pre><code class="language-text">문자열의 끝</code></pre>
<p>을 의미한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">^python</code></pre>
<p>은</p>
<pre><code class="language-text">python으로 시작</code></pre>
<p>이라는 의미이다.</p>
<hr>
<h2 id="여러-줄-문자열">여러 줄 문자열</h2>
<p>다음 데이터가 있다고 해보자.</p>
<pre><code class="language-python">data = &quot;&quot;&quot;python one
java two
python three&quot;&quot;&quot;</code></pre>
<p>그리고</p>
<pre><code class="language-python">p = re.compile(r&quot;^python&quot;)</code></pre>
<p>을 사용하면 기본적으로 문자열 전체의 처음을 기준으로 한다.</p>
<p>하지만</p>
<pre><code class="language-python">p = re.compile(r&quot;^python&quot;, re.MULTILINE)</code></pre>
<p>또는</p>
<pre><code class="language-python">p = re.compile(r&quot;^python&quot;, re.M)</code></pre>
<p>을 사용하면</p>
<pre><code class="language-text">각 줄의 시작</code></pre>
<p>을 기준으로 검사할 수 있다.</p>
<p>따라서</p>
<pre><code class="language-text">python one</code></pre>
<p>과</p>
<pre><code class="language-text">python three</code></pre>
<p>의 <code>python</code>을 각각 찾을 수 있다.</p>
<hr>
<h1 id="46-verbose">46. VERBOSE</h1>
<p>정규식이 짧을 때는 문제가 없다.</p>
<p>하지만 정규식이 길어지면</p>
<pre><code class="language-text">^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$</code></pre>
<p>처럼 매우 읽기 어려워진다.</p>
<p>이럴 때</p>
<pre><code class="language-python">re.VERBOSE</code></pre>
<p>또는</p>
<pre><code class="language-python">re.X</code></pre>
<p>를 사용할 수 있다.</p>
<hr>
<h2 id="예제-3">예제</h2>
<pre><code class="language-python">import re

email_pattern = re.compile(
    r&quot;&quot;&quot;
    ^                       # 문자열 시작
    [A-Za-z0-9._%+-]+       # 사용자 이름
    @                       # @
    [A-Za-z0-9.-]+          # 도메인
    \.                      # 실제 점
    [A-Za-z]{2,}            # 최상위 도메인
    $                       # 문자열 끝
    &quot;&quot;&quot;,
    re.VERBOSE
)</code></pre>
<p>이렇게 작성하면 복잡한 정규식을 여러 줄로 나누고 주석까지 작성할 수 있어 훨씬 이해하기 쉽다.</p>
<hr>
<h1 id="47-역슬래시--문제">47. 역슬래시 <code>\</code> 문제</h1>
<p>정규식에서 가장 헷갈릴 수 있는 부분 중 하나가</p>
<pre><code class="language-text">\</code></pre>
<p>이다.</p>
<p>정규식에서도 역슬래시에 특별한 의미가 있고 파이썬 문자열에서도 역슬래시는 <strong>이스케이프 문자</strong>로 사용된다.</p>
<p>즉</p>
<pre><code class="language-text">Python 문자열 해석
        +
정규식 해석</code></pre>
<p>두 단계가 겹칠 수 있다.</p>
<p>그래서 코드가 복잡해질 수 있다.</p>
<hr>
<h1 id="48-예를-들어-section을-찾는다면">48. 예를 들어 <code>\section</code>을 찾는다면?</h1>
<p>문자열 안에 실제로</p>
<pre><code class="language-text">\section</code></pre>
<p>이라는 문자가 있다고 해보자.</p>
<p>정규식에서</p>
<pre><code class="language-text">\section</code></pre>
<p>이라고 그대로 사용하면 문제가 발생할 수 있다.</p>
<p>왜냐하면 정규식 엔진은 앞부분</p>
<pre><code class="language-text">\s</code></pre>
<p>를</p>
<pre><code class="language-text">Whitespace</code></pre>
<p>라는 특별한 정규식으로 해석할 수 있기 때문이다.</p>
<hr>
<h1 id="49-역슬래시-자체-표현하기">49. 역슬래시 자체 표현하기</h1>
<p>정규식에서 실제 역슬래시를 의미하도록 만들려면 역슬래시를 이스케이프해야 한다.</p>
<p>즉 정규식 수준에서는</p>
<pre><code class="language-text">\\section</code></pre>
<p>처럼 표현한다.</p>
<p>그런데 일반적인 파이썬 문자열에서도 <code>\</code>가 특별한 의미를 가지므로 코드가 더 복잡해질 수 있다.</p>
<p>이 문제를 쉽게 해결하는 방법이 <strong>Raw String</strong>이다.</p>
<hr>
<h1 id="50-raw-string">50. Raw String</h1>
<p>정규식을 작성할 때 다음처럼 문자열 앞에</p>
<pre><code class="language-text">r</code></pre>
<p>을 붙이는 경우가 많다.</p>
<pre><code class="language-python">r&quot;\d+&quot;</code></pre>
<p>이것을 <strong>Raw String</strong>이라고 한다.</p>
<hr>
<h2 id="일반-문자열">일반 문자열</h2>
<pre><code class="language-python">&quot;\n&quot;</code></pre>
<p>은</p>
<pre><code class="language-text">줄바꿈</code></pre>
<p>으로 처리된다.</p>
<p>하지만</p>
<pre><code class="language-python">r&quot;\n&quot;</code></pre>
<p>은 문자열 안의 역슬래시를 정규식 작성에 보다 편리하게 사용할 수 있게 해 준다.</p>
<hr>
<h1 id="51-정규식에서는-raw-string을-쓰는-습관">51. 정규식에서는 Raw String을 쓰는 습관</h1>
<p>정규식에서는 다음처럼 쓰는 습관을 들이는 것이 좋다.</p>
<pre><code class="language-python">re.compile(r&quot;\d+&quot;)</code></pre>
<pre><code class="language-python">re.findall(r&quot;[0-9]+&quot;, text)</code></pre>
<pre><code class="language-python">re.search(r&quot;\w+&quot;, text)</code></pre>
<p>즉</p>
<pre><code class="language-text">정규식 문자열
      ↓
가능하면 r&quot;...&quot;</code></pre>
<p>형태로 작성한다고 기억하면 된다.</p>
<hr>
<h1 id="52-지금까지-배운-메타-문자">52. 지금까지 배운 메타 문자</h1>
<table>
<thead>
<tr>
<th align="center">표현</th>
<th align="left">의미</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>[abc]</code></td>
<td align="left">a, b, c 중 하나</td>
</tr>
<tr>
<td align="center"><code>[a-z]</code></td>
<td align="left">a~z</td>
</tr>
<tr>
<td align="center"><code>[^0-9]</code></td>
<td align="left">숫자가 아닌 문자</td>
</tr>
<tr>
<td align="center"><code>\d</code></td>
<td align="left">숫자</td>
</tr>
<tr>
<td align="center"><code>\D</code></td>
<td align="left">숫자가 아닌 문자</td>
</tr>
<tr>
<td align="center"><code>\s</code></td>
<td align="left">공백 계열</td>
</tr>
<tr>
<td align="center"><code>\S</code></td>
<td align="left">공백 계열이 아닌 문자</td>
</tr>
<tr>
<td align="center"><code>\w</code></td>
<td align="left">단어 문자</td>
</tr>
<tr>
<td align="center"><code>\W</code></td>
<td align="left">단어 문자가 아닌 것</td>
</tr>
<tr>
<td align="center"><code>.</code></td>
<td align="left">줄바꿈을 제외한 임의의 한 문자</td>
</tr>
<tr>
<td align="center"><code>*</code></td>
<td align="left">앞의 패턴 0번 이상</td>
</tr>
<tr>
<td align="center"><code>+</code></td>
<td align="left">앞의 패턴 1번 이상</td>
</tr>
<tr>
<td align="center"><code>?</code></td>
<td align="left">앞의 패턴 0번 또는 1번</td>
</tr>
<tr>
<td align="center"><code>{m}</code></td>
<td align="left">정확히 m번</td>
</tr>
<tr>
<td align="center"><code>{m,n}</code></td>
<td align="left">m~n번</td>
</tr>
</tbody></table>
<hr>
<h1 id="53-예제를-직접-해석해-보기">53. 예제를 직접 해석해 보기</h1>
<p>다음 정규식을 살펴보자.</p>
<pre><code class="language-text">\d{3}-\d{4}-\d{4}</code></pre>
<p>하나씩 나누면</p>
<pre><code class="language-text">\d{3}
 ↓
숫자 3개</code></pre>
<pre><code class="language-text">-
 ↓
하이픈</code></pre>
<pre><code class="language-text">\d{4}
 ↓
숫자 4개</code></pre>
<pre><code class="language-text">-
 ↓
하이픈</code></pre>
<pre><code class="language-text">\d{4}
 ↓
숫자 4개</code></pre>
<p>따라서 전체적으로</p>
<pre><code class="language-text">숫자 3개
-
숫자 4개
-
숫자 4개</code></pre>
<p>라는 형태를 찾는다.</p>
<hr>
<h1 id="54-ip-주소-형태-찾기">54. IP 주소 형태 찾기</h1>
<p>아주 단순하게 IP처럼 생긴 문자열을 찾는다면</p>
<pre><code class="language-text">\d+\.\d+\.\d+\.\d+</code></pre>
<p>처럼 표현할 수도 있다.</p>
<p>하나씩 보면</p>
<pre><code class="language-text">\d+
 ↓
숫자가 1개 이상

\.
 ↓
실제 점</code></pre>
<p>이 구조가 반복된다.</p>
<p>따라서</p>
<pre><code class="language-text">192.168.0.1</code></pre>
<p>과 같은 형태를 찾을 수 있다.</p>
<blockquote>
<p>다만 이 정규식은 <code>999.999.999.999</code>도 매치할 수 있기 때문에 IPv4 주소의 실제 유효 범위인 <code>0~255</code>까지 검증하는 정규식은 더 복잡하다.</p>
</blockquote>
<hr>
<h1 id="55-보안-로그-예제로-보기">55. 보안 로그 예제로 보기</h1>
<p>예를 들어 다음 로그가 있다고 해보자.</p>
<pre><code class="language-text">SRC=192.168.10.20 DST=10.0.0.10 PORT=443</code></pre>
<p>숫자들을 가져오려면</p>
<pre><code class="language-python">import re

log = &quot;SRC=192.168.10.20 DST=10.0.0.10 PORT=443&quot;

result = re.findall(r&quot;\d+&quot;, log)

print(result)</code></pre>
<p>결과는</p>
<pre><code class="language-text">[&#39;192&#39;, &#39;168&#39;, &#39;10&#39;, &#39;20&#39;, &#39;10&#39;, &#39;0&#39;, &#39;0&#39;, &#39;10&#39;, &#39;443&#39;]</code></pre>
<p>처럼 나온다.</p>
<p>정규식을 조금씩 조합하면 나중에는 로그나 보안 이벤트에서도 원하는 패턴을 추출할 수 있다.</p>
<hr>
<h1 id="56-08-2에서-가장-중요한-흐름">56. 08-2에서 가장 중요한 흐름</h1>
<p>정규식을 실제 파이썬 코드에서 사용하면 기본적으로 다음과 같은 흐름이 된다.</p>
<pre><code class="language-text">정규식 작성
   ↓
re.compile()
   ↓
Pattern 객체
   ↓
문자열 검색
   ↓
match()
search()
findall()
finditer()
   ↓
결과 확인</code></pre>
<p>또는 간단한 경우</p>
<pre><code class="language-text">re.match()
re.search()
re.findall()</code></pre>
<p>처럼 바로 사용할 수도 있다.</p>
<hr>
<h1 id="📌-핵심-정리">📌 핵심 정리</h1>
<h2 id="문자-클래스">문자 클래스</h2>
<pre><code class="language-text">[abc]

a 또는 b 또는 c</code></pre>
<pre><code class="language-text">[a-z]

영문 소문자</code></pre>
<pre><code class="language-text">[^0-9]

숫자가 아닌 문자</code></pre>
<hr>
<h2 id="자주-사용하는-문자-클래스">자주 사용하는 문자 클래스</h2>
<pre><code class="language-text">\d → 숫자
\D → 숫자가 아닌 것

\s → 공백 계열
\S → 공백 계열이 아닌 것

\w → 단어 문자
\W → 단어 문자가 아닌 것</code></pre>
<hr>
<h2 id="반복">반복</h2>
<pre><code class="language-text">*

0번 이상</code></pre>
<pre><code class="language-text">+

1번 이상</code></pre>
<pre><code class="language-text">?

0번 또는 1번</code></pre>
<pre><code class="language-text">{3}

정확히 3번</code></pre>
<pre><code class="language-text">{2,5}

2~5번</code></pre>
<hr>
<h2 id="match와-search">match와 search</h2>
<pre><code class="language-text">match()

문자열 처음부터 확인</code></pre>
<pre><code class="language-text">search()

문자열 전체에서 검색</code></pre>
<p>이 차이가 가장 중요하다.</p>
<hr>
<h2 id="findall">findall</h2>
<pre><code class="language-python">re.findall(r&quot;\d+&quot;, &quot;abc 100 def 200&quot;)</code></pre>
<pre><code class="language-text">[&#39;100&#39;, &#39;200&#39;]</code></pre>
<p>매치되는 <strong>모든 문자열을 리스트로 반환</strong>한다.</p>
<hr>
<h2 id="finditer">finditer</h2>
<p>모든 결과를 찾지만 각각의 결과를 <strong>Match 객체</strong> 형태로 다룰 수 있다.</p>
<hr>
<h2 id="match-객체">Match 객체</h2>
<pre><code class="language-text">group()
→ 매치된 문자열

start()
→ 시작 위치

end()
→ 끝 위치

span()
→ (시작 위치, 끝 위치)</code></pre>
<hr>
<h2 id="컴파일-옵션">컴파일 옵션</h2>
<pre><code class="language-text">re.S
→ .에 줄바꿈 포함

re.I
→ 대소문자 무시

re.M
→ 각 줄에 ^, $ 적용

re.X
→ 복잡한 정규식을 여러 줄과 주석으로 작성</code></pre>
<hr>
<h2 id="raw-string">Raw String</h2>
<p>정규식에서는 역슬래시가 자주 등장하므로</p>
<pre><code class="language-python">r&quot;\d+&quot;</code></pre>
<p>처럼 <code>r&quot;...&quot;</code> 형태로 작성하는 습관을 들이면 좋다.</p>
<hr>
<h1 id="⭐-08-2-전체-흐름">⭐ 08-2 전체 흐름</h1>
<pre><code class="language-text">문자열에서 원하는 규칙 정의
        ↓
정규 표현식 작성
        ↓
[ ]
.
*
+
?
{}
\d
\s
\w
        ↓
re.compile()
        ↓
Pattern 객체 생성
        ↓
 ┌───────────┬───────────┬────────────┬────────────┐
 │           │           │            │
match()   search()    findall()    finditer()
 │           │           │            │
처음부터   전체 검색    모두 리스트   모두 Iterator</code></pre>
<p>그리고 <code>match()</code>나 <code>search()</code>로 얻은 Match 객체에서는</p>
<pre><code class="language-text">group()
start()
end()
span()</code></pre>
<p>을 이용해</p>
<pre><code class="language-text">무엇이 매치되었는지
+
어디에서 매치되었는지</code></pre>
<p>확인할 수 있다.</p>
<blockquote>
<p><strong>08-2의 핵심은 정규식 기호를 단순히 외우는 것이 아니라 <code>[a-z]+</code>처럼 하나씩 분해해서 읽는 습관을 만드는 것이다.</strong></p>
</blockquote>
<p>예를 들어</p>
<pre><code class="language-text">[a-z]+</code></pre>
<p>를 보면 바로</p>
<pre><code class="language-text">[a-z]
→ 영문 소문자

+

→ 1번 이상

따라서

영문 소문자가 1글자 이상 연속</code></pre>
<p>이라고 해석할 수 있어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY17(정규 표현식 살펴보기)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY17%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 29 Sep 2026 13:26:40 GMT</pubDate>
            <description><![CDATA[<h1 id="08-1-정규-표현식-살펴보기">08-1 정규 표현식 살펴보기</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>1. 정규 표현식이란?</strong></span></li>
<li><span style="color:slateblue"><strong>2. 문자열의 패턴</strong></span></li>
<li><span style="color:slateblue"><strong>3. 정규 표현식은 왜 필요한가?</strong></span></li>
<li><span style="color:slateblue"><strong>4. 정규식 없이 문자열 처리하기</strong></span></li>
<li><span style="color:slateblue"><strong>5. 정규식을 사용하여 처리하기</strong></span></li>
<li><span style="color:slateblue"><strong>6. 일반 문자열 처리와 정규식 비교</strong></span></li>
</ul>
<hr>
<h1 id="1-정규-표현식이란">1. 정규 표현식이란?</h1>
<p><strong>정규 표현식(Regular Expression)</strong> 은</p>
<blockquote>
<p><strong>문자열에서 특정한 규칙을 가진 패턴을 표현하는 방법</strong></p>
</blockquote>
<p>이다.</p>
<p>줄여서 <strong>정규식</strong>이라고도 한다.</p>
<p>예를 들어 다음과 같은 문자열을 찾고 싶다고 해보자.</p>
<pre><code class="language-text">숫자 6개
+
하이픈(-)
+
숫자 7개</code></pre>
<p>일반적인 말로 표현하면 상당히 길지만 정규식에서는 다음처럼 표현할 수 있다.</p>
<pre><code class="language-text">\d{6}-\d{7}</code></pre>
<p>즉,</p>
<pre><code class="language-text">\d{6}
  ↓
숫자 6개

-

\d{7}
  ↓
숫자 7개</code></pre>
<p>라는 의미이다.</p>
<p>아직 각각의 문법을 외울 필요는 없다.</p>
<p>이번 장에서는 우선</p>
<blockquote>
<p><strong>&quot;정규식은 문자열의 모양이나 규칙을 표현하는 문법이다.&quot;</strong></p>
</blockquote>
<p>정도로 이해하면 된다.</p>
<hr>
<h1 id="2-문자열의-패턴">2. 문자열의 패턴</h1>
<p>정규식에서 중요한 단어가 바로 <strong>패턴(Pattern)</strong> 이다.</p>
<p>예를 들어 다음과 같은 데이터가 있다고 해보자.</p>
<pre><code class="language-text">010-1234-5678
010-9876-1234</code></pre>
<p>두 문자열의 실제 숫자는 서로 다르다.</p>
<p>하지만 형태를 보면</p>
<pre><code class="language-text">숫자 3개
-
숫자 4개
-
숫자 4개</code></pre>
<p>라는 동일한 규칙을 가지고 있다.</p>
<p>이러한 일정한 형태를 <strong>패턴</strong>이라고 생각하면 된다.</p>
<hr>
<h2 id="또-다른-예">또 다른 예</h2>
<p>이메일 주소를 생각해 보자.</p>
<pre><code class="language-text">pingu@example.com
python@test.co.kr
hello@gmail.com</code></pre>
<p>각각의 내용은 다르지만 대략적으로</p>
<pre><code class="language-text">문자열
  ↓
@
  ↓
도메인
  ↓
.
  ↓
확장자</code></pre>
<p>라는 공통적인 형태가 있다.</p>
<p>정규식은 이런 <strong>문자열의 규칙을 찾아내거나 검사할 때</strong> 사용할 수 있다.</p>
<hr>
<h1 id="3-정규-표현식은-파이썬만-사용하는가">3. 정규 표현식은 파이썬만 사용하는가?</h1>
<p>정규 표현식은 파이썬에서만 사용하는 기능이 아니다.</p>
<p>다양한 프로그래밍 언어에서 사용한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Python
JavaScript
Java
C#
...</code></pre>
<p>등에서도 정규식을 사용할 수 있다.</p>
<p>따라서 정규식의 기본 문법을 익혀 두면 다른 프로그래밍 언어에서도 활용할 수 있다.</p>
<hr>
<h1 id="4-정규-표현식은-왜-필요한가">4. 정규 표현식은 왜 필요한가?</h1>
<p>다음과 같은 문제를 생각해 보자.</p>
<pre><code class="language-text">문자열에 특정 번호 형식이 포함되어 있다.

앞의 숫자 6자리는 그대로 두고
뒤의 숫자 7자리를 * 문자로 변경하고 싶다.</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">hong 800905-1234567
kim  700905-2345678</code></pre>
<p>이라는 데이터가 있다면</p>
<pre><code class="language-text">hong 800905-*******
kim  700905-*******</code></pre>
<p>처럼 변경하고 싶은 것이다.</p>
<hr>
<h1 id="5-정규식-없이-해결하려면">5. 정규식 없이 해결하려면?</h1>
<p>정규식을 사용하지 않는다면 문자열을 하나씩 직접 검사해야 한다.</p>
<p>전체적인 흐름은 다음과 같다.</p>
<pre><code class="language-text">전체 문자열
    ↓
줄 단위로 나누기
    ↓
공백을 기준으로 단어 나누기
    ↓
각 단어 길이 확인
    ↓
앞 6자리가 숫자인지 확인
    ↓
하이픈 위치 확인
    ↓
뒤 7자리가 숫자인지 확인
    ↓
조건을 만족하면 뒤쪽을 *로 변경
    ↓
다시 문자열 합치기</code></pre>
<p>코드로 만들어 보자.</p>
<pre><code class="language-python">data = &quot;&quot;&quot;
hong 800905-1234567
kim  700905-2345678
&quot;&quot;&quot;

result = []

for line in data.split(&quot;\n&quot;):
    words = []

    for word in line.split(&quot; &quot;):
        if (
            len(word) == 14
            and word[:6].isdigit()
            and word[6] == &quot;-&quot;
            and word[7:].isdigit()
        ):
            word = word[:7] + &quot;*******&quot;

        words.append(word)

    result.append(&quot; &quot;.join(words))

print(&quot;\n&quot;.join(result))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">hong 800905-*******
kim  700905-*******</code></pre>
<p>원하는 결과는 얻을 수 있다.</p>
<p>하지만 코드를 보면 확인해야 하는 조건이 많다.</p>
<pre><code class="language-python">len(word) == 14</code></pre>
<pre><code class="language-python">word[:6].isdigit()</code></pre>
<pre><code class="language-python">word[6] == &quot;-&quot;</code></pre>
<pre><code class="language-python">word[7:].isdigit()</code></pre>
<p>즉 우리가 원하는 <strong>문자열의 형태를 하나하나 직접 검사하고 있다.</strong></p>
<hr>
<h1 id="6-코드-동작-과정">6. 코드 동작 과정</h1>
<p>예를 들어</p>
<pre><code class="language-text">800905-1234567</code></pre>
<p>을 검사한다고 해보자.</p>
<p>먼저 길이를 확인한다.</p>
<pre><code class="language-python">len(word) == 14</code></pre>
<pre><code class="language-text">14글자인가?
→ O</code></pre>
<p>그다음 앞의 6글자를 확인한다.</p>
<pre><code class="language-python">word[:6]</code></pre>
<p>결과</p>
<pre><code class="language-text">800905</code></pre>
<p>숫자인지 확인한다.</p>
<pre><code class="language-python">word[:6].isdigit()</code></pre>
<pre><code class="language-text">True</code></pre>
<p>하이픈도 확인한다.</p>
<pre><code class="language-python">word[6] == &quot;-&quot;</code></pre>
<pre><code class="language-text">True</code></pre>
<p>마지막으로 뒤의 7글자가 숫자인지 확인한다.</p>
<pre><code class="language-python">word[7:].isdigit()</code></pre>
<p>결과</p>
<pre><code class="language-text">True</code></pre>
<p>모든 조건을 만족했으므로</p>
<pre><code class="language-python">word = word[:7] + &quot;*******&quot;</code></pre>
<p>을 실행한다.</p>
<p>결과</p>
<pre><code class="language-text">800905-*******</code></pre>
<p>가 된다.</p>
<hr>
<h1 id="7-문제점">7. 문제점</h1>
<p>현재 코드도 정상적으로 동작한다.</p>
<p>하지만 문자열 규칙이 복잡해질수록 확인해야 할 조건이 계속 늘어난다.</p>
<p>예를 들어</p>
<pre><code class="language-text">숫자인가?
문자인가?
몇 글자인가?
하이픈이 있는가?
특수문자가 있는가?
특정 위치에 문자가 있는가?</code></pre>
<p>등을 직접 검사해야 한다.</p>
<p>따라서 코드가 점점 길고 복잡해질 수 있다.</p>
<p>이럴 때 <strong>정규 표현식</strong>을 사용하면 편리하다.</p>
<hr>
<h1 id="8-정규식을-사용해-보기">8. 정규식을 사용해 보기</h1>
<p>파이썬에서는 정규 표현식을 사용할 때 <code>re</code> 모듈을 사용한다.</p>
<pre><code class="language-python">import re</code></pre>
<p>그리고 찾으려는 패턴을 정규식으로 작성한다.</p>
<pre><code class="language-python">pattern = re.compile(r&quot;(\d{6})-\d{7}&quot;)</code></pre>
<p>아직 이 정규식의 문법을 모두 이해하지 않아도 된다.</p>
<p>현재는 대략</p>
<pre><code class="language-text">(\d{6})
   ↓
숫자 6개

-

\d{7}
   ↓
숫자 7개</code></pre>
<p>라고 이해하면 된다.</p>
<hr>
<h1 id="9-정규식을-이용한-변환">9. 정규식을 이용한 변환</h1>
<p>예를 들어 다음과 같이 작성할 수 있다.</p>
<pre><code class="language-python">import re

data = &quot;&quot;&quot;
hong 800905-1234567
kim  700905-2345678
&quot;&quot;&quot;

pattern = re.compile(r&quot;(\d{6})-\d{7}&quot;)

result = pattern.sub(r&quot;\1-*******&quot;, data)

print(result)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">hong 800905-*******
kim  700905-*******</code></pre>
<p>정규식을 사용하지 않았을 때보다 코드가 훨씬 짧아졌다.</p>
<hr>
<h1 id="10-recompile">10. re.compile()</h1>
<p>다음 코드를 살펴보자.</p>
<pre><code class="language-python">pattern = re.compile(r&quot;(\d{6})-\d{7}&quot;)</code></pre>
<p><code>re.compile()</code>은</p>
<blockquote>
<p><strong>정규 표현식 패턴을 만들어 사용할 수 있도록 준비하는 함수</strong></p>
</blockquote>
<p>라고 생각하면 된다.</p>
<p>즉,</p>
<pre><code class="language-text">정규식 문자열
      ↓
re.compile()
      ↓
정규식 패턴 객체</code></pre>
<p>의 형태이다.</p>
<p>이후 패턴 객체를 이용해 문자열을 검색하거나 변경할 수 있다.</p>
<hr>
<h1 id="11-sub">11. sub()</h1>
<p>다음 코드를 보자.</p>
<pre><code class="language-python">pattern.sub(r&quot;\1-*******&quot;, data)</code></pre>
<p><code>sub()</code>는 쉽게 말하면</p>
<blockquote>
<p><strong>정규식과 일치하는 문자열을 다른 문자열로 바꾸는 기능</strong></p>
</blockquote>
<p>이다.</p>
<p>전체적인 흐름만 보면</p>
<pre><code class="language-text">data에서
    ↓
정규식에 해당하는 문자열 찾기
    ↓
찾은 문자열 변경
    ↓
새로운 문자열 반환</code></pre>
<p>이라고 생각하면 된다.</p>
<p><code>sub()</code>에 대한 자세한 문법은 뒤에서 다시 학습하게 된다.</p>
<hr>
<h1 id="12-r은-무엇일까">12. r&quot;...&quot;은 무엇일까?</h1>
<p>정규식에서는 다음과 같은 코드를 자주 볼 수 있다.</p>
<pre><code class="language-python">r&quot;\d{6}&quot;</code></pre>
<p>문자열 앞에</p>
<pre><code class="language-text">r</code></pre>
<p>이 붙어 있다.</p>
<p>이것을 <strong>Raw String</strong>이라고 한다.</p>
<p>일반적인 파이썬 문자열에서는</p>
<pre><code class="language-python">&quot;\n&quot;</code></pre>
<p>이 줄바꿈을 의미한다.</p>
<p>하지만</p>
<pre><code class="language-python">r&quot;\n&quot;</code></pre>
<p>처럼 <code>r</code>을 붙이면 <code>\</code>를 특별하게 처리하지 않고 문자 그대로 다루기 쉬워진다.</p>
<p>정규식에서는</p>
<pre><code class="language-text">\d
\s
\w</code></pre>
<p>처럼 역슬래시 <code>\</code>를 많이 사용하기 때문에 Raw String을 자주 사용한다.</p>
<p>따라서 정규식을 볼 때</p>
<pre><code class="language-python">r&quot;...&quot;</code></pre>
<p>형태가 자주 등장한다는 것을 기억해 두자.</p>
<hr>
<h1 id="13-일반-문자열-처리와-정규식-비교">13. 일반 문자열 처리와 정규식 비교</h1>
<h3 id="정규식-없이-처리">정규식 없이 처리</h3>
<pre><code class="language-text">문자열 분리
 ↓
길이 확인
 ↓
숫자인지 확인
 ↓
특정 위치 확인
 ↓
조건 검사
 ↓
문자열 변경
 ↓
다시 합치기</code></pre>
<p>여러 조건을 직접 코드로 만들어야 한다.</p>
<hr>
<h3 id="정규식-사용">정규식 사용</h3>
<pre><code class="language-text">원하는 패턴 정의
       ↓
정규식으로 검색
       ↓
일치하는 문자열 처리</code></pre>
<p>문자열의 규칙 자체를 하나의 패턴으로 표현할 수 있다.</p>
<hr>
<h1 id="14-정규식은-언제-유용할까">14. 정규식은 언제 유용할까?</h1>
<p>정규 표현식은 특히 <strong>일정한 규칙을 가진 문자열</strong>을 처리할 때 유용하다.</p>
<p>예를 들어</p>
<pre><code class="language-text">전화번호
이메일 주소
IP 주소
URL
날짜
시간
로그 데이터
특정 문자열 패턴</code></pre>
<p>등을</p>
<pre><code class="language-text">찾기
검사하기
추출하기
바꾸기</code></pre>
<p>위해 사용할 수 있다.</p>
<hr>
<h1 id="15-보안에서도-많이-사용한다">15. 보안에서도 많이 사용한다</h1>
<p>정규식은 보안 분야에서도 자주 볼 수 있다.</p>
<p>예를 들어 로그나 네트워크 데이터에서</p>
<pre><code class="language-text">특정 URL 패턴
특정 User-Agent
IP 주소
명령어 패턴
파일 확장자
공격 문자열</code></pre>
<p>등을 찾을 때 정규식을 활용할 수 있다.</p>
<p>예를 들어 단순한 IPv4 형태를 찾는다면 다음과 같은 정규식을 볼 수도 있다.</p>
<pre><code class="language-text">\d+\.\d+\.\d+\.\d+</code></pre>
<p>지금은 문법을 외우기보다</p>
<pre><code class="language-text">정규식
 ↓
문자열의 특정 패턴 탐지</code></pre>
<p>라는 개념을 기억해 두면 된다.</p>
<hr>
<h1 id="16-정규식의-장점">16. 정규식의 장점</h1>
<p>정규식을 사용하면 복잡한 문자열 패턴을 비교적 짧게 표현할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">숫자 6개
+
-
+
숫자 7개</code></pre>
<p>라는 설명을</p>
<pre><code class="language-text">\d{6}-\d{7}</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>따라서</p>
<pre><code class="language-text">복잡한 문자열 검색
문자열 검증
문자열 추출
문자열 치환</code></pre>
<p>작업에서 유용하다.</p>
<hr>
<h1 id="17-정규식의-단점">17. 정규식의 단점</h1>
<p>정규식은 강력하지만 처음 보면 읽기가 어렵다.</p>
<p>예를 들어</p>
<pre><code class="language-text">\d{6}-\d{7}</code></pre>
<p>도 정규식 문법을 모르면 무슨 의미인지 알기 어렵다.</p>
<p>더 복잡해지면</p>
<pre><code class="language-text">^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$</code></pre>
<p>처럼 한눈에 이해하기 어려운 형태가 될 수 있다.</p>
<p>따라서</p>
<blockquote>
<p><strong>정규식은 무작정 외우기보다 각각의 기호가 무엇을 의미하는지 하나씩 이해하는 것이 중요하다.</strong></p>
</blockquote>
<hr>
<h1 id="18-이번-장에서-알아둘-것">18. 이번 장에서 알아둘 것</h1>
<p>아직 다음 문법을 완벽하게 이해할 필요는 없다.</p>
<pre><code class="language-text">\d
{}
()
\</code></pre>
<p>다음 장부터 하나씩 배우게 된다.</p>
<p>현재 가장 중요한 것은</p>
<pre><code class="language-text">정규식 = 문자열의 규칙을 표현하는 방법</code></pre>
<p>이라는 개념이다.</p>
<hr>
<h1 id="📌-핵심-정리">📌 핵심 정리</h1>
<h2 id="정규-표현식">정규 표현식</h2>
<p>문자열의 특정한 <strong>패턴을 표현하는 문법</strong>이다.</p>
<pre><code class="language-text">숫자 6개 - 숫자 7개

        ↓

\d{6}-\d{7}</code></pre>
<hr>
<h2 id="pattern">Pattern</h2>
<p>문자열이 가지고 있는 일정한 규칙을 의미한다.</p>
<pre><code class="language-text">010-1234-5678

숫자 3개
   ↓
-
   ↓
숫자 4개
   ↓
-
   ↓
숫자 4개</code></pre>
<hr>
<h2 id="파이썬의-re-모듈">파이썬의 re 모듈</h2>
<p>정규식을 사용할 때</p>
<pre><code class="language-python">import re</code></pre>
<p>를 사용한다.</p>
<hr>
<h2 id="recompile">re.compile()</h2>
<p>정규식 패턴을 준비한다.</p>
<pre><code class="language-python">pattern = re.compile(r&quot;\d{6}-\d{7}&quot;)</code></pre>
<hr>
<h2 id="sub">sub()</h2>
<p>정규식에 일치하는 문자열을 다른 문자열로 변경할 때 사용할 수 있다.</p>
<pre><code class="language-python">pattern.sub(...)</code></pre>
<hr>
<h2 id="raw-string">Raw String</h2>
<p>정규식에서는 역슬래시를 많이 사용하기 때문에</p>
<pre><code class="language-python">r&quot;...&quot;</code></pre>
<p>형태의 Raw String을 자주 사용한다.</p>
<hr>
<h1 id="⭐-08-1-전체-흐름">⭐ 08-1 전체 흐름</h1>
<pre><code class="language-text">문자열 데이터
      ↓
특정한 규칙을 가진 문자열을 찾고 싶음
      ↓
일반 코드 사용
      ↓
split / isdigit / 길이 검사 등
여러 조건을 직접 작성
      ↓
코드가 복잡해짐

            또는

문자열 데이터
      ↓
원하는 규칙 정의
      ↓
정규 표현식
      ↓
패턴 검색 / 추출 / 변경
      ↓
코드 간결화</code></pre>
<blockquote>
<p><strong>정규 표현식은 &quot;특정 문자열 자체&quot;를 찾는 것이 아니라, 내가 원하는 &quot;문자열의 형태와 규칙&quot;을 표현해 찾는 방법이라고 이해하면 된다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY17(파이썬 타입 이노테이션)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY16%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%83%80%EC%9E%85-%EC%9D%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY16%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%83%80%EC%9E%85-%EC%9D%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98</guid>
            <pubDate>Tue, 29 Sep 2026 10:18:05 GMT</pubDate>
            <description><![CDATA[<h1 id="07-4-파이썬-타입-어노테이션">07-4 파이썬 타입 어노테이션</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>1. 동적 언어와 정적 언어</strong></span></li>
<li><span style="color:slateblue"><strong>2. 동적 언어의 장단점</strong></span></li>
<li><span style="color:slateblue"><strong>3. 타입 어노테이션이란?</strong></span></li>
<li><span style="color:slateblue"><strong>4. 변수에 타입 지정하기</strong></span></li>
<li><span style="color:slateblue"><strong>5. 함수에 타입 지정하기</strong></span></li>
<li><span style="color:slateblue"><strong>6. 리스트와 딕셔너리 타입 지정하기</strong></span></li>
<li><span style="color:slateblue"><strong>7. typing 모듈</strong></span></li>
<li><span style="color:slateblue"><strong>8. Optional과 Union</strong></span></li>
<li><span style="color:slateblue"><strong>9. Callable과 Any</strong></span></li>
<li><span style="color:slateblue"><strong>10. mypy를 이용한 타입 검사</strong></span></li>
</ul>
<hr>
<h1 id="1-동적-언어와-정적-언어">1. 동적 언어와 정적 언어</h1>
<p>파이썬은 <strong>동적 타입 언어(Dynamically Typed Language)</strong> 이다.</p>
<p>다음 코드를 살펴보자.</p>
<pre><code class="language-python">a = 1

print(type(a))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">&lt;class &#39;int&#39;&gt;</code></pre>
<p>현재 <code>a</code>에는 정수 <code>1</code>이 저장되어 있으므로 자료형은 <code>int</code>이다.</p>
<p>이번에는 같은 변수에 문자열을 저장해 보자.</p>
<pre><code class="language-python">a = &quot;1&quot;

print(type(a))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">&lt;class &#39;str&#39;&gt;</code></pre>
<p>처음에는</p>
<pre><code class="language-text">a → int</code></pre>
<p>였지만 새로운 값을 대입하면서</p>
<pre><code class="language-text">a → str</code></pre>
<p>처럼 사용할 수 있다.</p>
<p>즉 파이썬에서는 프로그램 실행 중에도 변수에 서로 다른 타입의 값을 대입할 수 있다.</p>
<pre><code class="language-python">a = 10
a = &quot;Python&quot;
a = [1, 2, 3]</code></pre>
<p>이것이 파이썬을 <strong>동적 타입 언어</strong>라고 부르는 이유이다.</p>
<hr>
<h1 id="2-정적-타입-언어">2. 정적 타입 언어</h1>
<p>반대로 자바와 같은 언어는 <strong>정적 타입 언어(Statically Typed Language)</strong> 이다.</p>
<p>예를 들어 자바에서는</p>
<pre><code class="language-java">int a = 1;</code></pre>
<p>처럼 변수의 타입을 미리 지정한다.</p>
<p>여기서 <code>a</code>는 <code>int</code>형이므로</p>
<pre><code class="language-java">a = &quot;1&quot;;</code></pre>
<p>처럼 문자열을 저장할 수 없다.</p>
<p>즉,</p>
<pre><code class="language-text">정적 타입 언어

변수 생성
   ↓
타입 지정
   ↓
지정된 타입의 값만 사용</code></pre>
<p>하는 방식이다.</p>
<hr>
<h1 id="3-동적-언어와-정적-언어-비교">3. 동적 언어와 정적 언어 비교</h1>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="left">동적 타입</th>
<th align="left">정적 타입</th>
</tr>
</thead>
<tbody><tr>
<td align="center">대표 언어</td>
<td align="left">Python</td>
<td align="left">Java</td>
</tr>
<tr>
<td align="center">타입 결정</td>
<td align="left">실행 중</td>
<td align="left">미리 지정</td>
</tr>
<tr>
<td align="center">타입 변경</td>
<td align="left">비교적 자유로움</td>
<td align="left">제한됨</td>
</tr>
<tr>
<td align="center">코드 작성</td>
<td align="left">빠르고 유연함</td>
<td align="left">타입이 명확함</td>
</tr>
<tr>
<td align="center">타입 오류 발견</td>
<td align="left">실행 중 발견 가능</td>
<td align="left">실행 전 발견 가능</td>
</tr>
</tbody></table>
<hr>
<h1 id="4-동적-언어의-장점">4. 동적 언어의 장점</h1>
<p>파이썬과 같은 동적 타입 언어는 타입을 일일이 지정하지 않아도 된다.</p>
<p>예를 들어</p>
<pre><code class="language-python">name = &quot;pingu&quot;
age = 20
scores = [90, 80, 100]</code></pre>
<p>처럼 바로 값을 저장할 수 있다.</p>
<p>따라서</p>
<ul>
<li>코드가 간단함</li>
<li>빠르게 프로그램 작성 가능</li>
<li>타입에 비교적 자유로움</li>
<li>코드가 간결함</li>
</ul>
<p>이라는 장점이 있다.</p>
<hr>
<h1 id="5-동적-언어의-단점">5. 동적 언어의 단점</h1>
<p>하지만 프로그램의 규모가 커지면 문제가 발생할 수 있다.</p>
<p>다음 함수를 보자.</p>
<pre><code class="language-python">def add(a, b):
    return a + b</code></pre>
<p>코드만 보면</p>
<pre><code class="language-text">a에는 어떤 값이 들어와야 하지?
b는 숫자인가?
문자열인가?
결과는 어떤 자료형이지?</code></pre>
<p>를 바로 알기 어렵다.</p>
<p>특히 여러 사람이 함께 개발하는 큰 프로젝트에서는 타입을 잘못 사용해 버그가 발생할 가능성이 커질 수 있다.</p>
<p>이러한 문제를 보완하기 위해 사용하는 것이</p>
<blockquote>
<p><strong>타입 어노테이션(Type Annotation)</strong></p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="6-타입-어노테이션이란">6. 타입 어노테이션이란?</h1>
<p>타입 어노테이션은 변수나 함수에</p>
<blockquote>
<p><strong>어떤 타입을 사용할 것인지 표시해 주는 기능</strong></p>
</blockquote>
<p>이다.</p>
<p>예를 들어</p>
<pre><code class="language-python">num: int = 1</code></pre>
<p>이라고 작성한다.</p>
<p>여기서</p>
<pre><code class="language-python">: int</code></pre>
<p>부분이 타입 어노테이션이다.</p>
<p>뜻은</p>
<pre><code class="language-text">num 변수에는
int 타입의 값을 사용할 예정이다.</code></pre>
<p>라고 알려 주는 것이다.</p>
<hr>
<h1 id="7-타입-어노테이션은-강제-규칙이-아니다">7. 타입 어노테이션은 강제 규칙이 아니다</h1>
<p>이 부분이 매우 중요하다.</p>
<pre><code class="language-python">num: int = 1</code></pre>
<p>이라고 작성했다고 해서 파이썬이 반드시 <code>int</code>만 사용하도록 강제하는 것은 아니다.</p>
<p>예를 들어</p>
<pre><code class="language-python">num: int = 1

num = &quot;Python&quot;

print(num)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">Python</code></pre>
<p>정상적으로 실행된다.</p>
<p>즉,</p>
<pre><code class="language-text">타입 어노테이션
     ↓
타입 강제 X
     ↓
타입에 대한 힌트 O</code></pre>
<p>이다.</p>
<p>그래서 <strong>Type Hint</strong>라고도 부른다.</p>
<hr>
<h1 id="8-변수에-타입-지정하기">8. 변수에 타입 지정하기</h1>
<p>기본적인 변수 타입은 다음과 같이 지정할 수 있다.</p>
<pre><code class="language-python">num: int = 1

name: str = &quot;홍길동&quot;

height: float = 175.5

is_student: bool = True</code></pre>
<p>각각 의미는 다음과 같다.</p>
<table>
<thead>
<tr>
<th align="left">코드</th>
<th align="left">타입</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>num: int</code></td>
<td align="left">정수</td>
</tr>
<tr>
<td align="left"><code>name: str</code></td>
<td align="left">문자열</td>
</tr>
<tr>
<td align="left"><code>height: float</code></td>
<td align="left">실수</td>
</tr>
<tr>
<td align="left"><code>is_student: bool</code></td>
<td align="left">참/거짓</td>
</tr>
</tbody></table>
<hr>
<h1 id="9-기본-자료형-타입-어노테이션">9. 기본 자료형 타입 어노테이션</h1>
<p>자주 사용하는 타입은 다음과 같다.</p>
<pre><code class="language-text">int   → 정수
float → 실수
str   → 문자열
bool  → 불
list  → 리스트
tuple → 튜플
dict  → 딕셔너리
set   → 집합</code></pre>
<p>예를 들어</p>
<pre><code class="language-python">number: int = 10
name: str = &quot;Python&quot;
numbers: list = [1, 2, 3]</code></pre>
<p>처럼 사용할 수 있다.</p>
<hr>
<h1 id="10-함수에-타입-지정하기">10. 함수에 타입 지정하기</h1>
<p>타입 어노테이션은 함수에서도 많이 사용한다.</p>
<p>다음 함수를 살펴보자.</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b</code></pre>
<p>하나씩 보면</p>
<pre><code class="language-python">a: int</code></pre>
<p>은</p>
<pre><code class="language-text">a는 int 타입</code></pre>
<p>이라는 의미이다.</p>
<pre><code class="language-python">b: int</code></pre>
<p>은</p>
<pre><code class="language-text">b는 int 타입</code></pre>
<p>이라는 의미이다.</p>
<p>그리고</p>
<pre><code class="language-python">-&gt; int</code></pre>
<p>는</p>
<pre><code class="language-text">이 함수는 int 타입을 반환한다.</code></pre>
<p>라는 뜻이다.</p>
<hr>
<h1 id="11-함수-타입-어노테이션-구조">11. 함수 타입 어노테이션 구조</h1>
<p>기본 구조는 다음과 같다.</p>
<pre><code class="language-python">def 함수이름(매개변수: 타입) -&gt; 반환타입:
    ...</code></pre>
<p>예를 들어</p>
<pre><code class="language-python">def greet(name: str) -&gt; str:
    return f&quot;안녕하세요, {name}님!&quot;</code></pre>
<p>뜻은</p>
<pre><code class="language-text">name
 ↓
str 입력

greet()
 ↓

str 반환</code></pre>
<p>이다.</p>
<hr>
<h1 id="12-타입-어노테이션이-없는-경우">12. 타입 어노테이션이 없는 경우</h1>
<p>다음 코드를 보자.</p>
<pre><code class="language-python">def add(a, b):
    return a + b</code></pre>
<p>이 함수만 보면</p>
<pre><code class="language-text">a = ?
b = ?
return = ?</code></pre>
<p>를 바로 알기 어렵다.</p>
<p>하지만</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b</code></pre>
<p>라고 작성하면</p>
<pre><code class="language-text">입력
a → int
b → int

출력
return → int</code></pre>
<p>이라는 사실을 쉽게 알 수 있다.</p>
<p>즉 타입 어노테이션은 <strong>코드의 가독성</strong>을 높여 준다.</p>
<hr>
<h1 id="13-리스트-내부-타입-지정하기">13. 리스트 내부 타입 지정하기</h1>
<p>단순히</p>
<pre><code class="language-python">numbers: list</code></pre>
<p>라고 작성하면 리스트라는 사실만 알 수 있다.</p>
<p>하지만 리스트 안에 어떤 타입이 들어 있는지도 지정할 수 있다.</p>
<pre><code class="language-python">numbers: list[int] = [1, 2, 3]</code></pre>
<p>뜻은</p>
<pre><code class="language-text">numbers
 ↓
list
 ↓
리스트 내부 값은 int</code></pre>
<p>이다.</p>
<hr>
<h1 id="14-다양한-컬렉션-타입">14. 다양한 컬렉션 타입</h1>
<h3 id="리스트">리스트</h3>
<pre><code class="language-python">numbers: list[int] = [1, 2, 3]</code></pre>
<pre><code class="language-text">int를 저장하는 리스트</code></pre>
<hr>
<h3 id="딕셔너리">딕셔너리</h3>
<pre><code class="language-python">user_info: dict[str, int] = {
    &quot;age&quot;: 30
}</code></pre>
<p>뜻은</p>
<pre><code class="language-text">key   → str
value → int</code></pre>
<p>이다.</p>
<hr>
<h3 id="튜플">튜플</h3>
<pre><code class="language-python">coordinates: tuple[float, float] = (3.5, 7.2)</code></pre>
<p>뜻은</p>
<pre><code class="language-text">첫 번째 값 → float
두 번째 값 → float</code></pre>
<p>이다.</p>
<hr>
<h3 id="집합">집합</h3>
<pre><code class="language-python">numbers: set[int] = {1, 2, 3}</code></pre>
<p><code>int</code> 값을 저장하는 집합이라는 의미이다.</p>
<hr>
<h1 id="15-타입을-자세하게-적는-이유">15. 타입을 자세하게 적는 이유</h1>
<p>다음 두 코드를 비교해 보자.</p>
<pre><code class="language-python">users: list</code></pre>
<p>이 코드만 보면</p>
<pre><code class="language-text">리스트인 것은 알겠는데
안에 무엇이 들어 있지?</code></pre>
<p>라는 의문이 생긴다.</p>
<p>하지만</p>
<pre><code class="language-python">users: list[str]</code></pre>
<p>라고 작성하면</p>
<pre><code class="language-text">문자열을 저장하는 리스트</code></pre>
<p>라는 사실을 바로 알 수 있다.</p>
<p>따라서 코드를 읽고 이해하기 쉬워진다.</p>
<hr>
<h1 id="16-typing-모듈">16. typing 모듈</h1>
<p>타입 어노테이션을 보다 다양하게 사용하기 위해</p>
<pre><code class="language-python">typing</code></pre>
<p>모듈을 사용할 수 있다.</p>
<p>과거 파이썬에서는 다음과 같은 형태를 많이 사용했다.</p>
<pre><code class="language-python">from typing import List, Dict, Tuple

numbers: List[int] = [1, 2, 3]

user_info: Dict[str, int] = {
    &quot;age&quot;: 30
}</code></pre>
<hr>
<h1 id="17-최신-파이썬에서는">17. 최신 파이썬에서는?</h1>
<p>Python 3.9 이상에서는</p>
<pre><code class="language-python">List[int]
Dict[str, int]
Tuple[int, int]</code></pre>
<p>대신 기본 자료형을 직접 사용할 수 있다.</p>
<pre><code class="language-python">list[int]
dict[str, int]
tuple[int, int]</code></pre>
<p>따라서 현재는 다음처럼 작성하는 방식이 일반적이다.</p>
<pre><code class="language-python">numbers: list[int] = [1, 2, 3]

user_info: dict[str, int] = {
    &quot;age&quot;: 30
}</code></pre>
<p>즉</p>
<pre><code class="language-text">과거

List[int]
Dict[str, int]
Tuple[int, int]</code></pre>
<p>에서</p>
<pre><code class="language-text">현재

list[int]
dict[str, int]
tuple[int, int]</code></pre>
<p>형태로 사용할 수 있다.</p>
<hr>
<h1 id="18-typing-모듈이-여전히-필요한-경우">18. typing 모듈이 여전히 필요한 경우</h1>
<p>기본 자료형은</p>
<pre><code class="language-python">list[int]
dict[str, int]</code></pre>
<p>처럼 사용할 수 있지만 좀 더 특별한 타입을 표현할 때는 <code>typing</code> 모듈을 사용할 수 있다.</p>
<p>대표적으로</p>
<pre><code class="language-text">Optional
Union
Callable
Any</code></pre>
<p>등이 있다.</p>
<hr>
<h1 id="19-optional">19. Optional</h1>
<p>다음 상황을 생각해 보자.</p>
<p>사용자 이름을 저장하는 변수인데 사용자 이름이 없을 수도 있다.</p>
<p>즉</p>
<pre><code class="language-text">문자열일 수도 있음
또는
None일 수도 있음</code></pre>
<p>이다.</p>
<p>이럴 때 <code>Optional</code>을 사용할 수 있다.</p>
<pre><code class="language-python">from typing import Optional

user_name: Optional[str] = None</code></pre>
<p><code>Optional[str]</code>은</p>
<pre><code class="language-text">str 또는 None</code></pre>
<p>이라는 의미이다.</p>
<hr>
<h2 id="값이-있는-경우">값이 있는 경우</h2>
<pre><code class="language-python">user_name: Optional[str] = &quot;pingu&quot;</code></pre>
<p>가능하다.</p>
<hr>
<h2 id="값이-없는-경우">값이 없는 경우</h2>
<pre><code class="language-python">user_name: Optional[str] = None</code></pre>
<p>이것도 가능하다.</p>
<p>따라서</p>
<pre><code class="language-text">Optional[str]

      ┌─ str
      │
      └─ None</code></pre>
<p>이라고 생각하면 된다.</p>
<hr>
<h1 id="20-union">20. Union</h1>
<p>하나의 값에 여러 타입이 들어올 수 있다고 표시할 때 <code>Union</code>을 사용한다.</p>
<pre><code class="language-python">from typing import Union

user_id: Union[int, str]</code></pre>
<p>뜻은</p>
<pre><code class="language-text">user_id
   ↓
int 또는 str</code></pre>
<p>이다.</p>
<p>따라서</p>
<pre><code class="language-python">user_id = 100</code></pre>
<p>도 가능하고</p>
<pre><code class="language-python">user_id = &quot;pingu&quot;</code></pre>
<p>도 가능하다.</p>
<hr>
<h1 id="21-optional과-union-관계">21. Optional과 Union 관계</h1>
<p>사실</p>
<pre><code class="language-python">Optional[str]</code></pre>
<p>은 개념적으로</p>
<pre><code class="language-python">Union[str, None]</code></pre>
<p>과 같은 의미라고 이해하면 된다.</p>
<p>즉</p>
<pre><code class="language-text">Optional[str]

str 또는 None</code></pre>
<p>이다.</p>
<hr>
<h1 id="22-최신-파이썬-문법">22. 최신 파이썬 문법</h1>
<p>최근 파이썬에서는 <code>Union</code> 대신 <code>|</code>를 사용할 수도 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python">user_id: int | str</code></pre>
<p>은</p>
<pre><code class="language-text">int 또는 str</code></pre>
<p>을 의미한다.</p>
<p>그리고</p>
<pre><code class="language-python">user_name: str | None</code></pre>
<p>은</p>
<pre><code class="language-text">str 또는 None</code></pre>
<p>이라는 의미이다.</p>
<p>따라서 다음처럼 대응해서 이해하면 된다.</p>
<pre><code class="language-text">Optional[str]
      ↓
str | None

Union[int, str]
      ↓
int | str</code></pre>
<hr>
<h1 id="23-callable">23. Callable</h1>
<p>함수 자체의 타입도 표현할 수 있다.</p>
<p>이때 <code>Callable</code>을 사용할 수 있다.</p>
<pre><code class="language-python">from typing import Callable</code></pre>
<p>예를 들어</p>
<pre><code class="language-python">def process_data(
    callback: Callable[[int], str]
) -&gt; str:

    return callback(42)</code></pre>
<p>여기서</p>
<pre><code class="language-python">Callable[[int], str]</code></pre>
<p>은</p>
<pre><code class="language-text">int를 입력받고
     ↓
str을 반환하는 함수</code></pre>
<p>라는 의미이다.</p>
<hr>
<h2 id="구조">구조</h2>
<pre><code class="language-python">Callable[[매개변수 타입], 반환 타입]</code></pre>
<p>예를 들어</p>
<pre><code class="language-python">Callable[[int, int], int]</code></pre>
<p>은</p>
<pre><code class="language-text">int, int
   ↓
함수
   ↓
int 반환</code></pre>
<p>이라는 의미이다.</p>
<hr>
<h1 id="24-any">24. Any</h1>
<p>어떤 타입이 들어와도 괜찮다고 표현할 때는</p>
<pre><code class="language-python">Any</code></pre>
<p>를 사용할 수 있다.</p>
<pre><code class="language-python">from typing import Any

data: Any</code></pre>
<p>다음과 같은 값이 모두 가능하다.</p>
<pre><code class="language-python">data = 10
data = &quot;Python&quot;
data = [1, 2, 3]
data = {&quot;name&quot;: &quot;pingu&quot;}</code></pre>
<p>즉</p>
<pre><code class="language-text">Any
 ↓
어떤 타입이든 허용</code></pre>
<p>이라는 의미이다.</p>
<hr>
<h1 id="25-실무-형태-예제">25. 실무 형태 예제</h1>
<p>예를 들어 사용자를 찾는 함수를 만들어 보자.</p>
<pre><code class="language-python">from typing import Optional

def find_user(user_id: int) -&gt; Optional[dict[str, str]]:

    if user_id &gt; 0:
        return {
            &quot;name&quot;: &quot;홍길동&quot;,
            &quot;email&quot;: &quot;hong@example.com&quot;
        }

    return None</code></pre>
<p>반환 타입을 보면</p>
<pre><code class="language-python">Optional[dict[str, str]]</code></pre>
<p>이다.</p>
<p>즉 이 함수의 결과는</p>
<pre><code class="language-text">사용자를 찾음
      ↓
dict[str, str]

사용자를 못 찾음
      ↓
None</code></pre>
<p>중 하나이다.</p>
<hr>
<h1 id="26-타입-어노테이션은-실제로-검사할까">26. 타입 어노테이션은 실제로 검사할까?</h1>
<p>다음 코드를 살펴보자.</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b</code></pre>
<p><code>a</code>와 <code>b</code> 모두 <code>int</code>라고 지정했다.</p>
<p>그런데 다음처럼 실행해 보자.</p>
<pre><code class="language-python">result = add(3, 3.4)

print(result)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">6.4</code></pre>
<p>정상적으로 실행된다.</p>
<p>왜 그럴까?</p>
<p>파이썬의 타입 어노테이션은</p>
<pre><code class="language-text">타입 강제 기능</code></pre>
<p>이 아니라</p>
<pre><code class="language-text">타입에 대한 정보와 힌트</code></pre>
<p>이기 때문이다.</p>
<hr>
<h1 id="27-타입을-잘못-넣어도-파이썬은-실행한다">27. 타입을 잘못 넣어도 파이썬은 실행한다</h1>
<p>다음 코드에서</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b</code></pre>
<p>분명 <code>b</code>는</p>
<pre><code class="language-text">int</code></pre>
<p>라고 표시되어 있다.</p>
<p>하지만</p>
<pre><code class="language-python">add(3, 3.4)</code></pre>
<p>처럼 <code>float</code>을 사용해도 파이썬은 타입 어노테이션만 보고 실행을 막지 않는다.</p>
<p>즉</p>
<pre><code class="language-text">타입 어노테이션
       ↓
사람 / IDE / 검사 도구에게 정보 제공
       ↓
파이썬 실행 자체를 강제로 제한하지 않음</code></pre>
<p>이다.</p>
<hr>
<h1 id="28-mypy">28. mypy</h1>
<p>그렇다면 타입이 제대로 사용되고 있는지 어떻게 검사할까?</p>
<p>대표적인 도구가</p>
<blockquote>
<p><strong>mypy</strong></p>
</blockquote>
<p>이다.</p>
<p><code>mypy</code>는 파이썬 코드를 실행하기 전에 타입 어노테이션을 분석하는 <strong>정적 타입 검사 도구</strong>이다.</p>
<hr>
<h1 id="29-mypy-설치하기">29. mypy 설치하기</h1>
<p><code>mypy</code>는 파이썬 표준 라이브러리가 아니므로 설치해야 한다.</p>
<pre><code class="language-powershell">pip install mypy</code></pre>
<p>설치 후 파이썬 파일을 검사할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">typing_sample.py</code></pre>
<p>파일을 만들었다고 해보자.</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b


result = add(3, 3.4)

print(result)</code></pre>
<hr>
<h1 id="30-mypy로-검사하기">30. mypy로 검사하기</h1>
<p>터미널에서 실행한다.</p>
<pre><code class="language-powershell">mypy typing_sample.py</code></pre>
<p>그러면 <code>mypy</code>는</p>
<pre><code class="language-text">b는 int를 사용한다고 했는데
float 값이 전달되었다.</code></pre>
<p>라는 문제를 발견할 수 있다.</p>
<p>즉</p>
<pre><code class="language-python">add(3, 3.4)</code></pre>
<p>에서</p>
<pre><code class="language-text">3   → int O
3.4 → float X</code></pre>
<p>가 되는 것이다.</p>
<hr>
<h1 id="31-코드-수정하기">31. 코드 수정하기</h1>
<p>다음처럼 수정한다.</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b


result = add(3, 4)

print(result)</code></pre>
<p>다시 검사하면</p>
<pre><code class="language-powershell">mypy typing_sample.py</code></pre>
<p>타입 문제가 없다는 결과를 확인할 수 있다.</p>
<hr>
<h1 id="32-파이썬과-mypy의-차이">32. 파이썬과 mypy의 차이</h1>
<p>이 부분을 확실히 구분해야 한다.</p>
<h3 id="python-실행">Python 실행</h3>
<pre><code class="language-powershell">python typing_sample.py</code></pre>
<pre><code class="language-text">실제 프로그램 실행</code></pre>
<h3 id="mypy-검사">mypy 검사</h3>
<pre><code class="language-powershell">mypy typing_sample.py</code></pre>
<pre><code class="language-text">프로그램을 실행하기 전
타입 어노테이션 검사</code></pre>
<p>즉</p>
<pre><code class="language-text">코드 작성
   ↓
mypy
   ↓
타입 검사
   ↓
문제 수정
   ↓
Python 실행</code></pre>
<p>과 같은 흐름으로 사용할 수 있다.</p>
<hr>
<h1 id="33-타입-어노테이션을-사용하는-이유">33. 타입 어노테이션을 사용하는 이유</h1>
<p>타입 어노테이션의 가장 큰 목적은 코드를 이해하기 쉽게 만드는 것이다.</p>
<p>다음 함수를 비교해 보자.</p>
<h3 id="타입-어노테이션이-없는-함수">타입 어노테이션이 없는 함수</h3>
<pre><code class="language-python">def get_user(user_id):
    ...</code></pre>
<p>이것만 보면</p>
<pre><code class="language-text">user_id가 숫자인가?
문자열인가?

무엇을 반환하지?</code></pre>
<p>를 바로 알기 어렵다.</p>
<hr>
<h3 id="타입-어노테이션이-있는-함수">타입 어노테이션이 있는 함수</h3>
<pre><code class="language-python">def get_user(user_id: int) -&gt; dict[str, str]:
    ...</code></pre>
<p>이제 코드만 보고도</p>
<pre><code class="language-text">입력
 ↓
user_id = int

출력
 ↓
dict[str, str]</code></pre>
<p>이라는 사실을 알 수 있다.</p>
<hr>
<h1 id="34-특히-팀-개발에서-유용하다">34. 특히 팀 개발에서 유용하다</h1>
<p>혼자 작성한 작은 프로그램이라면 자신이 작성한 코드를 기억하고 있을 수 있다.</p>
<p>하지만 규모가 커지면</p>
<pre><code class="language-text">개발자 A
개발자 B
개발자 C
개발자 D
...</code></pre>
<p>여러 사람이 하나의 코드를 함께 사용한다.</p>
<p>이때 타입 어노테이션이 있다면 함수가 어떤 값을 요구하는지 쉽게 확인할 수 있다.</p>
<pre><code class="language-python">def login(
    username: str,
    password: str
) -&gt; bool:
    ...</code></pre>
<p>코드만 보고도</p>
<pre><code class="language-text">username → str
password → str
return   → bool</code></pre>
<p>이라는 구조를 알 수 있다.</p>
<hr>
<h1 id="35-ide에서도-활용된다">35. IDE에서도 활용된다</h1>
<p>VSCode나 PyCharm과 같은 개발 도구는 타입 어노테이션을 이용하여</p>
<ul>
<li>자동 완성</li>
<li>타입 정보 표시</li>
<li>잘못된 타입 경고</li>
<li>함수 사용 방법 표시</li>
</ul>
<p>등을 제공할 수 있다.</p>
<p>따라서 타입 어노테이션은 단순한 주석보다 훨씬 유용하다.</p>
<hr>
<h1 id="36-타입-어노테이션-전체-구조">36. 타입 어노테이션 전체 구조</h1>
<p>변수</p>
<pre><code class="language-python">변수명: 타입 = 값</code></pre>
<p>예</p>
<pre><code class="language-python">age: int = 20</code></pre>
<hr>
<p>함수 매개변수</p>
<pre><code class="language-python">def 함수(매개변수: 타입):</code></pre>
<p>예</p>
<pre><code class="language-python">def hello(name: str):</code></pre>
<hr>
<p>함수 반환값</p>
<pre><code class="language-python">def 함수() -&gt; 타입:</code></pre>
<p>예</p>
<pre><code class="language-python">def get_age() -&gt; int:</code></pre>
<hr>
<p>매개변수와 반환값</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b</code></pre>
<hr>
<h1 id="37-여러-타입-표현하기">37. 여러 타입 표현하기</h1>
<h3 id="int-리스트">int 리스트</h3>
<pre><code class="language-python">numbers: list[int]</code></pre>
<h3 id="문자열-딕셔너리">문자열 딕셔너리</h3>
<pre><code class="language-python">user: dict[str, str]</code></pre>
<h3 id="int-또는-str">int 또는 str</h3>
<pre><code class="language-python">user_id: int | str</code></pre>
<h3 id="str-또는-none">str 또는 None</h3>
<pre><code class="language-python">name: str | None</code></pre>
<h3 id="모든-타입">모든 타입</h3>
<pre><code class="language-python">from typing import Any

data: Any</code></pre>
<h3 id="함수">함수</h3>
<pre><code class="language-python">from typing import Callable

callback: Callable[[int], str]</code></pre>
<hr>
<h1 id="📌-핵심-정리">📌 핵심 정리</h1>
<h2 id="동적-타입-언어">동적 타입 언어</h2>
<p>파이썬은 변수에 서로 다른 타입의 값을 대입할 수 있다.</p>
<pre><code class="language-python">a = 1
a = &quot;Python&quot;</code></pre>
<hr>
<h2 id="타입-어노테이션">타입 어노테이션</h2>
<p>변수나 함수가 어떤 타입을 사용할 것인지 알려 주는 <strong>타입 힌트</strong>이다.</p>
<pre><code class="language-python">age: int = 20</code></pre>
<hr>
<h2 id="함수-타입-어노테이션">함수 타입 어노테이션</h2>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b</code></pre>
<pre><code class="language-text">a      → int
b      → int
return → int</code></pre>
<hr>
<h2 id="컬렉션-타입">컬렉션 타입</h2>
<pre><code class="language-python">numbers: list[int]

user: dict[str, str]

position: tuple[float, float]

numbers: set[int]</code></pre>
<hr>
<h2 id="optional">Optional</h2>
<pre><code class="language-python">from typing import Optional

name: Optional[str]</code></pre>
<pre><code class="language-text">str 또는 None</code></pre>
<hr>
<h2 id="union">Union</h2>
<pre><code class="language-python">from typing import Union

user_id: Union[int, str]</code></pre>
<pre><code class="language-text">int 또는 str</code></pre>
<p>최근 파이썬에서는 다음처럼 표현할 수도 있다.</p>
<pre><code class="language-python">user_id: int | str

name: str | None</code></pre>
<hr>
<h2 id="callable">Callable</h2>
<pre><code class="language-python">from typing import Callable

callback: Callable[[int], str]</code></pre>
<pre><code class="language-text">int 입력
   ↓
함수
   ↓
str 반환</code></pre>
<hr>
<h2 id="any">Any</h2>
<pre><code class="language-python">from typing import Any

data: Any</code></pre>
<p>어떤 타입이든 사용할 수 있다는 의미이다.</p>
<hr>
<h2 id="mypy">mypy</h2>
<p>타입 어노테이션이 올바르게 사용되었는지 검사하는 도구이다.</p>
<pre><code class="language-powershell">pip install mypy</code></pre>
<p>검사</p>
<pre><code class="language-powershell">mypy typing_sample.py</code></pre>
<hr>
<h1 id="💡-가장-중요한-부분">💡 가장 중요한 부분</h1>
<p>타입 어노테이션을 작성했다고 해서 파이썬이 타입을 강제로 제한하는 것은 아니다.</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b</code></pre>
<p>여기서</p>
<pre><code class="language-python">add(3, 3.4)</code></pre>
<p>를 실행해도 파이썬 자체는 실행할 수 있다.</p>
<p>따라서</p>
<pre><code class="language-text">Type Annotation
       ↓
타입을 강제한다 X

Type Annotation
       ↓
어떤 타입을 사용할지 알려 주는 힌트 O</code></pre>
<p>이다.</p>
<p>실제로 타입이 올바르게 사용되고 있는지 확인하려면</p>
<pre><code class="language-text">IDE
mypy</code></pre>
<p>와 같은 도구를 함께 사용한다.</p>
<hr>
<h1 id="⭐-07-4-전체-흐름">⭐ 07-4 전체 흐름</h1>
<pre><code class="language-text">Python
  ↓
동적 타입 언어
  ↓
타입 사용이 자유로움
  ↓
코드가 커지면 타입 파악이 어려울 수 있음
  ↓
Type Annotation
  ↓
변수와 함수에 타입 정보 표시
  ↓
코드 이해가 쉬워짐
  ↓
mypy
  ↓
타입 오류를 실행 전에 검사</code></pre>
<blockquote>
<p><strong>타입 어노테이션은 파이썬을 정적 타입 언어로 만드는 기능이 아니라, 동적 타입의 장점을 유지하면서 코드에 타입 정보를 추가하는 기능이라고 이해하면 된다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[매일 1% 성장하기 DAY-28]]></title>
            <link>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-28</link>
            <guid>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-28</guid>
            <pubDate>Tue, 29 Sep 2026 10:17:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="2026년-목표">2026년 목표</h1>
</blockquote>
<ul>
<li>클라우드 환경 운영 경험(상세 내용은 추후 작성)</li>
<li>보안관제 자동화 경험</li>
<li>블로그 꾸준히 작성</li>
<li>자격증 : 정보처리기사(9월 시작), 정보보안기사, AWS-SAA,/// CISA 및 CISSP(정보 보안 관련 경력 5년 이상 필요 (4년 경력+보안 관련 학위 가능))</li>
<li>개발 공부</li>
<li>영어 공부</li>
<li>운동</li>
<li>리팩토링 과정 복습 및 벨로그에 새롭게 작성</li>
<li>클라우드 보안 복습 및 벨로그에 작성
&lt;내년 예정 - 석사 과정&gt;</li>
</ul>
<blockquote>
<h2 id="📅-공부-순서---version-9-10월">📅 공부 순서 - (Version 9-10월)</h2>
</blockquote>
<ol>
<li>파이썬(점프 투 파이썬) - 9월 말까지 완료 목표</li>
<li>정보처리기사 실기 - 10월 25일에 시험있음 합격 목표</li>
<li>모의해킹(노말틱 강의) - 10월 말까지 완료 목표</li>
<li>영어 - 10월부터 시작!</li>
<li><span style="color:green"><strong>혼자공부하는 네트워크(인프런) 완강했음 - 10월에 지속적인 복습</strong> </span></li>
<li>리펙토링 과정 복습 시작(실습 및 블로그 재작성)</li>
<li>Docker(인프런_devsecops) 강의(모의해킹 끝나고 시작)</li>
<li>AWS 보안 가이드(인프런 강의) - (Docker 인프런 끝나고 시작)</li>
<li>JAVA - (파이썬 끝나고 시작)</li>
</ol>
<hr>
<blockquote>
<h3 id="📅-금주-목표---0928월1004일">📅 금주 목표 - (09/28(월)~10/04(일))</h3>
</blockquote>
<ul>
<li><strong>파이썬(점프 투 파이썬)</strong><ul>
<li>Youtube 강의 + Ebook 참고</li>
<li>목표 : 점프 투 파이썬 학습 계속 진행 및 진도율 향상</li>
</ul>
</li>
<li><strong>정보처리기사 실기</strong><ul>
<li>목표 : 2020년도 기출문제 2회차 풀이</li>
<li>범위 : 1회차 ~ 4회차</li>
<li>목표 : 2021년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
<li>목표 : 2022년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
</ul>
</li>
<li><strong>모의해킹</strong><ul>
<li>목표 : Day 1 ~ Day 2 완료</li>
<li>범위 : 1주차 ~ 2주차</li>
</ul>
</li>
</ul>
<p><span style="color:"></span></p>
<blockquote>
<h3 id="📅-오늘의-목표--span-stylecolorgreen완료span-or-span-stylecolorred미완료span-or-span-stylecoloryellow진행중span">📅 오늘의 목표 : <span style="color:green"><strong>완료</strong></span> OR <span style="color:red"><strong>미완료</strong></span> OR <span style="color:yellow"><strong>진행중</strong></span></h3>
</blockquote>
<ul>
<li><span style="color:red"><strong>모의해킹(노말틱 강의)</strong><ul>
<li><strong>1주차 ~ 하는데 까지 + 블로그 정리</strong></span></li>
</ul>
</li>
<li><span style="color:green"><strong>파이썬(점프 투 파이썬)</strong><ul>
<li><strong>07장 04 ~ 하는데 까지 + 블로그 정리</strong></span></li>
</ul>
</li>
<li><span style="color:red"><strong>household account 작성(23:50분에 작성)</strong></span> </li>
<li><span style="color:red"><strong>정보처리기사 DAY3(2020년도 2회차 문제풀이 및 복습) + 2021년도 1회차 문제풀이 + 2022년도 1회차 문제풀이</strong></span></li>
<li><span style="color:red"><strong>방청소</strong></span></li>
</ul>
<blockquote>
<h3 id="💻-오늘완료-한-일">💻 오늘(완료) 한 일</h3>
</blockquote>
<ul>
<li>점프 투 파이썬 모든 장 완료</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY16(이터레이터와 제너레이터)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY16%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0%EC%99%80-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY16%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0%EC%99%80-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0</guid>
            <pubDate>Mon, 28 Sep 2026 14:07:51 GMT</pubDate>
            <description><![CDATA[<h1 id="07-3-이터레이터와-제너레이터">07-3 이터레이터와 제너레이터</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>1. 반복 가능한 객체란?</strong></span></li>
<li><span style="color:slateblue"><strong>2. 이터레이터란?</strong></span></li>
<li><span style="color:slateblue"><strong>3. iter()와 next()</strong></span></li>
<li><span style="color:slateblue"><strong>4. 이터레이터 직접 만들기</strong></span></li>
<li><span style="color:slateblue"><strong>5. 제너레이터란?</strong></span></li>
<li><span style="color:slateblue"><strong>6. yield 동작 방식</strong></span></li>
<li><span style="color:slateblue"><strong>7. 제너레이터 표현식</strong></span></li>
<li><span style="color:slateblue"><strong>8. 이터레이터와 제너레이터 비교</strong></span></li>
<li><span style="color:slateblue"><strong>9. 제너레이터 활용하기</strong></span></li>
</ul>
<hr>
<h1 id="1-반복-가능한-객체란">1. 반복 가능한 객체란?</h1>
<p>다음과 같은 <code>for</code>문을 생각해 보자.</p>
<pre><code class="language-python">for a in [1, 2, 3]:
    print(a)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">1
2
3</code></pre>
<p>리스트 <code>[1, 2, 3]</code>의 값을 하나씩 가져와 출력한다.</p>
<p>이처럼 <code>for</code>문과 같은 반복문에서 사용할 수 있는 객체를</p>
<blockquote>
<p><strong>반복 가능한 객체(Iterable)</strong></p>
</blockquote>
<p>라고 한다.</p>
<p>대표적인 iterable 객체에는 다음과 같은 것들이 있다.</p>
<pre><code class="language-text">list
tuple
string
dict
set
range</code></pre>
<p>예를 들어</p>
<pre><code class="language-python">for char in &quot;Python&quot;:
    print(char)</code></pre>
<p>문자열도 하나씩 반복할 수 있으므로 iterable 객체이다.</p>
<hr>
<h1 id="2-이터레이터란">2. 이터레이터란?</h1>
<p><strong>이터레이터(Iterator)</strong> 는</p>
<blockquote>
<p><code>next()</code>를 이용해 값을 하나씩 꺼낼 수 있는 객체</p>
</blockquote>
<p>이다.</p>
<p>여기서 중요한 점이 있다.</p>
<blockquote>
<p><strong>반복 가능한 객체라고 해서 모두 이터레이터인 것은 아니다.</strong></p>
</blockquote>
<p>다음 코드를 실행해 보자.</p>
<pre><code class="language-python">a = [1, 2, 3]

next(a)</code></pre>
<p>오류가 발생한다.</p>
<pre><code class="language-text">TypeError: &#39;list&#39; object is not an iterator</code></pre>
<p>리스트는 <code>for</code>문으로 반복할 수 있기 때문에 iterable이지만, <code>next()</code>를 바로 사용할 수 있는 iterator는 아니다.</p>
<hr>
<h2 id="쉽게-비유하면">쉽게 비유하면</h2>
<p>다음처럼 생각하면 이해하기 쉽다.</p>
<pre><code class="language-text">리스트 → 책
이터레이터 → 책갈피</code></pre>
<p>책은 읽을 수 있는 대상이다.</p>
<p>하지만 현재 어느 페이지를 읽고 있는지 기억하면서 한 장씩 넘겨 주는 역할은 책갈피가 한다.</p>
<p>마찬가지로</p>
<pre><code class="language-text">Iterable
   ↓
반복할 수 있는 데이터

Iterator
   ↓
현재 위치를 기억하면서
값을 하나씩 꺼내 주는 객체</code></pre>
<p>라고 생각할 수 있다.</p>
<hr>
<h1 id="3-iter-함수">3. iter() 함수</h1>
<p>리스트와 같은 iterable 객체를 iterator로 만들 때</p>
<pre><code class="language-python">iter()</code></pre>
<p>함수를 사용한다.</p>
<pre><code class="language-python">a = [1, 2, 3]

ia = iter(a)</code></pre>
<p>자료형을 확인해 보자.</p>
<pre><code class="language-python">print(type(ia))</code></pre>
<p>결과</p>
<pre><code class="language-text">&lt;class &#39;list_iterator&#39;&gt;</code></pre>
<p>이제 <code>ia</code>는 리스트가 아니라 <strong>리스트 이터레이터</strong>이다.</p>
<hr>
<h1 id="4-next-함수">4. next() 함수</h1>
<p>이터레이터에서는 <code>next()</code>를 사용할 수 있다.</p>
<pre><code class="language-python">a = [1, 2, 3]

ia = iter(a)

print(next(ia))
print(next(ia))
print(next(ia))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">1
2
3</code></pre>
<p><code>next()</code>를 호출할 때마다 다음 값을 하나씩 가져온다.</p>
<p>동작 과정을 보면</p>
<pre><code class="language-text">[1, 2, 3]
    ↓
iter()
    ↓
이터레이터 생성
    ↓
next() → 1
    ↓
next() → 2
    ↓
next() → 3</code></pre>
<p>이다.</p>
<hr>
<h1 id="5-더-이상-값이-없다면">5. 더 이상 값이 없다면?</h1>
<p>다시 <code>next()</code>를 실행하면 어떻게 될까?</p>
<pre><code class="language-python">next(ia)</code></pre>
<p>결과</p>
<pre><code class="language-text">StopIteration</code></pre>
<p>이터레이터에서 가져올 값이 더 이상 없으면</p>
<pre><code class="language-text">StopIteration</code></pre>
<p>예외가 발생한다.</p>
<p>즉</p>
<pre><code class="language-text">next()
  ↓
다음 값 존재?
  ↓
Yes → 값 반환

No → StopIteration</code></pre>
<p>과 같이 동작한다.</p>
<hr>
<h1 id="6-for문과-이터레이터">6. for문과 이터레이터</h1>
<p>실제로는 우리가 <code>next()</code>와 <code>StopIteration</code>을 직접 처리하는 경우보다 <code>for</code>문을 사용하는 경우가 많다.</p>
<pre><code class="language-python">a = [1, 2, 3]

ia = iter(a)

for i in ia:
    print(i)</code></pre>
<p>결과</p>
<pre><code class="language-text">1
2
3</code></pre>
<p><code>for</code>문은 내부적으로 iterator에서 값을 하나씩 가져오고, 더 이상 값이 없으면 반복을 종료한다.</p>
<p>쉽게 보면 다음과 같은 흐름이다.</p>
<pre><code class="language-text">for문 시작
   ↓
next()
   ↓
값 존재
   ↓
값 사용
   ↓
next()
   ↓
...
   ↓
StopIteration
   ↓
반복 종료</code></pre>
<p>따라서 우리가 직접 <code>StopIteration</code>을 처리할 필요가 없다.</p>
<hr>
<h1 id="7-이터레이터는-한-번-사용하면-끝난다">7. 이터레이터는 한 번 사용하면 끝난다</h1>
<p>다음 코드를 살펴보자.</p>
<pre><code class="language-python">a = [1, 2, 3]

ia = iter(a)

for i in ia:
    print(i)</code></pre>
<p>결과</p>
<pre><code class="language-text">1
2
3</code></pre>
<p>다시 한번 반복해 보자.</p>
<pre><code class="language-python">for i in ia:
    print(i)</code></pre>
<p>이번에는 아무것도 출력되지 않는다.</p>
<p>왜 그럴까?</p>
<p>이터레이터는 현재 위치를 기억하면서 <strong>앞으로만 이동하기 때문</strong>이다.</p>
<pre><code class="language-text">처음

1 → 2 → 3
↑
현재 위치


next()

1 → 2 → 3
    ↑


next()

1 → 2 → 3
        ↑


모두 사용

1 → 2 → 3 → 끝</code></pre>
<p>이미 마지막까지 이동했기 때문에 다시 처음으로 돌아가지 않는다.</p>
<p>다시 사용하려면 새로운 이터레이터를 만들어야 한다.</p>
<pre><code class="language-python">ia = iter(a)</code></pre>
<hr>
<h1 id="8-iterable과-iterator-정리">8. Iterable과 Iterator 정리</h1>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="left">Iterable</th>
<th align="left">Iterator</th>
</tr>
</thead>
<tbody><tr>
<td align="center">의미</td>
<td align="left">반복 가능한 객체</td>
<td align="left">값을 하나씩 꺼내는 객체</td>
</tr>
<tr>
<td align="center"><code>for</code>문</td>
<td align="left">가능</td>
<td align="left">가능</td>
</tr>
<tr>
<td align="center"><code>next()</code></td>
<td align="left">바로 사용 불가능할 수 있음</td>
<td align="left">가능</td>
</tr>
<tr>
<td align="center">예</td>
<td align="left"><code>list</code>, <code>tuple</code>, <code>str</code></td>
<td align="left"><code>list_iterator</code></td>
</tr>
<tr>
<td align="center">변환</td>
<td align="left"><code>iter()</code> 사용</td>
<td align="left">이미 iterator</td>
</tr>
</tbody></table>
<p>핵심 관계는</p>
<pre><code class="language-text">Iterable
   ↓
 iter()
   ↓
Iterator
   ↓
 next()
   ↓
값 하나씩 반환</code></pre>
<p>이다.</p>
<hr>
<h1 id="9-이터레이터-직접-만들기">9. 이터레이터 직접 만들기</h1>
<p>이번에는 클래스를 이용하여 직접 이터레이터를 만들어 보자.</p>
<p>이터레이터를 만들기 위해 중요한 메서드는 두 가지이다.</p>
<pre><code class="language-python">__iter__()
__next__()</code></pre>
<hr>
<h2 id="iter"><strong>iter</strong>()</h2>
<pre><code class="language-python">def __iter__(self):
    return self</code></pre>
<p>이터레이터 객체 자신을 반환한다.</p>
<p><code>iter()</code>가 호출될 때 사용된다.</p>
<hr>
<h2 id="next"><strong>next</strong>()</h2>
<pre><code class="language-python">def __next__(self):</code></pre>
<p>다음 값을 하나씩 반환한다.</p>
<p>더 이상 반환할 값이 없다면</p>
<pre><code class="language-python">raise StopIteration</code></pre>
<p>을 사용한다.</p>
<hr>
<h1 id="10-myiterator-만들기">10. MyIterator 만들기</h1>
<pre><code class="language-python">class MyIterator:

    def __init__(self, data):
        self.data = data
        self.position = 0

    def __iter__(self):
        return self

    def __next__(self):

        if self.position &gt;= len(self.data):
            raise StopIteration

        result = self.data[self.position]

        self.position += 1

        return result</code></pre>
<p>사용</p>
<pre><code class="language-python">i = MyIterator([1, 2, 3])

for item in i:
    print(item)</code></pre>
<p>결과</p>
<pre><code class="language-text">1
2
3</code></pre>
<hr>
<h1 id="11-init-이해하기">11. <strong>init</strong> 이해하기</h1>
<p>객체를 만들면</p>
<pre><code class="language-python">i = MyIterator([1, 2, 3])</code></pre>
<p>다음 값들이 저장된다.</p>
<pre><code class="language-python">self.data = [1, 2, 3]
self.position = 0</code></pre>
<p>여기서</p>
<pre><code class="language-python">self.position</code></pre>
<p>은 현재 어디까지 읽었는지를 기억하는 변수이다.</p>
<p>처음에는</p>
<pre><code class="language-text">position = 0</code></pre>
<p>이다.</p>
<hr>
<h1 id="12-next-동작-과정">12. <strong>next</strong> 동작 과정</h1>
<p>첫 번째 <code>next()</code>를 생각해 보자.</p>
<pre><code class="language-python">result = self.data[self.position]</code></pre>
<p>현재</p>
<pre><code class="language-text">self.position = 0</code></pre>
<p>이므로</p>
<pre><code class="language-python">self.data[0]</code></pre>
<p>즉</p>
<pre><code class="language-text">1</code></pre>
<p>을 가져온다.</p>
<p>그리고</p>
<pre><code class="language-python">self.position += 1</code></pre>
<p>을 실행한다.</p>
<p>따라서</p>
<pre><code class="language-text">position = 1</code></pre>
<p>이 된다.</p>
<hr>
<h2 id="다음-next">다음 next()</h2>
<p>이번에는</p>
<pre><code class="language-python">self.data[1]</code></pre>
<p>이므로</p>
<pre><code class="language-text">2</code></pre>
<p>를 반환한다.</p>
<p>이 과정을 반복하면</p>
<table>
<thead>
<tr>
<th align="center">position</th>
<th align="center">반환 값</th>
<th align="center">다음 position</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">2</td>
<td align="center">2</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">3</td>
<td align="center">3</td>
</tr>
</tbody></table>
<p>마지막에는</p>
<pre><code class="language-python">self.position &gt;= len(self.data)</code></pre>
<p>조건이 참이 된다.</p>
<pre><code class="language-text">3 &gt;= 3
→ True</code></pre>
<p>따라서</p>
<pre><code class="language-python">raise StopIteration</code></pre>
<p>이 실행된다.</p>
<hr>
<h1 id="13-역순-이터레이터-만들기">13. 역순 이터레이터 만들기</h1>
<p>이번에는 데이터를 반대로 가져오는 이터레이터를 만들어 보자.</p>
<pre><code class="language-python">class ReverseIterator:

    def __init__(self, data):
        self.data = data
        self.position = len(self.data) - 1

    def __iter__(self):
        return self

    def __next__(self):

        if self.position &lt; 0:
            raise StopIteration

        result = self.data[self.position]

        self.position -= 1

        return result</code></pre>
<p>사용</p>
<pre><code class="language-python">i = ReverseIterator([1, 2, 3])

for item in i:
    print(item)</code></pre>
<p>결과</p>
<pre><code class="language-text">3
2
1</code></pre>
<p>이번에는 시작 위치가</p>
<pre><code class="language-python">len(self.data) - 1</code></pre>
<p>이다.</p>
<p>데이터가</p>
<pre><code class="language-python">[1, 2, 3]</code></pre>
<p>이라면</p>
<pre><code class="language-text">len = 3

3 - 1
= 2</code></pre>
<p>이므로</p>
<pre><code class="language-python">self.data[2]</code></pre>
<p>부터 시작한다.</p>
<p>즉</p>
<pre><code class="language-text">3 → 2 → 1</code></pre>
<p>순서로 가져오게 된다.</p>
<hr>
<h1 id="14-제너레이터란">14. 제너레이터란?</h1>
<p>클래스로 이터레이터를 만들려면</p>
<pre><code class="language-python">__iter__()
__next__()</code></pre>
<p>를 직접 구현해야 했다.</p>
<p>하지만 <strong>제너레이터(Generator)</strong> 를 사용하면 훨씬 간단하게 이터레이터를 만들 수 있다.</p>
<blockquote>
<p>제너레이터는 이터레이터를 쉽게 만들 수 있도록 해 주는 함수이다.</p>
</blockquote>
<p>제너레이터의 가장 중요한 키워드는</p>
<pre><code class="language-python">yield</code></pre>
<p>이다.</p>
<hr>
<h1 id="15-yield-사용하기">15. yield 사용하기</h1>
<p>다음 함수를 살펴보자.</p>
<pre><code class="language-python">def mygen():
    yield &quot;a&quot;
    yield &quot;b&quot;
    yield &quot;c&quot;</code></pre>
<p>일반적인 함수에서는</p>
<pre><code class="language-python">return</code></pre>
<p>을 사용하지만 제너레이터에서는</p>
<pre><code class="language-python">yield</code></pre>
<p>를 사용한다.</p>
<hr>
<h2 id="제너레이터-객체-만들기">제너레이터 객체 만들기</h2>
<pre><code class="language-python">g = mygen()</code></pre>
<p>자료형을 확인하면</p>
<pre><code class="language-python">print(type(g))</code></pre>
<p>결과</p>
<pre><code class="language-text">&lt;class &#39;generator&#39;&gt;</code></pre>
<p>가 된다.</p>
<hr>
<h1 id="16-제너레이터에서-next-사용하기">16. 제너레이터에서 next() 사용하기</h1>
<pre><code class="language-python">print(next(g))</code></pre>
<p>결과</p>
<pre><code class="language-text">a</code></pre>
<p>다시 실행하면</p>
<pre><code class="language-python">print(next(g))</code></pre>
<p>결과</p>
<pre><code class="language-text">b</code></pre>
<p>다시 실행하면</p>
<pre><code class="language-python">print(next(g))</code></pre>
<p>결과</p>
<pre><code class="language-text">c</code></pre>
<p>한 번 더 실행하면</p>
<pre><code class="language-python">next(g)</code></pre>
<pre><code class="language-text">StopIteration</code></pre>
<p>이 발생한다.</p>
<p>즉 제너레이터 역시 <strong>이터레이터</strong>이기 때문에 <code>next()</code>를 사용할 수 있다.</p>
<hr>
<h1 id="17-yield는-return과-무엇이-다를까">17. yield는 return과 무엇이 다를까?</h1>
<p><code>return</code>은 값을 반환하면 함수가 완전히 종료된다.</p>
<pre><code class="language-python">def test():
    return 1</code></pre>
<pre><code class="language-text">함수 실행
 ↓
return
 ↓
값 반환
 ↓
함수 종료</code></pre>
<p>하지만 <code>yield</code>는 조금 다르다.</p>
<pre><code class="language-python">def mygen():
    yield &quot;a&quot;
    yield &quot;b&quot;
    yield &quot;c&quot;</code></pre>
<p>첫 번째 <code>next()</code>를 호출하면</p>
<pre><code class="language-text">함수 시작
 ↓
yield &quot;a&quot;
 ↓
&quot;a&quot; 반환
 ↓
함수 일시 정지</code></pre>
<p>한다.</p>
<p>다음 <code>next()</code>를 호출하면 처음부터 다시 실행하는 것이 아니라 <strong>멈췄던 위치부터 다시 실행</strong>한다.</p>
<pre><code class="language-text">다음 next()
 ↓
이전 yield 다음부터 시작
 ↓
yield &quot;b&quot;
 ↓
&quot;b&quot; 반환
 ↓
다시 일시 정지</code></pre>
<hr>
<h1 id="18-음악-플레이어처럼-생각하기">18. 음악 플레이어처럼 생각하기</h1>
<p><code>yield</code>는 음악 플레이어의 일시 정지와 비슷하다.</p>
<pre><code class="language-text">▶ 재생

코드 실행
   ↓
yield
   ↓
⏸ 일시 정지

next()
   ↓
▶ 다시 재생
   ↓
다음 yield
   ↓
⏸ 다시 정지</code></pre>
<p>즉 제너레이터는 <strong>실행 상태를 기억한다.</strong></p>
<hr>
<h1 id="19-제너레이터-예제">19. 제너레이터 예제</h1>
<p>다음과 같이 숫자의 제곱을 하나씩 반환하는 제너레이터를 만들 수 있다.</p>
<pre><code class="language-python">def mygen():

    for i in range(1, 1000):
        result = i * i

        yield result</code></pre>
<p>제너레이터 객체를 만든다.</p>
<pre><code class="language-python">gen = mygen()</code></pre>
<p>그리고</p>
<pre><code class="language-python">print(next(gen))
print(next(gen))
print(next(gen))</code></pre>
<p>을 실행하면</p>
<pre><code class="language-text">1
4
9</code></pre>
<p>가 출력된다.</p>
<p>중요한 점은 <code>1~999</code>의 제곱을 한꺼번에 모두 만드는 것이 아니라는 것이다.</p>
<pre><code class="language-text">next()
 ↓
1² 계산
 ↓
1 반환
 ↓
정지

next()
 ↓
2² 계산
 ↓
4 반환
 ↓
정지

next()
 ↓
3² 계산
 ↓
9 반환</code></pre>
<p>처럼 <strong>필요할 때마다 하나씩 계산한다.</strong></p>
<hr>
<h1 id="20-제너레이터-표현식">20. 제너레이터 표현식</h1>
<p>앞의 코드는 다음과 같다.</p>
<pre><code class="language-python">def mygen():

    for i in range(1, 1000):
        yield i * i</code></pre>
<p>파이썬에서는 이것을 더 간단하게 만들 수도 있다.</p>
<pre><code class="language-python">gen = (i * i for i in range(1, 1000))</code></pre>
<p>이것을</p>
<blockquote>
<p><strong>제너레이터 표현식(Generator Expression)</strong></p>
</blockquote>
<p>이라고 한다.</p>
<hr>
<h1 id="21-리스트-컴프리헨션과-비교">21. 리스트 컴프리헨션과 비교</h1>
<p>리스트 컴프리헨션은</p>
<pre><code class="language-python">numbers = [i * i for i in range(1, 1000)]</code></pre>
<p>처럼 <code>[]</code>를 사용한다.</p>
<p>제너레이터 표현식은</p>
<pre><code class="language-python">numbers = (i * i for i in range(1, 1000))</code></pre>
<p>처럼 <code>()</code>를 사용한다.</p>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="center">리스트 컴프리헨션</th>
<th align="center">제너레이터 표현식</th>
</tr>
</thead>
<tbody><tr>
<td align="center">괄호</td>
<td align="center"><code>[ ]</code></td>
<td align="center"><code>( )</code></td>
</tr>
<tr>
<td align="center">결과</td>
<td align="center">리스트</td>
<td align="center">제너레이터</td>
</tr>
<tr>
<td align="center">값 생성</td>
<td align="center">모두 생성</td>
<td align="center">필요할 때 생성</td>
</tr>
<tr>
<td align="center">메모리</td>
<td align="center">상대적으로 많이 사용</td>
<td align="center">효율적</td>
</tr>
</tbody></table>
<hr>
<h2 id="주의">주의</h2>
<pre><code class="language-python">(i * i for i in range(1, 1000))</code></pre>
<p>에서 소괄호 <code>()</code>를 사용했다고 해서 튜플이 아니다.</p>
<p>자료형을 확인하면</p>
<pre><code class="language-python">gen = (i * i for i in range(1, 1000))

print(type(gen))</code></pre>
<p>결과는</p>
<pre><code class="language-text">&lt;class &#39;generator&#39;&gt;</code></pre>
<p>이다.</p>
<hr>
<h1 id="22-클래스-이터레이터와-제너레이터">22. 클래스 이터레이터와 제너레이터</h1>
<p>동일한 기능을 클래스로 만들 수도 있다.</p>
<pre><code class="language-python">class MyIterator:

    def __init__(self):
        self.data = 1

    def __iter__(self):
        return self

    def __next__(self):

        if self.data &gt;= 1000:
            raise StopIteration

        result = self.data * self.data

        self.data += 1

        return result</code></pre>
<p>하지만 같은 작업을 제너레이터 표현식으로 만들면</p>
<pre><code class="language-python">gen = (i * i for i in range(1, 1000))</code></pre>
<p>한 줄이면 된다.</p>
<p>간단한 반복 기능이라면 제너레이터를 이용하는 것이 훨씬 간결하다.</p>
<hr>
<h1 id="23-제너레이터를-사용하는-이유">23. 제너레이터를 사용하는 이유</h1>
<p>제너레이터의 가장 큰 장점 중 하나는</p>
<blockquote>
<p><strong>필요한 값만 그때그때 계산한다는 것</strong></p>
</blockquote>
<p>이다.</p>
<p>예를 들어 시간이 오래 걸리는 함수가 있다고 해보자.</p>
<pre><code class="language-python">import time

def longtime_job():

    print(&quot;job start&quot;)

    time.sleep(1)

    return &quot;done&quot;</code></pre>
<p>한 번 실행하는 데 약 1초가 걸린다고 가정한다.</p>
<hr>
<h1 id="24-리스트-컴프리헨션-사용">24. 리스트 컴프리헨션 사용</h1>
<pre><code class="language-python">list_job = [longtime_job() for i in range(5)]

print(list_job[0])</code></pre>
<p>리스트 컴프리헨션은 리스트를 만들기 위해 <code>longtime_job()</code>을 5번 모두 실행한다.</p>
<pre><code class="language-text">job start
job start
job start
job start
job start
done</code></pre>
<p>첫 번째 결과만 사용하고 싶어도 이미 모든 작업을 실행한다.</p>
<p>동작 과정은</p>
<pre><code class="language-text">리스트 생성 시작
 ↓
작업 1 실행
 ↓
작업 2 실행
 ↓
작업 3 실행
 ↓
작업 4 실행
 ↓
작업 5 실행
 ↓
모든 결과 저장
 ↓
첫 번째 결과 사용</code></pre>
<p>과 같다.</p>
<hr>
<h1 id="25-제너레이터-표현식-사용">25. 제너레이터 표현식 사용</h1>
<p>이번에는 제너레이터로 만들어 보자.</p>
<pre><code class="language-python">gen_job = (longtime_job() for i in range(5))

print(next(gen_job))</code></pre>
<p>결과</p>
<pre><code class="language-text">job start
done</code></pre>
<p><code>gen_job</code>을 만드는 순간에는 <code>longtime_job()</code>을 실행하지 않는다.</p>
<p>실제로</p>
<pre><code class="language-python">next(gen_job)</code></pre>
<p>을 호출하는 순간 첫 번째 작업만 실행한다.</p>
<pre><code class="language-text">제너레이터 생성
 ↓
아무 작업도 하지 않음

next()
 ↓
첫 번째 작업 실행
 ↓
결과 반환
 ↓
정지</code></pre>
<p>따라서 첫 번째 값만 필요하다면 나머지 작업은 실행하지 않아도 된다.</p>
<hr>
<h1 id="26-lazy-evaluation">26. Lazy Evaluation</h1>
<p>제너레이터처럼</p>
<blockquote>
<p><strong>필요한 순간에 값을 계산하는 방식</strong></p>
</blockquote>
<p>을</p>
<p><strong>느긋한 계산법(Lazy Evaluation)</strong></p>
<p>이라고 한다.</p>
<p>반대로 리스트는 모든 결과를 먼저 만들어 놓는다.</p>
<pre><code class="language-text">리스트

값1 생성
값2 생성
값3 생성
값4 생성
값5 생성
 ↓
모두 메모리에 저장</code></pre>
<p>제너레이터는</p>
<pre><code class="language-text">Generator

next()
 ↓
값1 생성

next()
 ↓
값2 생성

next()
 ↓
값3 생성</code></pre>
<p>처럼 요청받을 때 값을 생성한다.</p>
<hr>
<h1 id="27-메모리-차이">27. 메모리 차이</h1>
<p>예를 들어 매우 많은 데이터를 처리한다고 생각해 보자.</p>
<p>리스트</p>
<pre><code class="language-python">numbers = [i for i in range(10000000)]</code></pre>
<p>는 많은 값을 한꺼번에 만들어 메모리에 저장한다.</p>
<p>반면</p>
<pre><code class="language-python">numbers = (i for i in range(10000000))</code></pre>
<p>제너레이터는 모든 값을 미리 저장하지 않는다.</p>
<p>필요할 때</p>
<pre><code class="language-python">next(numbers)</code></pre>
<p>를 호출해 하나씩 만든다.</p>
<p>따라서 대용량 데이터를 처리할 때 메모리를 효율적으로 사용할 수 있다.</p>
<hr>
<h1 id="28-이터레이터와-제너레이터-관계">28. 이터레이터와 제너레이터 관계</h1>
<p>둘의 관계를 정리하면 다음과 같다.</p>
<pre><code class="language-text">Iterable
   ↓
iter()
   ↓
Iterator</code></pre>
<p>제너레이터 역시</p>
<pre><code class="language-text">Generator
   ↓
Iterator의 한 종류</code></pre>
<p>라고 생각할 수 있다.</p>
<p>즉 제너레이터에는</p>
<pre><code class="language-python">next()</code></pre>
<p>를 바로 사용할 수 있다.</p>
<hr>
<h1 id="29-이터레이터와-제너레이터-비교">29. 이터레이터와 제너레이터 비교</h1>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="left">이터레이터</th>
<th align="left">제너레이터</th>
</tr>
</thead>
<tbody><tr>
<td align="center">목적</td>
<td align="left">값을 하나씩 반환</td>
<td align="left">값을 하나씩 반환</td>
</tr>
<tr>
<td align="center"><code>next()</code></td>
<td align="left">사용 가능</td>
<td align="left">사용 가능</td>
</tr>
<tr>
<td align="center">종료</td>
<td align="left"><code>StopIteration</code></td>
<td align="left"><code>StopIteration</code></td>
</tr>
<tr>
<td align="center">만드는 방법</td>
<td align="left"><code>__iter__</code>, <code>__next__</code></td>
<td align="left"><code>yield</code></td>
</tr>
<tr>
<td align="center">상태 기억</td>
<td align="left">O</td>
<td align="left">O</td>
</tr>
<tr>
<td align="center">구현</td>
<td align="left">비교적 복잡</td>
<td align="left">간단</td>
</tr>
<tr>
<td align="center">대표 키워드</td>
<td align="left"><code>iter</code>, <code>next</code></td>
<td align="left"><code>yield</code></td>
</tr>
</tbody></table>
<p>쉽게 말하면</p>
<pre><code class="language-text">이터레이터

직접 구조를 구현
__iter__
__next__</code></pre>
<p>반면</p>
<pre><code class="language-text">제너레이터

yield 사용
 ↓
파이썬이 이터레이터 구조를 자동으로 만들어 줌</code></pre>
<p>이라고 생각하면 된다.</p>
<hr>
<h1 id="30-전체-흐름-정리">30. 전체 흐름 정리</h1>
<p>이번 장의 흐름을 한 번에 정리하면 다음과 같다.</p>
<pre><code class="language-text">리스트 [1, 2, 3]
      ↓
반복 가능
      ↓
Iterable
      ↓
iter()
      ↓
Iterator
      ↓
next()
      ↓
1
      ↓
next()
      ↓
2
      ↓
next()
      ↓
3
      ↓
StopIteration</code></pre>
<p>그리고 직접 이터레이터를 간단하게 만드는 방법이</p>
<pre><code class="language-text">Generator</code></pre>
<p>이다.</p>
<pre><code class="language-text">일반 함수
   ↓
yield 사용
   ↓
Generator 함수
   ↓
호출
   ↓
Generator 객체
   ↓
next()
   ↓
yield 값 반환
   ↓
실행 위치 기억
   ↓
다음 next()
   ↓
중단한 위치부터 다시 실행</code></pre>
<hr>
<h1 id="📌-핵심-정리">📌 핵심 정리</h1>
<h2 id="iterable">Iterable</h2>
<p><code>for</code>문으로 반복할 수 있는 객체이다.</p>
<pre><code class="language-python">[1, 2, 3]
&quot;Python&quot;
(1, 2, 3)</code></pre>
<hr>
<h2 id="iterator">Iterator</h2>
<p><code>next()</code>를 이용해 값을 하나씩 가져올 수 있는 객체이다.</p>
<pre><code class="language-python">a = [1, 2, 3]

ia = iter(a)

print(next(ia))</code></pre>
<pre><code class="language-text">1</code></pre>
<hr>
<h2 id="iter-1">iter()</h2>
<p>Iterable 객체를 Iterator로 만든다.</p>
<pre><code class="language-python">ia = iter([1, 2, 3])</code></pre>
<hr>
<h2 id="next-1">next()</h2>
<p>이터레이터의 다음 값을 가져온다.</p>
<pre><code class="language-python">next(ia)</code></pre>
<p>더 이상 값이 없다면</p>
<pre><code class="language-text">StopIteration</code></pre>
<p>이 발생한다.</p>
<hr>
<h2 id="이터레이터-클래스">이터레이터 클래스</h2>
<p>직접 이터레이터를 만들 때는</p>
<pre><code class="language-python">__iter__()
__next__()</code></pre>
<p>를 구현한다.</p>
<pre><code class="language-python">class MyIterator:

    def __iter__(self):
        return self

    def __next__(self):
        ...</code></pre>
<hr>
<h2 id="generator">Generator</h2>
<p><code>yield</code>를 이용해 간단하게 이터레이터를 만들 수 있다.</p>
<pre><code class="language-python">def mygen():
    yield 1
    yield 2
    yield 3</code></pre>
<hr>
<h2 id="yield">yield</h2>
<p>값을 반환하고 함수 실행을 완전히 종료하는 것이 아니라 <strong>현재 위치에서 일시 정지한다.</strong></p>
<pre><code class="language-text">실행
 ↓
yield
 ↓
값 반환 + 일시 정지
 ↓
next()
 ↓
중단한 위치부터 다시 실행</code></pre>
<hr>
<h2 id="제너레이터-표현식">제너레이터 표현식</h2>
<pre><code class="language-python">gen = (i * i for i in range(1, 1000))</code></pre>
<p>리스트 컴프리헨션</p>
<pre><code class="language-python">[i * i for i in range(1, 1000)]</code></pre>
<p>과 비슷하지만 <code>[]</code> 대신 <code>()</code>를 사용한다.</p>
<hr>
<h2 id="lazy-evaluation">Lazy Evaluation</h2>
<p>제너레이터는 모든 값을 미리 계산하지 않는다.</p>
<pre><code class="language-text">필요할 때
 ↓
하나 계산
 ↓
반환
 ↓
정지</code></pre>
<p>따라서</p>
<ul>
<li>대용량 데이터 처리</li>
<li>메모리 절약</li>
<li>시간이 오래 걸리는 작업</li>
<li>필요한 결과만 가져오는 작업</li>
</ul>
<p>등에서 유용하다.</p>
<hr>
<h1 id="💡-가장-중요한-차이">💡 가장 중요한 차이</h1>
<pre><code class="language-text">List

[1, 2, 3, 4, 5]

모든 값을
미리 만들어 저장</code></pre>
<pre><code class="language-text">Generator

next() → 1
next() → 2
next() → 3

필요할 때
하나씩 생성</code></pre>
<p>그리고 가장 중요한 흐름은</p>
<pre><code class="language-text">Iterable
   ↓
iter()
   ↓
Iterator
   ↓
next()
   ↓
값 하나씩 반환</code></pre>
<p>이다.</p>
<p>제너레이터는 이 Iterator를 직접 복잡하게 만들지 않고</p>
<pre><code class="language-python">yield</code></pre>
<p>하나로 쉽게 만들 수 있게 해 준다.</p>
<blockquote>
<p><strong><code>Iterable</code>은 반복할 수 있는 대상, <code>Iterator</code>는 그 대상에서 값을 하나씩 꺼내는 객체, <code>Generator</code>는 그런 Iterator를 <code>yield</code>로 쉽게 만드는 방법이라고 이해하면 된다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY16(클로저와 데코레이터)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY16%ED%81%B4%EB%A1%9C%EC%A0%80%EC%99%80-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY16%ED%81%B4%EB%A1%9C%EC%A0%80%EC%99%80-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0</guid>
            <pubDate>Mon, 28 Sep 2026 13:44:41 GMT</pubDate>
            <description><![CDATA[<h1 id="07-2-클로저와-데코레이터">07-2 클로저와 데코레이터</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>1. 클로저란?</strong></span></li>
<li><span style="color:slateblue"><strong>2. 클래스를 이용한 방법</strong></span></li>
<li><span style="color:slateblue"><strong>3. <code>__call__</code> 메서드</strong></span></li>
<li><span style="color:slateblue"><strong>4. 클로저 만들기</strong></span></li>
<li><span style="color:slateblue"><strong>5. 데코레이터란?</strong></span></li>
<li><span style="color:slateblue"><strong>6. <code>@</code>를 이용한 데코레이터</strong></span></li>
<li><span style="color:slateblue"><strong>7. <code>*args</code>, `</strong>kwargs`**</span></li>
</ul>
<hr>
<h1 id="1-클로저란">1. 클로저란?</h1>
<p><strong>클로저(Closure)</strong> 는 쉽게 말하면</p>
<blockquote>
<p><strong>외부 함수가 종료된 이후에도 외부 함수의 변수를 기억하고 사용하는 내부 함수</strong></p>
</blockquote>
<p>이다.</p>
<p>말로만 보면 조금 어렵기 때문에 예제를 통해 이해해 보자.</p>
<hr>
<h2 id="항상-3을-곱하는-함수">항상 3을 곱하는 함수</h2>
<p>어떤 숫자를 입력하면 항상 <code>3</code>을 곱해 주는 함수를 만들어 보자.</p>
<pre><code class="language-python">def mul3(n):
    return n * 3</code></pre>
<p>사용</p>
<pre><code class="language-python">print(mul3(10))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">30</code></pre>
<p>이번에는 항상 <code>5</code>를 곱하는 함수가 필요하다고 해보자.</p>
<pre><code class="language-python">def mul5(n):
    return n * 5</code></pre>
<p>사용</p>
<pre><code class="language-python">print(mul5(10))</code></pre>
<p>결과</p>
<pre><code class="language-text">50</code></pre>
<p>하지만 필요한 숫자가 계속 늘어난다면</p>
<pre><code class="language-python">mul3()
mul5()
mul6()
mul7()
mul8()
...</code></pre>
<p>처럼 함수를 계속 만들어야 한다.</p>
<p>이것은 비효율적이다.</p>
<hr>
<h1 id="2-클래스를-이용한-방법">2. 클래스를 이용한 방법</h1>
<p>먼저 클래스를 사용하면 이 문제를 해결할 수 있다.</p>
<pre><code class="language-python">class Mul:
    def __init__(self, m):
        self.m = m

    def mul(self, n):
        return self.m * n</code></pre>
<p>객체를 만들어 보자.</p>
<pre><code class="language-python">mul3 = Mul(3)
mul5 = Mul(5)</code></pre>
<p>여기서</p>
<pre><code class="language-python">Mul(3)</code></pre>
<p>을 실행하면</p>
<pre><code class="language-python">self.m = 3</code></pre>
<p>이 저장된다.</p>
<p>따라서</p>
<pre><code class="language-python">mul3.mul(10)</code></pre>
<p>을 실행하면</p>
<pre><code class="language-text">self.m * n
3 * 10
= 30</code></pre>
<p>이 된다.</p>
<p>전체 코드는 다음과 같다.</p>
<pre><code class="language-python">class Mul:
    def __init__(self, m):
        self.m = m

    def mul(self, n):
        return self.m * n


mul3 = Mul(3)
mul5 = Mul(5)

print(mul3.mul(10))
print(mul5.mul(10))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">30
50</code></pre>
<hr>
<h1 id="3-__call__-메서드">3. <code>__call__</code> 메서드</h1>
<p>앞에서 객체를 사용하려면</p>
<pre><code class="language-python">mul3.mul(10)</code></pre>
<p>처럼 메서드를 직접 호출해야 했다.</p>
<p>하지만 <code>__call__()</code>을 사용하면 <strong>객체 자체를 함수처럼 호출</strong>할 수 있다.</p>
<pre><code class="language-python">class Mul:
    def __init__(self, m):
        self.m = m

    def __call__(self, n):
        return self.m * n</code></pre>
<p>이제</p>
<pre><code class="language-python">mul3 = Mul(3)</code></pre>
<p>으로 객체를 만든 뒤</p>
<pre><code class="language-python">print(mul3(10))</code></pre>
<p>처럼 사용할 수 있다.</p>
<p>실행 결과</p>
<pre><code class="language-text">30</code></pre>
<hr>
<h2 id="__call__-동작-이해하기"><code>__call__</code> 동작 이해하기</h2>
<p>다음 코드를 실행하면</p>
<pre><code class="language-python">mul3(10)</code></pre>
<p>파이썬은 내부적으로</p>
<pre><code class="language-python">mul3.__call__(10)</code></pre>
<p>을 호출한다고 생각하면 된다.</p>
<p>즉,</p>
<pre><code class="language-text">mul3(10)
    ↓
__call__(10)
    ↓
self.m * 10
    ↓
3 * 10
    ↓
30</code></pre>
<p>이다.</p>
<hr>
<h1 id="4-클로저-만들기">4. 클로저 만들기</h1>
<p>클래스를 사용하지 않고 함수만으로도 같은 기능을 만들 수 있다.</p>
<pre><code class="language-python">def mul(m):
    def wrapper(n):
        return m * n

    return wrapper</code></pre>
<p>처음 보면 가장 헷갈리는 부분은 이것이다.</p>
<pre><code class="language-python">return wrapper</code></pre>
<p><code>wrapper()</code>를 실행하는 것이 아니라 <strong>함수 자체를 반환</strong>하고 있다.</p>
<hr>
<h2 id="함수를-변수에-저장할-수-있다">함수를 변수에 저장할 수 있다</h2>
<p>파이썬에서는 함수도 객체이기 때문에 변수에 저장할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python">def hello():
    print(&quot;Hello&quot;)

a = hello</code></pre>
<p>라고 하면 <code>a</code>에는 <code>hello</code> 함수 자체가 저장된다.</p>
<p>따라서</p>
<pre><code class="language-python">a()</code></pre>
<p>를 실행하면</p>
<pre><code class="language-text">Hello</code></pre>
<p>가 출력된다.</p>
<hr>
<h1 id="5-클로저-동작-과정">5. 클로저 동작 과정</h1>
<p>다시 다음 코드를 살펴보자.</p>
<pre><code class="language-python">def mul(m):
    def wrapper(n):
        return m * n

    return wrapper</code></pre>
<p>그리고</p>
<pre><code class="language-python">mul3 = mul(3)</code></pre>
<p>을 실행한다고 해보자.</p>
<hr>
<h2 id="①-mul3-호출">① <code>mul(3)</code> 호출</h2>
<pre><code class="language-python">mul(3)</code></pre>
<p>이므로</p>
<pre><code class="language-text">m = 3</code></pre>
<p>이 된다.</p>
<hr>
<h2 id="②-내부-함수-생성">② 내부 함수 생성</h2>
<pre><code class="language-python">def wrapper(n):
    return m * n</code></pre>
<p><code>wrapper()</code> 함수가 만들어진다.</p>
<p>이 함수는 외부 함수의 변수 <code>m</code>을 사용하고 있다.</p>
<hr>
<h2 id="③-wrapper-함수-반환">③ wrapper 함수 반환</h2>
<pre><code class="language-python">return wrapper</code></pre>
<p>에 의해 <code>wrapper</code> 함수 자체가 반환된다.</p>
<p>결과적으로</p>
<pre><code class="language-python">mul3 = mul(3)</code></pre>
<p>에서 <code>mul3</code>에는 <code>wrapper</code> 함수가 들어간다.</p>
<hr>
<h2 id="④-mul-함수-종료">④ mul 함수 종료</h2>
<p>여기서 중요한 점이 있다.</p>
<p><code>mul()</code> 함수는 이미 실행이 끝났다.</p>
<p>그런데</p>
<pre><code class="language-python">mul3(10)</code></pre>
<p>을 실행하면 결과가 어떻게 될까?</p>
<pre><code class="language-text">30</code></pre>
<p>이 출력된다.</p>
<hr>
<h2 id="왜-m--3이-남아-있을까">왜 <code>m = 3</code>이 남아 있을까?</h2>
<p>원래라면 <code>mul()</code> 함수가 끝났으므로 내부 변수 <code>m</code>도 사라질 것처럼 보인다.</p>
<p>하지만 반환된 <code>wrapper()</code> 함수가 <code>m</code>을 사용하고 있기 때문에</p>
<pre><code class="language-text">m = 3</code></pre>
<p>이라는 값을 기억하고 있다.</p>
<p>이것이 바로 <strong>클로저</strong>이다.</p>
<pre><code class="language-text">mul(3)
  ↓
m = 3
  ↓
wrapper 함수 생성
  ↓
wrapper가 m = 3을 기억
  ↓
mul 함수 종료
  ↓
mul3(10)
  ↓
3 × 10
  ↓
30</code></pre>
<hr>
<h1 id="6-여러-개의-클로저-만들기">6. 여러 개의 클로저 만들기</h1>
<p>다음처럼 사용할 수 있다.</p>
<pre><code class="language-python">def mul(m):
    def wrapper(n):
        return m * n

    return wrapper


mul3 = mul(3)
mul5 = mul(5)

print(mul3(10))
print(mul5(10))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">30
50</code></pre>
<p>여기서</p>
<pre><code class="language-python">mul3 = mul(3)</code></pre>
<p>은 <code>m = 3</code>을 기억하고,</p>
<pre><code class="language-python">mul5 = mul(5)</code></pre>
<p>는 <code>m = 5</code>를 기억한다.</p>
<p>즉 서로 다른 값을 기억하는 함수가 만들어진다.</p>
<pre><code class="language-text">mul3
 └─ m = 3 기억

mul5
 └─ m = 5 기억</code></pre>
<hr>
<h2 id="📌-클로저-핵심">📌 클로저 핵심</h2>
<pre><code class="language-python">def outer(a):

    def inner(b):
        return a + b

    return inner</code></pre>
<p>이 구조를 기억하면 된다.</p>
<pre><code class="language-text">외부 함수
   ↓
외부 변수 생성
   ↓
내부 함수 생성
   ↓
내부 함수가 외부 변수 사용
   ↓
내부 함수 반환
   ↓
외부 함수 종료
   ↓
내부 함수는 외부 변수를 계속 기억</code></pre>
<hr>
<h1 id="7-데코레이터란">7. 데코레이터란?</h1>
<p><strong>데코레이터(Decorator)</strong> 는</p>
<blockquote>
<p><strong>기존 함수의 코드를 직접 수정하지 않고 새로운 기능을 추가하는 방법</strong></p>
</blockquote>
<p>이다.</p>
<p>데코레이터를 이해하려면 앞에서 배운 <strong>클로저</strong>가 중요하다.</p>
<hr>
<h1 id="8-함수-실행-시간-측정하기">8. 함수 실행 시간 측정하기</h1>
<p>다음 함수가 있다고 해보자.</p>
<pre><code class="language-python">def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)</code></pre>
<p>이 함수가 실행되는 시간을 측정하고 싶다면 <code>time</code> 모듈을 사용할 수 있다.</p>
<pre><code class="language-python">import time

def myfunc():
    start = time.time()

    print(&quot;함수가 실행됩니다.&quot;)

    end = time.time()

    print(&quot;함수 수행시간: %f 초&quot; % (end - start))</code></pre>
<p>실행</p>
<pre><code class="language-python">myfunc()</code></pre>
<p>결과</p>
<pre><code class="language-text">함수가 실행됩니다.
함수 수행시간: 0.0000XX 초</code></pre>
<hr>
<h2 id="문제점">문제점</h2>
<p>함수가 한 개라면 괜찮다.</p>
<p>하지만 실행 시간을 측정해야 하는 함수가</p>
<pre><code class="language-text">func1()
func2()
func3()
func4()
...</code></pre>
<p>처럼 많다면 모든 함수에</p>
<pre><code class="language-python">start = time.time()

...

end = time.time()</code></pre>
<p>을 반복해서 작성해야 한다.</p>
<p>이런 경우 <strong>데코레이터</strong>를 사용할 수 있다.</p>
<hr>
<h1 id="9-실행-시간을-측정하는-데코레이터-만들기">9. 실행 시간을 측정하는 데코레이터 만들기</h1>
<pre><code class="language-python">import time

def elapsed(original_func):

    def wrapper():
        start = time.time()

        result = original_func()

        end = time.time()

        print(&quot;함수 수행시간: %f 초&quot; % (end - start))

        return result

    return wrapper</code></pre>
<p>여기에서도 클로저와 같은 구조가 사용된다.</p>
<pre><code class="language-text">elapsed()
   ↓
wrapper() 생성
   ↓
wrapper 반환</code></pre>
<p>하지만 이번에는 <code>elapsed()</code>가 <strong>함수를 입력값으로 받는다.</strong></p>
<pre><code class="language-python">def elapsed(original_func):</code></pre>
<hr>
<h1 id="10-함수도-인수로-전달할-수-있다">10. 함수도 인수로 전달할 수 있다</h1>
<p>다음 함수가 있다고 해보자.</p>
<pre><code class="language-python">def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)</code></pre>
<p>그리고</p>
<pre><code class="language-python">decorated_myfunc = elapsed(myfunc)</code></pre>
<p>을 실행한다.</p>
<p>여기서 <code>myfunc()</code>라고 하지 않은 점이 중요하다.</p>
<pre><code class="language-python">elapsed(myfunc)</code></pre>
<p>처럼 함수 자체를 전달했다.</p>
<hr>
<h2 id="동작-과정">동작 과정</h2>
<pre><code class="language-python">decorated_myfunc = elapsed(myfunc)</code></pre>
<p>실행</p>
<pre><code class="language-text">myfunc 함수
     ↓
elapsed()에 전달
     ↓
original_func = myfunc
     ↓
wrapper 함수 생성
     ↓
wrapper 함수 반환
     ↓
decorated_myfunc에 저장</code></pre>
<p>이제</p>
<pre><code class="language-python">decorated_myfunc()</code></pre>
<p>을 실행하면 실제로는 <code>wrapper()</code>가 실행된다.</p>
<hr>
<h2 id="wrapper-내부">wrapper 내부</h2>
<pre><code class="language-python">def wrapper():

    start = time.time()

    result = original_func()

    end = time.time()

    print(&quot;함수 수행시간: %f 초&quot; % (end - start))

    return result</code></pre>
<p>여기서</p>
<pre><code class="language-python">original_func()</code></pre>
<p>는 처음 전달했던</p>
<pre><code class="language-python">myfunc()</code></pre>
<p>를 의미한다.</p>
<p>따라서</p>
<pre><code class="language-text">wrapper 실행
    ↓
시작 시간 저장
    ↓
myfunc 실행
    ↓
종료 시간 저장
    ↓
실행 시간 출력</code></pre>
<p>이 된다.</p>
<hr>
<h1 id="11-데코레이터-구조-이해하기">11. 데코레이터 구조 이해하기</h1>
<p>전체 코드를 다시 보면</p>
<pre><code class="language-python">import time

def elapsed(original_func):

    def wrapper():
        start = time.time()

        result = original_func()

        end = time.time()

        print(&quot;함수 수행시간: %f 초&quot; % (end - start))

        return result

    return wrapper


def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)


decorated_myfunc = elapsed(myfunc)

decorated_myfunc()</code></pre>
<p>핵심은</p>
<pre><code class="language-python">elapsed(myfunc)</code></pre>
<p>가 기존 함수 <code>myfunc</code>를 감싸는 새로운 <code>wrapper</code> 함수를 만들어 준다는 것이다.</p>
<p>그래서 데코레이터를</p>
<blockquote>
<p><strong>함수를 꾸며 주는 함수</strong></p>
</blockquote>
<p>라고 생각할 수 있다.</p>
<hr>
<h1 id="12-를-이용한-데코레이터">12. <code>@</code>를 이용한 데코레이터</h1>
<p>파이썬에서는 위의 코드를 더욱 간단하게 표현할 수 있다.</p>
<pre><code class="language-python">@elapsed
def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)</code></pre>
<p>전체 코드는 다음과 같다.</p>
<pre><code class="language-python">import time

def elapsed(original_func):

    def wrapper():
        start = time.time()

        result = original_func()

        end = time.time()

        print(&quot;함수 수행시간: %f 초&quot; % (end - start))

        return result

    return wrapper


@elapsed
def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)


myfunc()</code></pre>
<hr>
<h2 id="elapsed의-의미"><code>@elapsed</code>의 의미</h2>
<p>다음 코드는</p>
<pre><code class="language-python">@elapsed
def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)</code></pre>
<p>개념적으로 다음과 같다.</p>
<pre><code class="language-python">myfunc = elapsed(myfunc)</code></pre>
<p>즉</p>
<pre><code class="language-text">기존 myfunc
      ↓
elapsed에 전달
      ↓
wrapper 반환
      ↓
myfunc에 다시 저장</code></pre>
<p>된다.</p>
<p>따라서 이제</p>
<pre><code class="language-python">myfunc()</code></pre>
<p>을 실행하면 단순히 원래 <code>myfunc()</code>만 실행되는 것이 아니라 <code>wrapper()</code>가 실행된다.</p>
<hr>
<h1 id="13-데코레이터-실행-흐름">13. 데코레이터 실행 흐름</h1>
<pre><code class="language-python">@elapsed
def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)</code></pre>
<p>그리고</p>
<pre><code class="language-python">myfunc()</code></pre>
<p>을 실행하면</p>
<pre><code class="language-text">myfunc()
   ↓
wrapper() 실행
   ↓
시작 시간 기록
   ↓
원래 myfunc() 실행
   ↓
종료 시간 기록
   ↓
수행 시간 출력</code></pre>
<p>이 된다.</p>
<hr>
<h1 id="14-매개변수가-있는-함수에서는-문제가-발생한다">14. 매개변수가 있는 함수에서는 문제가 발생한다</h1>
<p>이번에는 <code>myfunc()</code>가 값을 입력받도록 변경해 보자.</p>
<pre><code class="language-python">@elapsed
def myfunc(msg):
    print(&quot;&#39;%s&#39;을 출력합니다.&quot; % msg)</code></pre>
<p>사용</p>
<pre><code class="language-python">myfunc(&quot;You need python&quot;)</code></pre>
<p>그런데 기존의 <code>wrapper()</code>는 다음과 같다.</p>
<pre><code class="language-python">def wrapper():</code></pre>
<p>아무런 인수도 받을 수 없다.</p>
<p>그런데 우리는</p>
<pre><code class="language-python">myfunc(&quot;You need python&quot;)</code></pre>
<p>처럼 인수를 전달하고 있다.</p>
<p>따라서 오류가 발생한다.</p>
<pre><code class="language-text">TypeError</code></pre>
<hr>
<h1 id="15-args와-kwargs">15. <code>*args</code>와 <code>**kwargs</code></h1>
<p>이 문제를 해결하기 위해</p>
<pre><code class="language-python">*args</code></pre>
<p>와</p>
<pre><code class="language-python">**kwargs</code></pre>
<p>를 사용할 수 있다.</p>
<hr>
<h2 id="args"><code>*args</code></h2>
<p><code>*args</code>는 여러 개의 일반 인수를 받을 수 있다.</p>
<pre><code class="language-python">def func(*args):
    print(args)</code></pre>
<p>실행</p>
<pre><code class="language-python">func(1, 2, 3)</code></pre>
<p>결과</p>
<pre><code class="language-text">(1, 2, 3)</code></pre>
<p>즉 <code>args</code>는 <strong>튜플</strong>이 된다.</p>
<pre><code class="language-text">*args
  ↓
일반적인 여러 인수
  ↓
튜플</code></pre>
<hr>
<h1 id="16-kwargs">16. <code>**kwargs</code></h1>
<p><code>**kwargs</code>는</p>
<pre><code class="language-python">이름=값</code></pre>
<p>형태의 인수를 받을 수 있다.</p>
<pre><code class="language-python">def func(**kwargs):
    print(kwargs)</code></pre>
<p>실행</p>
<pre><code class="language-python">func(name=&quot;pingu&quot;, age=20)</code></pre>
<p>결과</p>
<pre><code class="language-python">{&#39;name&#39;: &#39;pingu&#39;, &#39;age&#39;: 20}</code></pre>
<p>즉 <code>kwargs</code>는 <strong>딕셔너리</strong>가 된다.</p>
<pre><code class="language-text">**kwargs
   ↓
key=value 형태의 인수
   ↓
딕셔너리</code></pre>
<hr>
<h1 id="17-args-kwargs-함께-사용하기">17. <code>*args</code>, <code>**kwargs</code> 함께 사용하기</h1>
<pre><code class="language-python">def func(*args, **kwargs):
    print(args)
    print(kwargs)</code></pre>
<p>실행</p>
<pre><code class="language-python">func(1, 2, 3, name=&quot;foo&quot;, age=3)</code></pre>
<p>결과</p>
<pre><code class="language-text">(1, 2, 3)
{&#39;name&#39;: &#39;foo&#39;, &#39;age&#39;: 3}</code></pre>
<p>정리하면</p>
<table>
<thead>
<tr>
<th align="center">매개변수</th>
<th align="left">받는 값</th>
<th align="center">저장 형태</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>*args</code></td>
<td align="left">일반 위치 인수</td>
<td align="center">튜플</td>
</tr>
<tr>
<td align="center"><code>**kwargs</code></td>
<td align="left"><code>key=value</code> 형태 인수</td>
<td align="center">딕셔너리</td>
</tr>
</tbody></table>
<hr>
<h1 id="18-데코레이터에-args-kwargs-적용하기">18. 데코레이터에 <code>*args</code>, <code>**kwargs</code> 적용하기</h1>
<p>이제 <code>wrapper()</code>를 다음처럼 변경한다.</p>
<pre><code class="language-python">def wrapper(*args, **kwargs):</code></pre>
<p>그리고 원래 함수를 실행할 때도 그대로 전달한다.</p>
<pre><code class="language-python">original_func(*args, **kwargs)</code></pre>
<p>전체 코드는 다음과 같다.</p>
<pre><code class="language-python">import time

def elapsed(original_func):

    def wrapper(*args, **kwargs):

        start = time.time()

        result = original_func(*args, **kwargs)

        end = time.time()

        print(&quot;함수 수행시간: %f 초&quot; % (end - start))

        return result

    return wrapper</code></pre>
<p>이제 어떤 형태의 인수를 사용하는 함수라도 데코레이터를 적용하기 쉬워진다.</p>
<hr>
<h1 id="19-최종-코드">19. 최종 코드</h1>
<pre><code class="language-python">import time


def elapsed(original_func):

    def wrapper(*args, **kwargs):

        start = time.time()

        result = original_func(*args, **kwargs)

        end = time.time()

        print(&quot;함수 수행시간: %f 초&quot; % (end - start))

        return result

    return wrapper


@elapsed
def myfunc(msg):
    print(&quot;&#39;%s&#39;을 출력합니다.&quot; % msg)


myfunc(&quot;You need python&quot;)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">&#39;You need python&#39;을 출력합니다.
함수 수행시간: 0.0000XX 초</code></pre>
<hr>
<h1 id="20-클로저와-데코레이터-차이">20. 클로저와 데코레이터 차이</h1>
<p>둘은 서로 관련이 있지만 완전히 같은 것은 아니다.</p>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="left">클로저</th>
<th align="left">데코레이터</th>
</tr>
</thead>
<tbody><tr>
<td align="center">핵심</td>
<td align="left">외부 함수의 값을 기억</td>
<td align="left">기존 함수에 기능 추가</td>
</tr>
<tr>
<td align="center">구조</td>
<td align="left">내부 함수를 반환</td>
<td align="left">함수를 받아 새로운 함수를 반환</td>
</tr>
<tr>
<td align="center">사용 목적</td>
<td align="left">상태나 값을 기억</td>
<td align="left">공통 기능 재사용</td>
</tr>
<tr>
<td align="center">예시</td>
<td align="left">곱할 숫자 기억</td>
<td align="left">실행 시간 측정</td>
</tr>
</tbody></table>
<p>데코레이터는 <strong>클로저 구조를 활용하는 대표적인 방법</strong>이라고 이해하면 쉽다.</p>
<hr>
<h1 id="21-클로저-구조-다시-보기">21. 클로저 구조 다시 보기</h1>
<pre><code class="language-python">def mul(m):

    def wrapper(n):
        return m * n

    return wrapper</code></pre>
<pre><code class="language-text">mul(3)
  ↓
m = 3
  ↓
wrapper 생성
  ↓
wrapper가 m을 기억
  ↓
wrapper 반환
  ↓
mul3(10)
  ↓
30</code></pre>
<p>핵심은</p>
<blockquote>
<p><strong>내부 함수가 외부 함수의 변수를 기억한다.</strong></p>
</blockquote>
<hr>
<h1 id="22-데코레이터-구조-다시-보기">22. 데코레이터 구조 다시 보기</h1>
<pre><code class="language-python">def decorator(original_func):

    def wrapper(*args, **kwargs):

        # 추가 기능

        result = original_func(*args, **kwargs)

        # 추가 기능

        return result

    return wrapper</code></pre>
<p>사용</p>
<pre><code class="language-python">@decorator
def myfunc():
    pass</code></pre>
<p>구조는</p>
<pre><code class="language-text">원래 함수
   ↓
데코레이터에 전달
   ↓
wrapper가 원래 함수 기억
   ↓
wrapper 반환
   ↓
원래 함수 대신 wrapper 실행</code></pre>
<p>이라고 이해하면 된다.</p>
<hr>
<h1 id="📌-핵심-정리">📌 핵심 정리</h1>
<h2 id="클로저">클로저</h2>
<pre><code class="language-python">def outer(a):

    def inner(b):
        return a + b

    return inner</code></pre>
<p><code>inner()</code>는 <code>outer()</code> 함수가 종료된 이후에도 <code>a</code>의 값을 기억한다.</p>
<pre><code class="language-text">외부 함수의 값
      ↓
내부 함수가 기억
      ↓
외부 함수 종료 후에도 사용 가능</code></pre>
<hr>
<h2 id="__call__"><code>__call__</code></h2>
<p>객체를 함수처럼 사용할 수 있게 해주는 특수 메서드이다.</p>
<pre><code class="language-python">class Test:
    def __call__(self):
        print(&quot;실행&quot;)


a = Test()

a()</code></pre>
<hr>
<h2 id="데코레이터">데코레이터</h2>
<p>기존 함수를 수정하지 않고 기능을 추가한다.</p>
<pre><code class="language-python">@decorator
def myfunc():
    pass</code></pre>
<p>개념적으로</p>
<pre><code class="language-python">myfunc = decorator(myfunc)</code></pre>
<p>와 같은 구조이다.</p>
<hr>
<h2 id="args-1"><code>*args</code></h2>
<p>여러 개의 일반 인수를 받는다.</p>
<pre><code class="language-python">def func(*args):
    print(args)</code></pre>
<pre><code class="language-text">튜플로 저장</code></pre>
<hr>
<h2 id="kwargs"><code>**kwargs</code></h2>
<p><code>key=value</code> 형태의 여러 인수를 받는다.</p>
<pre><code class="language-python">def func(**kwargs):
    print(kwargs)</code></pre>
<pre><code class="language-text">딕셔너리로 저장</code></pre>
<hr>
<h1 id="💡-가장-중요한-흐름">💡 가장 중요한 흐름</h1>
<h3 id="클로저-1">클로저</h3>
<pre><code class="language-text">외부 함수 실행
     ↓
외부 변수 생성
     ↓
내부 함수 생성
     ↓
내부 함수 반환
     ↓
외부 함수 종료
     ↓
내부 함수가 외부 변수 기억</code></pre>
<h3 id="데코레이터-1">데코레이터</h3>
<pre><code class="language-text">기존 함수
   ↓
데코레이터에 전달
   ↓
wrapper 함수 생성
   ↓
기존 함수 + 추가 기능
   ↓
wrapper 반환
   ↓
함수 호출</code></pre>
<blockquote>
<p><strong>클로저는 &quot;값을 기억하는 함수&quot;, 데코레이터는 &quot;기존 함수를 감싸서 기능을 추가하는 함수&quot;라고 먼저 이해하면 훨씬 쉽다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[매일 1% 성장하기 DAY-27]]></title>
            <link>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-27</link>
            <guid>https://velog.io/@pingu_122/%EB%A7%A4%EC%9D%BC-1-%EC%84%B1%EC%9E%A5%ED%95%98%EA%B8%B0-DAY-27</guid>
            <pubDate>Sun, 27 Sep 2026 16:51:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="2026년-목표">2026년 목표</h1>
</blockquote>
<ul>
<li>클라우드 환경 운영 경험(상세 내용은 추후 작성)</li>
<li>보안관제 자동화 경험</li>
<li>블로그 꾸준히 작성</li>
<li>자격증 : 정보처리기사(9월 시작), 정보보안기사, AWS-SAA,/// CISA 및 CISSP(정보 보안 관련 경력 5년 이상 필요 (4년 경력+보안 관련 학위 가능))</li>
<li>개발 공부</li>
<li>영어 공부</li>
<li>운동</li>
<li>리팩토링 과정 복습 및 벨로그에 새롭게 작성</li>
<li>클라우드 보안 복습 및 벨로그에 작성
&lt;내년 예정 - 석사 과정&gt;</li>
</ul>
<blockquote>
<h2 id="📅-공부-순서---version-9-10월">📅 공부 순서 - (Version 9-10월)</h2>
</blockquote>
<ol>
<li>파이썬(점프 투 파이썬) - 9월 말까지 완료 목표</li>
<li>정보처리기사 실기 - 10월 25일에 시험있음 합격 목표</li>
<li>모의해킹(노말틱 강의) - 10월 말까지 완료 목표</li>
<li>영어 - 10월부터 시작!</li>
<li><span style="color:green"><strong>혼자공부하는 네트워크(인프런) 완강했음 - 10월에 지속적인 복습</strong> </span></li>
<li>리펙토링 과정 복습 시작(실습 및 블로그 재작성)</li>
<li>Docker(인프런_devsecops) 강의(모의해킹 끝나고 시작)</li>
<li>AWS 보안 가이드(인프런 강의) - (Docker 인프런 끝나고 시작)</li>
<li>JAVA - (파이썬 끝나고 시작)</li>
</ol>
<hr>
<blockquote>
<h3 id="📅-금주-목표---0928월1004일">📅 금주 목표 - (09/28(월)~10/04(일))</h3>
</blockquote>
<ul>
<li><strong>파이썬(점프 투 파이썬)</strong><ul>
<li>Youtube 강의 + Ebook 참고</li>
<li>목표 : 점프 투 파이썬 학습 계속 진행 및 진도율 향상</li>
</ul>
</li>
<li><strong>정보처리기사 실기</strong><ul>
<li>목표 : 2020년도 기출문제 2회차 풀이</li>
<li>범위 : 1회차 ~ 4회차</li>
<li>목표 : 2021년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
<li>목표 : 2022년도 기출문제 1회차 풀이</li>
<li>범위 : 1회차 ~ 3회차</li>
</ul>
</li>
<li><strong>모의해킹</strong><ul>
<li>목표 : Day 1 ~ Day 2 완료</li>
<li>범위 : 1주차 ~ 2주차</li>
</ul>
</li>
</ul>
<p><span style="color:"></span></p>
<blockquote>
<h3 id="📅-오늘의-목표--span-stylecolorgreen완료span-or-span-stylecolorred미완료span-or-span-stylecoloryellow진행중span">📅 오늘의 목표 : <span style="color:green"><strong>완료</strong></span> OR <span style="color:red"><strong>미완료</strong></span> OR <span style="color:yellow"><strong>진행중</strong></span></h3>
</blockquote>
<ul>
<li><span style="color:red"><strong>모의해킹(노말틱 강의)</strong><ul>
<li><strong>1주차 ~ 하는데 까지 + 블로그 정리</strong></span></li>
</ul>
</li>
<li><span style="color:green"><strong>파이썬(점프 투 파이썬)</strong><ul>
<li><strong>07장 ~ 하는데 까지 + 블로그 정리</strong></span></li>
</ul>
</li>
<li><span style="color:red"><strong>household account 작성(23:50분에 작성)</strong></span> </li>
<li><span style="color:red"><strong>정보처리기사 DAY3(2020년도 2회차 문제풀이 및 복습) + 2021년도 1회차 문제풀이 + 2022년도 1회차 문제풀이</strong></span></li>
<li><span style="color:red"><strong>방청소</strong></span></li>
</ul>
<blockquote>
<h3 id="💻-오늘완료-한-일">💻 오늘(완료) 한 일</h3>
</blockquote>
<ul>
<li>파이썬(점프 투 파이썬)<ul>
<li>07장 03까지 완료 + 블로그 정리</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[점프 투 파이썬 DAY16(파이썬과 유니코드)]]></title>
            <link>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY15%ED%8C%8C%EC%9D%B4%EC%8D%AC%EA%B3%BC-%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@pingu_122/%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-DAY15%ED%8C%8C%EC%9D%B4%EC%8D%AC%EA%B3%BC-%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C</guid>
            <pubDate>Sun, 27 Sep 2026 11:49:21 GMT</pubDate>
            <description><![CDATA[<h1 id="07-1-파이썬과-유니코드">07-1 파이썬과 유니코드</h1>
<blockquote>
<p><code><strong>목차</strong></code></p>
</blockquote>
<ul>
<li><span style="color:slateblue"><strong>1. 최초의 문자 셋, 아스키코드</strong></span></li>
<li><span style="color:slateblue"><strong>2. 유니코드의 등장</strong></span></li>
<li><span style="color:slateblue"><strong>3. 유니코드로 문자열 다루기</strong></span></li>
<li><span style="color:slateblue"><strong>4. 인코딩하기</strong></span></li>
<li><span style="color:slateblue"><strong>5. 디코딩하기</strong></span></li>
<li><span style="color:slateblue"><strong>6. 입출력과 인코딩</strong></span></li>
<li><span style="color:slateblue"><strong>7. 소스 코드의 인코딩</strong></span></li>
</ul>
<hr>
<h1 id="1-최초의-문자-셋-아스키코드">1. 최초의 문자 셋, 아스키코드</h1>
<p>컴퓨터는 기본적으로 <code>0</code>과 <code>1</code>, 즉 숫자만 처리할 수 있다.</p>
<p>그렇다면 우리가 입력하는</p>
<pre><code class="language-text">A
B
가
나
★</code></pre>
<p>같은 문자는 컴퓨터가 어떻게 이해할까?</p>
<p>문자를 특정 숫자와 연결해 놓는 방법을 사용한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">65 → A
66 → B
67 → C</code></pre>
<p>처럼 <strong>문자와 숫자를 서로 대응</strong>시키는 것이다.</p>
<p>이러한 문자와 숫자의 대응표를 <strong>문자 셋(Character Set)</strong> 이라고 한다.</p>
<hr>
<h2 id="ascii">ASCII</h2>
<p>초기에는 컴퓨터 회사마다 서로 다른 문자 셋을 사용했다.</p>
<p>예를 들어 어떤 컴퓨터에서는</p>
<pre><code class="language-text">65 → A</code></pre>
<p>라고 사용하지만 다른 컴퓨터에서는</p>
<pre><code class="language-text">65 → X</code></pre>
<p>라고 사용한다면 서로 문서를 주고받을 때 문제가 발생한다.</p>
<p>이러한 문제를 해결하기 위해 만들어진 표준이 바로</p>
<blockquote>
<p><strong>ASCII(American Standard Code for Information Interchange)</strong></p>
</blockquote>
<p>이다.</p>
<p>ASCII는 총 <strong>128개의 문자</strong>를 표현한다.</p>
<p>주로 다음과 같은 문자들이 포함되어 있다.</p>
<ul>
<li>영문 대문자</li>
<li>영문 소문자</li>
<li>숫자</li>
<li>특수문자</li>
<li>제어문자</li>
</ul>
<p>예를 들어 파이썬에서 <code>ord()</code>를 사용하면 문자의 숫자 값을 확인할 수 있다.</p>
<pre><code class="language-python">ord(&#39;A&#39;)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">65</code></pre>
<p>반대로 숫자를 문자로 변환하려면 <code>chr()</code>를 사용한다.</p>
<pre><code class="language-python">chr(65)</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">&#39;A&#39;</code></pre>
<p>즉,</p>
<pre><code class="language-text">&#39;A&#39;
 ↓
ord()
 ↓
65
 ↓
chr()
 ↓
&#39;A&#39;</code></pre>
<p>와 같은 관계이다.</p>
<hr>
<h1 id="2-유니코드의-등장">2. 유니코드의 등장</h1>
<p>ASCII는 영어를 표현하는 데에는 문제가 없었지만 한계가 있었다.</p>
<p>ASCII가 표현할 수 있는 문자는 총 <code>128개</code>뿐이기 때문이다.</p>
<p>따라서</p>
<pre><code class="language-text">한글
중국어
일본어
아랍어</code></pre>
<p>등을 모두 표현하기에는 부족했다.</p>
<p>그래서 각 나라에서는 각각 다른 문자 셋을 만들었다.</p>
<p>예를 들어</p>
<pre><code class="language-text">한국 → KSC5601
일본 → JIS
중국 → GB2312</code></pre>
<p>와 같은 방식이다.</p>
<p>하지만 또 다른 문제가 생겼다.</p>
<p>각 나라가 서로 다른 문자 셋을 사용하면</p>
<pre><code class="language-text">한국에서 만든 파일
        ↓
일본 컴퓨터에서 열기
        ↓
글자가 깨짐</code></pre>
<p>과 같은 문제가 발생할 수 있다.</p>
<hr>
<h2 id="unicode">Unicode</h2>
<p>이러한 문제를 해결하기 위해 등장한 것이 <strong>유니코드(Unicode)</strong> 이다.</p>
<p>유니코드는 쉽게 말하면</p>
<blockquote>
<p><strong>전 세계에서 사용하는 문자에 공통된 번호를 부여한 문자 체계</strong></p>
</blockquote>
<p>라고 생각하면 된다.</p>
<p>따라서 하나의 문자열 안에서도</p>
<pre><code class="language-text">Hello
안녕하세요
こんにちは
你好</code></pre>
<p>와 같은 여러 나라의 문자를 함께 사용할 수 있다.</p>
<hr>
<h1 id="3-파이썬과-유니코드">3. 파이썬과 유니코드</h1>
<p>파이썬 3에서는 일반적인 문자열을 모두 <strong>유니코드 문자열</strong>로 처리한다.</p>
<p>예를 들어</p>
<pre><code class="language-python">a = &quot;안녕하세요&quot;</code></pre>
<p>에서 <code>a</code>의 자료형을 확인하면</p>
<pre><code class="language-python">print(type(a))</code></pre>
<p>결과</p>
<pre><code class="language-text">&lt;class &#39;str&#39;&gt;</code></pre>
<p>이다.</p>
<p>즉 파이썬에서 우리가 일반적으로 사용하는</p>
<pre><code class="language-python">&quot;Hello&quot;
&quot;Python&quot;
&quot;한글&quot;</code></pre>
<p>등은 모두 <code>str</code> 객체이다.</p>
<hr>
<h1 id="4-인코딩encoding">4. 인코딩(Encoding)</h1>
<p>여기서 중요한 개념이 등장한다.</p>
<p>파이썬 내부에서는 문자열을 유니코드 문자열로 처리하지만, 파일에 저장하거나 네트워크로 전송할 때는 실제 <strong>바이트(Byte)</strong> 데이터가 필요하다.</p>
<p>즉,</p>
<pre><code class="language-text">문자열(str)
    ↓
실제 저장하거나 전송할 수 있는
바이트(bytes)</code></pre>
<p>로 변환해야 한다.</p>
<p>이 과정을 <strong>인코딩(Encoding)</strong> 이라고 한다.</p>
<hr>
<h2 id="encode">encode()</h2>
<p>파이썬에서는 <code>encode()</code>를 사용한다.</p>
<pre><code class="language-python">a = &quot;Life is too short&quot;

b = a.encode(&#39;utf-8&#39;)

print(b)
print(type(b))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">b&#39;Life is too short&#39;
&lt;class &#39;bytes&#39;&gt;</code></pre>
<p>원래 <code>a</code>는</p>
<pre><code class="language-text">str</code></pre>
<p>자료형이었다.</p>
<p>하지만</p>
<pre><code class="language-python">a.encode(&#39;utf-8&#39;)</code></pre>
<p>을 실행하면</p>
<pre><code class="language-text">bytes</code></pre>
<p>자료형으로 변환된다.</p>
<p>즉,</p>
<pre><code class="language-text">str
 ↓ encode()
bytes</code></pre>
<p>이다.</p>
<hr>
<h2 id="문자열-앞의-b는-무엇일까">문자열 앞의 b는 무엇일까?</h2>
<p>다음 결과를 보면</p>
<pre><code class="language-python">b&#39;Life is too short&#39;</code></pre>
<p>문자열 앞에 <code>b</code>가 붙어 있다.</p>
<p>이 <code>b</code>는</p>
<blockquote>
<p>이 데이터가 일반 문자열(<code>str</code>)이 아니라 <strong>바이트 문자열(<code>bytes</code>)</strong> 이라는 의미이다.</p>
</blockquote>
<p>예를 들어</p>
<pre><code class="language-python">a = &quot;Python&quot;
b = b&quot;Python&quot;

print(type(a))
print(type(b))</code></pre>
<p>결과</p>
<pre><code class="language-text">&lt;class &#39;str&#39;&gt;
&lt;class &#39;bytes&#39;&gt;</code></pre>
<p>두 값은 화면에서 비슷하게 보여도 자료형은 서로 다르다.</p>
<hr>
<h1 id="5-한글을-인코딩해-보기">5. 한글을 인코딩해 보기</h1>
<p>이번에는 한글을 사용해 보자.</p>
<pre><code class="language-python">a = &quot;한글&quot;

print(a.encode(&quot;utf-8&quot;))</code></pre>
<p>실행하면 다음과 같은 바이트 값이 출력된다.</p>
<pre><code class="language-text">b&#39;\xed\x95\x9c\xea\xb8\x80&#39;</code></pre>
<p>여기서</p>
<pre><code class="language-text">\x</code></pre>
<p>뒤의 두 문자는 <strong>16진수로 표현된 1바이트 값</strong>이라고 생각하면 된다.</p>
<hr>
<h2 id="ascii로-한글을-인코딩하면">ASCII로 한글을 인코딩하면?</h2>
<p>다음 코드를 실행해 보자.</p>
<pre><code class="language-python">a = &quot;한글&quot;

a.encode(&quot;ascii&quot;)</code></pre>
<p>오류가 발생한다.</p>
<pre><code class="language-text">UnicodeEncodeError</code></pre>
<p>왜 그럴까?</p>
<p>ASCII에는 한글 문자가 정의되어 있지 않기 때문이다.</p>
<p>즉,</p>
<pre><code class="language-text">ASCII
 ├─ A → O
 ├─ B → O
 ├─ 1 → O
 └─ 한 → X</code></pre>
<p>이므로 한글을 ASCII 방식으로 표현할 수 없다.</p>
<hr>
<h1 id="6-utf-8과-euc-kr">6. UTF-8과 EUC-KR</h1>
<p>같은 한글이라도 어떤 인코딩 방식을 사용하느냐에 따라 실제 바이트 값은 달라질 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python">a = &quot;한글&quot;

print(a.encode(&quot;utf-8&quot;))
print(a.encode(&quot;euc-kr&quot;))</code></pre>
<p>두 결과는 서로 다르다.</p>
<p>왜냐하면</p>
<pre><code class="language-text">UTF-8 방식
EUC-KR 방식</code></pre>
<p>이 문자를 바이트로 변환하는 규칙이 서로 다르기 때문이다.</p>
<p>쉽게 생각하면</p>
<pre><code class="language-text">&quot;한글&quot;

UTF-8이라는 규칙으로 변환
        ↓
바이트 A

EUC-KR이라는 규칙으로 변환
        ↓
바이트 B</code></pre>
<p>처럼 <strong>같은 문자라도 인코딩 규칙에 따라 다른 바이트 값</strong>이 만들어질 수 있다.</p>
<hr>
<h1 id="7-디코딩decoding">7. 디코딩(Decoding)</h1>
<p>인코딩과 반대되는 과정이 <strong>디코딩(Decoding)</strong> 이다.</p>
<p>인코딩이</p>
<pre><code class="language-text">문자열 → 바이트</code></pre>
<p>라면 디코딩은</p>
<pre><code class="language-text">바이트 → 문자열</code></pre>
<p>이다.</p>
<p>파이썬에서는 <code>decode()</code>를 사용한다.</p>
<hr>
<h2 id="decode">decode()</h2>
<pre><code class="language-python">a = &quot;한글&quot;

b = a.encode(&quot;euc-kr&quot;)

print(b.decode(&quot;euc-kr&quot;))</code></pre>
<p>실행 결과</p>
<pre><code class="language-text">한글</code></pre>
<p>과정을 보면</p>
<pre><code class="language-text">&quot;한글&quot;
  ↓
encode(&quot;euc-kr&quot;)
  ↓
bytes
  ↓
decode(&quot;euc-kr&quot;)
  ↓
&quot;한글&quot;</code></pre>
<p>이다.</p>
<hr>
<h1 id="8-인코딩-방식과-디코딩-방식은-맞아야-한다">8. 인코딩 방식과 디코딩 방식은 맞아야 한다</h1>
<p>다음 코드를 살펴보자.</p>
<pre><code class="language-python">a = &quot;한글&quot;

b = a.encode(&quot;euc-kr&quot;)</code></pre>
<p>현재 <code>b</code>는 <strong>EUC-KR 규칙으로 만들어진 바이트 데이터</strong>이다.</p>
<p>따라서 다시 문자열로 변환할 때도</p>
<pre><code class="language-python">b.decode(&quot;euc-kr&quot;)</code></pre>
<p>처럼 EUC-KR을 사용해야 한다.</p>
<p>그런데 다음과 같이 작성한다면</p>
<pre><code class="language-python">b.decode(&quot;utf-8&quot;)</code></pre>
<p>잘못된 규칙으로 바이트를 해석하게 된다.</p>
<p>따라서</p>
<pre><code class="language-text">UnicodeDecodeError</code></pre>
<p>와 같은 오류가 발생할 수 있다.</p>
<hr>
<h2 id="핵심">핵심</h2>
<pre><code class="language-text">UTF-8로 인코딩
       ↓
UTF-8로 디코딩

EUC-KR로 인코딩
       ↓
EUC-KR로 디코딩</code></pre>
<p>처럼 <strong>사용한 인코딩 방식을 알고 있어야 제대로 디코딩할 수 있다.</strong></p>
<hr>
<h1 id="9-encode와-decode-한-번에-정리하기">9. encode와 decode 한 번에 정리하기</h1>
<p>이 부분이 가장 중요하다.</p>
<pre><code class="language-text">유니코드 문자열(str)
        │
        │ encode(&quot;utf-8&quot;)
        ↓
바이트 문자열(bytes)
        │
        │ decode(&quot;utf-8&quot;)
        ↓
유니코드 문자열(str)</code></pre>
<p>즉,</p>
<h3 id="encode-1">encode()</h3>
<pre><code class="language-text">str → bytes</code></pre>
<h3 id="decode-1">decode()</h3>
<pre><code class="language-text">bytes → str</code></pre>
<p>이다.</p>
<p>코드로 보면 다음과 같다.</p>
<pre><code class="language-python">text = &quot;안녕하세요&quot;

data = text.encode(&quot;utf-8&quot;)
text2 = data.decode(&quot;utf-8&quot;)

print(type(text))
print(type(data))
print(type(text2))</code></pre>
<p>결과</p>
<pre><code class="language-text">&lt;class &#39;str&#39;&gt;
&lt;class &#39;bytes&#39;&gt;
&lt;class &#39;str&#39;&gt;</code></pre>
<hr>
<h1 id="10-쉽게-기억하는-방법">10. 쉽게 기억하는 방법</h1>
<p>나는 다음처럼 기억하면 편할 것 같다.</p>
<pre><code class="language-text">encode
문자를 컴퓨터가 저장하거나 전송할 수 있는
바이트로 포장

decode
바이트를 사람이 사용하는
문자열로 해석</code></pre>
<p>따라서</p>
<pre><code class="language-text">문자열
 ↓
encode
 ↓
바이트
 ↓
파일 / 네트워크
 ↓
바이트
 ↓
decode
 ↓
문자열</code></pre>
<p>의 흐름으로 생각하면 된다.</p>
<hr>
<h1 id="11-입출력과-인코딩">11. 입출력과 인코딩</h1>
<p>프로그램에서 인코딩 문제가 많이 발생하는 부분이 <strong>파일이나 네트워크 등 외부 데이터와 통신할 때</strong>이다.</p>
<p>기본적인 원칙은 다음과 같이 생각할 수 있다.</p>
<pre><code class="language-text">외부에서 데이터 입력
        ↓
bytes
        ↓
decode
        ↓
str
        ↓
프로그램 내부에서 처리
        ↓
str
        ↓
encode
        ↓
bytes
        ↓
외부로 출력</code></pre>
<p>즉 프로그램 내부에서는 가능한 한 일반 문자열인 <code>str</code>을 사용하고, 외부에서 데이터를 받거나 보낼 때 인코딩과 디코딩을 처리하는 방식이다.</p>
<hr>
<h1 id="12-파일의-인코딩-지정하기">12. 파일의 인코딩 지정하기</h1>
<p>파일을 읽을 때 <code>open()</code>의 <code>encoding</code> 옵션을 이용할 수 있다.</p>
<p>예를 들어 EUC-KR로 저장된 파일이라면</p>
<pre><code class="language-python">with open(&quot;euc_kr.txt&quot;, encoding=&quot;euc-kr&quot;) as f:
    data = f.read()</code></pre>
<p>처럼 작성할 수 있다.</p>
<p>파일을 읽은 <code>data</code>는 파이썬에서 일반 문자열인 <code>str</code>로 사용할 수 있다.</p>
<pre><code class="language-python">print(type(data))</code></pre>
<pre><code class="language-text">&lt;class &#39;str&#39;&gt;</code></pre>
<hr>
<h2 id="문자열-수정하기">문자열 수정하기</h2>
<pre><code class="language-python">data = data + &quot;\n&quot; + &quot;추가 문자열&quot;</code></pre>
<p>프로그램 내부에서는 일반 문자열로 작업한다.</p>
<hr>
<h2 id="다시-파일에-저장하기">다시 파일에 저장하기</h2>
<pre><code class="language-python">with open(&quot;euc_kr.txt&quot;, mode=&quot;w&quot;, encoding=&quot;euc-kr&quot;) as f:
    f.write(data)</code></pre>
<p>정리하면 다음과 같다.</p>
<pre><code class="language-text">EUC-KR 파일
    ↓
open(... encoding=&quot;euc-kr&quot;)
    ↓
파이썬 str
    ↓
문자열 처리
    ↓
open(... encoding=&quot;euc-kr&quot;)
    ↓
EUC-KR 파일</code></pre>
<hr>
<h1 id="13-encoding을-직접-지정하는-습관">13. encoding을 직접 지정하는 습관</h1>
<p>텍스트 파일을 다룰 때는 가능하면 인코딩을 직접 지정하는 습관을 들이는 것이 좋다.</p>
<pre><code class="language-python">with open(&quot;test.txt&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    data = f.read()</code></pre>
<p>파일을 저장할 때도 마찬가지이다.</p>
<pre><code class="language-python">with open(&quot;test.txt&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    f.write(&quot;안녕하세요&quot;)</code></pre>
<p>특히 운영체제나 실행 환경이 달라지면 기본 인코딩 차이 때문에 한글이 깨지는 문제가 발생할 수 있으므로</p>
<pre><code class="language-python">encoding=&quot;utf-8&quot;</code></pre>
<p>처럼 명시적으로 작성하면 문제를 줄일 수 있다.</p>
<hr>
<h1 id="14-소스-코드의-인코딩">14. 소스 코드의 인코딩</h1>
<p>파이썬으로 작성한 <code>.py</code> 파일 역시 하나의 <strong>텍스트 파일</strong>이다.</p>
<p>따라서 소스 코드 파일에도 인코딩 방식이 존재한다.</p>
<p>파이썬 3에서는 기본적으로 소스 코드를 <strong>UTF-8</strong>로 처리한다.</p>
<p>따라서 일반적인 경우에는 다음 코드를 따로 작성할 필요가 없다.</p>
<pre><code class="language-python"># -*- coding: utf-8 -*-</code></pre>
<p>하지만 UTF-8이 아닌 다른 인코딩으로 저장된 소스 파일이라면 인코딩을 명시할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-python"># -*- coding: euc-kr -*-</code></pre>
<p>처럼 작성할 수 있다.</p>
<hr>
<h1 id="15-문자열과-바이트-비교">15. 문자열과 바이트 비교</h1>
<p>마지막으로 <code>str</code>과 <code>bytes</code>의 차이를 정리해 보자.</p>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="center"><code>str</code></th>
<th align="center"><code>bytes</code></th>
</tr>
</thead>
<tbody><tr>
<td align="center">의미</td>
<td align="center">유니코드 문자열</td>
<td align="center">실제 바이트 데이터</td>
</tr>
<tr>
<td align="center">예시</td>
<td align="center"><code>&quot;한글&quot;</code></td>
<td align="center"><code>b&#39;\xed...&#39;</code></td>
</tr>
<tr>
<td align="center">주 사용 위치</td>
<td align="center">프로그램 내부</td>
<td align="center">파일, 네트워크 등</td>
</tr>
<tr>
<td align="center">변환</td>
<td align="center"><code>.encode()</code></td>
<td align="center"><code>.decode()</code></td>
</tr>
<tr>
<td align="center">변환 결과</td>
<td align="center"><code>bytes</code></td>
<td align="center"><code>str</code></td>
</tr>
</tbody></table>
<p>가장 중요한 관계는</p>
<pre><code class="language-text">str
 ↓ encode()
bytes

bytes
 ↓ decode()
str</code></pre>
<p>이다.</p>
<hr>
<h1 id="📌-07-1-핵심-정리">📌 07-1 핵심 정리</h1>
<h3 id="ascii-1">ASCII</h3>
<p>문자와 숫자를 대응시킨 초기 표준 문자 셋이다.</p>
<pre><code class="language-text">65 → A
66 → B</code></pre>
<p>하지만 표현할 수 있는 문자가 적어 한글과 같은 문자를 표현할 수 없다.</p>
<hr>
<h3 id="unicode-1">Unicode</h3>
<p>전 세계의 문자를 하나의 체계로 표현하기 위한 문자 표준이다.</p>
<p>파이썬 3의 일반 문자열은 유니코드 문자열이다.</p>
<pre><code class="language-python">a = &quot;한글&quot;

print(type(a))</code></pre>
<pre><code class="language-text">&lt;class &#39;str&#39;&gt;</code></pre>
<hr>
<h3 id="encoding">Encoding</h3>
<p>문자열을 바이트로 변환한다.</p>
<pre><code class="language-python">text.encode(&quot;utf-8&quot;)</code></pre>
<pre><code class="language-text">str → bytes</code></pre>
<hr>
<h3 id="decoding">Decoding</h3>
<p>바이트를 문자열로 변환한다.</p>
<pre><code class="language-python">data.decode(&quot;utf-8&quot;)</code></pre>
<pre><code class="language-text">bytes → str</code></pre>
<hr>
<h3 id="가장-중요한-구조">가장 중요한 구조</h3>
<pre><code class="language-text">                encode()
문자열(str) ────────────────→ 바이트(bytes)
     ↑                              │
     │                              │
     └──────────────────────────────┘
                decode()</code></pre>
<p>예를 들어</p>
<pre><code class="language-python">text = &quot;한글&quot;

data = text.encode(&quot;utf-8&quot;)
result = data.decode(&quot;utf-8&quot;)

print(text)
print(data)
print(result)</code></pre>
<pre><code class="language-text">한글
b&#39;\xed\x95\x9c\xea\xb8\x80&#39;
한글</code></pre>
<hr>
<h2 id="💡-한-줄-정리">💡 한 줄 정리</h2>
<blockquote>
<p><strong>파이썬 내부에서는 <code>str</code>을 사용하고, 파일 저장이나 네트워크 전송처럼 외부와 데이터를 주고받을 때 필요에 따라 <code>encode()</code>와 <code>decode()</code>를 이용해 <code>bytes</code>와 변환한다.</strong></p>
</blockquote>
<pre><code class="language-text">사람이 보는 문자
     ↓
    str
     ↓
  encode()
     ↓
   bytes
     ↓
파일 / 네트워크
     ↓
   bytes
     ↓
  decode()
     ↓
    str
     ↓
사람이 보는 문자</code></pre>
]]></description>
        </item>
    </channel>
</rss>