<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>어제 오늘 내일</title>
    <link>https://hianna.tistory.com/</link>
    <description>여행, IT 정보를 나눕니다.</description>
    <language>ko</language>
    <pubDate>Wed, 20 May 2026 22:08:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hi.anna</managingEditor>
    <image>
      <title>어제 오늘 내일</title>
      <url>https://t1.daumcdn.net/cfile/tistory/210C593354638A6417</url>
      <link>https://hianna.tistory.com</link>
    </image>
    <item>
      <title>[Python 기초] &amp;quot;원하는 곳만 골라서 반전!&amp;quot; 리스트 부분 슬라이싱 및 뒤집기</title>
      <link>https://hianna.tistory.com/1307</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리스트의 전체가 아니라, 예를 들어 &lt;b&gt;&quot;3번 인덱스부터 7번 인덱스까지만 거꾸로 뒤집고 싶다&quot;&lt;/b&gt;면 어떻게 해야 할까요? 파이썬의 슬라이싱 규칙을 정확히 이해하면 아주 간단하게 해결됩니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 기본 원리: 부분 추출 후 뒤집기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 특정 구간을 슬라이싱으로 뽑아낸 뒤, 그 결과에 다시 뒤집기(&lt;code&gt;[::-1]&lt;/code&gt;)를 적용하는 방식입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 인덱스 2번부터 5번 직전까지 뒤집기&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 1. 2번 인덱스부터 5번 직전(4번)까지 추출: [2, 3, 4]
sub_list = numbers[2:5]

# 2. 추출한 부분만 뒤집기: [4, 3, 2]
reversed_sub = sub_list[::-1]

print(reversed_sub) 
# 출력: [4, 3, 2]
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 고급 기술: 원본의 특정 구간만 '교체'하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 잘라서 출력하는 게 아니라, &lt;b&gt;원본 리스트 내의 특정 구간만 순서를 뒤집어서 다시 끼워 넣고 싶을 때&lt;/b&gt; 사용하는 실전 기술입니다. 리스트 슬라이싱의 '치환' 기능을 활용합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 원본 리스트의 일부만 반전시키기&lt;/h4&gt;&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g']

# 인덱스 1번('b')부터 4번 직전('d')까지의 구간을 
# 해당 구간을 뒤집은 값으로 치환합니다.
alphabet[1:4] = alphabet[1:4][::-1]

print(alphabet)
# 출력: ['a', 'd', 'c', 'b', 'e', 'f', 'g']
# 설명: 'b', 'c', 'd' 구간이 'd', 'c', 'b'로 바뀌었습니다.
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 더 정교한 조절: Step(보폭) 활용하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이싱의 세 번째 인자인 &lt;code&gt;step&lt;/code&gt;을 활용하면 훨씬 정교한 작업이 가능합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 전체 리스트에서 2칸씩 건너뛰며 뒤집기&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 처음부터 끝까지 가되, 역순(-1)으로 2칸씩(-2) 건너뜁니다.
stepped_rev = data[::-2]

print(stepped_rev)
# 출력: [9, 7, 5, 3, 1]
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  실전 팁: 슬라이싱 뒤집기 공식 정리&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 공식만 기억하면 어떤 구간이든 자유자재로 요리할 수 있습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;list[start:stop]&lt;/code&gt;&lt;/b&gt;: &lt;code&gt;start&lt;/code&gt;부터 &lt;code&gt;stop-1&lt;/code&gt;까지 자르기&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;list[start:stop][::-1]&lt;/code&gt;&lt;/b&gt;: 해당 구간을 잘라서 뒤집기&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;list[start:stop] = list[start:stop][::-1]&lt;/code&gt;&lt;/b&gt;: 해당 구간만 뒤집어서 원본에 덮어쓰기&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;list[stop-1 : start-1 : -1]&lt;/code&gt;&lt;/b&gt;: 슬라이싱 한 번으로 특정 구간을 역순으로 가져오기 (비어있는 &lt;code&gt;start&lt;/code&gt; 값이 있을 때 주의가 필요하여 위 방법을 더 추천합니다.)&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;특정 구간 슬라이싱은 데이터를 필요한 만큼만 떼어내어 가공할 때 매우 유용합니다. 특히 &lt;b&gt;이미지 데이터의 픽셀을 반전&lt;/b&gt;시키거나, &lt;b&gt;문자열의 특정 부분만 암호화&lt;/b&gt;할 때 자주 사용되는 기술이죠.&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1307</guid>
      <comments>https://hianna.tistory.com/1307#entry1307comment</comments>
      <pubDate>Tue, 19 May 2026 22:08:54 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;거꾸로 줄을 서시오!&amp;quot; 파이썬 리스트 뒤집기 3가지 방법</title>
      <link>https://hianna.tistory.com/1306</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 다루다 보면 시간순으로 쌓인 데이터를 역순으로 보여주거나, 리스트의 앞뒤를 반전시켜야 할 때가 있습니다. 파이썬에서 가장 많이 쓰이는 방식들을 정리해 드릴게요.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 원본을 직접 뒤집기: &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트가 자체적으로 가지고 있는 기능입니다. 이 함수를 쓰면 &lt;b&gt;원본 리스트의 순서가 영구적으로 뒤집힙니다.&lt;/b&gt; 별도의 결과값을 반환하지 않으므로 주의해야 합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 리스트 자체를 반전&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;numbers = [1, 2, 3, 4, 5]

# 원본 리스트를 뒤집습니다.
numbers.reverse()

print(numbers)  
# 출력: [5, 4, 3, 2, 1]
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 뒤집힌 복사본 만들기: &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;원본은 그대로 유지하고 싶을 때 사용합니다. &lt;code&gt;reversed()&lt;/code&gt; 함수는 뒤집힌 상태의 '반복 가능한 객체'를 만들어주기 때문에, 다시 &lt;code&gt;list()&lt;/code&gt;로 감싸주어야 우리가 아는 리스트 형태가 됩니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 원본 보존하며 뒤집기&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;origin = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;]

# 원본은 놔두고 뒤집힌 새 리스트 생성
rev_list = list(reversed(origin))

print(&quot;원본:&quot;, origin)    # 출력: ['A', 'B', 'C']
print(&quot;뒤집힘:&quot;, rev_list) # 출력: ['C', 'B', 'A']
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 가장 파이썬스러운 방법: 슬라이싱 &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 꽃이라 불리는 &lt;b&gt;슬라이싱(Slicing)&lt;/b&gt;을 이용하는 방법입니다. 코드가 가장 짧고 직관적이어서 실무에서 가장 많이 사용됩니다. 이 방식 역시 원본은 그대로 유지됩니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 슬라이싱 활용&lt;/h4&gt;&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;colors = [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;]

# [시작:끝:증감폭]인데 증감폭을 -1로 주면 거꾸로 갑니다.
reversed_colors = colors[::-1]

print(reversed_colors)
# 출력: ['blue', 'green', 'red']
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며: 어떤 것을 쓸까요?&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;원본 리스트의 순서를 완전히 바꿔도 된다면?&lt;/b&gt; ➔ &lt;code&gt;.reverse()&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;원본은 소중하니까 놔두고 뒤집힌 리스트만 따로 필요하다면?&lt;/b&gt; ➔ &lt;code&gt;[::-1]&lt;/code&gt; (슬라이싱)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;리스트가 너무 커서 메모리를 아끼며 하나씩 꺼내 쓰고 싶다면?&lt;/b&gt; ➔ &lt;code&gt;reversed()&lt;/code&gt; (반복문과 함께 사용)&lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1306</guid>
      <comments>https://hianna.tistory.com/1306#entry1306comment</comments>
      <pubDate>Mon, 18 May 2026 23:56:02 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;미리 빈 칸을 만들어두자!&amp;quot; 파이썬 리스트 크기 지정과 초기화</title>
      <link>https://hianna.tistory.com/1305</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트의 크기를 미리 지정한다는 것은, 마치 아파트 입주 전에 미리 몇 호실까지 있을지 정해두고 빈 방을 만들어두는 것과 같습니다. 데이터가 들어올 자리를 미리 확보해두면 나중에 특정 위치(&lt;code&gt;index&lt;/code&gt;)에 값을 바로 집어넣을 수 있어 편리합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 가장 많이 쓰는 방법: 곱하기() 연산자&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 직관적이고 빠른 방법입니다. &lt;code&gt;[초기값] * 크기&lt;/code&gt; 형태로 작성합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 0으로 채워진 리스트 만들기&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 10칸짜리 리스트를 만들고 모두 0으로 초기화
list_size = 10
zeros = [0] * list_size

print(zeros)
# 출력: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

# 이제 원하는 위치에 바로 값을 넣을 수 있습니다.
zeros[5] = 100
print(zeros)
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 연속된 숫자로 크기 지정:&amp;nbsp;&amp;nbsp;활용&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 빈 칸이 아니라 1, 2, 3... 처럼 순서대로 숫자가 채워진 리스트가 필요할 때 사용합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 1부터 10까지 채워진 리스트&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 1부터 10까지 숫자가 들어있는 크기 10의 리스트
numbers = list(range(1, 11))

print(numbers)
# 출력: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. [중요] 2차원 리스트 크기 지정하기 (주의사항!)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 실수가 많은 부분입니다. 2차원 리스트(행렬)를 만들 때 곱하기(&lt;code&gt;*&lt;/code&gt;) 연산자를 잘못 쓰면 모든 행이 같은 주소를 가리키는 대참사가 발생합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 방법 (복사 버그 발생)&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 3행 3열을 만들고 싶어서 이렇게 하면...
bad_matrix = [[0] * 3] * 3
bad_matrix[0][0] = 99

print(bad_matrix)
# 출력: [[99, 0, 0], [99, 0, 0], [99, 0, 0]] 
# (첫 번째 칸만 바꿨는데 모든 행의 첫 번째 칸이 다 바뀌어 버립니다!)
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ 올바른 방법 (리스트 컴프리헨션)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;각 행을 독립적인 객체로 새로 생성해주어야 합니다.&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 3행 3열 2차원 리스트 만들기
rows, cols = 3, 3
matrix = [[0 for _ in range(cols)] for _ in range(rows)]

matrix[0][0] = 99
print(matrix)
# 출력: [[99, 0, 0], [0, 0, 0], [0, 0, 0]] (정상적으로 하나만 바뀜!)
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 값이 없는 상태로 칸만 만들기:&amp;nbsp;&amp;nbsp;사용&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;숫자 0도 넣기 싫고, &quot;아직 값이 없음&quot;을 명시적으로 표현하고 싶을 때 &lt;code&gt;None&lt;/code&gt;을 사용합니다.&lt;/p&gt;&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;# 데이터 5개가 들어올 자리를 미리 비워둠
data_slots = [None] * 5

print(data_slots)
# 출력: [None, None, None, None, None]
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 요약입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;1차원 리스트&lt;/b&gt;: &lt;code&gt;[초기값] * 크기&lt;/code&gt; 로 빠르게 만든다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;2차원 리스트&lt;/b&gt;: 반드시 &lt;b&gt;리스트 컴프리헨션&lt;/b&gt;(&lt;code&gt;[[0] * 열 for _ in range(행)]&lt;/code&gt;)을 사용한다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;숫자 채우기&lt;/b&gt;: &lt;code&gt;list(range(시작, 끝))&lt;/code&gt; 을 활용한다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트의 크기를 정해두고 나면, 이제 그 안에 든 &lt;b&gt;숫자들을 정렬하거나 특정 조건의 값만 필터링하는 작업&lt;/b&gt;이 훨씬 수월해집니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1305</guid>
      <comments>https://hianna.tistory.com/1305#entry1305comment</comments>
      <pubDate>Mon, 18 May 2026 07:29:31 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;이 바구니에 데이터가 몇 개 들어있지?&amp;quot; 파이썬 리스트 길이 구하기 (len)</title>
      <link>https://hianna.tistory.com/1304</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 리스트의 길이를 구하는 마법의 단어는 바로 &lt;b&gt;&lt;code&gt;len()&lt;/code&gt;&lt;/b&gt;입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;'길이'를 뜻하는 영어 단어 &lt;b&gt;Length&lt;/b&gt;의 앞 글자를 딴 함수입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 가장 기본: 리스트의 전체 개수 구하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 안에 들어있는 요소가 몇 개인지 궁금할 때 &lt;code&gt;len()&lt;/code&gt; 안에 리스트 변수명을 넣어주면 끝납니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 기본 사용법&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# 5명의 학생 명단
students = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;, &quot;지훈&quot;, &quot;수진&quot;]

# len() 함수로 길이를 잽니다.
count = len(students)

print(f&quot;현재 학생 수는 {count}명입니다.&quot;)
# 출력: 현재 학생 수는 5명입니다.
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 2차원 리스트의 길이는 어떻게 될까?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 안에 리스트가 들어있는 경우(2차원 리스트), &lt;code&gt;len()&lt;/code&gt;을 쓰면 &lt;b&gt;&quot;큰 바구니 안에 들어있는 작은 바구니의 개수&quot;&lt;/b&gt;를 알려줍니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 중첩 리스트의 길이&lt;/h4&gt;&lt;pre class=&quot;hsp&quot;&gt;&lt;code&gt;# 3개의 팀, 각 팀당 2명씩 있는 2차원 리스트
teams = [
    [&quot;철수&quot;, &quot;영희&quot;], 
    [&quot;민수&quot;, &quot;지훈&quot;], 
    [&quot;수진&quot;, &quot;동석&quot;]
]

print(len(teams))  
# 출력: 3 (작은 리스트가 3개 들어있기 때문입니다.)

# 특정 팀 내부의 인원수가 궁금하다면?
print(len(teams[0])) 
# 출력: 2 (0번 인덱스인 [&quot;철수&quot;, &quot;영희&quot;]의 길이를 잽니다.)
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실무 응용: 리스트가 비어있는지 확인하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 리스트의 개수 자체보다 &lt;b&gt;&quot;데이터가 하나라도 들어있는지&quot;&lt;/b&gt; 확인하는 용도로 &lt;code&gt;len()&lt;/code&gt;을 자주 씁니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 데이터 유무 확인&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cart = []  # 빈 장바구니

if len(cart) == 0:
    print(&quot;장바구니가 비어 있습니다. 물건을 담아주세요!&quot;)

#   더 파이썬스러운 방법 (len을 안 써도 됩니다!)
if not cart:
    print(&quot;장바구니가 비어 있습니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 문자열의 길이도 잴 수 있나요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;네! &lt;code&gt;len()&lt;/code&gt; 함수는 리스트뿐만 아니라 &lt;b&gt;문자열&lt;/b&gt;의 길이를 잴 때도 똑같이 사용됩니다. 공백(띄어쓰기)까지 포함해서 개수를 세어줍니다.&lt;/p&gt;&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;text = &quot;Python is Fun!&quot;
print(len(text)) 
# 출력: 14 (글자 12개 + 공백 2개)
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 요약입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;len(리스트)&lt;/code&gt;&lt;/b&gt;: 리스트의 전체 요소 개수를 반환한다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;2차원 리스트&lt;/b&gt;에서는 바깥쪽 리스트의 개수를 먼저 센다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;문자열, 딕셔너리, 집합&lt;/b&gt; 등 거의 모든 자료형의 길이를 잴 때 공통으로 사용된다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트의 길이를 재는 법을 익혔으니, 이제 이 데이터를 가지고 &lt;b&gt;평균값을 구하거나 반복문(&lt;code&gt;for&lt;/code&gt;)의 범위를 지정하는 일&lt;/b&gt;을 훨씬 수월하게 하실 수 있을 거예요!&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1304</guid>
      <comments>https://hianna.tistory.com/1304#entry1304comment</comments>
      <pubDate>Sun, 17 May 2026 12:11:33 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전 팁] &amp;quot;누가 겹치고 누가 빠졌지?&amp;quot; 파이썬 리스트 교집합, 차집합 비교하기</title>
      <link>https://hianna.tistory.com/1303</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 리스트를 비교할 때, 파이썬 고수들은 리스트를 일단 &lt;b&gt;집합(&lt;code&gt;set&lt;/code&gt;)&lt;/b&gt;으로 변환하고 시작합니다. 집합 자료형은 더하기, 빼기 같은 기호를 써서 데이터를 직관적으로 비교하는 엄청난 능력을 갖추고 있기 때문입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;비교할 두 개의 리스트를 먼저 준비해 보겠습니다.&lt;/p&gt;&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# A팀 명단과 B팀 명단
team_a = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;, &quot;지훈&quot;]
team_b = [&quot;영희&quot;, &quot;지훈&quot;, &quot;수진&quot;, &quot;동석&quot;]

# 1. 비교를 위해 리스트를 집합(Set)으로 변환합니다.
set_a = set(team_a)
set_b = set(team_b)
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 교집합 (Intersection): 양쪽 리스트에 모두 있는 데이터 찾기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;A팀과 B팀에 &lt;b&gt;공통으로&lt;/b&gt; 속해 있는 사람은 누구일까?&quot;&lt;br&gt;키보드의 &lt;b&gt;앰퍼샌드(&lt;code&gt;&amp;amp;&lt;/code&gt;)&lt;/b&gt; 기호를 사용하면 두 집합이 겹치는 부분만 쏙 뽑아냅니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 공통 데이터 찾기&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# '&amp;amp;' 기호를 사용해 교집합을 구합니다.
common_members = set_a &amp;amp; set_b

# 다루기 편하게 다시 리스트로 묶어주고 정렬까지 해볼까요?
result_list = sorted(list(common_members))

print(&quot;양쪽에 다 있는 사람 (교집합):&quot;, result_list)
# 출력: ['영희', '지훈']
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(  참고: 기호 대신 &lt;code&gt;set_a.intersection(set_b)&lt;/code&gt; 라는 함수를 써도 결과는 완전히 똑같습니다!)&lt;/i&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 차집합 (Difference): 한쪽 리스트에만 있는 데이터 찾기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;A팀에는 있는데, &lt;b&gt;B팀에는 없는&lt;/b&gt; 사람은 누구일까?&quot;&lt;br&gt;우리가 흔히 아는 &lt;b&gt;빼기(&lt;code&gt;-&lt;/code&gt;)&lt;/b&gt; 기호를 사용하면 됩니다. 앞의 집합에서 뒤의 집합과 겹치는 부분을 쿨하게 빼버립니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 누락된 데이터 찾기&lt;/h4&gt;&lt;pre class=&quot;golo&quot;&gt;&lt;code&gt;# A팀에는 있지만 B팀에는 없는 사람 (A - B)
only_in_a = set_a - set_b
print(&quot;A팀에만 있는 사람:&quot;, list(only_in_a))
# 출력: ['민수', '철수']

# B팀에는 있지만 A팀에는 없는 사람 (B - A)
only_in_b = set_b - set_a
print(&quot;B팀에만 있는 사람:&quot;, list(only_in_b))
# 출력: ['동석', '수진']
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 차집합 기술은 실무에서 &lt;b&gt;&quot;전체 회원 명단(&lt;code&gt;set_a&lt;/code&gt;)&quot; - &quot;이메일 수신 거부자 명단(&lt;code&gt;set_b&lt;/code&gt;)&quot; = &quot;이메일 발송 가능 명단&quot;&lt;/b&gt;을 뽑아낼 때 아주 기가 막히게 쓰입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 합집합 (Union)과 대칭차집합 (Symmetric Difference)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;자주 쓰이진 않지만, 알아두면 요긴한 나머지 두 가지 벤 다이어그램입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 합집합 ( 기호)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;두 리스트의 데이터를 하나로 합치되, &lt;b&gt;중복은 알아서 하나로 처리&lt;/b&gt;해 줍니다. 키보드 엔터(Enter) 키 위에 있는 파이프(&lt;code&gt;|&lt;/code&gt;) 기호를 씁니다.&lt;/p&gt;&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;# A팀과 B팀의 모든 사람을 중복 없이 모아보기
all_members = set_a | set_b
print(&quot;전체 명단 (합집합):&quot;, list(all_members))
# 출력: ['지훈', '민수', '수진', '철수', '동석', '영희']
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 대칭차집합 ( 기호)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;양쪽에 다 속한 박쥐(공통 데이터)는 빼고, &lt;b&gt;오직 한 팀에만 순수하게 속한 사람들&lt;/b&gt;만 모아줘!&quot;&lt;br&gt;교집합의 정확히 반대되는 개념입니다.&lt;/p&gt;&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;# 겹치는 '영희', '지훈'을 제외한 나머지 사람들만 모입니다.
pure_members = set_a ^ set_b
print(&quot;한 팀에만 속한 사람 (대칭차집합):&quot;, list(pure_members))
# 출력: ['민수', '수진', '철수', '동석']
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;비교할 데이터가 10만 개든 100만 개든, 집합(Set) 연산을 사용하면 파이썬이 내부적으로 엄청나게 빠른 속도로 데이터를 대조해 냅니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;공통점 찾기 (교집합):&lt;/b&gt; &lt;code&gt;set_a &amp;amp; set_b&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;차이점 찾기 (차집합):&lt;/b&gt; &lt;code&gt;set_a - set_b&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;중복 없이 합치기 (합집합):&lt;/b&gt; &lt;code&gt;set_a | set_b&lt;/code&gt;&lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 기호들만 기억해 두시면 엑셀 VLOOKUP으로 끙끙대며 데이터를 대조하던 시간을 1초로 단축하실 수 있습니다!&lt;br&gt;지금까지 리스트와 텍스트 데이터를 자유자재로 해체하고 조립하는 법을 마스터하셨습니다!&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1303</guid>
      <comments>https://hianna.tistory.com/1303#entry1303comment</comments>
      <pubDate>Thu, 14 May 2026 21:39:11 +0900</pubDate>
    </item>
    <item>
      <title>[Python] &amp;quot;중복 데이터, 대체 어디 숨어있어?&amp;quot; 파이썬 리스트 중복 인덱스(위치) 찾기 완벽 가이드</title>
      <link>https://hianna.tistory.com/1302</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 리스트에서 특정 데이터의 위치를 찾을 때, 초보자분들이 가장 많이 하는 실수가 바로 &lt;code&gt;.index()&lt;/code&gt; 함수를 사용하는 것입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;my_list.index(&quot;사과&quot;)&lt;/code&gt;를 쓰면 위치를 알려주긴 하지만, &lt;b&gt;치명적인 단점&lt;/b&gt;이 있습니다. 바로 &lt;b&gt;&quot;가장 먼저 발견된 '사과'의 위치 딱 하나만 알려주고 검색을 멈춰버린다&quot;&lt;/b&gt;는 것입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;뒤에 숨어있는 두 번째, 세 번째 '사과'의 위치까지 싹 다 찾아내려면 어떻게 해야 할까요? 상황에 맞는 2가지 확실한 해결책을 소개합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 특정 타겟 데이터의 모든 위치 찾기 ( 활용)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 리스트에서 '사과'가 도대체 몇 번, 몇 번 방에 들어있어?&quot;&lt;br&gt;이렇게 내가 찾고 싶은 &lt;b&gt;특정 타겟&lt;/b&gt;이 정해져 있을 때 가장 직관적이고 '파이썬스러운' 방법입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 반복문 단원에서 배웠던 &lt;b&gt;&lt;code&gt;enumerate()&lt;/code&gt;&lt;/b&gt; (인덱스 번호와 데이터를 같이 뽑아주는 마법의 함수)와 &lt;b&gt;리스트 컴프리헨션&lt;/b&gt;을 결합합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 특정 값의 모든 인덱스 찾기&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;fruits = [&quot;사과&quot;, &quot;바나나&quot;, &quot;사과&quot;, &quot;포도&quot;, &quot;사과&quot;, &quot;귤&quot;]
target = &quot;사과&quot;

# 1. 일반 for문으로 찾기 (이해하기 쉬운 버전)
target_indices = []
for i, fruit in enumerate(fruits):
    if fruit == target:
        target_indices.append(i)

print(f&quot;일반 for문 결과 - '{target}'의 위치: {target_indices}&quot;)


# 2. 리스트 컴프리헨션으로 찾기 (실무용 한 줄 컷!)
# &quot;fruits에서 인덱스(i)와 과일(x)을 꺼내는데, 과일이 '사과'일 때만 그 인덱스(i)를 리스트에 담아라!&quot;
target_indices_short = [i for i, x in enumerate(fruits) if x == target]

print(f&quot;한 줄 컷 결과 - '{target}'의 위치: {target_indices_short}&quot;)
# 출력: [0, 2, 4] (사과는 0번, 2번, 4번 방에 있습니다!)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 한 줄짜리 코드는 실무에서 특정 조건에 맞는 데이터의 위치를 뽑아낼 때 숨 쉬듯이 사용됩니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 리스트 전체 데이터의 중복 위치 그룹화하기 (딕셔너리 활용)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 상황이 다릅니다. &quot;사과만 찾는 게 아니라, &lt;b&gt;이 리스트에 있는 모든 데이터가 각각 몇 번 방에 있는지 싹 다 정리해 줘!&lt;/b&gt;&quot; 라는 요구사항이 들어왔습니다.&lt;br&gt;이때는 데이터의 이름을 이름표(Key)로, 위치(Index)들을 바구니(Value 리스트)에 담아주는 &lt;b&gt;딕셔너리(Dictionary)&lt;/b&gt;를 활용하는 것이 정석입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 모든 데이터의 인덱스 맵(Map) 만들기&lt;/h4&gt;&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;# 출석체크 명단 (중복 발생)
attendance = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;, &quot;철수&quot;, &quot;지훈&quot;, &quot;영희&quot;, &quot;철수&quot;]

# 데이터를 정리할 빈 딕셔너리를 만듭니다.
index_map = {}

for i, name in enumerate(attendance):
    # 만약 딕셔너리에 처음 등장하는 이름이라면, 빈 리스트를 먼저 만들어줍니다.
    if name not in index_map:
        index_map[name] = []

    # 해당 이름표(Key)의 리스트(Value)에 현재 인덱스(i)를 추가합니다.
    index_map[name].append(i)

print(&quot;▼ 전체 데이터 인덱스 정리 결과 ▼&quot;)
for name, indices in index_map.items():
    print(f&quot;{name}: {indices}&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;▼ 전체 데이터 인덱스 정리 결과 ▼
철수: [0, 3, 6]
영희: [1, 5]
민수: [2]
지훈: [4]
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;어떤가요? 철수가 0, 3, 6번째 줄에 있고, 민수는 2번째 줄에 딱 한 번 있다는 것이 완벽하게 정리되었습니다!&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 심화: 중복이 발생한(2번 이상 등장한) 데이터만 뽑아내기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 만든 &lt;code&gt;index_map&lt;/code&gt; 딕셔너리를 조금만 응용하면, &lt;b&gt;&quot;1번만 나온 정상 데이터는 빼고, 2번 이상 중복해서 나온 문제의 데이터들만 인덱스를 보여줘!&quot;&lt;/b&gt;라는 것도 아주 쉽게 구현할 수 있습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 중복된 항목만 필터링하기&lt;/h4&gt;&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# (위의 index_map 딕셔너리가 만들어져 있다고 가정합니다)

print(&quot;▼ 중복 발생 데이터 및 위치 ▼&quot;)

# 딕셔너리에서 이름과 위치리스트를 하나씩 꺼내봅니다.
for name, indices in index_map.items():
    # 리스트의 길이(len)가 1보다 크다 = 2번 이상 등장했다 = 중복이다!
    if len(indices) &amp;gt; 1:
        print(f&quot;경고! '{name}' 데이터가 다음 위치에서 중복 발견됨: {indices}&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;▼ 중복 발생 데이터 및 위치 ▼
경고! '철수' 데이터가 다음 위치에서 중복 발견됨: [0, 3, 6]
경고! '영희' 데이터가 다음 위치에서 중복 발견됨: [1, 5]
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  보너스: 로 코드 더 줄이기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2번 예제에서 &quot;딕셔너리에 Key가 없으면 빈 리스트를 만든다(&lt;code&gt;if name not in index_map:&lt;/code&gt;)&quot;는 코드를 적었죠?&lt;br&gt;파이썬의 내장 모듈인 &lt;code&gt;collections.defaultdict&lt;/code&gt;를 쓰면 이 귀찮은 확인 과정을 파이썬이 알아서 대신해 줍니다. (실무 고수들이 정말 좋아하는 모듈입니다.)&lt;/p&gt;&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;from collections import defaultdict

attendance = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;, &quot;철수&quot;]

# &quot;Key가 없으면 알아서 빈 리스트(list)를 기본값으로 세팅해 주는 딕셔너리를 만들게!&quot;
index_map = defaultdict(list)

for i, name in enumerate(attendance):
    # Key가 있는지 없는지 검사할 필요 없이, 무지성으로 append만 하면 됩니다!
    index_map[name].append(i)

print(dict(index_map))
# 출력: {'철수': [0, 3], '영희': [1], '민수': [2]}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;핵심만 다시 정리해 볼까요?&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;.index()&lt;/code&gt;의 배신:&lt;/b&gt; 가장 먼저 찾은 위치 1개만 반환하므로 중복 찾기에는 쓸 수 없다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;특정 값 1개만 찾을 때:&lt;/b&gt; &lt;code&gt;[i for i, x in enumerate(리스트) if x == 타겟]&lt;/code&gt; (원샷원킬 리스트 컴프리헨션)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;전체 데이터의 위치를 정리할 때:&lt;/b&gt; 딕셔너리(또는 &lt;code&gt;defaultdict&lt;/code&gt;)에 &lt;code&gt;이름: [인덱스들]&lt;/code&gt; 형태로 모아준다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법들을 숙지하고 계시면, 수만 줄짜리 엑셀이나 CSV 파일을 파이썬으로 불러왔을 때 불량(중복) 데이터가 몇 번째 줄에 숨어있는지 1초 만에 색출해 내실 수 있습니다!&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1302</guid>
      <comments>https://hianna.tistory.com/1302#entry1302comment</comments>
      <pubDate>Tue, 12 May 2026 22:55:53 +0900</pubDate>
    </item>
    <item>
      <title>[Python] &amp;quot;리스트 안의 리스트?&amp;quot; 2차원 리스트 중복 제거</title>
      <link>https://hianna.tistory.com/1301</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1차원 리스트의 중복을 제거할 때는 마법의 주머니 &lt;code&gt;set()&lt;/code&gt;을 쓰면 아주 쉽게 해결되었습니다.&lt;br /&gt;하지만 실무에서 엑셀 데이터나 표 형태의 데이터를 다루다 보면 &lt;b&gt;리스트 안에 또 리스트가 들어있는 '2차원 리스트'&lt;/b&gt;를 만나게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 평소 하던 대로 &lt;code&gt;set()&lt;/code&gt;을 씌우면 파이썬은 &lt;b&gt;&lt;code&gt;TypeError: unhashable type: 'list'&lt;/code&gt;&lt;/b&gt;라는 무시무시한 에러를 뿜어냅니다. 오늘은 이 에러가 왜 발생하는지 알아보고, 2차원 리스트의 중복을 3가지 방법으로 완벽하게 날려버리는 비법을 전수해 드립니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 왜 그냥 &lt;code&gt;set()&lt;/code&gt;을 쓰면 에러가 날까요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 집합(&lt;code&gt;set&lt;/code&gt;)이나 딕셔너리의 이름표(&lt;code&gt;Key&lt;/code&gt;)에는 아주 엄격한 규칙이 하나 있습니다. 바로 &lt;b&gt;&quot;내용물이 중간에 바뀔 위험이 있는 데이터(Mutable)는 절대 들어올 수 없다&quot;&lt;/b&gt;는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 아는 &lt;b&gt;리스트(&lt;code&gt;[]&lt;/code&gt;)&lt;/b&gt;는 &lt;code&gt;.append()&lt;/code&gt;로 값을 추가하거나 수정할 수 있는 자유로운 영혼입니다. 반면, 이전에 배웠던 &lt;b&gt;튜플(&lt;code&gt;()&lt;/code&gt;)&lt;/b&gt;은 한 번 만들어지면 절대 수정할 수 없는 자물쇠 채운 상자(Immutable)죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬은 내용물이 변할 수 있는 리스트를 집합에 넣는 것을 불안해해서 거부합니다. 따라서 해결책은 아주 간단합니다. &lt;b&gt;잠깐 자물쇠(튜플)를 채워서 중복을 제거하고, 다시 리스트로 풀어주면 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 방법 1: 튜플(Tuple)로 변신시켜서 중복 날리기 (순서 무시)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적이고 파이썬다운 방법입니다. 지난 시간에 배운 '리스트 컴프리헨션'을 활용하면 코드가 아주 깔끔해집니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: &lt;code&gt;set&lt;/code&gt;과 &lt;code&gt;tuple&lt;/code&gt;의 콜라보&lt;/h4&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;# 이름, 나이, 부서가 담긴 2차원 리스트 (홍길동 데이터가 중복됨)
data = [
    [&quot;홍길동&quot;, 30, &quot;영업팀&quot;],
    [&quot;김철수&quot;, 25, &quot;개발팀&quot;],
    [&quot;홍길동&quot;, 30, &quot;영업팀&quot;],  # 완벽히 똑같은 중복 데이터!
    [&quot;이영희&quot;, 28, &quot;디자인팀&quot;]
]

# 1. 안쪽 리스트들을 모두 튜플()로 바꾼 뒤, set()에 넣어 중복을 날립니다.
# (이때 데이터의 원래 순서는 뒤죽박죽 섞이게 됩니다.)
unique_tuples = set(tuple(row) for row in data)

# 2. 튜플로 묶여있던 데이터를 다시 리스트[]로 풀어줍니다.
clean_data = [list(row) for row in unique_tuples]

print(clean_data)
# 출력 예시 (순서는 다를 수 있음): 
# [['이영희', 28, '디자인팀'], ['홍길동', 30, '영업팀'], ['김철수', 25, '개발팀']]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 방법 2: 원래 순서까지 완벽하게 지키기 (&lt;code&gt;dict.fromkeys&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롤링을 했거나 시간순으로 쌓인 데이터라서 &lt;b&gt;&quot;처음 들어온 순서를 무조건 유지&quot;&lt;/b&gt;해야 한다면, 1차원 리스트 때 썼던 딕셔너리 꼼수를 똑같이 적용하면 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 순서 유지하며 중복 제거하기&lt;/h4&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;data = [
    [&quot;홍길동&quot;, 30, &quot;영업팀&quot;],
    [&quot;김철수&quot;, 25, &quot;개발팀&quot;],
    [&quot;홍길동&quot;, 30, &quot;영업팀&quot;],  # 중복
    [&quot;이영희&quot;, 28, &quot;디자인팀&quot;]
]

# 1. 안쪽 리스트를 튜플로 바꿔서 딕셔너리의 Key로 만듭니다. (중복 제거 + 순서 기억)
dict_data = dict.fromkeys(tuple(row) for row in data)

# 2. 딕셔너리의 Key들만 쏙쏙 뽑아서 다시 리스트로 감싸줍니다.
ordered_clean_data = [list(key) for key in dict_data]

print(&quot;순서 유지 결과:&quot;)
for row in ordered_clean_data:
    print(row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;순서 유지 결과:
['홍길동', 30, '영업팀']
['김철수', 25, '개발팀']
['이영희', 28, '디자인팀']
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홍길동이 맨 처음에 등장했던 그 순서 그대로 예쁘게 살아남았습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 방법 3: 보너스 트랙! 판다스(Pandas)로 1초 컷&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여러분이 엑셀처럼 방대한 데이터를 다루고 있고 이미 &lt;code&gt;pandas&lt;/code&gt; 패키지를 알고 계신다면, 저렇게 복잡하게 튜플로 변환할 필요가 전혀 없습니다. 판다스는 2차원 리스트(표 형태)를 다루는 끝판왕이기 때문입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 판다스의 &lt;code&gt;drop_duplicates()&lt;/code&gt;&lt;/h4&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;import pandas as pd

data = [
    [&quot;홍길동&quot;, 30, &quot;영업팀&quot;],
    [&quot;김철수&quot;, 25, &quot;개발팀&quot;],
    [&quot;홍길동&quot;, 30, &quot;영업팀&quot;],
    [&quot;이영희&quot;, 28, &quot;디자인팀&quot;]
]

# 1. 2차원 리스트를 판다스 데이터프레임으로 만듭니다.
df = pd.DataFrame(data)

# 2. 중복을 날리고(.drop_duplicates()), 다시 리스트로 되돌립니다(.values.tolist())
pandas_clean_data = df.drop_duplicates().values.tolist()

print(pandas_clean_data)
# 출력: [['홍길동', 30, '영업팀'], ['김철수', 25, '개발팀'], ['이영희', 28, '디자인팀']]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 한 줄(&lt;code&gt;df.drop_duplicates().values.tolist()&lt;/code&gt;)로 순서까지 갓벽하게 유지하면서 중복을 없앴습니다. 실무에서 데이터 분석가들이 가장 사랑하는 방식입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 요약입니다.&lt;br /&gt;2차원 리스트는 그냥 &lt;code&gt;set()&lt;/code&gt;에 넣으면 에러가 납니다! 따라서 안쪽 데이터들을 자물쇠 채운 &lt;b&gt;튜플(&lt;code&gt;tuple&lt;/code&gt;)&lt;/b&gt;로 변환하는 과정을 거쳐야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;순서 상관없을 때:&lt;/b&gt; &lt;code&gt;[list(x) for x in set(tuple(x) for x in data)]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순서 지켜야 할 때:&lt;/b&gt; &lt;code&gt;[list(x) for x in dict.fromkeys(tuple(x) for x in data)]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;판다스를 쓸 줄 안다면:&lt;/b&gt; &lt;code&gt;pd.DataFrame(data).drop_duplicates().values.tolist()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아무리 복잡하게 얽힌 다차원 리스트를 만나도 가뿐하게 중복을 날려버리실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1301</guid>
      <comments>https://hianna.tistory.com/1301#entry1301comment</comments>
      <pubDate>Fri, 17 Apr 2026 08:44:36 +0900</pubDate>
    </item>
    <item>
      <title>[Python] &amp;quot;원하는 데이터만 쏙쏙!&amp;quot; 파이썬 정규표현식(Regex) 기초 완벽 가이드</title>
      <link>https://hianna.tistory.com/1300</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정규표현식(Regex)&lt;/b&gt;은 특정한 규칙을 가진 문자열의 집합을 표현하는 '형식 언어'입니다. 처음 보면 마치 외계어처럼 생겼지만, 원리만 알면 수백 줄의 코드를 단 한 줄로 줄여주는 기적을 경험할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서는 기본으로 제공하는 &lt;b&gt;&lt;code&gt;re&lt;/code&gt; 모듈&lt;/b&gt;을 가져와서 사용합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 정규표현식의 핵심 기호 (마법의 주문)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규표현식은 '패턴'을 만드는 것입니다. &quot;숫자 3개, 하이픈(-), 숫자 4개&quot; 같은 규칙을 기호로 표현합니다. 실무에서 가장 많이 쓰는 핵심 기호만 빠르게 훑어보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;\d&lt;/code&gt; : &lt;b&gt;숫자&lt;/b&gt; (Digit, 0~9)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\w&lt;/code&gt; : &lt;b&gt;문자+숫자&lt;/b&gt; (Word, 알파벳, 한글, 숫자 등)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\s&lt;/code&gt; : &lt;b&gt;공백&lt;/b&gt; (Space, 띄어쓰기, 탭 등)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt; : 앞의 문자가 &lt;b&gt;1번 이상&lt;/b&gt; 반복됨 (예: &lt;code&gt;\d+&lt;/code&gt; = 숫자가 1개 이상 있음)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; : 앞의 문자가 &lt;b&gt;0번 이상&lt;/b&gt; 반복됨 (없을 수도 있음)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{n}&lt;/code&gt; : 정확히 &lt;b&gt;n번&lt;/b&gt; 반복됨 (예: &lt;code&gt;\d{3}&lt;/code&gt; = 숫자가 정확히 3개)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[ ]&lt;/code&gt; : 괄호 안의 문자 중 &lt;b&gt;하나&lt;/b&gt; (예: &lt;code&gt;[a-z]&lt;/code&gt; = 소문자 a부터 z까지 중 하나)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기호들을 레고 블록처럼 조립해서 우리가 원하는 패턴을 만듭니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 숨은 데이터 싹 다 찾기: &lt;code&gt;re.findall()&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수백 장의 텍스트 문서 안에서 &lt;b&gt;전화번호(010-XXXX-XXXX)&lt;/b&gt; 패턴만 전부 찾아내서 리스트로 만들어주는 기능입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 텍스트에서 전화번호만 추출하기&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import re  # 정규표현식 모듈을 불러옵니다.

text = &quot;&quot;&quot;
안녕하세요, 홍길동입니다. 제 번호는 010-1234-5678입니다.
급한 일은 010-9876-5432로 연락 주세요.
사무실 번호 02-111-2222는 당분간 안 받습니다.
&quot;&quot;&quot;

# 패턴 만들기: 숫자 3개 + 하이픈(-) + 숫자 3~4개 + 하이픈(-) + 숫자 4개
# \d{3} : 숫자 3개
# \d{3,4} : 숫자 3개 또는 4개
phone_pattern = r&quot;\d{3}-\d{3,4}-\d{4}&quot;

# re.findall(패턴, 텍스트) : 텍스트 안에서 패턴과 일치하는 모든 것을 '리스트'로 찾아줍니다.
results = re.findall(phone_pattern, text)

print(&quot;찾아낸 전화번호 목록:&quot;, results)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;찾아낸 전화번호 목록: ['010-1234-5678', '010-9876-5432', '02-111-2222']
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  꿀팁: &lt;code&gt;r&lt;/code&gt;의 의미&lt;/b&gt;&lt;br /&gt;패턴 문자열 앞에 &lt;code&gt;r&lt;/code&gt;을 붙이는 것(&lt;code&gt;r&quot;...&quot;&lt;/code&gt;)은 Raw String이라는 뜻으로, 파이썬에게 &quot;이 안의 역슬래시(&lt;code&gt;\&lt;/code&gt;)를 특수기호로 처리하지 말고 있는 그대로(정규식 기호로) 봐줘!&quot;라고 알려주는 필수 관행입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 원하는 형태로 싹 바꾸기: &lt;code&gt;re.sub()&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 수집했는데, 개인정보 보호를 위해 전화번호 뒷자리를 별표(&lt;code&gt;*&lt;/code&gt;)로 마스킹 처리하거나, 특수문자를 다 지워버리고 싶을 때 사용합니다. 엑셀의 '찾아 바꾸기'의 초강력 업그레이드 버전입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 주민등록번호 뒷자리 마스킹하기&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import re

info = &quot;저의 주민번호는 901225-1234567 입니다. 친구는 911111-2345678 이에요.&quot;

# 패턴: 숫자 6개 + 하이픈(-) + 숫자 7개
# 괄호 ( )를 쳐주면 그 부분을 '그룹'으로 묶어서 기억할 수 있습니다.
# 앞부분(생년월일과 -)을 그룹 1번으로 묶어줍니다.
jumin_pattern = r&quot;(\d{6}-)\d{7}&quot;

# re.sub(패턴, 바꿀형태, 텍스트)
# \g&amp;lt;1&amp;gt; 은 &quot;방금 패턴에서 괄호로 묶었던 1번 그룹(앞부분)은 그대로 두고!&quot; 라는 뜻입니다.
masked_info = re.sub(jumin_pattern, r&quot;\g&amp;lt;1&amp;gt;*******&quot;, info)

print(&quot;원본:&quot;, info)
print(&quot;마스킹 결과:&quot;, masked_info)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;원본: 저의 주민번호는 901225-1234567 입니다. 친구는 911111-2345678 이에요.
마스킹 결과: 저의 주민번호는 901225-******* 입니다. 친구는 911111-******* 이에요.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 지저분한 특수문자 한 방에 날리기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롤링한 텍스트에서 한글, 알파벳, 숫자, 띄어쓰기만 남기고 이상한 기호들을 다 지우고 싶을 때 아주 유용합니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import re

dirty_text = &quot;안녕!@#$ 파이썬(Python) 100점^^ 완성!!!&quot;

# 패턴: [^가-힣a-zA-Z0-9\s] 
# 대괄호 안의 ^ 는 &quot;NOT(이것들 빼고 전부)&quot; 이라는 뜻입니다.
# 즉, &quot;한글, 영문 대소문자, 숫자, 공백(\s) 빼고 나머지 전부 다 찾아라!&quot;
clean_pattern = r&quot;[^가-힣a-zA-Z0-9\s]&quot;

# 특수문자들을 &quot;&quot; (빈 문자열)로 교체하여 아예 지워버립니다.
clean_text = re.sub(clean_pattern, &quot;&quot;, dirty_text)

print(&quot;정제된 텍스트:&quot;, clean_text)
# 출력: 안녕 파이썬Python 100점 완성
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 실전 응용: 이메일 주소만 추출하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 대표적인 정규표현식 예제인 이메일 추출입니다. 이메일은 &lt;code&gt;아이디@도메인.주소&lt;/code&gt; 형태를 띠고 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import re

text = &quot;&quot;&quot;
제 개인 이메일은 python_king123@gmail.com 이고요,
회사 이메일은 admin@company.co.kr 입니다.
연락 주세요!
&quot;&quot;&quot;

# 이메일 패턴 분석:
# 1. [a-zA-Z0-9_-]+ : 영문자, 숫자, 언더바, 하이픈으로 이루어진 아이디 (1개 이상)
# 2. @ : 골뱅이 기호
# 3. [a-zA-Z0-9.-]+ : 도메인 이름 (영문자, 숫자, 마침표, 하이픈)
# 4. \. : 진짜 마침표(.) 기호 (정규식에서 그냥 .을 쓰면 '모든 문자'를 뜻하므로 역슬래시를 붙여야 함)
# 5. [a-zA-Z]{2,} : com, net, kr 같은 최상위 도메인 (영문자 2개 이상)

email_pattern = r&quot;[a-zA-Z0-9_-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}&quot;

emails = re.findall(email_pattern, text)
print(&quot;추출된 이메일:&quot;, emails)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;추출된 이메일: ['python_king123@gmail.com', 'admin@company.co.kr']
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 배운 정규표현식 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;import re&lt;/code&gt;&lt;/b&gt;: 파이썬에서 정규식을 쓰기 위한 마법의 모듈.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;re.findall(패턴, 텍스트)&lt;/code&gt;&lt;/b&gt;: 패턴에 맞는 모든 글자를 찾아서 리스트로 뽑아준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;re.sub(패턴, 교체할_글자, 텍스트)&lt;/code&gt;&lt;/b&gt;: 패턴에 맞는 글자를 찾아 다른 글자로 싹 바꿔준다. (데이터 정제 필수품!)&lt;/li&gt;
&lt;li&gt;패턴 앞에는 항상 &lt;b&gt;&lt;code&gt;r&lt;/code&gt;&lt;/b&gt;을 붙이는 습관을 들이자! (&lt;code&gt;r&quot;\d+&quot;&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규표현식은 처음엔 외계어 같지만, 파이썬뿐만 아니라 엑셀, 자바스크립트 등 거의 모든 IT 환경에서 똑같이 쓰이는 &lt;b&gt;만국 공통어&lt;/b&gt;입니다.&lt;br /&gt;복잡한 패턴을 직접 다 외울 필요는 없습니다. 구글에 &quot;이메일 정규표현식&quot;, &quot;전화번호 정규식&quot; 이라고 검색하면 전 세계 개발자들이 만들어둔 훌륭한 패턴들이 넘쳐나니 복사해서 &lt;code&gt;re&lt;/code&gt; 모듈에 적용하기만 하면 됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분은 파이썬으로 데이터를 자유자재로 요리할 수 있는 완벽한 셰프가 되셨습니다!&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1300</guid>
      <comments>https://hianna.tistory.com/1300#entry1300comment</comments>
      <pubDate>Fri, 17 Apr 2026 01:43:58 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;중복 제거와 정렬을 단 한 줄로!&amp;quot; 파이썬 리스트 완벽 정리 비법</title>
      <link>https://hianna.tistory.com/1299</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 다루다 보면 &quot;중복된 데이터를 없애고, 가나다순이나 크기순으로 깔끔하게 정렬해 줘!&quot;라는 요구사항을 정말 많이 받게 됩니다.&lt;br /&gt;이 두 가지 작업을 따로따로 하려면 코드가 길어지겠지만, 파이썬에서는 &lt;b&gt;단 한 줄&lt;/b&gt;로 아주 우아하게 해결할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황별로 골라 쓸 수 있는 완벽한 콤보 기술을 알아보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 실무 정석: &lt;code&gt;sorted()&lt;/code&gt;와 &lt;code&gt;set()&lt;/code&gt;의 환상적인 콜라보&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적이고 파이썬다운(Pythonic) 방법입니다.&lt;br /&gt;중복을 없애는 &lt;code&gt;set()&lt;/code&gt;과, 정렬된 새 리스트를 뱉어내는 &lt;code&gt;sorted()&lt;/code&gt;를 양파 껍질처럼 겹쳐서 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 기본 오름차순 (작은 것부터 / 가나다순)&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;raw_data = [5, 2, 9, 1, 5, 2, 3, 9]

# 1. 안쪽의 set(raw_data)가 먼저 실행되어 중복을 날립니다. -&amp;gt; {1, 2, 3, 5, 9}
# 2. 바깥쪽의 sorted()가 그 결과를 받아 정렬된 '리스트'로 만듭니다.
clean_sorted_data = sorted(set(raw_data))

print(&quot;원본:&quot;, raw_data)
print(&quot;중복 제거 + 정렬:&quot;, clean_sorted_data)
# 출력: [1, 2, 3, 5, 9]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 반대로 정렬하기: 내림차순 (큰 것부터)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 1등부터 꼴등 순서로, 혹은 알파벳 Z부터 A 순서로 정렬하고 싶다면 &lt;code&gt;sorted()&lt;/code&gt; 함수의 마법의 옵션인 &lt;code&gt;reverse=True&lt;/code&gt;를 덧붙여주면 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 내림차순 콤보&lt;/h4&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;fruits = [&quot;banana&quot;, &quot;apple&quot;, &quot;cherry&quot;, &quot;apple&quot;, &quot;banana&quot;]

# set으로 중복을 없애고, sorted의 reverse=True로 역순 정렬을 지시합니다.
desc_fruits = sorted(set(fruits), reverse=True)

print(&quot;내림차순 정렬:&quot;, desc_fruits)
# 출력: ['cherry', 'banana', 'apple']
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 응용편: 내 마음대로 정렬 기준 세우기 (&lt;code&gt;key&lt;/code&gt; 옵션)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 크기순이나 알파벳순이 아니라, &lt;b&gt;&quot;중복은 지우되, 글자 길이가 짧은 순서대로 정렬해 줘!&quot;&lt;/b&gt;라는 복잡한 조건이 붙는다면 어떻게 할까요?&lt;br /&gt;이때는 &lt;code&gt;sorted()&lt;/code&gt;의 또 다른 옵션인 &lt;code&gt;key&lt;/code&gt;를 활용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 글자 길이(len) 순으로 정렬하기&lt;/h4&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;words = [&quot;apple&quot;, &quot;kiwi&quot;, &quot;banana&quot;, &quot;fig&quot;, &quot;kiwi&quot;, &quot;apple&quot;]

# 1. set(words)로 중복 제거 -&amp;gt; {'fig', 'banana', 'apple', 'kiwi'}
# 2. key=len 옵션을 주어 글자 수가 적은 단어부터 앞으로 오게 정렬
length_sorted_words = sorted(set(words), key=len)

print(&quot;글자 길이순 정렬:&quot;, length_sorted_words)
# 출력: ['fig', 'kiwi', 'apple', 'banana']
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. ⚠️ 잠깐! &lt;code&gt;.sort()&lt;/code&gt;를 쓰면 안 되나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에는 리스트 원본 자체를 섞어버리는 &lt;code&gt;.sort()&lt;/code&gt; 메서드도 있습니다. 이걸로 중복 제거와 정렬을 같이 하려면 코드를 두 줄로 나누어 써야 합니다.&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;data = [3, 1, 2, 3]

# 1. 먼저 중복을 지우고 다시 리스트로 바꿉니다.
data = list(set(data))

# 2. 그 리스트 자체의 정렬 기능을 호출합니다.
data.sort()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식도 틀린 것은 아니지만, 앞서 배운 &lt;code&gt;sorted(set())&lt;/code&gt; 방식이 코드 한 줄로 끝나고 &lt;b&gt;원본 데이터(raw data)도 훼손하지 않고 안전하게 보존&lt;/b&gt;할 수 있어서 실무에서는 압도적으로 선호됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심, 이 공식 딱 하나만 기억하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⭐ &lt;code&gt;새_리스트 = sorted(set(원본_리스트))&lt;/code&gt; ⭐&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 지저분한 데이터를 만나더라도, 이 한 줄이면 중복 하나 없이 깔끔하게 각 잡힌 리스트를 얻어내실 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1299</guid>
      <comments>https://hianna.tistory.com/1299#entry1299comment</comments>
      <pubDate>Thu, 16 Apr 2026 08:43:31 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;똑같은 데이터 다 나와!&amp;quot; 파이썬 리스트 중복 제거 완벽 가이드 (순서 유지 vs 순서 무시)</title>
      <link>https://hianna.tistory.com/1298</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;수만 건의 크롤링 데이터나 설문조사 결과를 취합하다 보면, 필연적으로 &lt;b&gt;중복된 데이터&lt;/b&gt;가 섞여 들어옵니다. 파이썬에서는 이 중복 데이터를 아주 쉽게 날려버릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 데이터의 '순서'를 중요하게 생각하느냐 마느냐에 따라 골라 쓸 수 있는 &lt;b&gt;리스트 중복 제거의 3가지 비법&lt;/b&gt;을 소개해 드립니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 가장 쉽고 빠른 방법: &lt;code&gt;set()&lt;/code&gt; 활용하기 (순서 섞임 주의)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 중복을 제거할 때 가장 먼저 떠올려야 할 마법의 단어는 바로 &lt;b&gt;집합(&lt;code&gt;set&lt;/code&gt;)&lt;/b&gt;입니다. 집합 자료형은 태생적으로 &quot;같은 값을 두 번 허용하지 않는다&quot;는 강력한 규칙을 가지고 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: set()으로 중복 날리기&lt;/h4&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# 중복이 섞여 있는 원본 리스트
raw_data = [&quot;사과&quot;, &quot;바나나&quot;, &quot;사과&quot;, &quot;딸기&quot;, &quot;바나나&quot;, &quot;포도&quot;]

# 1. 리스트를 집합(set)으로 변환하면 알아서 중복이 날아갑니다.
unique_set = set(raw_data)

# 2. 다루기 편하게 다시 리스트(list)로 되돌려줍니다.
clean_list = list(unique_set)

print(&quot;원본:&quot;, raw_data)
print(&quot;중복 제거 후:&quot;, clean_list)
# 출력 예시: ['딸기', '포도', '바나나', '사과']
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  이 방법의 특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 코드가 가장 짧고, 실행 속도도 압도적으로 빠릅니다. &lt;code&gt;list(set(데이터))&lt;/code&gt; 한 줄이면 끝납니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 집합은 '순서'라는 개념이 없습니다. 따라서 위 출력 예시처럼 &lt;b&gt;데이터가 원래 리스트에 있던 순서와 완전히 다르게 뒤죽박죽 섞여버립니다.&lt;/b&gt; (순서가 상관없을 때만 쓰세요!)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 전통적이고 직관적인 방법: &lt;code&gt;for&lt;/code&gt; 반복문 사용하기 (순서 유지)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;데이터가 들어온 순서를 무조건 지켜야 해요!&quot;&lt;br /&gt;이럴 때 초보자분들이 가장 이해하기 쉬운 직관적인 방법은 바로 빈 바구니를 하나 만들고, 원래 바구니에서 하나씩 꺼내보며 &lt;b&gt;&quot;새 바구니에 이 값이 이미 있나?&quot;&lt;/b&gt; 확인하는 것입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: for문과 in 연산자로 중복 걸러내기&lt;/h4&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;raw_data = [&quot;사과&quot;, &quot;바나나&quot;, &quot;사과&quot;, &quot;딸기&quot;, &quot;바나나&quot;, &quot;포도&quot;]

# 중복을 제거한 데이터를 담을 '새로운 빈 바구니'를 만듭니다.
clean_list = []

for item in raw_data:
    # 만약 새 바구니(clean_list) 안에 이 과일(item)이 '없다면(not in)'
    if item not in clean_list:
        clean_list.append(item)  # 새 바구니에 추가합니다.

print(&quot;중복 제거 후 (순서 유지):&quot;, clean_list)
# 출력: ['사과', '바나나', '딸기', '포도'] 
# (원본에서 처음 등장했던 순서가 그대로 유지됨!)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  이 방법의 특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 논리가 아주 명확해서 누구나 코드를 읽고 이해하기 쉽습니다. 원래 데이터의 순서도 완벽하게 지켜줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 데이터가 100만 개쯤 되면, 매번 새 바구니를 뒤져봐야 하므로 컴퓨터가 처리하는 속도가 꽤 느려집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 파이썬 고수들의 실무 방식: &lt;code&gt;dict.fromkeys()&lt;/code&gt; (순서 유지 + 빠른 속도)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서도 유지하고 싶고, 속도도 빠르게 처리하고 싶다면 어떻게 해야 할까요?&lt;br /&gt;파이썬 개발자들이 실무에서 가장 사랑하는 우아한 방법은 바로 &lt;b&gt;딕셔너리(Dictionary)&lt;/b&gt;의 성질을 이용하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리의 이름표(Key)는 절대 중복될 수 없다는 특징을 활용합니다. 파이썬 3.7 버전부터는 딕셔너리가 &lt;b&gt;데이터가 입력된 순서를 기억&lt;/b&gt;하도록 업그레이드되었기 때문에 가능한 꼼수이자 정석입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 딕셔너리를 활용한 스마트한 중복 제거&lt;/h4&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;raw_data = [&quot;사과&quot;, &quot;바나나&quot;, &quot;사과&quot;, &quot;딸기&quot;, &quot;바나나&quot;, &quot;포도&quot;]

# 1. dict.fromkeys()를 쓰면 리스트의 값들이 딕셔너리의 'Key'로 들어갑니다.
# (이 과정에서 중복된 Key는 무시되고, 처음 들어온 순서는 기억됩니다.)
dict_data = dict.fromkeys(raw_data)

# 2. 만들어진 딕셔너리를 다시 리스트로 감싸줍니다.
clean_list = list(dict_data)

# 실무에서는 보통 위 과정을 한 줄로 씁니다: 
# clean_list = list(dict.fromkeys(raw_data))

print(&quot;중복 제거 후 (고수 방식):&quot;, clean_list)
# 출력: ['사과', '바나나', '딸기', '포도']
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  이 방법의 특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 코드가 단 한 줄로 깔끔하며, 원래 순서를 완벽하게 유지하면서도 &lt;code&gt;for&lt;/code&gt;문을 쓰는 것보다 실행 속도가 훨씬 빠릅니다. (현재 파이썬에서 가장 권장되는 방식입니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 중복 제거의 3가지 방법 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;순서가 섞여도 상관없을 때 (가장 빠름):&lt;/b&gt; &lt;code&gt;list(set(데이터))&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순서를 지켜야 하고, 직관적인 코드가 좋을 때:&lt;/b&gt; &lt;code&gt;for&lt;/code&gt;문과 &lt;code&gt;not in&lt;/code&gt; 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순서를 지키면서, 실무에서 가장 세련되게 쓸 때:&lt;/b&gt; &lt;code&gt;list(dict.fromkeys(데이터))&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 중복 데이터가 산더미처럼 쌓여있어도 당황하지 않고 상황에 맞는 칼을 꺼내 드실 수 있겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1298</guid>
      <comments>https://hianna.tistory.com/1298#entry1298comment</comments>
      <pubDate>Thu, 16 Apr 2026 01:43:08 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;뒤죽박죽 데이터 깔끔하게 줄 세우기!&amp;quot; 파이썬 리스트 정렬 (sort vs sorted)</title>
      <link>https://hianna.tistory.com/1297</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑몰에서 '가격 낮은 순', '평점 높은 순'으로 상품을 보거나, 학교에서 학생들의 시험 점수를 '1등부터 꼴등까지' 나열할 때 우리는 &lt;b&gt;정렬(Sorting)&lt;/b&gt;을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬은 아주 강력하고 직관적인 정렬 기능을 제공합니다. 오늘은 가장 많이 쓰이는 두 가지 마법의 단어, &lt;code&gt;sort()&lt;/code&gt;와 &lt;code&gt;sorted()&lt;/code&gt;, 그리고 내가 원하는 기준대로 정렬하는 &lt;code&gt;key&lt;/code&gt; 옵션에 대해 알아보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 원본을 직접 줄 세우기: &lt;code&gt;.sort()&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 방법은 리스트가 가지고 있는 자체 기능인 &lt;b&gt;&lt;code&gt;.sort()&lt;/code&gt; 메서드&lt;/b&gt;를 사용하는 것입니다.&lt;br /&gt;이 방법을 사용하면 &lt;b&gt;원본 리스트 자체가 정렬된 상태로 영구적으로 바뀌게 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 오름차순과 내림차순 정렬&lt;/h4&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;# 1. 숫자 오름차순 정렬 (작은 수 -&amp;gt; 큰 수)
scores = [85, 100, 70, 95, 60]
scores.sort()
print(&quot;오름차순:&quot;, scores)  
# 출력: 오름차순: [60, 70, 85, 95, 100]

# 2. 숫자 내림차순 정렬 (큰 수 -&amp;gt; 작은 수)
# 괄호 안에 reverse=True (반대로 할게: 참) 옵션을 넣어줍니다!
scores.sort(reverse=True)
print(&quot;내림차순:&quot;, scores)  
# 출력: 내림차순: [100, 95, 85, 70, 60]

# 3. 문자열 정렬 (알파벳순, 가나다순)
fruits = [&quot;banana&quot;, &quot;apple&quot;, &quot;cherry&quot;, &quot;사과&quot;, &quot;바나나&quot;]
fruits.sort()
print(&quot;문자열 정렬:&quot;, fruits)
# 출력: 문자열 정렬: ['apple', 'banana', 'cherry', '바나나', '사과']
# (참고: 영어 대문자 -&amp;gt; 영어 소문자 -&amp;gt; 한글 순서로 정렬됩니다.)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚠️ 주의할 점:&lt;/b&gt; &lt;code&gt;scores.sort()&lt;/code&gt;는 원본을 바꿀 뿐, 결과값을 밖으로 뱉어내지(return) 않습니다. 따라서 &lt;code&gt;new_list = scores.sort()&lt;/code&gt;라고 쓰면 &lt;code&gt;new_list&lt;/code&gt;에는 아무것도 없는 &lt;code&gt;None&lt;/code&gt;이 들어가 버리니 주의하세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 원본은 놔두고, 새로운 리스트 만들기: &lt;code&gt;sorted()&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분석을 하다 보면 원본 데이터(예: 수집한 시간순 데이터)는 그대로 보존해야 할 때가 많습니다. 이때는 원본을 건드리지 않고, 정렬된 &lt;b&gt;'새로운 리스트'&lt;/b&gt;를 복사해서 만들어주는 &lt;b&gt;&lt;code&gt;sorted()&lt;/code&gt; 파이썬 내장 함수&lt;/b&gt;를 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 안전하게 정렬하기&lt;/h4&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;original_numbers = [3, 1, 4, 2, 5]

# 원본은 그대로 두고, 정렬된 새 리스트를 변수에 담습니다.
sorted_numbers = sorted(original_numbers)

print(&quot;원본 리스트:&quot;, original_numbers)
print(&quot;새로운 리스트:&quot;, sorted_numbers)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;원본 리스트: [3, 1, 4, 2, 5]
새로운 리스트: [1, 2, 3, 4, 5]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;sorted()&lt;/code&gt; 함수 역시 내림차순을 원한다면 괄호 안에 &lt;code&gt;reverse=True&lt;/code&gt;를 추가해주면 됩니다. (예: &lt;code&gt;sorted(numbers, reverse=True)&lt;/code&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &quot;내 맘대로 줄 세울래!&quot; 마법의 &lt;code&gt;key&lt;/code&gt; 옵션 (실전용)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 가나다순, 크기순이 아니라 &lt;b&gt;&quot;글자 길이가 짧은 순서대로 정렬해 줘!&quot;&lt;/b&gt; 라거나, &lt;b&gt;&quot;이름과 점수가 같이 있는데, 점수를 기준으로 정렬해 줘!&quot;&lt;/b&gt;라고 디테일한 명령을 내리고 싶을 때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용하는 것이 바로 &lt;b&gt;&lt;code&gt;key&lt;/code&gt;&lt;/b&gt; 옵션입니다. 정렬의 기준(Key)이 될 함수를 지정해 주는 아주 강력한 기능입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 글자 길이로 정렬하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 길이를 재는 내장 함수인 &lt;code&gt;len&lt;/code&gt;을 기준으로 삼아보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;words = [&quot;apple&quot;, &quot;kiwi&quot;, &quot;banana&quot;, &quot;fig&quot;, &quot;strawberry&quot;]

# 기준(key)을 글자 길이(len)로 설정합니다.
# 알파벳 순서가 아니라 짧은 단어부터 긴 단어 순으로 정렬됩니다.
words.sort(key=len)

print(&quot;글자 길이순 정렬:&quot;, words)
# 출력: 글자 길이순 정렬: ['fig', 'kiwi', 'apple', 'banana', 'strawberry']
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 복잡한 데이터 정렬하기 (람다 함수 활용)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 배운 &lt;b&gt;람다(lambda)&lt;/b&gt; 함수와 결합하면 그 위력이 배가 됩니다. 학생의 이름과 점수가 묶여있는 리스트를 '점수' 기준으로 정렬해 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# [이름, 점수] 로 이루어진 리스트
students = [
    [&quot;홍길동&quot;, 85],
    [&quot;김철수&quot;, 100],
    [&quot;이영희&quot;, 92]
]

# 그냥 sort()를 하면 첫 번째 데이터인 '이름(가나다순)'으로 정렬되어 버립니다.
# &quot;x가 들어오면, x의 1번 인덱스(즉, 점수)를 기준으로 정렬해라!&quot;
students.sort(key=lambda x: x[1], reverse=True)

print(&quot;점수 높은 순 정렬:&quot;)
for student in students:
    print(student)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;점수 높은 순 정렬:
['김철수', 100]
['이영희', 92]
['홍길동', 85]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 정렬의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;.sort()&lt;/code&gt;&lt;/b&gt;: 원본 리스트를 직접 정렬해 버린다. (메모리 절약)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;sorted()&lt;/code&gt;&lt;/b&gt;: 원본은 안전하게 보존하고 정렬된 새 리스트를 만든다. (안전함)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;reverse=True&lt;/code&gt;&lt;/b&gt;: 내림차순(큰 것부터)으로 정렬할 때 쓰는 마법의 옵션.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;key=함수&lt;/code&gt;&lt;/b&gt;: 정렬 기준을 내 마음대로 커스텀할 때 사용한다. (&lt;code&gt;lambda&lt;/code&gt;와 찰떡궁합!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 4가지만 알고 계시면 파이썬에서 만나는 어떤 데이터든 예쁘게 줄 세우실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1297</guid>
      <comments>https://hianna.tistory.com/1297#entry1297comment</comments>
      <pubDate>Wed, 15 Apr 2026 08:42:44 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전 보너스] 18. &amp;quot;100만 줄 엑셀도 1초 컷!&amp;quot; 데이터 분석의 끝판왕, 판다스(Pandas) 찍어 먹기</title>
      <link>https://hianna.tistory.com/1296</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀에서 &lt;code&gt;VLOOKUP&lt;/code&gt;을 쓰거나 피벗 테이블을 돌려본 적 있으신가요?&lt;br /&gt;&lt;b&gt;판다스(Pandas)&lt;/b&gt;는 이런 복잡한 표 계산과 데이터 조작을 단 몇 줄의 파이썬 코드로 순식간에 처리해 주는 마법의 도구입니다. 데이터 과학자나 AI 개발자들에게는 숨 쉬는 것과도 같은 필수 패키지입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 준비 운동: 판다스 설치와 핵심 개념 (DataFrame)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 터미널을 열고 판다스를 설치해 줍니다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;pip install pandas
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판다스를 이해하려면 딱 하나의 단어만 기억하면 됩니다. 바로 &lt;b&gt;데이터프레임(DataFrame)&lt;/b&gt;입니다.&lt;br /&gt;데이터프레임은 쉽게 말해 &lt;b&gt;'파이썬 안에 띄워놓은 가상의 엑셀 시트'&lt;/b&gt;입니다. 행(Row)과 열(Column)로 이루어진 완벽한 표 형태를 띠고 있죠.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 엑셀/CSV 파일 한 번에 빨아들이기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;openpyxl&lt;/code&gt;에서는 빈 워크북을 만들고 시트를 찾아서 한 줄씩 읽어야 했지만, 판다스는 그런 자잘한 과정을 모두 생략합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 단 한 줄로 데이터 불러오기&lt;/h4&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;import pandas as pd  # pandas는 관행적으로 pd라는 별명으로 줄여 씁니다.

# 1. CSV 파일이나 엑셀 파일을 통째로 읽어서 '데이터프레임'으로 만듭니다.
# (미리 &quot;sales_data.csv&quot; 파일이 있다고 가정합니다)
df = pd.read_csv(&quot;sales_data.csv&quot;)

# 엑셀 파일이라면? df = pd.read_excel(&quot;sales_data.xlsx&quot;) 라고 쓰면 끝입니다!

# 2. 데이터가 어떻게 생겼는지 위에서부터 5줄만 살짝 엿보기
print(df.head())
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과 (터미널에 표가 예쁘게 출력됩니다!):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;         날짜     부서   직급      매출액
0  2026-02-01  영업1팀   대리  3500000
1  2026-02-01  영업2팀   사원  2100000
2  2026-02-02  마케팅팀   과장  5000000
3  2026-02-02  영업1팀   부장  8000000
4  2026-02-03  영업2팀   대리  4200000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 왼쪽에 0, 1, 2... 하고 붙은 숫자는 판다스가 알아서 매겨준 데이터의 &lt;b&gt;주민등록번호(인덱스)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 판다스의 진가: 데이터 필터링과 계산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판다스가 진짜 무서운 이유는 조건 검색과 계산입니다. 엑셀에서 마우스로 필터를 걸고 클릭하던 작업을 코드로 아주 직관적으로 할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 조건에 맞는 데이터만 뽑아내기&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import pandas as pd

# 데이터 불러오기
df = pd.read_csv(&quot;sales_data.csv&quot;)

# --- 1. 특정 열(Column)만 뽑아보기 ---
# 딕셔너리에서 Key로 값을 찾듯, 대괄호 안에 열 이름을 넣습니다.
print(df[&quot;매출액&quot;]) 

print(&quot;-&quot; * 30)

# --- 2. 조건 필터링 (영업1팀 데이터만 찾기) ---
# &quot;df의 부서가 '영업1팀'인 것만 다시 df에서 골라내라!&quot;
team1_data = df[df[&quot;부서&quot;] == &quot;영업1팀&quot;]
print(&quot;▼ 영업1팀 데이터 ▼&quot;)
print(team1_data)

print(&quot;-&quot; * 30)

# --- 3. 복합 조건 (매출액 400만 원 이상인 대리 찾기) ---
# 괄호()로 조건을 묶고, &amp;amp; (그리고) 기호로 연결합니다.
vip_sales = df[(df[&quot;매출액&quot;] &amp;gt;= 4000000) &amp;amp; (df[&quot;직급&quot;] == &quot;대리&quot;)]
print(&quot;▼ 400만 원 이상 매출을 낸 대리 ▼&quot;)
print(vip_sales)

print(&quot;-&quot; * 30)

# --- 4. 통계 계산 1초 컷 ---
total_sales = df[&quot;매출액&quot;].sum()    # 총합
avg_sales = df[&quot;매출액&quot;].mean()     # 평균
max_sales = df[&quot;매출액&quot;].max()      # 최댓값

print(f&quot;전체 총 매출액: {total_sales}원&quot;)
print(f&quot;평균 매출액: {avg_sales}원&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 작업을 순수 파이썬의 &lt;code&gt;for&lt;/code&gt;문과 &lt;code&gt;if&lt;/code&gt;문으로 짰다면 코드가 10줄은 훌쩍 넘어갔겠지만, 판다스를 쓰면 단 한 줄로 끝납니다. 심지어 데이터가 100만 개여도 눈 깜짝할 사이에 계산을 끝냅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 분석한 결과를 다시 새로운 엑셀로 저장하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요리가 끝났으니 다시 그릇에 담아내야겠죠? 판다스는 저장하는 것도 예술적으로 간단합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 데이터프레임을 엑셀로 내보내기&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import pandas as pd

df = pd.read_csv(&quot;sales_data.csv&quot;)

# 영업1팀 데이터만 필터링
team1_data = df[df[&quot;부서&quot;] == &quot;영업1팀&quot;]

# 결과를 새로운 엑셀 파일로 저장합니다.
# index=False 옵션을 주면, 맨 왼쪽에 있던 0, 1, 2... 번호표는 빼고 깔끔하게 저장됩니다.
team1_data.to_excel(&quot;영업1팀_실적보고서.xlsx&quot;, index=False)

print(&quot;엑셀 파일 저장이 완료되었습니다!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;read_csv()&lt;/code&gt;로 빨아들이고, &lt;code&gt;to_excel()&lt;/code&gt;로 뱉어낸다. 정말 직관적이지 않나요?&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보너스 트랙의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;판다스(Pandas)&lt;/b&gt;: 파이썬에서 표 형태의 데이터를 엑셀처럼, 아니 엑셀보다 훨씬 빠르고 강력하게 다루게 해주는 패키지.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;pd.read_csv()&lt;/code&gt; / `pd.read_excel()&lt;/b&gt;`: 파일을 한 번에 읽어와 데이터프레임으로 만든다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;df[ 조건 ]&lt;/code&gt;&lt;/b&gt;: 직관적인 코드로 원하는 데이터만 쏙쏙 필터링할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;df.to_excel()&lt;/code&gt;&lt;/b&gt;: 분석이 끝난 데이터를 깔끔하게 다시 파일로 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 크롤링(&lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;BeautifulSoup&lt;/code&gt;)으로 긁어온 수만 건의 데이터를 판다스(&lt;code&gt;pandas&lt;/code&gt;) 데이터프레임에 넣고, 중복을 제거하고 통계를 낸 뒤 엑셀 파일로 저장한다!&lt;br /&gt;이것이 바로 현업에서 매일같이 쓰이는 &lt;b&gt;데이터 수집 및 분석의 정석 파이프라인&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말, 정말로 파이썬 기초 여행이 모두 끝났습니다!&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1296</guid>
      <comments>https://hianna.tistory.com/1296#entry1296comment</comments>
      <pubDate>Wed, 15 Apr 2026 00:54:16 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 17. &amp;quot;크롤링보다 100배 쉽고 정확하다!&amp;quot; 공공데이터 API 연동과 JSON 다루기</title>
      <link>https://hianna.tistory.com/1295</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롤링이 남의 집 담벼락을 몰래 훔쳐보는 것이라면, &lt;b&gt;API&lt;/b&gt;는 정문으로 당당하게 들어가서 &quot;여기 날씨 정보 1인분 포장해 주세요!&quot;라고 주문하는 것과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합법적이고, 데이터가 아주 깔끔하게 정리되어 오기 때문에 실무에서는 무조건 크롤링보다 API를 우선적으로 사용합니다. 오늘은 별도의 회원가입이나 인증키 없이 무료로 사용할 수 있는 날씨 API를 통해 그 원리를 파헤쳐 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. API란 무엇인가요? (식당 점원 비유)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 가장 쉽게 이해하는 방법은 식당의 &lt;b&gt;'점원(웨이터)'&lt;/b&gt;으로 생각하는 것입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;손님 (파이썬 코드):&lt;/b&gt; 메뉴판을 보고 &quot;서울 날씨 주세요!&quot;라고 주문(요청, Request)합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;점원 (API):&lt;/b&gt; 손님의 주문을 주방에 전달하고, 완성된 요리를 가져다줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주방 (서버/데이터베이스):&lt;/b&gt; 실제 날씨 데이터를 가지고 요리하는 곳입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 주방장이 요리를 어떻게 하는지(서버가 어떻게 동작하는지) 알 필요가 없습니다. 그저 &lt;b&gt;점원(API)이 알아들을 수 있는 규칙(메뉴판)&lt;/b&gt;에 맞춰서 파이썬 코드로 주문만 하면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. JSON: 전 세계 컴퓨터들의 공용어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 크롤링을 할 때는 지저분한 HTML 태그들(&lt;code&gt;&amp;lt;div class=&quot;...&quot;&amp;gt;&lt;/code&gt;) 사이에서 텍스트를 발라내야 했습니다. 하지만 API는 컴퓨터가 읽기 가장 편한 형태인 &lt;b&gt;JSON(JavaScript Object Notation)&lt;/b&gt;이라는 형식으로 요리를 내어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JSON의 가장 큰 특징은 우리가 배운 파이썬의 '딕셔너리(Dictionary)'와 99.9% 똑같이 생겼다는 것입니다.&lt;/b&gt; 중괄호 &lt;code&gt;{}&lt;/code&gt; 안에 &lt;code&gt;Key: Value&lt;/code&gt; 쌍으로 이루어져 있어서, 데이터를 꺼내 쓰기가 예술적으로 편합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실전! 무료 날씨 API로 서울 기온 가져오기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증키 발급 없이 바로 사용할 수 있는 유명한 무료 날씨 API인 &lt;b&gt;Open-Meteo&lt;/b&gt;를 사용해 보겠습니다.&lt;br /&gt;우리의 목표는 &lt;b&gt;현재 서울의 온도&lt;/b&gt;를 파이썬으로 가져오는 것입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: API 요청하고 JSON 데이터 받아오기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간에 크롤링할 때 썼던 &lt;code&gt;requests&lt;/code&gt; 패키지를 그대로 사용합니다!&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import requests

# 1. API 메뉴판에 맞게 요청 주소(URL) 만들기
# 서울의 위도(latitude=37.56)와 경도(longitude=126.97)를 입력하고, 현재 날씨(current_weather=true)를 요청합니다.
api_url = &quot;https://api.open-meteo.com/v1/forecast?latitude=37.56&amp;amp;longitude=126.97&amp;amp;current_weather=true&quot;

# 2. requests 패키지로 API 점원에게 주문서 넣기 (GET 방식)
response = requests.get(api_url)

# 3. 응답 확인하기 (200이면 성공)
if response.status_code == 200:
    # 4. 받아온 데이터를 파이썬 딕셔너리로 완벽하게 변환하기! (이게 핵심입니다)
    weather_data = response.json()

    # 데이터가 어떻게 생겼는지 한번 볼까요?
    print(&quot;▼ API가 보내준 원본 JSON 데이터 ▼&quot;)
    print(weather_data)
else:
    print(&quot;API 요청에 실패했습니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과 (원본 JSON):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;▼ API가 보내준 원본 JSON 데이터 ▼
{'latitude': 37.56, 'longitude': 126.97, 'generationtime_ms': 0.123, 'utc_offset_seconds': 0, 'timezone': 'GMT', 'timezone_abbreviation': 'GMT', 'elevation': 38.0, 'current_weather': {'temperature': 15.2, 'windspeed': 5.4, 'winddirection': 210.0, 'weathercode': 1, 'is_day': 1, 'time': '2026-02-24T14:00'}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보기엔 복잡해 보이지만, 자세히 보면 중괄호 &lt;code&gt;{}&lt;/code&gt;와 쉼표 &lt;code&gt;,&lt;/code&gt;로 이루어진 전형적인 &lt;b&gt;파이썬 딕셔너리&lt;/b&gt;입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 딕셔너리에서 원하는 데이터만 쏙쏙 뽑아내기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 저 복잡한 딕셔너리에서 우리가 진짜 원하는 &lt;b&gt;'온도(temperature)'&lt;/b&gt; 값만 꺼내보겠습니다.&lt;br /&gt;이전에 딕셔너리 단원에서 배운 &lt;code&gt;변수[&quot;Key이름&quot;]&lt;/code&gt; 문법을 그대로 쓰면 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 중첩 딕셔너리 데이터 추출하기&lt;/h4&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# (위 코드에 이어서 작성합니다)

# 1. 전체 데이터에서 'current_weather'라는 Key의 데이터를 꺼냅니다.
# 꺼내고 나면 그 안에도 또 딕셔너리가 들어있습니다! (중첩 딕셔너리)
current = weather_data[&quot;current_weather&quot;]

# 2. 꺼낸 데이터 안에서 다시 'temperature'라는 Key의 데이터를 꺼냅니다.
temperature = current[&quot;temperature&quot;]

# 3. 시간 정보도 꺼내볼까요?
time = current[&quot;time&quot;]

print(&quot;\n▼ 깔끔하게 추출한 데이터 ▼&quot;)
print(f&quot;기준 시간: {time}&quot;)
print(f&quot;현재 서울 기온: {temperature}도&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;▼ 깔끔하게 추출한 데이터 ▼
기준 시간: 2026-02-24T14:00
현재 서울 기온: 15.2도
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  한 줄로 줄여서 쓰기:&lt;/b&gt;&lt;br /&gt;딕셔너리 안의 딕셔너리를 접근할 때는 대괄호를 연달아 붙여서 &lt;code&gt;weather_data[&quot;current_weather&quot;][&quot;temperature&quot;]&lt;/code&gt; 처럼 한 번에 접근할 수도 있습니다. 아주 파이썬다운 깔끔한 문법이죠!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 이제 무엇을 할 수 있을까요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원리 하나만 깨우치면, 파이썬으로 할 수 있는 일은 무궁무진해집니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;공공데이터포털(data.go.kr):&lt;/b&gt; 미세먼지 수치, 아파트 실거래가, 전국 약국 위치 정보 등을 API로 받아와서 엑셀로 자동 정리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;카카오톡/텔레그램 API:&lt;/b&gt; 오늘 추출한 이 날씨 데이터를 매일 아침 7시마다 내 카카오톡 메시지로 쏴주는 나만의 비서를 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;금융/주식 API:&lt;/b&gt; 비트코인 시세나 주식 가격을 1초마다 API로 받아와서, 특정 가격이 되면 자동으로 문자를 보내게 할 수도 있죠.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  대단원의 막을 내리며 (시리즈 총정리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분, 정말 고생 많으셨습니다! 첫 번째 포스팅에서 파이썬을 설치하던 것이 엊그제 같은데, 어느덧 데이터를 수집하고 엑셀을 다루며 API까지 호출하는 수준에 도달하셨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 함께 걸어온 길을 돌아볼까요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Phase 1~3:&lt;/b&gt; 변수, 리스트, 딕셔너리, &lt;code&gt;if&lt;/code&gt;문, &lt;code&gt;for&lt;/code&gt;문 등 파이썬의 &lt;b&gt;기본 근육&lt;/b&gt;을 키웠습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Phase 4~5:&lt;/b&gt; 함수, 모듈(&lt;code&gt;import&lt;/code&gt;), 파일 다루기, 예외 처리 등 남의 코드를 읽고 내 컴퓨터를 다루는 &lt;b&gt;실전 뼈대&lt;/b&gt;를 세웠습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Phase 6:&lt;/b&gt; 크롤링, &lt;code&gt;openpyxl&lt;/code&gt;(엑셀), API 연동을 통해 여러분의 업무 시간을 획기적으로 줄여줄 &lt;b&gt;무기&lt;/b&gt;를 장착했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로그래밍은 암기가 아닙니다.&lt;/b&gt; 문법을 완벽히 외울 필요는 전혀 없습니다. &quot;아, 리스트 안에 딕셔너리가 있었지!&quot;, &quot;반복문으로 엑셀 한 줄씩 넣으면 되겠네!&quot;라는 &lt;b&gt;아이디어와 흐름&lt;/b&gt;만 떠올릴 수 있다면, 구체적인 코드는 검색과 AI의 도움을 받아 얼마든지 짜맞출 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 배운 것들을 활용해 여러분의 일상과 업무에서 작고 귀찮은 일들을 하나씩 자동화해 보세요. 직접 코드를 짜서 돌아갈 때의 그 짜릿함이 여러분을 더 멋진 개발의 세계로 이끌어 줄 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 수고하셨습니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1295</guid>
      <comments>https://hianna.tistory.com/1295#entry1295comment</comments>
      <pubDate>Tue, 14 Apr 2026 08:53:36 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 16. &amp;quot;엑셀 노가다 완벽 해방!&amp;quot; openpyxl로 파이썬에서 엑셀 다루기</title>
      <link>https://hianna.tistory.com/1294</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매달 말일마다 부서별로 흩어진 수십 개의 엑셀 파일을 열어서 하나의 시트로 합치거나, 특정 서식을 복사 붙여넣기 하는 작업을 해보신 적 있나요? 사람이 직접 하면 눈도 아프고 실수하기도 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 파이썬의 &lt;b&gt;&lt;code&gt;openpyxl&lt;/code&gt;&lt;/b&gt; 패키지를 사용하면 엑셀을 켜지 않고도 백그라운드에서 순식간에 데이터를 쓰고, 읽고, 서식까지 지정할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 준비 운동: 패키지 설치와 엑셀의 3단 구조 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 터미널(명령 프롬프트)을 열고 외부 패키지를 설치해 줍니다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;pip install openpyxl
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬으로 엑셀을 다루기 전에 반드시 알아야 할 &lt;b&gt;엑셀의 3단 구조&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;워크북 (Workbook):&lt;/b&gt; 엑셀 파일 그 자체 (&lt;code&gt;.xlsx&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;워크시트 (Worksheet):&lt;/b&gt; 파일 안 하단에 있는 탭 (Sheet1, Sheet2...)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;셀 (Cell):&lt;/b&gt; 데이터를 입력하는 각각의 네모 칸 (A1, B2...)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 짤 때도 항상 &lt;b&gt;&quot;파일을 열고 ➔ 시트를 고르고 ➔ 셀에 값을 넣는다&quot;&lt;/b&gt; 순서로 진행됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 무에서 유를 창조하기: 새 엑셀 파일 만들고 데이터 쓰기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무것도 없는 백지상태에서 새로운 엑셀 파일을 만들고, 특정 칸에 데이터를 집어넣어 보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 새 엑셀 파일 생성하기&lt;/h4&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;import openpyxl

# 1. 워크북(엑셀 파일) 생성하기
wb = openpyxl.Workbook()

# 2. 현재 활성화된 시트(기본 시트) 가져오기
ws = wb.active

# 시트 이름도 내 마음대로 바꿀 수 있습니다.
ws.title = &quot;크롤링_결과&quot;

# 3. 셀에 데이터 입력하기 (A1 셀, B1 셀 지정)
ws[&quot;A1&quot;] = &quot;순번&quot;
ws[&quot;B1&quot;] = &quot;뉴스 제목&quot;

ws[&quot;A2&quot;] = 1
ws[&quot;B2&quot;] = &quot;파이썬으로 업무 자동화 성공!&quot;

# 4. 워크북 저장하기 (반드시 마지막에 저장해야 실제 파일이 생깁니다!)
wb.save(&quot;news_data.xlsx&quot;)
# 작업이 끝났으면 닫아주는 것이 좋습니다.
wb.close()

print(&quot;엑셀 파일이 성공적으로 만들어졌습니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 실행하고 폴더를 확인해 보면 &lt;code&gt;news_data.xlsx&lt;/code&gt; 파일이 예쁘게 생성된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실무 필수 스킬: 리스트 데이터를 한 줄씩 추가하기 (&lt;code&gt;append&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 위처럼 &lt;code&gt;ws[&quot;A1&quot;]&lt;/code&gt;, &lt;code&gt;ws[&quot;A2&quot;]&lt;/code&gt; 하나하나 주소를 지정해서 데이터를 넣지 않습니다. 지난 시간에 크롤링한 데이터처럼, 수십 개의 데이터가 담긴 &lt;b&gt;리스트(List)&lt;/b&gt;를 한 번에 한 줄씩 밀어 넣는 방식을 압도적으로 많이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용하는 마법의 함수가 바로 &lt;b&gt;&lt;code&gt;append()&lt;/code&gt;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 여러 줄의 데이터를 엑셀에 한 번에 쓰기&lt;/h4&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;import openpyxl

# 저장할 데이터가 담긴 리스트 (크롤링 결과라고 가정해 봅시다)
crawling_data = [
    [&quot;날짜&quot;, &quot;부서&quot;, &quot;매출액&quot;],  # 첫 번째 줄 (헤더)
    [&quot;2026-02-24&quot;, &quot;영업1팀&quot;, 5000000],
    [&quot;2026-02-24&quot;, &quot;영업2팀&quot;, 4500000],
    [&quot;2026-02-25&quot;, &quot;마케팅팀&quot;, 3000000]
]

wb = openpyxl.Workbook()
ws = wb.active
ws.title = &quot;매출보고서&quot;

# for문을 돌면서 리스트를 한 줄씩 엑셀의 맨 아랫줄에 추가합니다.
for row in crawling_data:
    # 엑셀 시트의 맨 아래 빈 줄에 row(리스트)를 통째로 끼워 넣습니다.
    ws.append(row)

wb.save(&quot;sales_report.xlsx&quot;)
wb.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 4줄의 &lt;code&gt;for&lt;/code&gt;문만으로 복잡한 표가 엑셀 파일에 완벽하게 들어갔습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 이미 있는 엑셀 파일 불러와서 읽기 (&lt;code&gt;load_workbook&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 반대로, 컴퓨터에 이미 저장되어 있는 엑셀 파일을 파이썬으로 불러와서 안에 있는 데이터를 읽어보겠습니다.&lt;br /&gt;이 기능은 &quot;특정 단어가 포함된 행만 지우기&quot;, &quot;오타 한 번에 수정하기&quot; 같은 매크로 작업을 할 때 필수적입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 엑셀 파일 불러오기&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import openpyxl

# 1. 이미 존재하는 엑셀 파일 불러오기
# data_only=True 옵션을 주면 엑셀의 '수식' 대신 계산된 '결과값'만 가져옵니다.
wb = openpyxl.load_workbook(&quot;sales_report.xlsx&quot;, data_only=True)

# 2. 작업할 시트 이름으로 정확히 찝어서 가져오기
ws = wb[&quot;매출보고서&quot;]

# 3. 특정 셀의 값 읽기 (A2 셀)
print(&quot;A2 셀의 값:&quot;, ws[&quot;A2&quot;].value) # 주의: 그냥 ws[&quot;A2&quot;]를 출력하면 셀 객체 자체가 나옵니다. 반드시 .value를 붙이세요!

print(&quot;-&quot; * 20)

# 4. 시트에 있는 모든 데이터 한 줄씩 꺼내서 보기 (iter_rows 활용)
# min_row=2 를 주면 1번째 줄(헤더)을 건너뛰고 2번째 줄부터 가져옵니다.
for row in ws.iter_rows(min_row=2):
    date = row[0].value
    team = row[1].value
    sales = row[2].value

    print(f&quot;{date} | {team} | {sales}원&quot;)

wb.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  &lt;code&gt;.value&lt;/code&gt;를 잊지 마세요!&lt;/b&gt;&lt;br /&gt;&lt;code&gt;ws[&quot;A1&quot;]&lt;/code&gt;은 &quot;A1이라는 네모 칸(상자)&quot; 자체를 의미합니다. 그 상자 안에 든 내용물을 꺼내려면 반드시 뒤에 &lt;b&gt;&lt;code&gt;.value&lt;/code&gt;&lt;/b&gt;를 붙여주어야 합니다. 초보자들이 가장 많이 하는 실수입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;openpyxl.Workbook()&lt;/code&gt;&lt;/b&gt;: 완전히 새로운 빈 엑셀 파일을 하나 만든다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;openpyxl.load_workbook(&quot;파일명.xlsx&quot;)&lt;/code&gt;&lt;/b&gt;: 이미 있는 엑셀 파일을 파이썬으로 불러온다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;ws.append(리스트)&lt;/code&gt;&lt;/b&gt;: 엑셀 시트의 맨 아랫줄에 데이터를 한 줄(행) 전체로 깔끔하게 밀어 넣는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;셀.value&lt;/code&gt;&lt;/b&gt;: 셀 상자 안에 들어있는 진짜 데이터 값을 꺼낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분은 파이썬으로 크롤링을 하고(&lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;BeautifulSoup&lt;/code&gt;), 그 결과를 엑셀 파일(&lt;code&gt;.xlsx&lt;/code&gt;)로 저장해 팀원들에게 자동으로 공유할 수 있는 엄청난 능력을 갖추게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길고 길었던 파이썬 정복 시리즈도 어느덧 마지막을 향해 달려가고 있습니다.&lt;br /&gt;다음 포스팅에서는 대망의 마지막 프로젝트, &lt;b&gt;&quot;날씨 정보를 내 카톡으로? 공공데이터 API 연동 기초&quot;&lt;/b&gt;를 다루어 보겠습니다. 남이 정성껏 만들어둔 데이터를 가장 안전하고 확실하게 가져오는 API의 세계로 초대합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1294</guid>
      <comments>https://hianna.tistory.com/1294#entry1294comment</comments>
      <pubDate>Tue, 14 Apr 2026 00:53:10 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 15. &amp;quot;뉴스 제목 1초 만에 긁어오기&amp;quot; 웹 크롤링 기초 (requests, BeautifulSoup)</title>
      <link>https://hianna.tistory.com/1293</link>
      <description>&lt;p&gt;매일 아침 출근해서 관심 있는 분야의 뉴스 기사 제목을 엑셀로 정리하는 업무가 있다고 가정해 봅시다. 마우스로 드래그하고, 복사하고, 붙여넣기... 100개의 기사를 정리하려면 한참이 걸립니다.&lt;/p&gt;
&lt;p&gt;하지만 파이썬을 사용하면 이 모든 과정을 &lt;strong&gt;단 1초&lt;/strong&gt; 만에 끝낼 수 있습니다. 인터넷 웹페이지에 있는 데이터를 파이썬이 대신 읽어서 필요한 부분만 쏙쏙 뽑아오는 기술, 바로 &lt;strong&gt;웹 크롤링(Web Scraping/Crawling)&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;오늘은 크롤링의 양대 산맥인 &lt;strong&gt;&lt;code&gt;requests&lt;/code&gt;&lt;/strong&gt; 패키지와 &lt;strong&gt;&lt;code&gt;BeautifulSoup&lt;/code&gt;&lt;/strong&gt; 패키지를 사용해 보겠습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;1. 크롤링 준비물 챙기기 (패키지 설치)&lt;/h3&gt;
&lt;p&gt;크롤링을 하려면 남이 만든 훌륭한 도구(외부 패키지) 두 개가 필요합니다. 터미널(명령 프롬프트)을 열고 아래 명령어를 입력해 설치해 주세요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. 웹페이지 접속을 도와주는 패키지
pip install requests

# 2. 복잡한 웹페이지 문서(HTML)에서 원하는 데이터만 예쁘게 발라내는 패키지
pip install beautifulsoup4
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;2. 웹 크롤링의 3단계 작동 원리&lt;/h3&gt;
&lt;p&gt;크롤링은 우리가 웹 브라우저(크롬, 사파리 등)로 인터넷을 하는 과정과 완전히 똑같습니다. 단지 사람이 눈으로 보는 대신, 파이썬 코드가 대신 읽어주는 것뿐입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;요청(Request):&lt;/strong&gt; 파이썬이 대상 웹사이트에 &amp;quot;이 페이지 좀 보여줘!&amp;quot;라고 접속을 요청합니다. (&lt;code&gt;requests&lt;/code&gt; 사용)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;응답(Response):&lt;/strong&gt; 웹사이트가 파이썬에게 해당 페이지의 설계도인 &lt;strong&gt;HTML 문서&lt;/strong&gt;를 던져줍니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;추출(Parsing):&lt;/strong&gt; 받은 HTML 문서에서 제목, 날짜, 본문 등 &amp;quot;내가 원하는 글자&amp;quot;만 예쁘게 핀셋으로 뽑아냅니다. (&lt;code&gt;BeautifulSoup&lt;/code&gt; 사용)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;3. F12 (개발자 도구)로 웹페이지 구조 훔쳐보기&lt;/h3&gt;
&lt;p&gt;크롤링을 하려면 내가 가져오고 싶은 데이터가 HTML 설계도 안에서 &lt;strong&gt;&amp;#39;어떤 이름표(클래스)&amp;#39;&lt;/strong&gt;를 달고 있는지 알아야 합니다.&lt;/p&gt;
&lt;p&gt;크롬(Chrome) 브라우저에서 네이버 뉴스(또는 아무 웹페이지)를 띄우고 키보드의 &lt;strong&gt;&lt;code&gt;F12&lt;/code&gt;&lt;/strong&gt;를 눌러보세요. 오른쪽에 복잡한 외계어 같은 코드가 나타납니다. 이것이 바로 웹페이지의 민낯인 HTML입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;돋보기(또는 화살표) 아이콘을 누르고 뉴스 제목을 클릭해 보세요.&lt;/li&gt;
&lt;li&gt;뉴스 제목이 &lt;code&gt;&amp;lt;a href=&amp;quot;...&amp;quot; class=&amp;quot;news_tit&amp;quot;&amp;gt;&lt;/code&gt; 같은 태그(Tag)로 감싸져 있는 것을 볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;여기서 &lt;strong&gt;&lt;code&gt;class=&amp;quot;news_tit&amp;quot;&lt;/code&gt;&lt;/strong&gt; 이 부분이 바로 우리가 핀셋으로 집어낼 &lt;strong&gt;고유한 이름표&lt;/strong&gt;입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;4. 실전! 파이썬으로 뉴스 제목 긁어오기&lt;/h3&gt;
&lt;p&gt;자, 이제 코드를 작성해 볼 차례입니다. (참고: 네이버 검색 결과의 뉴스 탭을 기준으로 한 예시입니다. 웹사이트의 구조는 수시로 바뀌므로, 클래스 이름은 F12를 눌러 직접 확인한 값으로 변경해야 할 수도 있습니다.)&lt;/p&gt;
&lt;h4&gt;  예제 코드: 파이썬 크롤러 만들기&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import requests
from bs4 import BeautifulSoup

# 1. 접속할 웹페이지 주소 (네이버에 &amp;#39;파이썬&amp;#39;을 검색한 뉴스 탭 결과)
url = &amp;quot;https://search.naver.com/search.naver?where=news&amp;amp;query=파이썬&amp;quot;

# ⚠️ 꿀팁: 로봇(크롤러)이 아닌 척하기 위한 신분증(User-Agent) 생성
# 네이버 같은 대형 사이트는 기계가 접속하는 것을 차단하기도 합니다. &amp;quot;나 진짜 사람 브라우저야!&amp;quot;라고 속이는 코드입니다.
headers = {&amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/100.0.4896.75&amp;quot;}

# 2. 웹사이트에 접속해서 HTML 문서 받아오기 (requests)
response = requests.get(url, headers=headers)

# 접속이 잘 되었는지 확인 (200이 나오면 정상)
if response.status_code == 200:

    # 3. 받아온 HTML 문서를 BeautifulSoup이 분석할 수 있게 끓이기(Parsing)
    # response.text: 받아온 문서 전체 내용
    # &amp;quot;html.parser&amp;quot;: HTML 번역기 사용
    soup = BeautifulSoup(response.text, &amp;quot;html.parser&amp;quot;)

    # 4. 원하는 데이터만 핀셋으로 쏙쏙 뽑아내기
    # select() 함수는 조건에 맞는 모든 데이터를 &amp;#39;리스트&amp;#39; 형태로 찾아줍니다.
    # HTML에서 class 이름으로 찾을 때는 이름 앞에 점(.)을 찍습니다!
    titles = soup.select(&amp;quot;.news_tit&amp;quot;)

    # 5. 찾은 데이터들(리스트)을 for문으로 하나씩 꺼내서 출력하기
    # enumerate를 써서 1번부터 번호표를 붙여줍니다. (기억나시죠?)
    for i, title in enumerate(titles, start=1):
        # title.text: HTML 태그(&amp;lt;a...&amp;gt;)는 다 떼어버리고 진짜 &amp;#39;글자&amp;#39;만 가져옵니다.
        # title[&amp;quot;href&amp;quot;]: 기사를 클릭했을 때 이동하는 진짜 &amp;#39;링크 주소&amp;#39;를 가져옵니다.
        print(f&amp;quot;{i}번째 기사: {title.text}&amp;quot;)
        print(f&amp;quot;링크: {title[&amp;#39;href&amp;#39;]}&amp;quot;)
        print(&amp;quot;-&amp;quot; * 50)

else:
    print(f&amp;quot;웹페이지 접속 실패! 에러 코드: {response.status_code}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실행 결과:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1번째 기사: &amp;quot;파이썬으로 코딩해요&amp;quot;… 00구, 초등생 대상 디지털 교육
링크: https://n.news.naver.com/mnews/article/...
--------------------------------------------------
2번째 기사: 파이썬 등 IT 전문 인력 모십니다.
링크: https://www.어쩌구저쩌구.com/...
--------------------------------------------------
... (쭉 이어짐)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;  코드 핵심 설명:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requests.get()&lt;/code&gt;: 지정한 주소로 접속해 데이터를 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;soup.select(&amp;quot;.클래스이름&amp;quot;)&lt;/code&gt;: HTML 문서에서 해당 클래스 이름표를 단 모든 요소를 찾아 리스트로 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.text&lt;/code&gt;: 복잡한 기호들을 싹 지우고 우리 눈에 보이는 &lt;strong&gt;순수 텍스트&lt;/strong&gt;만 추출합니다. 정말 편리하죠!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;5. 크롤링할 때 반드시 지켜야 할 매너 (주의사항)&lt;/h3&gt;
&lt;p&gt;파이썬 크롤링은 너무나 강력해서, 자칫하면 상대방 웹사이트 서버를 다운시킬 수도 있습니다(이것이 바로 디도스 공격의 원리입니다). 따라서 지켜야 할 윤리가 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;너무 빠르게 반복하지 마세요:&lt;/strong&gt; &lt;code&gt;for&lt;/code&gt;문을 돌려 수천 페이지를 긁어올 때는, 지난 시간에 배운 표준 라이브러리인 &lt;code&gt;time.sleep(1)&lt;/code&gt; (1초 쉬기) 등을 사용해 서버에 숨통을 트여주어야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저작권과 개인정보를 주의하세요:&lt;/strong&gt; 남의 사이트에서 긁어온 데이터를 상업적으로 함부로 팔거나 배포하면 법적 처벌을 받을 수 있습니다. 가급적 &amp;#39;개인 학습 및 업무 자동화&amp;#39; 용도로만 사용하세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;  마치며&lt;/h3&gt;
&lt;p&gt;오늘의 핵심 요약입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;requests.get(url)&lt;/code&gt;&lt;/strong&gt;: 해당 웹페이지의 전체 소스 코드(HTML)를 다운로드해온다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;BeautifulSoup(html, &amp;quot;html.parser&amp;quot;)&lt;/code&gt;&lt;/strong&gt;: 다운로드한 코드를 탐색하기 쉽게 정돈해 준다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;soup.select(&amp;quot;.클래스명&amp;quot;)&lt;/code&gt;&lt;/strong&gt;: 정돈된 코드 중에서 내가 원하는 데이터(태그)만 쏙 뽑아 리스트로 만든다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.text&lt;/code&gt;&lt;/strong&gt;: 뽑아낸 데이터에서 군더더기를 버리고 순수 텍스트만 추출한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 여러분은 인터넷에 널려 있는 수많은 정보(주식 시세, 영화 평점, 부동산 가격 등)를 내 컴퓨터로 자동으로 가져올 수 있는 마법사가 되었습니다.&lt;/p&gt;
&lt;p&gt;하지만 이렇게 터미널(까만 창)에 출력만 해두면 나중에 다시 보기가 힘들죠?&lt;br&gt;다음 포스팅에서는 &lt;strong&gt;&amp;quot;파이썬으로 엑셀 파일 만들고 서식까지 꾸미기&amp;quot; - &lt;code&gt;openpyxl&lt;/code&gt; 라이브러리를 활용한 엑셀 노가다 해방 일지&lt;/strong&gt;에 대해 알아보겠습니다. 방금 크롤링한 뉴스 제목들을 엑셀 파일로 예쁘게 저장해 볼게요!&lt;/p&gt;
&lt;p&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1293</guid>
      <comments>https://hianna.tistory.com/1293#entry1293comment</comments>
      <pubDate>Mon, 13 Apr 2026 08:52:35 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 14. &amp;quot;에러가 나도 멈추지 않아!&amp;quot; 프로그램 생명 연장, try-except 예외 처리</title>
      <link>https://hianna.tistory.com/1292</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬이 열심히 코드를 한 줄 한 줄 실행하다가, 도저히 계산할 수 없거나 처리할 수 없는 상황을 마주치면 어떻게 할까요? 파이썬은 즉시 두 손을 들고 파업을 선언합니다. 이것을 우리는 &lt;b&gt;예외(Exception)가 발생했다&lt;/b&gt;고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실무에서 돌아가는 프로그램이 에러 하나 났다고 픽픽 꺼져버리면 안 되겠죠? 이럴 때 파이썬에게 &quot;에러가 나면 멈추지 말고, 이렇게 대처해!&quot;라고 지시하는 문법이 바로 &lt;code&gt;try-except&lt;/code&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 예외(Exception)란 무엇인가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법을 틀려서 발생하는 오타(Syntax Error)와 달리, 문법은 완벽하지만 &lt;b&gt;실행 도중에 발생하는 문제&lt;/b&gt;를 예외라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 대표적인 예외 상황들을 볼까요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ZeroDivisionError&lt;/code&gt;: 숫자를 0으로 나누려고 할 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ValueError&lt;/code&gt;: 숫자로 바꿀 수 없는 문자를 &lt;code&gt;int()&lt;/code&gt;로 바꾸려고 할 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileNotFoundError&lt;/code&gt;: 없는 파일을 &lt;code&gt;open()&lt;/code&gt;으로 열려고 할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황을 대비해 안전망을 깔아보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 기본 안전망: &lt;code&gt;try-except&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법은 아주 직관적입니다. 에러가 날 것 같은 불안한 코드를 &lt;code&gt;try&lt;/code&gt; 블록(구역) 안에 넣고, 에러가 났을 때 실행할 대안 코드를 &lt;code&gt;except&lt;/code&gt; 블록 안에 넣습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 0으로 나누기 방어하기&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;print(&quot;계산기를 켭니다.&quot;)

# 1. 에러가 날 수도 있는 코드를 try 안에 넣습니다.
try:
    result = 10 / 0  # 숫자를 0으로 나누면 에러가 발생합니다!
    print(f&quot;결과는 {result}입니다.&quot;)  # 위에서 에러가 났으므로 이 줄은 무시됩니다.

# 2. 에러가 발생하면 파이썬이 즉시 except 구역으로 도망쳐 옵니다.
except:
    print(&quot;앗! 숫자를 0으로 나눌 수는 없습니다.&quot;)

# 에러를 안전하게 처리했으므로 프로그램이 죽지 않고 끝까지 실행됩니다!
print(&quot;계산기를 끕니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;계산기를 켭니다.
앗! 숫자를 0으로 나눌 수는 없습니다.
계산기를 끕니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;code&gt;try-except&lt;/code&gt;가 없었다면 프로그램은 10 / 0 부분에서 시뻘건 에러를 뿜어내며 멈췄을 것이고, &quot;계산기를 끕니다&quot;라는 마지막 문장은 영영 출력되지 못했을 것입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 에러 종류별로 다르게 대처하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병원에서도 배가 아픈 환자와 뼈가 부러진 환자의 처방이 다르듯, 에러의 종류에 따라 다른 메시지를 보여줄 수 있습니다. &lt;code&gt;except&lt;/code&gt; 뒤에 구체적인 에러 이름을 적어주면 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 여러 가지 에러 대처하기&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;data_list = [&quot;10&quot;, &quot;20&quot;, &quot;파이썬&quot;, &quot;40&quot;]

for item in data_list:
    try:
        # 문자를 숫자로 바꾸고, 100을 그 숫자로 나눠봅니다.
        number = int(item)
        print(100 / number)

    except ValueError:
        # int(&quot;파이썬&quot;) 처럼 숫자로 바꿀 수 없을 때
        print(f&quot;[{item}]은(는) 숫자로 바꿀 수 없는 데이터입니다!&quot;)

    except ZeroDivisionError:
        # 100 / 0 처럼 0으로 나누려 할 때 (위 데이터엔 0이 없지만 예시로 넣음)
        print(&quot;0으로는 나눌 수 없어요!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;파이썬&quot;이라는 문자를 만났을 때 &lt;code&gt;ValueError&lt;/code&gt;가 발생합니다.&lt;/li&gt;
&lt;li&gt;파이썬은 여러 개의 &lt;code&gt;except&lt;/code&gt; 문을 훑어보다가, &lt;code&gt;ValueError&lt;/code&gt;에 해당하는 처방전을 찾아 실행하고 다음 반복으로 부드럽게 넘어갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 에러의 진짜 원인 파악하기 (&lt;code&gt;as e&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 나긴 났는데, 정확히 무슨 에러인지 컴퓨터가 알려주는 메시지를 그대로 보고 싶을 때가 있습니다. (주로 개발자가 디버깅을 할 때 씁니다.)&lt;br /&gt;이때는 &lt;b&gt;&lt;code&gt;except Exception as e&lt;/code&gt;&lt;/b&gt;라는 마법의 주문을 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 에러 메시지 캡처하기&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;try:
    # 존재하지 않는 파일을 열려고 시도해 봅니다.
    with open(&quot;ghost_file.txt&quot;, &quot;r&quot;) as f:
        content = f.read()

# 모든 에러(Exception)를 잡아서 그 내용을 변수 'e'에 담습니다.
except Exception as e:
    print(&quot;오류가 발생했습니다!&quot;)
    print(f&quot;상세 원인: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;오류가 발생했습니다!
상세 원인: [Errno 2] No such file or directory: 'ghost_file.txt'
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 무조건 실행되는 마무리 투수: &lt;code&gt;finally&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;try&lt;/code&gt; 블록에서 에러가 나든 안 나든, &lt;b&gt;마지막에 무조건 100% 실행되어야 하는 코드&lt;/b&gt;가 있을 때 &lt;code&gt;finally&lt;/code&gt;를 사용합니다.&lt;br /&gt;주로 데이터베이스(DB) 접속을 끊거나 외부 자원(네트워크 등)을 해제할 때 아주 요긴합니다. (물론 &lt;code&gt;with&lt;/code&gt;문을 쓰면 파일은 알아서 닫히지만, 다른 자원에는 여전히 유용합니다.)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 에러 여부와 상관없는 실행 보장&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;try:
    print(&quot;은행 시스템에 접속했습니다.&quot;)
    print(&quot;계좌이체를 시작합니다...&quot;)
    # 강제로 에러를 발생시킵니다!
    raise Exception(&quot;잔액 부족&quot;)

except Exception as e:
    print(f&quot;이체 실패: {e}&quot;)

finally:
    # 이체가 성공하든 실패하든, 시스템 접속은 무조건 끊어야 합니다!
    print(&quot;은행 시스템 접속을 종료합니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;은행 시스템에 접속했습니다.
계좌이체를 시작합니다...
이체 실패: 잔액 부족
은행 시스템 접속을 종료합니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 조용히 무시하기: &lt;code&gt;pass&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔은 크롤링을 하거나 데이터를 분석할 때, 사소한 에러는 그냥 무시하고 넘어가고 싶을 때가 있습니다. 이때는 &lt;code&gt;except&lt;/code&gt; 문 안에 파이썬의 &lt;code&gt;pass&lt;/code&gt; 키워드 하나만 딸랑 적어두면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;try:
    # 에러가 날 만한 복잡한 크롤링 작업
    print(1 / 0)
except:
    # &quot;에러 나도 괜찮아, 아무 일도 없었던 것처럼 그냥 넘어가!&quot;
    pass

print(&quot;프로그램이 계속 실행됩니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚠️ 주의:&lt;/b&gt; &lt;code&gt;pass&lt;/code&gt;를 남용하면 진짜 치명적인 버그가 생겼을 때조차 에러 메시지가 뜨지 않아서, 밤을 새워도 원인을 못 찾는 대참사가 일어날 수 있습니다. 정말 확신할 때만 쓰세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;try&lt;/code&gt;&lt;/b&gt;: 일단 여기서 에러가 날 수도 있는 코드를 조심스럽게 실행해 본다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;except&lt;/code&gt;&lt;/b&gt;: 만약 위에서 에러가 터지면, 프로그램이 멈추지 않게 이곳의 코드를 실행하며 대처한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;Exception as e&lt;/code&gt;&lt;/b&gt;: 파이썬이 알려주는 구체적인 에러 원인을 &lt;code&gt;e&lt;/code&gt;라는 변수에 담아서 출력할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;finally&lt;/code&gt;&lt;/b&gt;: 에러가 나든 안 나든 마지막에 무조건 한 번 실행되는 정리 정돈 코드다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/b&gt;: 에러를 완전히 무시하고 다음 코드로 부드럽게 넘어가는 키워드다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분의 프로그램은 돌발 상황에서도 꿋꿋하게 살아남을 수 있는 강인한 생명력을 갖게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 길고 길었던 &lt;b&gt;파이썬 기초 문법(Phase 1 ~ Phase 5)&lt;/b&gt; 과정을 모두 수료하셨습니다. 정말 고생 많으셨습니다!  &lt;br /&gt;하지만 진짜 재미있는 건 지금부터입니다. 우리가 배운 문법들을 조합해서 실생활에 써먹을 수 있는 도구를 만들어봐야죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅부터는 &lt;b&gt;Phase 6. 미니 프로젝트&lt;/b&gt;의 첫 번째 시간!&lt;br /&gt;&lt;b&gt;&quot;네이버 뉴스 제목 1초 만에 긁어오기&quot; - &lt;code&gt;requests&lt;/code&gt;와 &lt;code&gt;BeautifulSoup&lt;/code&gt;을 이용한 웹 크롤링 기초&lt;/b&gt;에 대해 알아보겠습니다. 드디어 마법 지팡이를 휘두를 시간입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1292</guid>
      <comments>https://hianna.tistory.com/1292#entry1292comment</comments>
      <pubDate>Mon, 13 Apr 2026 00:52:11 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 13. &amp;quot;데이터를 내 컴퓨터에 영구 저장하자!&amp;quot; 텍스트 파일과 CSV 읽고 쓰기 (open, with)</title>
      <link>https://hianna.tistory.com/1291</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;크롤링으로 수집한 10,000건의 뉴스 기사, 혹은 반복문으로 열심히 계산한 결과물들. 이것들을 파이썬 프로그램이 종료된 후에도 남겨두려면 '파일' 형태로 저장해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 파이썬으로 가장 기본이 되는 텍스트 파일(&lt;code&gt;.txt&lt;/code&gt;)을 다루는 법과, 엑셀에서 바로 열어볼 수 있는 &lt;code&gt;.csv&lt;/code&gt; 파일을 직접 만들어보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 파일 다루기의 3단계: 열기(open) ➔ 작업하기 ➔ 닫기(close)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 파일을 다룰 때는 마치 냉장고를 쓰는 것과 같습니다. 냉장고 문을 열고, 내용물을 넣거나 빼고, 반드시 문을 닫아야 하죠. 문을 안 닫으면 냉장고가 고장 나듯, 파이썬에서도 파일을 닫지 않으면 에러가 발생할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 메모장 파일 만들고 글씨 쓰기&lt;/h4&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;# 1. 파일 열기 (open)
# &quot;memo.txt&quot;라는 파일을 &quot;w&quot;(쓰기 모드)로 엽니다.
# 한글이 깨지지 않게 encoding=&quot;utf-8&quot;을 꼭 적어주세요!
f = open(&quot;memo.txt&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;)

# 2. 작업하기 (글씨 쓰기)
f.write(&quot;안녕하세요! 파이썬으로 만든 첫 번째 파일입니다.\n&quot;)
f.write(&quot;두 번째 줄입니다.&quot;)

# 3. 파일 닫기 (close) - 매우 중요!
f.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행하고 VS Code의 왼쪽 탐색기를 보면, 방금 전까지 없었던 &lt;code&gt;memo.txt&lt;/code&gt; 파일이 마법처럼 생성된 것을 볼 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  파일 모드 3가지&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;&quot;w&quot;&lt;/code&gt; (Write):&lt;/b&gt; 쓰기 모드. 파일이 없으면 새로 만들고, 파일이 이미 있으면 &lt;b&gt;기존 내용을 다 지우고&lt;/b&gt; 새로 씁니다. (덮어쓰기)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;&quot;a&quot;&lt;/code&gt; (Append):&lt;/b&gt; 추가 모드. 기존 파일 내용 &lt;b&gt;맨 끝에 이어서&lt;/b&gt; 새로운 내용을 추가합니다. (일기장이나 로그 남길 때 유용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;&quot;r&quot;&lt;/code&gt; (Read):&lt;/b&gt; 읽기 모드. 이미 있는 파일의 내용을 파이썬으로 읽어옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 파이썬의 마법: &lt;code&gt;with&lt;/code&gt; 문으로 자동 닫기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 냉장고 문(&lt;code&gt;close()&lt;/code&gt;)을 닫는 게 아주 중요하다고 했습니다. 하지만 코드가 길어지다 보면 문 닫는 걸 깜빡하기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 파이썬 개발자들은 &lt;b&gt;작업이 끝나면 알아서 파일을 닫아주는 아주 우아한 문법&lt;/b&gt;을 만들었습니다. 바로 &lt;b&gt;&lt;code&gt;with&lt;/code&gt; 문&lt;/b&gt;입니다. 실무에서는 100% 이 방식을 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: &lt;code&gt;with&lt;/code&gt; 문을 활용한 안전한 파일 쓰기/읽기&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;# --- 파일 쓰기 (with 문 사용) ---
# &quot;f = open(...)&quot; 대신 &quot;with open(...) as f:&quot; 형태를 씁니다.
with open(&quot;diary.txt&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    f.write(&quot;오늘은 파이썬 파일 입출력을 배웠다.\n&quot;)
    f.write(&quot;with 문을 쓰니까 close()를 안 해도 돼서 정말 편하다!&quot;)

# 들여쓰기가 끝나는 순간, 파이썬이 알아서 f.close()를 실행해 줍니다.


# --- 파일 읽기 ---
# 읽어올 때는 모드를 &quot;r&quot;로 설정합니다.
with open(&quot;diary.txt&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    # f.read()는 파일 내용 전체를 하나의 거대한 문자열로 가져옵니다.
    content = f.read()

print(&quot;--- 불러온 내용 ---&quot;)
print(content)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤가요? 들여쓰기 구역 안에서만 파일이 열려있고, 구역을 빠져나오면 자동으로 파일이 닫히니 훨씬 안전하고 코드가 깔끔해집니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실전 응용: 엑셀에서 열리는 CSV 파일 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 파일만 만들면 아쉽죠? 데이터 분석의 기본이 되는 엑셀 파일을 파이썬으로 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 쉬운 방법은 &lt;b&gt;CSV(Comma Separated Values)&lt;/b&gt; 형태의 텍스트 파일을 만드는 것입니다. 이름 그대로 &lt;b&gt;&quot;데이터를 쉼표(,)로 구분해 놓은 파일&quot;&lt;/b&gt;인데, 엑셀에서 열면 쉼표를 기준으로 알아서 예쁘게 표를 만들어줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 학생 점수표를 CSV 파일로 저장하기&lt;/h4&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;# 파이썬 리스트에 담긴 데이터
students = [
    [&quot;이름&quot;, &quot;국어&quot;, &quot;수학&quot;],  # 엑셀의 첫 번째 줄(헤더)
    [&quot;홍길동&quot;, 90, 85],
    [&quot;김철수&quot;, 80, 100],
    [&quot;이영희&quot;, 100, 95]
]

# 확장자를 .csv로 지정하고, 쓰기(&quot;w&quot;) 모드로 엽니다.
with open(&quot;scores.csv&quot;, &quot;w&quot;, encoding=&quot;utf-8-sig&quot;) as f:

    for row in students:
        # 각 리스트의 요소를 문자로 바꾸고 쉼표(,)로 이어붙입니다.
        # 예: [&quot;홍길동&quot;, 90, 85] -&amp;gt; &quot;홍길동,90,85&quot;
        line = f&quot;{row[0]},{row[1]},{row[2]}\n&quot;
        f.write(line)

print(&quot;scores.csv 파일이 성공적으로 생성되었습니다!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(  꿀팁: 엑셀에서 한글 CSV를 열 때 글자가 깨진다면, 인코딩을 &lt;code&gt;&quot;utf-8&quot;&lt;/code&gt; 대신 &lt;code&gt;&quot;utf-8-sig&quot;&lt;/code&gt;로 적어주면 완벽하게 해결됩니다!)&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 실행하고 폴더에 생긴 &lt;code&gt;scores.csv&lt;/code&gt; 파일을 더블클릭해서 엑셀로 열어보세요. 파이썬 데이터가 엑셀 표에 깔끔하게 들어간 것을 보고 엄청난 희열을 느끼실 수 있을 겁니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 파일을 통째로 읽어 리스트로 만들기: &lt;code&gt;readlines()&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 반대로, 컴퓨터에 저장된 여러 줄의 데이터를 파이썬의 '리스트'로 한 줄씩 예쁘게 가져와 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;with open(&quot;memo.txt&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    # readlines()는 파일의 모든 줄을 읽어서 '리스트' 형태로 만들어줍니다.
    lines = f.readlines()

print(lines)
# 출력 예시: ['안녕하세요! 파이썬으로 만든 첫 번째 파일입니다.\n', '두 번째 줄입니다.']

# 리스트니까 for문으로 하나씩 꺼내 쓰기 좋겠죠?
for line in lines:
    # 파일에 적힌 줄바꿈(\n)과 print()의 줄바꿈이 중복되지 않게 strip()으로 청소해줍니다.
    print(line.strip()) 
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;open(&quot;파일명&quot;, &quot;모드&quot;)&lt;/code&gt;&lt;/b&gt;: 파일을 다루는 기본 함수. 모드에는 쓰기(&lt;code&gt;w&lt;/code&gt;), 추가(&lt;code&gt;a&lt;/code&gt;), 읽기(&lt;code&gt;r&lt;/code&gt;)가 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;encoding=&quot;utf-8&quot;&lt;/code&gt;&lt;/b&gt;: 한글을 다룰 때 글자가 깨지지 않게 해주는 필수 방패.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;with open(...) as f:&lt;/code&gt;&lt;/b&gt;: 파일을 열고 작업한 뒤, 안전하게 자동으로 닫아주는(close) 파이썬만의 우아한 문법.&lt;/li&gt;
&lt;li&gt;데이터를 쉼표(&lt;code&gt;,&lt;/code&gt;)로 구분해서 &lt;code&gt;.csv&lt;/code&gt; 파일로 저장하면 엑셀에서 쉽게 열어볼 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 파이썬과 여러분의 하드디스크가 연결되었습니다. 크롤링한 데이터를 저장하거나, 매일 쌓이는 로그 데이터를 기록하는 등 진짜 쓸모 있는 프로그램에 한 발짝 더 다가섰네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 프로그램을 돌리다 보면 &quot;파일이 존재하지 않습니다!&quot; 라거나 &quot;숫자를 0으로 나눌 수 없습니다!&quot; 같은 돌발 상황(에러)이 무조건 발생합니다. 에러가 나면 프로그램은 그 자리에서 빨간 글씨를 뿜으며 처참하게 죽어버리죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;에러가 나도 프로그램이 죽지 않게 보호하는 방어막! 예외 처리(try-except)&quot;&lt;/b&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1291</guid>
      <comments>https://hianna.tistory.com/1291#entry1291comment</comments>
      <pubDate>Sun, 12 Apr 2026 07:02:26 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 12. &amp;quot;객체지향이긴 한데...&amp;quot; 파이썬 클래스(Class)와 self의 정체 완벽 이해</title>
      <link>https://hianna.tistory.com/1290</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;여러분이 RPG 게임을 만든다고 상상해 보세요. 슬라임, 오크, 드래곤 같은 몬스터들을 만들어야 합니다. 몬스터는 '이름', '체력'이라는 &lt;b&gt;데이터(변수)&lt;/b&gt;를 가지고 있고, '공격하기'라는 &lt;b&gt;행동(함수)&lt;/b&gt;을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몬스터가 100마리라면 변수와 함수를 100개씩 따로 만들어야 할까요?&lt;br /&gt;이럴 때 필요한 것이 바로 &lt;b&gt;&quot;몬스터를 찍어내는 붕어빵 틀&quot;&lt;/b&gt;, 즉 &lt;b&gt;클래스(Class)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 붕어빵 틀(Class)과 붕어빵(Object/Instance)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 유명하고 찰떡같은 비유를 들어보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;클래스(Class):&lt;/b&gt; 똑같은 모양의 붕어빵을 무한대로 찍어낼 수 있는 &lt;b&gt;'틀(설계도)'&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체(Object) 또는 인스턴스(Instance):&lt;/b&gt; 그 틀에서 구워져 나온 &lt;b&gt;'실제 붕어빵'&lt;/b&gt;입니다. 팥을 넣으면 팥붕어빵, 슈크림을 넣으면 슈크림붕어빵이 되듯, 틀은 하나지만 결과물은 여러 개를 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 가장 단순한 붕어빵 틀 만들기&lt;/h4&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;# 1. 클래스(틀) 정의하기
# 클래스 이름은 대문자로 시작하는 것이 파이썬의 암묵적인 규칙입니다.
class Monster:
    pass  # pass는 &quot;일단 아무 기능 없이 빈 껍데기만 둘게&quot;라는 뜻입니다.

# 2. 객체(붕어빵) 찍어내기
slime = Monster()  # 몬스터 틀로 'slime'이라는 객체를 하나 만듦
orc = Monster()    # 똑같은 틀로 'orc'라는 객체를 하나 더 만듦

# 출력해보면 서로 다른 메모리에 존재하는 별개의 객체임을 알 수 있습니다.
print(type(slime))
print(type(orc))
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 초기화의 마법사: &lt;code&gt;__init__&lt;/code&gt; 생성자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 껍데기 몬스터는 재미가 없죠. 몬스터가 세상에 처음 태어날 때(객체가 생성될 때) &lt;b&gt;&quot;너의 이름은 뭐고, 체력은 몇이야!&quot;&lt;/b&gt;라고 초기 설정을 해주려면 어떻게 해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서는 이름 앞뒤로 언더바가 두 개씩 붙은 &lt;b&gt;&lt;code&gt;__init__&lt;/code&gt; (initialize의 약자)&lt;/b&gt;라는 특별한 함수를 사용합니다. 붕어빵이 구워질 때 앙금을 넣는 작업이라고 생각하시면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 미스터리 풀기: &lt;code&gt;self&lt;/code&gt;는 도대체 누구인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 배우면 무조건 마주치는 단어가 바로 &lt;code&gt;self&lt;/code&gt;입니다.&lt;br /&gt;&lt;code&gt;self&lt;/code&gt;를 한마디로 정의하자면 &lt;b&gt;&quot;수많은 붕어빵 중에서, 지금 당장 일을 하고 있는 바로 '나 자신(현재 객체)'&quot;&lt;/b&gt;을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 안에서 만들어지는 모든 함수(이를 &lt;b&gt;메서드&lt;/b&gt;라고 부릅니다)는 &lt;b&gt;무조건 첫 번째 재료(매개변수)로 &lt;code&gt;self&lt;/code&gt;를 적어주어야 합니다.&lt;/b&gt; 그래야 파이썬이 &quot;아, 지금 슬라임이 공격하는 거구나&quot;, &quot;오크가 공격하는 거구나&quot;를 구분할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 체력과 이름을 가진 몬스터 클래스 완성하기&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;class Monster:
    # 1. 생성자 함수 (__init__)
    # 몬스터가 만들어질 때 자동으로 딱 한 번 실행됩니다.
    def __init__(self, name, hp):
        # self.name: &quot;나 자신의 이름(변수)에 방금 입력받은 name을 저장해라!&quot;
        self.name = name  
        self.hp = hp      
        print(f&quot;야생의 {self.name}이(가) 나타났다! (체력: {self.hp})&quot;)

    # 2. 일반 메서드 (몬스터의 행동)
    # 반드시 첫 번째 매개변수로 self를 넣어야 합니다.
    def attack(self):
        # self.name을 쓰면, 알아서 '자신의 이름'을 찾아옵니다.
        print(f&quot;[{self.name}]의 맹공격! 상대에게 피해를 입혔습니다.&quot;)

# --- 클래스 사용해보기 ---

# 슬라임 객체 생성 (이때 __init__이 자동 실행되며 이름과 체력이 세팅됨)
# 주의: 괄호 안에 self는 우리가 직접 넣지 않습니다! 파이썬이 알아서 '나 자신'을 몰래 넣어줍니다.
slime = Monster(&quot;초록 슬라임&quot;, 100)

# 오크 객체 생성
orc = Monster(&quot;성난 오크&quot;, 300)

print(&quot;-&quot; * 20)

# 몬스터 행동 개시!
slime.attack() # 슬라임이 공격합니다. (이때 내부적으로 self는 slime이 됩니다.)
orc.attack()   # 오크가 공격합니다. (이때 내부적으로 self는 orc가 됩니다.)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;야생의 초록 슬라임이(가) 나타났다! (체력: 100)
야생의 성난 오크이(가) 나타났다! (체력: 300)
--------------------
[초록 슬라임]의 맹공격! 상대에게 피해를 입혔습니다.
[성난 오크]의 맹공격! 상대에게 피해를 입혔습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 핵심 설명:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Monster(&quot;초록 슬라임&quot;, 100)&lt;/code&gt;을 호출하면 파이썬은 내부적으로 &lt;code&gt;__init__(slime, &quot;초록 슬라임&quot;, 100)&lt;/code&gt;처럼 동작합니다.&lt;/li&gt;
&lt;li&gt;클래스 내부에서 &lt;code&gt;self.name&lt;/code&gt;이라고 적어두면, 슬라임이 부를 땐 '슬라임의 이름'이 되고, 오크가 부를 땐 '오크의 이름'이 됩니다. 즉, &lt;b&gt;객체들끼리 데이터가 섞이지 않도록 지켜주는 역할&lt;/b&gt;을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 언제 클래스를 써야 할까요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 데이터 전처리나 파일 이름 바꾸기 같은 1회성 스크립트를 짤 때는 굳이 클래스를 만들 필요가 없습니다. (함수 몇 개로 충분합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다음과 같은 상황에서는 클래스가 압도적으로 유리합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터와 행동이 밀접하게 연관되어 있을 때&lt;/b&gt; (예: 유저 정보 데이터 + 로그인/로그아웃 행동)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;똑같은 구조를 가진 무언가를 여러 개 만들어야 할 때&lt;/b&gt; (예: 웹 크롤러를 사이트별로 여러 개 띄울 때)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;규모가 큰 프로그램을 짤 때&lt;/b&gt; (코드를 부품화해서 관리하기 편함)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;클래스(Class):&lt;/b&gt; 변수와 함수를 하나로 묶어놓은 &lt;b&gt;붕어빵 틀(설계도)&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체/인스턴스:&lt;/b&gt; 클래스라는 틀을 통해 실제로 메모리에 만들어진 &lt;b&gt;붕어빵&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;__init__&lt;/code&gt;:&lt;/b&gt; 객체가 생성될 때 가장 먼저 실행되어 초기 상태를 세팅해 주는 특별한 함수다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;self&lt;/code&gt;:&lt;/b&gt; 수많은 객체 중에서 현재 일하고 있는 &lt;b&gt;'나 자신'&lt;/b&gt;을 가리키는 파이썬의 필수 키워드다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해하기 조금 까다로울 수 있지만, 파이썬으로 작성된 오픈소스나 라이브러리를 열어보면 99%가 이 클래스 형태로 짜여 있습니다. &lt;code&gt;self&lt;/code&gt;의 개념만 잡아두셔도 남의 코드를 읽는 눈이 확 트이실 겁니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 이제 문법적인 기초 훈련(Phase 4)이 모두 끝났습니다!&lt;br /&gt;다음 포스팅부터는 &lt;b&gt;Phase 5. 실전 기술&lt;/b&gt;로 들어갑니다. 파이썬 세상 안에서만 놀던 데이터를 드디어 내 컴퓨터로 끄집어낼 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 주제는 &lt;b&gt;&quot;엑셀, 텍스트 파일 읽고 쓰기 (open, with문)&quot;&lt;/b&gt;입니다. 파이썬으로 메모장을 열고 글씨를 써보는 신기한 경험을 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1290</guid>
      <comments>https://hianna.tistory.com/1290#entry1290comment</comments>
      <pubDate>Sun, 12 Apr 2026 01:01:09 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 11. &amp;quot;남이 만든 바퀴를 가져다 쓰자!&amp;quot; 파이썬 모듈(import)과 pip 패키지 가이드</title>
      <link>https://hianna.tistory.com/1289</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬이 데이터 분석, 인공지능, 웹 개발 등 모든 분야를 휩쓸고 있는 가장 큰 이유는 바로 &lt;b&gt;압도적인 생태계&lt;/b&gt; 때문입니다. 내가 상상하는 거의 모든 기능이 이미 누군가에 의해 만들어져서 무료로 배포되고 있습니다. 우리는 그저 '다운로드'하고 '가져다 쓰기'만 하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 그 마법의 주문인 &lt;code&gt;import&lt;/code&gt;와, 파이썬의 앱스토어 역할을 하는 &lt;code&gt;pip&lt;/code&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 모듈(Module)과 기본 주문: &lt;code&gt;import&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모듈&lt;/b&gt;이란 쉽게 말해 '잘 만들어진 파이썬 파일(&lt;code&gt;.py&lt;/code&gt;)'입니다. 파일 하나에 유용한 함수와 변수들을 잔뜩 모아놓은 공구 상자라고 생각하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬을 설치하면 기본적으로 제공되는 훌륭한 공구 상자들이 있습니다. 이를 &lt;b&gt;표준 라이브러리(Standard Library)&lt;/b&gt;라고 부릅니다. 이 공구 상자를 열기 위해 사용하는 키워드가 바로 &lt;b&gt;&lt;code&gt;import&lt;/code&gt;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 표준 라이브러리 활용하기 (math, datetime)&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 1. math 모듈 전체를 가져오기
import math

# math 상자 안에 있는 원주율(pi)과 올림(ceil) 함수 사용
print(math.pi)        # 출력: 3.141592653589793
print(math.ceil(3.2)) # 출력: 4

print(&quot;-&quot; * 20)

# 2. 모듈에서 특정 기능만 콕 집어서 가져오기 (from ~ import ~)
# &quot;datetime 모듈 안에서 datetime이라는 기능만 가져올게!&quot;
from datetime import datetime

# 지금 현재 시간 출력하기
now = datetime.now()
print(f&quot;현재 시간: {now}&quot;) 

print(&quot;-&quot; * 20)

# 3. 모듈 이름이 너무 길면 별명(alias) 붙여주기 (import ~ as ~)
import random as rd

# random 대신 rd라는 짧은 이름으로 사용 가능
lucky_number = rd.randint(1, 45) # 1부터 45 사이의 무작위 정수 뽑기
print(f&quot;행운의 번호: {lucky_number}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;import 모듈명&lt;/code&gt;: 모듈 전체를 가져옵니다. 사용할 때마다 &lt;code&gt;모듈명.함수이름()&lt;/code&gt; 형태로 써야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;from 모듈명 import 기능&lt;/code&gt;: 상자 전체를 가져오지 않고, 필요한 도구만 쏙 빼옵니다. 쓸 때 모듈 이름을 생략할 수 있어 편리합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import 모듈명 as 별명&lt;/code&gt;: 이름이 길 때 줄임말을 지정해 줍니다. 데이터 분석에서 &lt;code&gt;import pandas as pd&lt;/code&gt; 형태로 아주 많이 쓰입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 외부 패키지 다운로드: &lt;code&gt;pip&lt;/code&gt;의 등장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬이 기본으로 제공하는 도구 상자(표준 라이브러리)도 훌륭하지만, 인터넷 데이터를 수집하거나 엑셀 파일을 다루는 등 더 전문적인 작업을 하려면 전 세계 개발자들이 만든 &lt;b&gt;외부 패키지&lt;/b&gt;를 다운로드해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용하는 것이 파이썬의 앱스토어인 &lt;b&gt;PyPI (Python Package Index)&lt;/b&gt;와, 여기서 패키지를 다운받아 설치해 주는 매니저 프로그램인 &lt;b&gt;&lt;code&gt;pip&lt;/code&gt;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  터미널(명령 프롬프트)에서 패키지 설치하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VS Code 하단의 터미널(Terminal) 창을 열고 아래와 같이 입력합니다. (파이썬 코드를 적는 곳이 아닙니다!)&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 인터넷 웹페이지의 데이터를 쉽게 가져오게 해주는 'requests' 패키지 설치
pip install requests

# 설치된 패키지 목록 확인하기
pip list

# 패키지 삭제하기 (필요 없어졌을 때)
pip uninstall requests
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 한 줄의 명령어로 복잡한 인터넷 통신 기능을 내 컴퓨터에 설치했습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 다운받은 외부 패키지 사용해 보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 설치한 &lt;code&gt;requests&lt;/code&gt; 패키지를 이용해서, 실제로 인터넷에 있는 데이터를 내 파이썬 코드로 가져와 보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: requests 패키지로 웹 데이터 가져오기&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 방금 pip로 설치한 외부 패키지를 가져옵니다.
import requests

# 구글 메인 페이지에 접속(GET 요청)해서 응답을 받아옵니다.
response = requests.get(&quot;https://www.google.com&quot;)

# 접속이 성공적으로 되었는지 상태 코드(200이면 정상)를 확인합니다.
print(f&quot;응답 코드: {response.status_code}&quot;)

if response.status_code == 200:
    print(&quot;구글에 성공적으로 접속했습니다!&quot;)
else:
    print(&quot;접속에 실패했습니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 터미널에서 &lt;code&gt;pip install requests&lt;/code&gt;를 하지 않고 위 코드를 실행했다면, 파이썬은 &lt;b&gt;&lt;code&gt;ModuleNotFoundError&lt;/code&gt;&lt;/b&gt;(그런 모듈 없는데요?)라는 에러를 뱉어냅니다. 외부 패키지는 반드시 먼저 설치해야 한다는 점을 잊지 마세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 실무 꿀팁: 내 컴퓨터의 환경을 팀원과 공유하기 (&lt;code&gt;requirements.txt&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만든 파이썬 프로그램을 다른 팀원에게 주거나, 서버에 올릴 때 문제가 하나 생깁니다.&lt;br /&gt;&quot;내가 내 컴퓨터에 어떤 패키지들을 &lt;code&gt;pip install&lt;/code&gt; 했는지 어떻게 다 기억하지?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용하는 것이 &lt;code&gt;requirements.txt&lt;/code&gt; 파일입니다. 내 컴퓨터에 설치된 패키지 목록과 버전을 텍스트 파일로 쫙 뽑아주는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;터미널에 입력:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;# 현재 설치된 패키지 목록을 requirements.txt 파일로 내보내기
pip freeze &amp;gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 텍스트 파일을 넘겨받은 다른 팀원은, 일일이 패키지를 설치할 필요 없이 아래 명령어 한 줄이면 나와 똑같은 개발 환경을 1분 만에 세팅할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;터미널에 입력:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;# 파일에 적힌 패키지들을 한 번에 싹 다 설치하기
pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;모듈과 `import&lt;/b&gt;`: 이미 만들어진 파이썬 코드를 내 코드로 불러와서 부품처럼 사용하는 방법.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;표준 라이브러리&lt;/b&gt;: 파이썬을 깔면 기본적으로 제공되는 도구들 (&lt;code&gt;math&lt;/code&gt;, &lt;code&gt;datetime&lt;/code&gt;, &lt;code&gt;random&lt;/code&gt; 등).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;pip&lt;/code&gt;&lt;/b&gt;: 파이썬의 앱스토어에서 외부 개발자가 만든 강력한 패키지(&lt;code&gt;requests&lt;/code&gt; 등)를 다운로드하는 마법의 명령어.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;import&lt;/code&gt;와 &lt;code&gt;pip&lt;/code&gt;를 알게 된 순간, 여러분의 파이썬 실력은 한계가 없어졌습니다. 이제 인터넷에 있는 수만 개의 도구들을 자유롭게 가져다 쓸 수 있게 되었으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 남이 만든 도구를 잘 가져다 쓰려면, 그 도구가 어떻게 설계되었는지 이해하는 것도 중요합니다. 다음 포스팅에서는 초보자들이 가장 헷갈려 하지만 반드시 넘어야 할 산, &lt;b&gt;&quot;객체지향과 클래스(Class), 그리고 &lt;code&gt;self&lt;/code&gt;의 정체&quot;&lt;/b&gt;에 대해 아주 쉽게 풀어드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1289</guid>
      <comments>https://hianna.tistory.com/1289#entry1289comment</comments>
      <pubDate>Sat, 11 Apr 2026 08:00:38 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 10. &amp;quot;똑같은 코드 또 쓰지 마세요!&amp;quot; 파이썬 함수(def)와 람다(Lambda)</title>
      <link>https://hianna.tistory.com/1288</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍에서 &lt;b&gt;함수(Function)&lt;/b&gt;란 자판기나 믹서기 같은 '마법의 상자'입니다.&lt;br /&gt;딸기와 우유(입력값)를 넣고 버튼을 누르면, 딸기 우유(결과값)가 짠 하고 나오는 기계와 같습니다. 한 번 튼튼한 믹서기를 만들어두면, 나중에 바나나나 키위를 넣어도 알아서 주스를 만들어주니 아주 편하겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 파이썬에서 나만의 믹서기를 직접 조립하는 방법을 배워보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 나만의 마법 상자 만들기: &lt;code&gt;def&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 함수를 정의할 때는 &lt;b&gt;&lt;code&gt;def&lt;/code&gt; (define의 약자)&lt;/b&gt;라는 키워드를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 공식:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;def 함수이름(재료1, 재료2):
    실행할 코드
    return 결과물
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 커피 머신 함수 만들기&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 1. 함수 정의하기 (믹서기 만들기)
def make_coffee(bean, water_ml):
    print(&quot;커피를 내리는 중입니다 삐빅...&quot;)
    result = f&quot;{bean} 원두로 내린 커피 {water_ml}ml&quot;
    return result  # 완성된 결과물을 밖으로 반환(배출)합니다.

# 2. 함수 사용하기 (버튼 누르기)
# 브라질 원두와 물 250ml를 넣습니다.
my_coffee = make_coffee(&quot;브라질&quot;, 250) 
print(&quot;주문하신 음료:&quot;, my_coffee)

print(&quot;-&quot; * 20)

# 다른 재료를 넣으면 다른 결과가 나옵니다!
your_coffee = make_coffee(&quot;에티오피아&quot;, 300)
print(&quot;주문하신 음료:&quot;, your_coffee)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;커피를 내리는 중입니다 삐빅...
주문하신 음료: 브라질 원두로 내린 커피 250ml
--------------------
커피를 내리는 중입니다 삐빅...
주문하신 음료: 에티오피아 원두로 내린 커피 300ml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;bean&lt;/code&gt;과 &lt;code&gt;water_ml&lt;/code&gt;은 함수 안으로 들어가는 &lt;b&gt;매개변수(Parameter)&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;return&lt;/code&gt;은 함수가 작업을 끝내고 최종적으로 뱉어내는 &lt;b&gt;반환값&lt;/b&gt;입니다. &lt;code&gt;return&lt;/code&gt;을 만나는 순간 함수는 즉시 종료됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 센스 있는 기본값 설정 (Default Parameter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 매번 &quot;물은 250ml로 해주세요&quot;라고 말하기 귀찮다면, 함수를 만들 때 아예 &lt;b&gt;기본값&lt;/b&gt;을 세팅해 둘 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 기본값이 있는 인사 함수&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# msg 매개변수에 미리 &quot;안녕하세요!&quot;라는 기본값을 넣어둡니다.
def greet(name, msg=&quot;안녕하세요!&quot;):
    print(f&quot;{name}님, {msg}&quot;)

# 1. 두 가지 재료를 다 넣었을 때
greet(&quot;김파이&quot;, &quot;반갑습니다~&quot;)  # 출력: 김파이님, 반갑습니다~

# 2. 하나의 재료만 넣었을 때 (알아서 기본값이 들어감!)
greet(&quot;박코드&quot;)                 # 출력: 박코드님, 안녕하세요!
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 이름조차 필요 없는 한 줄짜리 함수: &lt;code&gt;lambda&lt;/code&gt; (람다)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 함수는 코드가 단 한 줄이면 충분할 정도로 너무 단순합니다. 게다가 딱 한 번 쓰고 버릴 거라서 굳이 &lt;code&gt;def&lt;/code&gt;를 써서 거창하게 이름을 붙여주기도 귀찮을 때가 있죠.&lt;br /&gt;이때 사용하는 것이 바로 &lt;b&gt;람다(Lambda) 함수&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 공식:&lt;/b&gt;&lt;br /&gt;&lt;code&gt;lambda 입력값 : 출력할 표현식&lt;/code&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: def와 lambda의 비교&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 일반적인 함수 (def 사용)
def add_ten(x):
    return x + 10

print(add_ten(5))  # 출력: 15

# 람다 함수 (lambda 사용)
# &quot;x가 들어오면, x + 10을 뱉어라&quot;라는 뜻입니다.
lambda_add_ten = lambda x: x + 10

print(lambda_add_ten(5))  # 출력: 15
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 람다의 진짜 진가: &lt;code&gt;map()&lt;/code&gt;과 &lt;code&gt;filter()&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 람다는 단독으로 쓰일 때보다, &lt;b&gt;다른 함수의 재료(매개변수)로 쏙 들어갈 때&lt;/b&gt; 진짜 빛을 발합니다. 특히 리스트의 데이터를 한 방에 조작하는 &lt;code&gt;map()&lt;/code&gt;과 &lt;code&gt;filter()&lt;/code&gt;의 단짝 친구입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;① &lt;code&gt;map()&lt;/code&gt; : 리스트의 모든 값에 함수를 똑같이 적용하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;리스트에 있는 모든 숫자에 3을 곱해줘!&quot;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;numbers = [1, 2, 3, 4, 5]

# map(적용할 함수, 데이터 꾸러미)
# lambda x: x * 3 이라는 이름 없는 함수를 numbers 전체에 적용합니다.
result = map(lambda x: x * 3, numbers)

# map의 결과물은 눈에 보이지 않는 상태이므로, list()로 감싸서 꺼내줍니다.
print(list(result))  # 출력: [3, 6, 9, 12, 15]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;② &lt;code&gt;filter()&lt;/code&gt; : 조건에 맞는 값만 걸러내기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;리스트에 있는 숫자 중에서 짝수만 뽑아줘!&quot;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# filter(조건 함수, 데이터 꾸러미)
# lambda x: x % 2 == 0 (짝수인지 묻는 조건)이 True인 녀석들만 통과시킵니다.
even_numbers = filter(lambda x: x % 2 == 0, numbers)

print(list(even_numbers))  # 출력: [2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(  참고: 지난 시간에 배운 '리스트 컴프리헨션'으로도 똑같은 작업을 할 수 있습니다. 실무에서는 리스트 컴프리헨션을 더 선호하는 경향이 있지만, 이 &lt;code&gt;map/filter&lt;/code&gt; 문법도 다른 사람의 코드를 읽기 위해 반드시 알아두어야 합니다!)&lt;/i&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;def&lt;/code&gt;&lt;/b&gt;: 반복되는 코드는 묶어서 이름을 붙이고 재사용하자. 입력(매개변수)과 출력(&lt;code&gt;return&lt;/code&gt;)이 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기본값&lt;/b&gt;: &lt;code&gt;변수=값&lt;/code&gt; 형태로 함수 매개변수의 기본 옵션을 설정할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;lambda&lt;/code&gt;&lt;/b&gt;: &lt;code&gt;def&lt;/code&gt;를 쓰기엔 너무 단순한 1회용 한 줄 함수.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;map/filter&lt;/code&gt;&lt;/b&gt;: 리스트의 데이터를 일괄적으로 변환하거나 필터링할 때 람다와 함께 쓰면 찰떡궁합이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만든 함수들을 모아두면 아주 강력한 무기가 됩니다. 그런데 만약 전 세계의 천재 개발자들이 이미 만들어둔 엄청난 함수들이 있다면 어떨까요? 그걸 내 코드로 쏙 가져와서 쓸 수 있다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;남이 만든 바퀴를 다시 발명하지 마라! 모듈(Module)과 pip 패키지 관리&quot;&lt;/b&gt;에 대해 알아보겠습니다. 드디어 진짜 파이썬 생태계에 발을 들이는 순간입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1288</guid>
      <comments>https://hianna.tistory.com/1288#entry1288comment</comments>
      <pubDate>Sat, 11 Apr 2026 01:00:07 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 9. &amp;quot;세 줄을 한 줄로 끝낸다!&amp;quot; 파이썬의 꽃, 리스트 컴프리헨션</title>
      <link>https://hianna.tistory.com/1287</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 개발자들은 코드가 길어지는 것을 별로 좋아하지 않습니다. &quot;어떻게 하면 더 직관적이고 짧게 쓸 수 있을까?&quot;를 늘 고민하죠. (이런 스타일을 '파이썬스럽다', 즉 &lt;b&gt;Pythonic&lt;/b&gt;하다고 부릅니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 Pythonic의 결정체가 바로 오늘 배울 &lt;b&gt;리스트 컴프리헨션&lt;/b&gt;입니다. 우리말로는 '리스트 내포'라고도 부르는데, 쉽게 말해 &lt;b&gt;&quot;리스트 안에 &lt;code&gt;for&lt;/code&gt;문을 쏙 집어넣어서, 새로운 리스트를 뚝딱 만들어내는 마법의 문법&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 기존 방식 vs 파이썬 방식 (비교 체험)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자가 담긴 리스트에서 모든 숫자에 2를 곱한 &lt;b&gt;새로운 리스트&lt;/b&gt;를 만든다고 가정해 봅시다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 기존 방식 (일반적인 &lt;code&gt;for&lt;/code&gt;문)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 바구니를 만들고, 하나씩 꺼내서, 계산하고, 다시 빈 바구니에 넣는 3단계 과정이 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;numbers = [1, 2, 3, 4, 5]
double_numbers = []  # 1. 빈 리스트 생성

for n in numbers:    # 2. 반복문 돌리기
    double_numbers.append(n * 2)  # 3. 계산해서 집어넣기

print(double_numbers)  # 출력: [2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ 파이썬 방식 (리스트 컴프리헨션)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 3줄짜리 코드를 단 &lt;b&gt;한 줄&lt;/b&gt;로 압축해 버립니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;numbers = [1, 2, 3, 4, 5]

# &quot;n * 2를 리스트에 담을 건데, 그 n은 numbers에서 하나씩 꺼내온 거야.&quot;
double_numbers = [n * 2 for n in numbers]

print(double_numbers)  # 출력: [2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤가요? 처음엔 어색할 수 있지만, 영어 문장을 읽듯 뒤에서부터 앞으로 읽어보면 아주 직관적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;with arrows explaining the flow]&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 조건문(&lt;code&gt;if&lt;/code&gt;) 필터링 섞어 쓰기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문법이 진짜 무서운 이유는 &lt;code&gt;for&lt;/code&gt;문 뒤에 &lt;code&gt;if&lt;/code&gt;문까지 딱 붙여서 &lt;b&gt;원하는 데이터만 골라낼 수 있다&lt;/b&gt;는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 공식:&lt;/b&gt;&lt;br /&gt;&lt;code&gt;[ 넣을_데이터 for 임시변수 in 리스트 if 조건식 ]&lt;/code&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 짝수만 골라서 제곱하기&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# &quot;numbers에서 n을 하나씩 꺼내는데, 만약 n이 짝수(n % 2 == 0)라면, n의 제곱(n**2)을 리스트에 담아라!&quot;
even_squares = [n ** 2 for n in numbers if n % 2 == 0]

print(even_squares)  
# 출력: [4, 16, 36, 64, 100]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 일반 &lt;code&gt;for&lt;/code&gt;문으로 짰다면 &lt;code&gt;for&lt;/code&gt;문 안에 &lt;code&gt;if&lt;/code&gt;문이 들어가고 &lt;code&gt;append&lt;/code&gt;를 하는 등 코드가 4~5줄로 훌쩍 길어졌을 겁니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실무에서는 어떻게 쓰일까? (데이터 전처리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분석이나 업무 자동화를 하다 보면 엑셀이나 텍스트 파일에서 데이터를 긁어올 때가 많습니다. 이때 데이터 주변에 지저분한 띄어쓰기(공백)나 불필요한 문자가 묻어있는 경우가 태반이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 컴프리헨션을 쓰면 이런 '데이터 청소'를 1초 만에 끝낼 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 지저분한 텍스트 데이터 깔끔하게 다듬기&lt;/h4&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;# 웹 크롤링으로 긁어왔는데 공백이 잔뜩 묻어있는 데이터
raw_names = [&quot;  김파이  &quot;, &quot;이썬 &quot;, &quot;  박코드&quot;, &quot;최자동  &quot;]

# 지난 시간에 배운 문자열 함수 .strip()을 활용합니다!
# &quot;raw_names에서 이름을 하나씩 꺼내서, 양쪽 공백을 다 지운(strip) 다음 새 리스트로 만들어!&quot;
clean_names = [name.strip() for name in raw_names]

print(clean_names)
# 출력: ['김파이', '이썬', '박코드', '최자동']
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤가요? 실무에서 정말 숨 쉬듯이 쓰이는 패턴입니다. 데이터가 10만 개여도 코드는 저 한 줄이면 충분합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. ⚠️ 주의사항: 무조건 짧다고 좋은 건 아니다!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 컴프리헨션이 아무리 좋아도 &lt;b&gt;남용하면 독&lt;/b&gt;이 됩니다.&lt;br /&gt;조건이 너무 복잡해서 &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, 그리고 이중 &lt;code&gt;for&lt;/code&gt;문까지 한 줄에 다 구겨 넣으려고 하면, 코드를 짠 본인조차 다음 날 읽지 못하는 참사가 벌어집니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# ❌ 너무 복잡한 리스트 컴프리헨션 (나쁜 예)
result = [x * y for x in range(10) if x % 2 == 0 for y in range(10) if y % 3 == 0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가독성이 생명입니다.&lt;/b&gt; 코드가 너무 길어지고 복잡해진다면, 억지로 한 줄로 만들지 말고 원래 하던 대로 정직하게 &lt;code&gt;for&lt;/code&gt;문과 &lt;code&gt;if&lt;/code&gt;문을 여러 줄로 나누어 쓰는 것이 훨씬 좋은 프로그래밍 습관입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;리스트 컴프리헨션&lt;/b&gt;은 새로운 리스트를 만들 때 &lt;code&gt;for&lt;/code&gt;문과 &lt;code&gt;append&lt;/code&gt;를 한 줄로 압축하는 파이썬만의 문법이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[표현식 for 변수 in 데이터]&lt;/code&gt; 형태로 작성한다.&lt;/li&gt;
&lt;li&gt;맨 뒤에 &lt;code&gt;if 조건&lt;/code&gt;을 붙여서 원하는 데이터만 쏙쏙 골라낼 수 있다. (데이터 전처리에 최고!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 여러분은 변수, 자료형, 조건문, 반복문 등 파이썬의 '기초 체력'을 모두 다졌습니다.&lt;br /&gt;이제는 내가 만든 이 훌륭한 로직들을 &lt;b&gt;&quot;이름을 붙여서 언제든 꺼내 쓸 수 있는 나만의 도구&quot;&lt;/b&gt;로 만들 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 Phase 4의 첫 번째 시간, &lt;b&gt;&quot;코드의 재사용성을 극대화하는 함수(Function) 만들기&quot;&lt;/b&gt;에 대해 완벽하게 파헤쳐 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1287</guid>
      <comments>https://hianna.tistory.com/1287#entry1287comment</comments>
      <pubDate>Fri, 10 Apr 2026 08:59:31 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 8. &amp;quot;100번의 단순 노동을 단 세 줄로!&amp;quot; 파이썬의 for-in 반복문 완벽 가이드</title>
      <link>https://hianna.tistory.com/1286</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;만약 엑셀 파일 100개를 열어서 각각의 파일명을 바꿔야 한다고 상상해 보세요. 사람이 직접 하면 마우스 클릭을 수천 번 해야 하고 실수하기도 쉽습니다. 하지만 프로그래밍의 &lt;b&gt;반복문&lt;/b&gt;을 사용하면 단 몇 줄의 코드로 1초 만에 끝낼 수 있습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 파이썬에서 가장 많이 쓰이고, 가장 직관적인 형태를 가진 &lt;b&gt;&lt;code&gt;for-in&lt;/code&gt; 반복문&lt;/b&gt;과 그 단짝 친구들(&lt;code&gt;range&lt;/code&gt;, &lt;code&gt;enumerate&lt;/code&gt;, &lt;code&gt;zip&lt;/code&gt;)을 완벽하게 마스터해 보겠습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 파이썬 반복문의 핵심:&amp;nbsp;&amp;nbsp;구조&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 &lt;code&gt;for&lt;/code&gt;문은 아주 직관적입니다. &lt;b&gt;&quot;저 바구니(리스트)에 있는 물건들을 하나씩 꺼내서, 다 떨어질 때까지 이 작업을 반복해!&quot;&lt;/b&gt;라는 뜻입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 공식:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;for 임시변수 in 데이터꾸러미:
    반복할 코드
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 장바구니 물건 하나씩 꺼내기&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cart = [&quot;사과&quot;, &quot;바나나&quot;, &quot;포도&quot;]

for fruit in cart:
    print(f&quot;장바구니에서 {fruit}를 꺼냈습니다.&quot;)

print(&quot;장바구니가 비었습니다!&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;장바구니에서 사과를 꺼냈습니다.
장바구니에서 바나나를 꺼냈습니다.
장바구니에서 포도를 꺼냈습니다.
장바구니가 비었습니다!
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;cart&lt;/code&gt;라는 리스트 안에 있는 데이터를 맨 앞에서부터 하나씩 꺼냅니다.&lt;/li&gt; 
 &lt;li&gt;꺼낸 데이터는 &lt;code&gt;fruit&lt;/code&gt;라는 임시 상자(변수)에 잠시 담깁니다.&lt;/li&gt; 
 &lt;li&gt;들여쓰기 된 &lt;code&gt;print&lt;/code&gt; 문장이 실행됩니다.&lt;/li&gt; 
 &lt;li&gt;리스트의 끝까지 도달하면 들여쓰기 블록을 빠져나와 마지막 문장을 출력합니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 원하는 횟수만큼 반복하기:&amp;nbsp;&amp;nbsp;함수&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터 꾸러미(리스트)가 없고, 그냥 &lt;b&gt;&quot;이 문장을 5번 반복해 줘!&quot;&lt;/b&gt;라고 시키고 싶을 때는 어떻게 할까요? 이때 숫자를 자동으로 만들어주는 마법의 기계, &lt;b&gt;&lt;code&gt;range()&lt;/code&gt;&lt;/b&gt; 함수를 사용합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: range()의 3가지 변신&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 1. 종료 숫자만 지정하기: 0부터 '종료 숫자 직전'까지
for i in range(3):
    print(f&quot;{i}번째 인사: 안녕하세요!&quot;)
# 출력: 0, 1, 2 (총 3번)

print(&quot;-&quot; * 20)

# 2. 시작과 종료 숫자 지정하기: 1부터 5 앞(4)까지
for i in range(1, 5):
    print(f&quot;번호표 {i}번 고객님~&quot;)
# 출력: 1, 2, 3, 4

print(&quot;-&quot; * 20)

# 3. 건너뛰기 (step) 추가: 1부터 10 앞까지 2칸씩 뛰어서
for i in range(1, 10, 2):
    print(f&quot;홀수만 출력: {i}&quot;)
# 출력: 1, 3, 5, 7, 9
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚠️ 주의사항:&lt;/b&gt; 슬라이싱 때와 마찬가지로 &lt;code&gt;range(A, B)&lt;/code&gt;에서 &lt;b&gt;끝 번호 B는 절대 포함되지 않습니다!&lt;/b&gt; B의 바로 앞 숫자까지만 만들어냅니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 순서(번호표)와 데이터를 동시에: &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 반복문을 돌리다 보면 데이터뿐만 아니라 &lt;b&gt;&quot;이 데이터가 몇 번째 데이터인지(인덱스)&quot;&lt;/b&gt; 알아야 할 때가 정말 많습니다. 파이썬에서는 아주 우아한 해결책인 &lt;b&gt;&lt;code&gt;enumerate()&lt;/code&gt;&lt;/b&gt;를 제공합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 달리기 대회 순위 발표&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;runners = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;, &quot;지훈&quot;]

# 데이터만 꺼내는 방식
# for name in runners:
#     print(name)

# 순서(인덱스)와 데이터를 함께 꺼내는 방식 (start=1 옵션으로 1등부터 시작!)
for rank, name in enumerate(runners, start=1):
    print(f&quot;{rank}등: {name} 선수&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1등: 철수 선수
2등: 영희 선수
3등: 민수 선수
4등: 지훈 선수
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;enumerate()&lt;/code&gt;를 쓰면 &lt;code&gt;rank&lt;/code&gt;에는 번호표가, &lt;code&gt;name&lt;/code&gt;에는 실제 데이터가 동시에 쏙쏙 들어갑니다. 코드가 훨씬 깔끔해지죠.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 여러 개의 주머니를 한 번에 털기: &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 들어있는 리스트 하나, 점수가 들어있는 리스트 하나. 이 두 리스트를 동시에 짝지어서 돌리고 싶다면 어떻게 해야 할까요?&lt;br&gt;이름처럼 두 리스트의 지퍼를 쫙 채워주는 &lt;b&gt;&lt;code&gt;zip()&lt;/code&gt;&lt;/b&gt; 함수를 씁니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 학생 이름과 점수 짝지어 출력하기&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;names = [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;]
scores = [95, 80, 100]

# 두 리스트를 zip으로 묶으면, 같은 위치(인덱스)에 있는 것끼리 짝꿍이 됩니다.
for name, score in zip(names, scores):
    print(f&quot;이름: {name}, 점수: {score}점&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;이름: Alice, 점수: 95점
이름: Bob, 점수: 80점
이름: Charlie, 점수: 100점
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 무한루프와 제어기:&amp;nbsp;&amp;nbsp;문, , &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;횟수가 정해져 있지 않고, &lt;b&gt;&quot;특정 조건이 만족될 때까지 계속해!&quot;&lt;/b&gt;라고 할 때는 &lt;code&gt;while&lt;/code&gt; 문을 사용합니다.&lt;br&gt;이때 반복문을 강제로 멈추거나 건너뛰는 버튼도 있습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 비밀번호 맞추기 게임&lt;/h4&gt;&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;while True:  # True이므로 영원히 반복되는 무한루프입니다.
    pwd = input(&quot;비밀번호를 입력하세요: &quot;)  # 사용자에게 입력을 받습니다.

    if pwd == &quot;1234&quot;:
        print(&quot;로그인 성공!&quot;)
        break  # 반복문을 즉시 박살 내고(종료하고) 빠져나옵니다.

    if pwd == &quot;0000&quot;:
        print(&quot;0000은 너무 쉬운 비밀번호입니다. 다시 시도하세요.&quot;)
        continue  # 아래 코드를 무시하고, 다시 반복문의 맨 처음으로 올라갑니다.

    print(&quot;비밀번호가 틀렸습니다. 다시 시도하세요.&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;break&lt;/code&gt;&lt;/b&gt;: 브레이크를 밟아서 반복문을 아예 끝내버립니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;continue&lt;/code&gt;&lt;/b&gt;: 이번 턴(차례)만 건너뛰고 다음 반복으로 넘어갑니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 배운 핵심 요약입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;for 변수 in 리스트:&lt;/code&gt;&lt;/b&gt; - 리스트의 데이터를 하나씩 순서대로 꺼내며 반복한다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;range()&lt;/code&gt;&lt;/b&gt; - 원하는 횟수만큼 숫자를 자동으로 만들어준다. (끝 숫자는 포함 X)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;enumerate()&lt;/code&gt;&lt;/b&gt; - 데이터의 순서(인덱스)와 값을 동시에 뽑아준다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;zip()&lt;/code&gt;&lt;/b&gt; - 여러 개의 리스트를 같은 순서끼리 짝지어서 뽑아준다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 4가지만 자유자재로 다뤄도 여러분은 엑셀 수천 줄의 데이터를 파이썬으로 가볍게 요리할 수 있습니다.&lt;br&gt;그런데 파이썬 개발자들은 이 반복문조차도 여러 줄 쓰는 것을 귀찮아해서, &lt;b&gt;아예 한 줄로 압축해 버리는 마법&lt;/b&gt;을 만들어냈습니다.&lt;br&gt;다음 포스팅에서는 &lt;b&gt;&quot;리스트를 만드는 가장 파이썬스러운 방법, 코드의 길이를 반으로 줄여주는 리스트 컴프리헨션(List Comprehension)&quot;&lt;/b&gt;에 대해 알아보겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1286</guid>
      <comments>https://hianna.tistory.com/1286#entry1286comment</comments>
      <pubDate>Fri, 10 Apr 2026 01:19:40 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 7. &amp;quot;괄호가 없어서 어색해요...&amp;quot; 파이썬의 생명, 들여쓰기(Indentation)와 if문</title>
      <link>https://hianna.tistory.com/1285</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램이 똑똑해 보이려면 상황에 맞게 다른 행동을 해야 합니다. 로그인할 때 비밀번호가 맞으면 환영해주고, 틀리면 다시 입력하라고 경고창을 띄우는 것처럼요.&lt;br&gt;이런 논리적인 흐름을 제어하는 제1원칙이 바로 &lt;b&gt;조건문(if문)&lt;/b&gt;입니다. 그리고 파이썬에서 조건문을 쓸 때 절대 모르면 안 되는 아주 독특하고 아름다운 규칙이 하나 있습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 파이썬의 생명줄: 들여쓰기 (Indentation)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다른 프로그래밍 언어들을 보면 복잡한 중괄호 &lt;code&gt;{ }&lt;/code&gt;를 사용해서 &quot;여기서부터 여기까지가 한 묶음이야!&quot;라고 표시합니다.&lt;br&gt;하지만 파이썬 창시자는 코드가 지저분해지는 것을 싫어했습니다. 그래서 괄호를 과감히 없애고, &lt;b&gt;'들여쓰기(여백)'&lt;/b&gt;로 코드의 구역을 나누도록 만들었습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;규칙 1:&lt;/b&gt; 조건문 끝에는 반드시 &lt;b&gt;콜론(&lt;code&gt;:&lt;/code&gt;)&lt;/b&gt;을 붙여야 합니다. (&quot;내 밑으로 주목!&quot; 이라는 뜻입니다.)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;규칙 2:&lt;/b&gt; 콜론 다음 줄부터는 무조건 &lt;b&gt;들여쓰기(Space 4번 또는 Tab 1번)&lt;/b&gt;를 해야 합니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;규칙 3:&lt;/b&gt; 들여쓰기가 끝나는 시점이 조건문이 끝나는 시점입니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 들여쓰기의 중요성&lt;/h4&gt;&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;weather = &quot;비&quot;

if weather == &quot;비&quot;:
    print(&quot;우산을 챙기세요!&quot;)  # 들여쓰기 됨 (if문에 포함)
    print(&quot;장화도 신으세요!&quot;)  # 들여쓰기 됨 (if문에 포함)

print(&quot;외출을 합니다.&quot;)       # 들여쓰기 없음 (if문과 상관없이 무조건 실행)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;code&gt;print(&quot;우산을 챙기세요!&quot;)&lt;/code&gt; 앞에 띄어쓰기를 깜빡한다면 파이썬은 &lt;b&gt;&lt;code&gt;IndentationError(들여쓰기 에러)&lt;/code&gt;&lt;/b&gt;를 내뿜으며 파업해버립니다. 처음엔 낯설지만, 이 규칙 덕분에 세상 모든 파이썬 코드는 누가 짜든 아주 깔끔하고 읽기 쉽습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 기본 조건문: if, else, elif&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;조건이 하나만 있는 게 아니죠? &quot;비가 오면 우산, 눈이 오면 패딩, 둘 다 아니면 겉옷&quot;처럼 여러 갈래로 나눌 때 사용하는 삼총사입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;if&lt;/code&gt;: 만약 ~라면 (가장 처음에 등장)&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;elif&lt;/code&gt;: 그게 아니고, 만약 ~라면 (여러 번 사용 가능, else if의 줄임말)&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;else&lt;/code&gt;: 위의 조건들이 모두 아니라면 (가장 마지막에 등장, 조건식 불필요)&lt;/li&gt; 
&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 점수에 따른 학점 매기기&lt;/h4&gt;&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;score = 85

if score &amp;gt;= 90:
    print(&quot;A학점입니다.&quot;)
elif score &amp;gt;= 80:
    print(&quot;B학점입니다.&quot;)  # score가 85이므로 여기가 실행됩니다!
elif score &amp;gt;= 70:
    print(&quot;C학점입니다.&quot;)
else:
    print(&quot;재수강을 권장합니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;위에서부터 차례대로 조건을 검사합니다.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;score &amp;gt;= 90&lt;/code&gt;이 거짓(False)이므로 다음 &lt;code&gt;elif&lt;/code&gt;로 넘어갑니다.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;score &amp;gt;= 80&lt;/code&gt;이 참(True)이므로 &quot;B학점입니다.&quot;를 출력하고, &lt;b&gt;그 아래의 조건들은 거들떠보지도 않고 빠져나옵니다.&lt;/b&gt;&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 파이썬만의 아름다운 논리 연산자 (and, or, not)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;조건을 여러 개 섞어야 할 때도 파이썬은 기호를 쓰지 않고 영어 단어를 그대로 씁니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;and&lt;/code&gt; (그리고):&lt;/b&gt; 양쪽 조건이 모두 참이어야 실행&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;or&lt;/code&gt; (또는):&lt;/b&gt; 둘 중 하나만 참이어도 실행&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;not&lt;/code&gt; (반대):&lt;/b&gt; 참이면 거짓으로, 거짓이면 참으로 뒤집음&lt;/li&gt; 
&lt;/ol&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 놀이기구 탑승 조건&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;age = 15
height = 160

# 나이가 14세 이상 '그리고' 키가 150 이상이어야 탑승 가능
if age &amp;gt;= 14 and height &amp;gt;= 150:
    print(&quot;롤러코스터 탑승 가능!&quot;)
else:
    print(&quot;탑승 불가!&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  파이썬만의 사기 스킬: &lt;code&gt;in&lt;/code&gt; 연산자&lt;/b&gt;&lt;br&gt;리스트나 문자열 안에 내가 찾는 값이 들어있는지 확인할 때, 다른 언어들은 복잡한 코드를 짜야 하지만 파이썬은 단어 하나면 끝납니다.&lt;/p&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cart = [&quot;사과&quot;, &quot;바나나&quot;, &quot;포도&quot;]

if &quot;사과&quot; in cart:
    print(&quot;장바구니에 사과가 이미 있습니다!&quot;)

if &quot;수박&quot; not in cart:
    print(&quot;수박을 추가로 사야겠네요.&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. [보너스] 인터넷에서 자주 보이는&amp;nbsp;&amp;nbsp;은 뭘까요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 코드를 보다 보면 파일 맨 밑에 이런 요상한 코드가 있는 걸 보게 됩니다.&lt;/p&gt;&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;if __name__ == &quot;__main__&quot;:
    print(&quot;프로그램을 시작합니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이건 파이썬만의 독특한 관용구입니다. 쉽게 번역하자면 &lt;b&gt;&quot;이 파일을 다른 곳에서 부품(모듈)으로 몰래 가져다 쓴 게 아니라, 내가 직접 더블클릭해서(메인으로) 실행한 게 맞나요?&quot;&lt;/b&gt;라고 묻는 조건문입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;내가 직접 실행함 -&amp;gt; 저 안의 코드가 작동함&lt;/li&gt; 
 &lt;li&gt;다른 파일에서 &lt;code&gt;import&lt;/code&gt;로 가져감 -&amp;gt; 저 안의 코드는 작동하지 않고 조용히 기능만 빌려줌&lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;지금 완벽히 이해하지 않아도 괜찮습니다. &quot;아, 프로그램의 진짜 시작점을 알려주는 표지판이구나!&quot; 정도로만 기억해 두세요.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;파이썬에서 코드를 하나로 묶으려면 괄호 대신 &lt;b&gt;들여쓰기(Space 4번)&lt;/b&gt;를 쓴다.&lt;/li&gt; 
 &lt;li&gt;조건문(&lt;code&gt;if&lt;/code&gt;, &lt;code&gt;elif&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;) 끝에는 항상 &lt;b&gt;콜론(&lt;code&gt;:&lt;/code&gt;)&lt;/b&gt;을 잊지 말자.&lt;/li&gt; 
 &lt;li&gt;여러 조건을 묶을 땐 직관적인 &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;in&lt;/code&gt;을 활용하자.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;조건문을 배우셨으니 이제 여러분의 프로그램은 스스로 판단을 내릴 수 있게 되었습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 만약 &quot;이 동작을 100번 반복해!&quot;라고 시키고 싶다면 어떻게 할까요? &lt;code&gt;print()&lt;/code&gt;를 100줄 복사 붙여넣기 할 수는 없겠죠.&lt;br&gt;다음 포스팅에서는 &lt;b&gt;&quot;수백 번의 단순 노동을 한 방에 끝내는 마법! 파이썬의 for-in 반복문&quot;&lt;/b&gt;에 대해 완벽하게 알아보겠습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1285</guid>
      <comments>https://hianna.tistory.com/1285#entry1285comment</comments>
      <pubDate>Thu, 9 Apr 2026 09:36:57 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 6. &amp;quot;데이터에 이름표를 달아주자!&amp;quot; 파이썬 딕셔너리(Dictionary)와 집합(Set)</title>
      <link>https://hianna.tistory.com/1284</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;만약 한 사람의 정보를 리스트로 저장한다면 이렇게 될 것입니다. &lt;code&gt;user = [&quot;홍길동&quot;, 30, &quot;010-1234-5678&quot;]&lt;/code&gt;.&lt;br&gt;나이를 꺼내려면 &lt;code&gt;user[1]&lt;/code&gt;을 찾아야 하죠. 1번 방이 나이였다는 사실을 개발자가 매번 외우고 있어야 합니다. 너무 불편하지 않나요?&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 사용하는 것이 바로 파이썬의 꽃, &lt;b&gt;딕셔너리(Dictionary)&lt;/b&gt;입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 딕셔너리(Dictionary): Key와 Value의 환상 짝꿍&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리는 말 그대로 '사전'입니다. 사전에서 단어(Key)를 찾으면 뜻(Value)이 나오는 것처럼, 파이썬 딕셔너리도 &lt;b&gt;이름표(Key)&lt;/b&gt;와 &lt;b&gt;실제 데이터(Value)&lt;/b&gt;가 한 쌍으로 묶여 있습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;만드는 방법은 중괄호 &lt;code&gt;{ }&lt;/code&gt;를 사용하며, 내부는 &lt;code&gt;이름표 : 데이터&lt;/code&gt; 형태로 적어줍니다. 웹 개발에서 자주 쓰이는 JSON 포맷과 완전히 똑같이 생겼습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 딕셔너리 만들고 데이터 꺼내기&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 딕셔너리 생성 (중괄호 사용, Key: Value 형태)
user_info = {
    &quot;name&quot;: &quot;홍길동&quot;,
    &quot;age&quot;: 30,
    &quot;phone&quot;: &quot;010-1234-5678&quot;
}

# 1. 데이터 꺼내기 (대괄호 안에 인덱스 번호 대신 'Key 이름'을 넣습니다!)
print(user_info[&quot;name&quot;])  # 출력: 홍길동
print(user_info[&quot;age&quot;])   # 출력: 30

# 2. 안전하게 꺼내기 (.get() 함수 사용)
print(user_info.get(&quot;email&quot;)) # Key가 없으면 에러 대신 'None(없음)'을 반환합니다.
# print(user_info[&quot;email&quot;])   # 이렇게 하면 KeyError가 발생하며 프로그램이 멈춥니다.
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;user_info[1]&lt;/code&gt;처럼 숫자로 찾는 것이 아니라, &lt;code&gt;user_info[&quot;name&quot;]&lt;/code&gt;처럼 직관적인 이름표로 데이터를 찾습니다. 코드를 읽기만 해도 무슨 데이터인지 바로 알 수 있죠!&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 딕셔너리 요리하기: 추가, 수정, 삭제&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리도 리스트처럼 내용물을 자유롭게 바꿀 수 있습니다. 방법은 아주 직관적입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 딕셔너리 데이터 조작하기&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;my_dict = {&quot;apple&quot;: &quot;사과&quot;, &quot;banana&quot;: &quot;바나나&quot;}

# 1. 새로운 데이터 추가하기 (없는 Key에 값을 넣으면 새로 생김)
my_dict[&quot;grape&quot;] = &quot;포도&quot;
print(my_dict)  # 출력: {'apple': '사과', 'banana': '바나나', 'grape': '포도'}

# 2. 기존 데이터 수정하기 (있는 Key에 값을 넣으면 덮어쓰기 됨)
my_dict[&quot;apple&quot;] = &quot;꿀사과&quot;
print(my_dict)  # 출력: {'apple': '꿀사과', 'banana': '바나나', 'grape': '포도'}

# 3. 데이터 삭제하기 (del 키워드 사용)
del my_dict[&quot;banana&quot;]
print(my_dict)  # 출력: {'apple': '꿀사과', 'grape': '포도'}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 딕셔너리의 유용한 도구들 (keys, values, items)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리 안에 어떤 이름표(Key)들이 있는지, 혹은 어떤 값(Value)들만 있는지 모아서 보고 싶을 때가 있습니다. 이때 아래 세 가지 함수를 사용합니다.&lt;/p&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;score_dict = {&quot;국어&quot;: 90, &quot;영어&quot;: 85, &quot;수학&quot;: 100}

# 1. Key(과목명)만 다 모아줘!
print(score_dict.keys())    # 출력: dict_keys(['국어', '영어', '수학'])

# 2. Value(점수)만 다 모아줘!
print(score_dict.values())  # 출력: dict_values([90, 85, 100])

# 3. Key와 Value를 쌍으로 묶어서 다 모아줘!
print(score_dict.items())   
# 출력: dict_items([('국어', 90), ('영어', 85), ('수학', 100)])
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(  참고: 이 함수들은 나중에 배울 &lt;code&gt;for&lt;/code&gt; 반복문과 결합하면 엄청난 시너지를 발휘합니다.)&lt;/i&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 집합(Set): 중복을 허락하지 않는 주머니&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 알아볼 자료형은 &lt;b&gt;집합(Set)&lt;/b&gt;입니다. 딕셔너리와 똑같이 중괄호 &lt;code&gt;{ }&lt;/code&gt;를 쓰지만, &lt;code&gt;Key: Value&lt;/code&gt; 쌍이 아니라 리스트처럼 &lt;b&gt;값만 덩그러니&lt;/b&gt; 들어있습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;집합의 가장 강력한 특징 두 가지는 다음과 같습니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;중복을 허용하지 않는다.&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;순서가 없다.&lt;/b&gt; (인덱싱 불가능)&lt;/li&gt;&lt;/ol&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 리스트의 중복 데이터 1초 만에 날리기&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;집합은 언제 쓸까요? 바로 &lt;b&gt;&quot;수많은 데이터 중에서 중복을 제거할 때&quot;&lt;/b&gt; 가장 많이 쓰입니다.&lt;/p&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 1. 중복이 잔뜩 있는 리스트
attendance_list = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;, &quot;철수&quot;, &quot;철수&quot;, &quot;영희&quot;]

# 2. 리스트를 집합(Set)으로 변환해 버립니다! (이때 중복이 마법처럼 사라짐)
unique_set = set(attendance_list)
print(unique_set)  # 출력: {'영희', '민수', '철수'} (순서는 뒤죽박죽일 수 있음)

# 3. 다시 다루기 편하게 리스트로 되돌려줍니다.
final_list = list(unique_set)
print(final_list)  # 출력: ['영희', '민수', '철수']
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  수학 시간에 배운 교집합, 합집합도 가능합니다.&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}

print(set1 &amp;amp; set2)  # 교집합 (공통으로 있는 것) -&amp;gt; {3, 4}
print(set1 | set2)  # 합집합 (전체 다 합친 것, 중복은 하나만) -&amp;gt; {1, 2, 3, 4, 5, 6}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 배운 파이썬의 4대 데이터 바구니를 총정리해 보겠습니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;리스트 `[ ]&lt;/b&gt;`: 순서가 있고, 중복도 허용하고, 내용물 수정도 가능. (가장 만만함)&lt;/li&gt;&lt;li&gt;&lt;b&gt;튜플 `( )&lt;/b&gt;`: 리스트와 같지만, 내용물 수정이 &lt;b&gt;절대 불가능&lt;/b&gt;. (안전함)&lt;/li&gt;&lt;li&gt;&lt;b&gt;딕셔너리 `{Key: Value}&lt;/b&gt;`: 순서 대신 &lt;b&gt;이름표(Key)&lt;/b&gt;로 값을 찾음. (직관적임)&lt;/li&gt;&lt;li&gt;&lt;b&gt;집합 `{ }&lt;/b&gt;`: 순서가 없고, &lt;b&gt;중복을 제거&lt;/b&gt;해 줌. (필터 역할)&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 맞게 적절한 바구니를 고르는 것이 좋은 코드를 작성하는 첫걸음입니다!&lt;br&gt;재료 준비는 모두 끝났습니다. 이제 이 데이터들을 가지고 &quot;비가 오면 우산을 쓰고, 안 오면 그냥 간다&quot; 같은 &lt;b&gt;조건&lt;/b&gt;을 만들어 컴퓨터에게 생각하는 법을 가르칠 차례입니다.&lt;br&gt;다음 포스팅에서는 &lt;b&gt;&quot;들여쓰기의 마법! 프로그램의 흐름을 통제하는 if문(조건문)&quot;&lt;/b&gt;에 대해 알아보겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1284</guid>
      <comments>https://hianna.tistory.com/1284#entry1284comment</comments>
      <pubDate>Thu, 9 Apr 2026 00:36:13 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 5. &amp;quot;수십 개의 데이터를 상자 하나에!&amp;quot; 파이썬 리스트(List)와 튜플(Tuple) 완벽 가이드</title>
      <link>https://hianna.tistory.com/1283</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 다루다 보면 &quot;여러 개의 데이터&quot;를 한 묶음으로 처리해야 할 일이 무조건 생깁니다. 로또 번호 6개, 우리 팀원 5명의 이름, 한 달 치 온도 기록처럼 말이죠.&lt;br&gt;파이썬은 이런 여러 개의 데이터를 하나의 변수 이름표로 묶어서 관리할 수 있는 아주 강력하고 유연한 바구니를 제공합니다. 그중 가장 대표적인 것이 바로 &lt;b&gt;리스트(List)&lt;/b&gt;입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 리스트(List): 무엇이든 담을 수 있는 마법의 바구니&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트를 만드는 방법은 아주 간단합니다. 대괄호 &lt;code&gt;[&lt;/code&gt;와 &lt;code&gt;]&lt;/code&gt;로 데이터를 감싸고, 각 데이터는 쉼표 &lt;code&gt;,&lt;/code&gt;로 구분해주면 됩니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 놀라운 점은 파이썬의 리스트에는 &lt;b&gt;종류가 다른 데이터도 한 바구니에 마구잡이로 담을 수 있다&lt;/b&gt;는 것입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 리스트 만들고 꺼내보기&lt;/h4&gt;&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;# 1. 숫자만 담은 리스트
lotto_numbers = [3, 15, 22, 38, 41, 45]

# 2. 문자만 담은 리스트
team_members = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;]

# 3. 짬뽕 리스트 (숫자, 문자, 불린형 심지어 다른 리스트까지 가능!)
mixed_list = [100, &quot;파이썬&quot;, True, [1, 2, 3]]

# 리스트 출력하기
print(team_members)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;scheme&quot;&gt;&lt;code&gt;['철수', '영희', '민수']
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 리스트 다루기: 인덱싱과 슬라이싱&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간에 문자열에서 배웠던 '인덱싱(방 번호)'과 '슬라이싱(자르기)' 기억하시나요? &lt;b&gt;리스트에서도 똑같이 0번부터 시작하는 방 번호가 적용됩니다!&lt;/b&gt;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 리스트에서 원하는 데이터 뽑기&lt;/h4&gt;&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;subway = [&quot;사당&quot;, &quot;방배&quot;, &quot;서초&quot;, &quot;교대&quot;, &quot;강남&quot;]

# 1. 인덱싱 (특정 위치 데이터 가져오기)
print(subway[0])   # 0번 방 (첫 번째): 사당
print(subway[-1])  # -1번 방 (마지막): 강남

# 2. 슬라이싱 (구간 잘라오기)
print(subway[1:4]) # 1번 방부터 4번 앞(3번)까지 가져오기
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;사당
강남
['방배', '서초', '교대']
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;주의: 슬라이싱으로 잘라낸 결과물 역시 '리스트' 형태로 나옵니다.&lt;/i&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 리스트 수정하기: 추가, 삭제, 변경&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트의 가장 큰 장점은 바구니 안에 든 &lt;b&gt;내용물을 마음대로 바꾸고, 더하고, 뺄 수 있다(Mutable)&lt;/b&gt;는 점입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 리스트 요리조리 주무르기&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cart = [&quot;사과&quot;, &quot;바나나&quot;]

# 1. 추가하기 (append: 맨 뒤에 쏙 넣기)
cart.append(&quot;포도&quot;)
print(cart)  # 출력: ['사과', '바나나', '포도']

# 2. 특정 위치에 끼워 넣기 (insert)
cart.insert(1, &quot;딸기&quot;) # 1번 방에 '딸기'를 새치기시킴
print(cart)  # 출력: ['사과', '딸기', '바나나', '포도']

# 3. 삭제하기 (remove: 이름으로 지우기)
cart.remove(&quot;바나나&quot;)
print(cart)  # 출력: ['사과', '딸기', '포도']

# 4. 내용물 바꾸기 (인덱스로 접근해서 값 덮어쓰기)
cart[0] = &quot;수박&quot;  # 0번 방에 있던 '사과'를 빼고 '수박'을 넣음
print(cart)  # 출력: ['수박', '딸기', '포도']

# 5. 리스트 크기(데이터 개수) 확인하기 (len 함수)
print(len(cart))  # 출력: 3
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append&lt;/code&gt;와 &lt;code&gt;len&lt;/code&gt;은 앞으로 코딩하시면서 숨 쉬듯이 쓰게 될 필수 기능이니 꼭 눈에 익혀두세요!&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 튜플(Tuple): 수정할 수 없는 자물쇠 채운 바구니&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리스트와 거의 똑같이 생겼지만, 성질이 완전히 다른 형제가 있습니다. 바로 &lt;b&gt;튜플(Tuple)&lt;/b&gt;입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;튜플은 대괄호 &lt;code&gt;[]&lt;/code&gt; 대신 &lt;b&gt;소괄호 &lt;code&gt;()&lt;/code&gt;&lt;/b&gt;를 사용해서 만듭니다. 가장 큰 특징은 &lt;b&gt;&quot;한 번 만들면 내용물을 절대로 수정할 수 없다(Immutable)&quot;&lt;/b&gt;는 것입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 튜플 만들고 수정 시도해 보기&lt;/h4&gt;&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;# 튜플 만들기 (소괄호 사용)
menu_tuple = (&quot;돈까스&quot;, &quot;치즈돈까스&quot;)

# 인덱싱은 리스트와 똑같이 가능합니다. (가져다 보는 건 문제없음)
print(menu_tuple[0])  # 출력: 돈까스

# ❌ 오류 발생: 내용물 바꾸기 시도
# menu_tuple[0] = &quot;생선까스&quot; 
# -&amp;gt; TypeError: 'tuple' object does not support item assignment (튜플은 값 변경을 지원하지 않습니다!)

# ❌ 오류 발생: 데이터 추가 시도
# menu_tuple.append(&quot;우동&quot;) 
# -&amp;gt; AttributeError: 'tuple' object has no attribute 'append' (튜플은 append 기능이 없습니다!)
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  튜플은 왜 쓸까요?&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;수정도 못 하는 거 불편하게 왜 쓰나요?&quot;라는 생각이 드실 수 있습니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;안전성:&lt;/b&gt; 요금표, 주민등록번호, 프로그램 설정값 등 중간에 실수로라도 내용이 바뀌면 큰일 나는 중요한 데이터를 보호할 때 씁니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;속도:&lt;/b&gt; 내용물 변경 기능이 빠져있기 때문에 컴퓨터가 처리하는 속도가 리스트보다 미세하게 더 빠르고, 메모리도 적게 차지합니다.&lt;/li&gt;&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;리스트(&lt;code&gt;[]&lt;/code&gt;)&lt;/b&gt;: 여러 데이터를 한곳에 담는 바구니. 자유롭게 추가, 삭제, 변경이 가능하다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;튜플(&lt;code&gt;()&lt;/code&gt;)&lt;/b&gt;: 리스트와 비슷하지만, 한 번 담으면 내용을 바꿀 수 없는 읽기 전용 바구니.&lt;/li&gt; 
 &lt;li&gt;리스트와 튜플 모두 0부터 시작하는 &lt;b&gt;인덱싱&lt;/b&gt;과 &lt;b&gt;슬라이싱&lt;/b&gt;을 사용할 수 있다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 데이터를 효율적으로 묶어서 관리하는 방법을 알게 되었습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 만약 회원가입 정보를 담아야 하는데 &lt;code&gt;['홍길동', 30, '010-1234-5678']&lt;/code&gt; 처럼 리스트로 담아두면, 0번이 이름인지 1번이 나이인지 헷갈리겠죠? 데이터에 &lt;b&gt;이름표(Key)&lt;/b&gt;를 달아서 직관적으로 관리할 수 있다면 얼마나 좋을까요?&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;이름표와 데이터의 찰떡궁합, JSON이랑 똑같이 생긴 파이썬 딕셔너리(Dictionary)&quot;&lt;/b&gt;에 대해 완벽하게 파헤쳐 보겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1283</guid>
      <comments>https://hianna.tistory.com/1283#entry1283comment</comments>
      <pubDate>Wed, 8 Apr 2026 09:35:51 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 4. &amp;quot;문자열을 떡 주무르듯이&amp;quot; 인덱싱, 슬라이싱, 그리고 f-string</title>
      <link>https://hianna.tistory.com/1282</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 엑셀을 쓰다 보면 '주민등록번호 앞자리만 빼오기', '이메일 주소에서 아이디만 분리하기' 같은 작업을 자주 하죠? 파이썬에서는 이런 텍스트 가공 작업이 정말 눈 깜짝할 사이에 끝납니다.&lt;br&gt;오늘은 문자열의 특정 위치를 콕 집어내는 &lt;b&gt;인덱싱(Indexing)&lt;/b&gt;, 원하는 만큼 싹둑 잘라내는 &lt;b&gt;슬라이싱(Slicing)&lt;/b&gt;, 그리고 문장 사이에 변수를 예쁘게 끼워 넣는 &lt;b&gt;f-string&lt;/b&gt; 기술을 완벽하게 마스터해 보겠습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 인덱싱(Indexing): 글자의 '방 번호' 알아내기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 문자열은 글자들이 기차처럼 일렬로 쭉 늘어선 형태입니다. 그리고 각 글자에는 &lt;b&gt;0번부터 시작하는 방 번호(인덱스)&lt;/b&gt;가 매겨져 있습니다.&lt;br&gt;가장 중요한 규칙 두 가지를 기억하세요.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;파이썬은 숫자를 &lt;b&gt;0부터 셉니다.&lt;/b&gt; (첫 번째 글자는 0번 방)&lt;/li&gt;&lt;li&gt;뒤에서부터 셀 때는 &lt;b&gt;-1부터 셉니다.&lt;/b&gt; (마지막 글자는 -1번 방)&lt;/li&gt;&lt;/ol&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 글자 하나씩 뽑아오기&lt;/h4&gt;&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;text = &quot;PYTHON&quot;

# 1. 앞에서부터 뽑기 (양수 인덱스)
print(text[0])  # 첫 번째 글자: P
print(text[2])  # 세 번째 글자: T

# 2. 뒤에서부터 뽑기 (음수 인덱스)
print(text[-1]) # 맨 마지막 글자: N
print(text[-2]) # 뒤에서 두 번째 글자: O
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;변수 이름 뒤에 대괄호 &lt;code&gt;[]&lt;/code&gt;를 붙이고, 그 안에 방 번호를 적어주면 해당 위치의 글자 하나만 쏙 빼옵니다.&lt;/li&gt; 
 &lt;li&gt;음수 인덱스는 &quot;문장의 길이가 얼마나 되든 무조건 맨 끝 글자를 가져와!&quot;라고 할 때 매우 유용합니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 슬라이싱(Slicing): 원하는 구간만큼 싹둑 자르기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;글자 하나가 아니라 여러 글자를 통째로 잘라내고 싶다면 &lt;b&gt;슬라이싱&lt;/b&gt;을 사용합니다. 대괄호 안에 콜론(&lt;code&gt;:&lt;/code&gt;)을 넣어 시작과 끝을 정해줍니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 공식: `변수명[시작 번호 : 끝 번호]&lt;/b&gt;`&lt;br&gt;(단, '끝 번호'에 해당하는 글자는 &lt;b&gt;포함되지 않고 그 직전까지만&lt;/b&gt; 잘라냅니다!)&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 날짜 데이터 쪼개기&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;date_info = &quot;20260224&quot;

# 1. 연도 뽑기 (0번부터 4번 앞(3번)까지)
year = date_info[0:4]
print(&quot;연도:&quot;, year)  # 출력: 2026

# 2. 월 뽑기 (4번부터 6번 앞(5번)까지)
month = date_info[4:6]
print(&quot;월:&quot;, month)   # 출력: 02

# 3. 일 뽑기 (6번부터 끝까지)
day = date_info[6:]   
print(&quot;일:&quot;, day)     # 출력: 24
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  슬라이싱 생략의 미학:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;[0:4]&lt;/code&gt;는 시작이 0이므로 &lt;code&gt;[:4]&lt;/code&gt;라고 써도 똑같이 작동합니다. (처음부터 4번 앞까지)&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;[6:]&lt;/code&gt;처럼 끝 번호를 비워두면, 시작 번호부터 &lt;b&gt;문장의 맨 끝까지&lt;/b&gt; 전부 가져옵니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. f-string: 마법의 문자열 포맷팅&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;제 이름은 OOO이고, 나이는 OO살입니다.&quot;&lt;br&gt;이런 문장을 만들 때, OOO 자리에 변수를 넣으려면 어떻게 해야 할까요? 파이썬 3.6 버전부터 도입된 &lt;b&gt;f-string&lt;/b&gt;을 쓰면 너무나도 직관적이고 편하게 문장을 조립할 수 있습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;방법은 아주 간단합니다. 따옴표 앞에 소문자 &lt;b&gt;&lt;code&gt;f&lt;/code&gt;&lt;/b&gt;를 붙이고, 변수가 들어갈 자리에 중괄호 &lt;b&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/b&gt;를 적어주면 끝입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: f-string으로 깔끔한 문장 만들기&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;name = &quot;김파이&quot;
age = 30
job = &quot;개발자&quot;

# 예전 방식 (더하기 기호 사용 - 띄어쓰기 챙기기 귀찮고, 숫자는 문자로 바꿔야 함)
old_way = &quot;제 이름은 &quot; + name + &quot;이고, 나이는 &quot; + str(age) + &quot;살 &quot; + job + &quot;입니다.&quot;

# f-string 방식 (압도적으로 깔끔함!)
new_way = f&quot;제 이름은 {name}이고, 나이는 {age}살 {job}입니다.&quot;

print(old_way)
print(new_way)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;제 이름은 김파이이고, 나이는 30살 개발자입니다.
제 이름은 김파이이고, 나이는 30살 개발자입니다.
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;f-string 안에서는 &lt;code&gt;{age}&lt;/code&gt;처럼 숫자가 담긴 변수도 억지로 문자로 바꿀(&lt;code&gt;str()&lt;/code&gt;) 필요 없이 파이썬이 알아서 문장 속에 자연스럽게 녹여줍니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 알아두면 퇴근이 빨라지는 문자열 함수 Top 3&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에는 문자열을 가공하는 아주 유용한 내장 기능(함수)들이 있습니다. 그중 실무에서 매일 쓰는 3가지만 소개합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: replace, split, strip&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;text = &quot;   맛있는 사과, 달콤한 사과   &quot;

# 1. replace: 단어 교체하기 (사과 -&amp;gt; 바나나)
changed_text = text.replace(&quot;사과&quot;, &quot;바나나&quot;)
print(changed_text) 
# 출력: &quot;   맛있는 바나나, 달콤한 바나나   &quot;

# 2. strip: 양쪽 공백(띄어쓰기) 제거하기 (크롤링할 때 필수!)
clean_text = changed_text.strip()
print(clean_text)   
# 출력: &quot;맛있는 바나나, 달콤한 바나나&quot;

# 3. split: 특정 기준(여기선 쉼표)으로 문자열을 쪼개서 분리하기
items = clean_text.split(&quot;, &quot;)
print(items)        
# 출력: ['맛있는 바나나', '달콤한 바나나'] (이건 다음 시간에 배울 '리스트' 형태입니다!)
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 핵심 요약입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;인덱싱(&lt;code&gt;[번호]&lt;/code&gt;)&lt;/b&gt;: 파이썬은 0부터 숫자를 센다. 맨 끝은 -1이다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;슬라이싱(&lt;code&gt;[시작:끝]&lt;/code&gt;)&lt;/b&gt;: 끝 번호 글자는 포함되지 않으니 주의해서 자르자.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;f-string (&lt;code&gt;f&quot;안녕 {변수}&quot;&lt;/code&gt;)&lt;/b&gt;: 변수를 문장에 끼워 넣을 때는 고민 없이 f-string을 쓰자.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 다루기는 파이썬의 꽃입니다. 이것만 잘해도 엑셀의 &lt;code&gt;LEFT&lt;/code&gt;, &lt;code&gt;MID&lt;/code&gt;, &lt;code&gt;RIGHT&lt;/code&gt; 함수보다 훨씬 복잡한 텍스트 전처리 작업을 쉽게 해낼 수 있습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 예제에서 &lt;code&gt;.split()&lt;/code&gt;을 썼을 때 대괄호 &lt;code&gt;[&lt;/code&gt; &lt;code&gt;]&lt;/code&gt;로 묶인 데이터가 나온 것을 보셨나요? 이것이 바로 여러 개의 데이터를 한 번에 담는 파이썬의 핵심 자료구조입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;배열인 듯 배열 아닌&quot; 수많은 데이터를 한 바구니에 담는 리스트(List)와 튜플(Tuple)&lt;/b&gt;에 대해 완벽하게 파헤쳐 보겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1282</guid>
      <comments>https://hianna.tistory.com/1282#entry1282comment</comments>
      <pubDate>Wed, 8 Apr 2026 01:35:13 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 3. &amp;quot;Hello World&amp;quot;는 이제 그만! 변수와 자료형(Data Type) 제대로 알기</title>
      <link>https://hianna.tistory.com/1281</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간에는 화면에 글자를 띄워보는 기초적인 작업을 해보았습니다. 하지만 진짜 쓸모 있는 프로그램을 만들려면 데이터를 기억하고, 계산하고, 조작할 줄 알아야 합니다.&lt;br&gt;오늘은 데이터를 담는 상자인 &lt;b&gt;변수(Variable)&lt;/b&gt;와, 그 상자에 들어가는 데이터의 종류인 &lt;b&gt;자력형(Data Type)&lt;/b&gt;에 대해 완벽하게 정리해 드리겠습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 변수(Variable): 데이터를 담는 이름표 붙인 상자&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에게 &quot;이 숫자 좀 잠깐 기억해 줘!&quot;라고 명령할 때 사용하는 것이 바로 &lt;b&gt;변수&lt;/b&gt;입니다. 쉽게 말해, 데이터에 이름표를 붙여서 상자에 보관하는 것과 같습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서는 수학 기호인 &lt;code&gt;=&lt;/code&gt; (등호)를 사용해서 변수에 값을 넣습니다. (프로그래밍에서 &lt;code&gt;=&lt;/code&gt;는 '같다'가 아니라 &lt;b&gt;'오른쪽의 값을 왼쪽 상자에 넣어라(대입해라)'&lt;/b&gt;라는 뜻입니다.)&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 변수 만들기&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# 이름, 나이, 키를 각각의 변수에 저장합니다.
name = &quot;홍길동&quot;
age = 25
height = 175.5

# 저장된 변수들을 출력해 봅니다.
print(name)
print(age)
print(height)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;name&lt;/code&gt;이라는 상자를 만들고 &lt;code&gt;&quot;홍길동&quot;&lt;/code&gt;이라는 글자를 넣었습니다.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;age&lt;/code&gt;라는 상자에는 &lt;code&gt;25&lt;/code&gt;라는 숫자를 넣었습니다.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;print()&lt;/code&gt; 괄호 안에 변수 이름을 넣으면, 상자 안에 들어있던 실제 값이 화면에 출력됩니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 파이썬의 핵심! 동적 타이핑 (Dynamic Typing)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 가장 강력한 매력 중 하나는 프로그래머가 &lt;b&gt;상자의 종류를 미리 정해주지 않아도 된다&lt;/b&gt;는 것입니다.&lt;br&gt;파이썬은 아주 똑똑한 통역사라서, 여러분이 상자에 숫자를 넣으면 '아, 이건 숫자 상자구나!', 글자를 넣으면 '아, 이건 글자 상자구나!' 하고 &lt;b&gt;스스로 알아서 판단&lt;/b&gt;합니다. 이를 &lt;b&gt;동적 타이핑&lt;/b&gt;이라고 부릅니다.&lt;br&gt;심지어 쓰던 상자의 내용물을 아예 다른 종류로 바꿔버려도 에러가 나지 않습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 똑똑한 파이썬의 자료형 자동 인식&lt;/h4&gt;&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;# 처음엔 숫자를 넣습니다.
my_box = 100
print(type(my_box))  # type()은 상자 안에 든 데이터의 종류를 알려줍니다.

# 같은 상자에 이번엔 글자를 넣어버립니다!
my_box = &quot;안녕하세요&quot;
print(type(my_box))
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&amp;lt;class 'int'&amp;gt;
&amp;lt;class 'str'&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;type()&lt;/code&gt; 함수는 변수가 현재 어떤 &lt;b&gt;자료형&lt;/b&gt;을 담고 있는지 알려주는 아주 유용한 마법 도구입니다.&lt;/li&gt; 
 &lt;li&gt;처음엔 &lt;code&gt;int&lt;/code&gt;(정수)라고 대답하더니, 글자를 넣자 불평 없이 &lt;code&gt;str&lt;/code&gt;(문자열)로 상자의 성질을 바꿔버립니다. 정말 유연하죠?&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 가장 많이 쓰는 기본 자료형 3가지&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 파이썬에는 어떤 종류의 데이터들이 있을까요? 가장 기본이 되는 세 가지만 알면 충분합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 숫자형 (Numbers)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;소수점이 없는 &lt;b&gt;정수(int)&lt;/b&gt;와 소수점이 있는 &lt;b&gt;실수(float)&lt;/b&gt;로 나뉩니다. 사칙연산이 아주 자유롭습니다.&lt;/p&gt;&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;a = 10      # 정수 (int)
b = 3.14    # 실수 (float)

print(a + 2)    # 덧셈: 12
print(a - 2)    # 뺄셈: 8
print(a * 2)    # 곱셈: 20
print(a / 3)    # 나눗셈: 3.3333...

# 파이썬만의 유용한 수학 기호들
print(a // 3)   # 몫만 구하기: 3
print(a % 3)    # 나머지만 구하기: 1
print(a ** 2)   # 거듭제곱 (10의 2승): 100
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 문자열 (String, str)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;글자, 단어, 문장을 의미합니다. 반드시 &lt;b&gt;큰따옴표(&lt;code&gt;&quot; &quot;&lt;/code&gt;)&lt;/b&gt;나 &lt;b&gt;작은따옴표(&lt;code&gt;' '&lt;/code&gt;)&lt;/b&gt;로 감싸주어야 합니다. 따옴표로 감싸면 숫자라도 문자로 취급됩니다.&lt;/p&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;greeting = &quot;Hello&quot;
target = 'World'

# 문자열끼리 더하면 글자가 이어붙습니다!
message = greeting + &quot; &quot; + target 
print(message)  # 결과: Hello World

# 문자열을 곱하면 그 수만큼 반복됩니다!
print(&quot;하&quot; * 3)  # 결과: 하하하
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;③ 불린형 (Boolean, bool)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;참(&lt;code&gt;True&lt;/code&gt;)과 거짓(&lt;code&gt;False&lt;/code&gt;), 딱 두 가지 값만 가지는 자료형입니다. 주로 &quot;이 조건이 맞는지 틀린 지&quot; 검사할 때 씁니다. (첫 글자는 반드시 대문자로 적어야 합니다!)&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;is_adult = True
is_student = False

# 조건문을 만들 때 결과값으로 자주 등장합니다.
print(10 &amp;gt; 5)   # 10은 5보다 크다 -&amp;gt; 결과: True
print(10 == 5)  # 10과 5는 같다 (등호 2개 사용!) -&amp;gt; 결과: False
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 자료형 변환 (Type Casting): 상자 내용물 억지로 바꾸기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가끔은 문자로 된 숫자를 진짜 숫자로 바꾸거나, 숫자를 문자로 바꿔야 할 때가 있습니다. 이때는 원하는 자료형의 이름을 함수처럼 써주면 됩니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 자료형 바꾸기&lt;/h4&gt;&lt;pre class=&quot;hsp&quot;&gt;&lt;code&gt;str_num = &quot;100&quot;  # 따옴표가 있으니 이것은 '문자'입니다.
# print(str_num + 50) -&amp;gt; 에러 발생! (문자와 숫자는 더할 수 없습니다)

# 문자 &quot;100&quot;을 진짜 숫자(정수)로 변환합니다.
real_num = int(str_num)
print(real_num + 50)  # 결과: 150

# 반대로 숫자를 문자로 바꿀 수도 있습니다.
age = 25
# print(&quot;제 나이는 &quot; + age + &quot;입니다.&quot;) -&amp;gt; 에러 발생!
print(&quot;제 나이는 &quot; + str(age) + &quot;입니다.&quot;) # 숫자를 문자로 변환하여 이어붙임
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;데이터를 저장할 때는 &lt;b&gt;변수(이름표 붙인 상자)&lt;/b&gt;를 쓴다.&lt;/li&gt; 
 &lt;li&gt;파이썬은 상자에 넣는 데이터의 종류(&lt;b&gt;자료형&lt;/b&gt;)를 스스로 파악하는 아주 똑똑한 언어다.&lt;/li&gt; 
 &lt;li&gt;기본 자료형에는 &lt;b&gt;숫자(&lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;)&lt;/b&gt;, &lt;b&gt;문자(&lt;code&gt;str&lt;/code&gt;)&lt;/b&gt;, &lt;b&gt;참/거짓(&lt;code&gt;bool&lt;/code&gt;)&lt;/b&gt;이 있다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기초적인 재료 다듬기가 끝났습니다.&lt;br&gt;다음 포스팅에서는 이 재료 중에서도 가장 많이 다루게 될 &lt;b&gt;&quot;문자열(String)&quot;&lt;/b&gt;을 도마 위에 올려놓고, 요리조리 자르고 붙이는 &lt;b&gt;&quot;문자열 슬라이싱(Slicing)과 포맷팅&quot;&lt;/b&gt; 기술을 배워보겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1281</guid>
      <comments>https://hianna.tistory.com/1281#entry1281comment</comments>
      <pubDate>Tue, 7 Apr 2026 08:23:05 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 2. 5분 컷! 파이썬 설치하고 VS Code와 연동하기 (윈도우/맥)</title>
      <link>https://hianna.tistory.com/1280</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요리를 하려면 '식재료'와 '도마'가 필요하듯, 파이썬을 하려면 &lt;b&gt;파이썬 인터프리터(명령어를 컴퓨터 언어로 번역해 주는 프로그램)&lt;/b&gt;와 &lt;b&gt;코드 에디터(코드를 작성하는 도마)&lt;/b&gt;가 필요합니다.&lt;br&gt;시중에는 '아나콘다(Anaconda)'라는 데이터 분석용 통합 패키지도 있지만, 처음 배우기에는 용량이 크고 무거울 수 있습니다. 오늘은 가장 가볍고, 현업 개발자들이 가장 많이 사용하는 &lt;b&gt;&quot;순정 파이썬 + VS Code&quot;&lt;/b&gt; 조합으로 세팅해 보겠습니다. 딱 5분이면 충분합니다!&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: 파이썬(Python) 설치하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 컴퓨터에게 파이썬 문법을 이해할 수 있는 능력을 심어줘야 합니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://www.python.org/downloads/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;python.org&lt;/span&gt;&lt;/a&gt;&lt;/b&gt; 공식 홈페이지에 접속합니다.&lt;/li&gt;&lt;li&gt;메인 화면에 있는 &lt;b&gt;&quot;Download Python 3.x.x&quot;&lt;/b&gt; 노란색 버튼을 클릭해 설치 파일을 다운로드합니다. (버전은 계속 업데이트되니 최신 버전으로 받으시면 됩니다.)&lt;/li&gt;&lt;/ol&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;⚠️ [윈도우 사용자 필독] 가장 중요한 체크박스!&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;설치 파일을 실행하면 첫 화면 아래쪽에 체크박스가 두 개 나옵니다. 여기서 &lt;b&gt;두 번째 체크박스&lt;/b&gt;를 잊지 마세요.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;Add Python 3.x to PATH&lt;/code&gt; (또는 &lt;code&gt;Add python.exe to PATH&lt;/code&gt;)&lt;/b&gt;를 &lt;b&gt;꼭! 체크하세요.&lt;/b&gt;&lt;/li&gt; 
 &lt;li&gt;이 옵션을 켜야 윈도우의 어느 폴더에서든 파이썬 명령어를 자유롭게 실행할 수 있습니다. (체크하지 않으면 나중에 파이썬을 찾을 수 없다는 에러가 뜹니다.)&lt;/li&gt; 
 &lt;li&gt;체크 후 상단의 &lt;b&gt;Install Now&lt;/b&gt;를 누르면 설치가 완료됩니다. 맥(Mac) 사용자는 기본 설정대로 쭉 넘겨서 설치하시면 됩니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: VS Code (Visual Studio Code) 설치하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기본 메모장으로도 코딩은 할 수 있지만, 오타도 안 잡아주고 색깔도 없어서 매우 힘듭니다. 전 세계에서 가장 사랑받는 무료 코드 에디터인 &lt;b&gt;VS Code&lt;/b&gt;를 설치합니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://code.visualstudio.com/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;code.visualstudio.com&lt;/span&gt;&lt;/a&gt;&lt;/b&gt; 에 접속합니다.&lt;/li&gt;&lt;li&gt;자신의 운영체제(Windows/Mac)에 맞는 파란색 다운로드 버튼을 눌러 설치합니다. 설치 과정은 '다음'만 누르시면 무사히 끝납니다.&lt;/li&gt;&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: VS Code와 파이썬 연결하기 (확장 프로그램)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;VS Code 자체는 여러 언어를 다룰 수 있는 '빈 도화지' 같습니다. 여기에 &lt;b&gt;&quot;파이썬 맞춤형 도구&quot;&lt;/b&gt;를 추가해 줘야 합니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;VS Code를 실행합니다.&lt;/li&gt; 
 &lt;li&gt;왼쪽 메뉴바에서 &lt;b&gt;테트리스 블록 모양 아이콘 (Extensions, 확장)&lt;/b&gt;을 클릭합니다.&lt;/li&gt; 
 &lt;li&gt;검색창에 &lt;b&gt;&lt;code&gt;Python&lt;/code&gt;&lt;/b&gt;이라고 입력합니다.&lt;/li&gt; 
 &lt;li&gt;Microsoft가 직접 만든 &lt;b&gt;Python&lt;/b&gt; 확장을 찾아 &lt;b&gt;Install(설치)&lt;/b&gt; 버튼을 누릅니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 VS Code가 파이썬 코드를 인식하고, 오타를 찾아주며, 코드를 예쁘게 칠해주는(Syntax Highlighting) 모든 준비를 마쳤습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4단계: 역사적인 첫 코드 실행해 보기 (Hello World)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;모든 준비가 끝났습니다. 코드를 직접 치고 실행해 봅시다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;컴퓨터 바탕화면이나 원하는 곳에 &lt;code&gt;python_study&lt;/code&gt;라는 새 폴더를 하나 만듭니다.&lt;/li&gt; 
 &lt;li&gt;VS Code 상단 메뉴에서 &lt;b&gt;File(파일) -&amp;gt; Open Folder(폴더 열기)&lt;/b&gt;를 눌러 방금 만든 폴더를 엽니다.&lt;/li&gt; 
 &lt;li&gt;왼쪽 탐색기 빈 공간에 마우스 우클릭 -&amp;gt; &lt;b&gt;New File(새 파일)&lt;/b&gt;을 클릭하고, 이름을 &lt;code&gt;hello.py&lt;/code&gt;라고 입력합니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(  참고: &lt;code&gt;.py&lt;/code&gt;는 이 파일이 파이썬 코드라는 것을 알려주는 확장자입니다.)&lt;/i&gt;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: &lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;방금 만든 파일에 아래 두 줄을 똑같이 입력해 보세요.&lt;/p&gt;&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(&quot;Hello, Python!&quot;)
print(&quot;파이썬 설치 성공!&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 방법:&lt;/b&gt;&lt;br&gt;코드를 다 적었다면, 우측 상단에 있는 &lt;b&gt;▷ (재생 버튼, Run Python File)&lt;/b&gt;을 누릅니다.&lt;br&gt;&lt;b&gt;실행 결과 (하단 터미널 창):&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;Hello, Python!
파이썬 설치 성공!
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;하단에 텍스트가 잘 출력되었나요? 그렇다면 축하합니다!  &lt;br&gt;이제 여러분의 컴퓨터는 파이썬 코드를 이해하고 실행할 준비가 완벽하게 되었습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  보너스: 가상환경(Virtual Environment)이란?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 파이썬을 다루다 보면 '가상환경'이라는 단어를 자주 듣게 됩니다.&lt;br&gt;파이썬은 다양한 외부 라이브러리(남들이 만들어둔 코드 뭉치)를 다운받아 씁니다. 만약 A 프로젝트는 '버전 1.0'이 필요하고, B 프로젝트는 '버전 2.0'이 필요하다면 어떻게 될까요? 둘이 충돌해서 에러가 날 수 있습니다.&lt;br&gt;이때 프로젝트마다 &lt;b&gt;&quot;서로 간섭하지 않는 독립된 방&quot;&lt;/b&gt;을 만들어 주는 기술이 바로 &lt;b&gt;가상환경&lt;/b&gt;입니다. 지금 당장 만들 필요는 없지만, &quot;프로젝트마다 독립된 폴더 환경을 구축하는구나!&quot; 정도로만 기억해 두시면 나중에 큰 도움이 됩니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 파이썬의 세계를 탐험할 튼튼한 배(VS Code)와 나침반(Python)이 준비되었습니다. 첫 코드를 실행했을 때의 짜릿함을 잊지 마세요!&lt;br&gt;다음 포스팅부터는 본격적으로 파이썬의 문법을 배워봅니다. 다음 주제는 &lt;b&gt;&quot;Hello World는 이제 그만! 변수와 자료형(Data Type) 제대로 알기&quot;&lt;/b&gt;입니다. 숫자, 텍스트를 파이썬이 어떻게 다루는지 알아보겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <category>Python</category>
      <category>파이썬</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1280</guid>
      <comments>https://hianna.tistory.com/1280#entry1280comment</comments>
      <pubDate>Tue, 7 Apr 2026 00:01:53 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 1. &amp;quot;엑셀보다 쉽고 강력하다?&amp;quot; 왜 지금 파이썬을 배워야 할까요?</title>
      <link>https://hianna.tistory.com/1279</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 여러분의 업무 자동화와 칼퇴를 돕는 하이애나입니다.&lt;br&gt;&quot;코딩, 전공자만 하는 거 아닌가요?&quot;&lt;br&gt;&quot;엑셀 함수도 어려운데 프로그래밍을 어떻게 해요?&quot;&lt;br&gt;이런 걱정은 잠시 접어두셔도 좋습니다. 오늘 소개할 &lt;b&gt;파이썬(Python)&lt;/b&gt;은 전 세계에서 &lt;b&gt;&quot;가장 배우기 쉬운 프로그래밍 언어&quot;&lt;/b&gt; 1위로 꼽힙니다.&lt;br&gt;구글, 넷플릭스, NASA, 그리고 수많은 데이터 분석가들이 파이썬을 선택한 이유, 그리고 우리가 왜 지금 이것을 배워야 하는지 그 매력을 알아보겠습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 사람이 읽기 쉬운 언어 (Readability)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 가장 큰 특징은 &lt;b&gt;&quot;코드 자체가 영어 문장처럼 읽힌다&quot;&lt;/b&gt;는 점입니다.&lt;br&gt;복잡한 기호나 괄호를 최대한 배제하고, &lt;b&gt;들여쓰기(Indentation)&lt;/b&gt;를 통해 문단을 구분합니다.&lt;br&gt;백문이 불여일견, 코드를 한번 볼까요?&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 점수에 따른 합격 여부 판단&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;score = 85  # 점수 변수 설정

if score &amp;gt;= 60:
    print(&quot;축하합니다!&quot;)
    print(&quot;합격입니다.&quot;)
else:
    print(&quot;아쉽네요.&quot;)
    print(&quot;불합격입니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;if score &amp;gt;= 60:&lt;/code&gt;: 만약(&lt;code&gt;if&lt;/code&gt;) 점수가 60점 이상이면... (마치 영어 문장 같죠?)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;들여쓰기:&lt;/b&gt; &lt;code&gt;if&lt;/code&gt;문 아래에 안쪽으로 들여쓰기 된 부분(&lt;code&gt;print...&lt;/code&gt;)이 조건이 맞을 때 실행되는 코드입니다. 별도의 괄호&lt;code&gt;{}&lt;/code&gt; 없이도 구조가 한눈에 들어옵니다.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;else:&lt;/code&gt;: 그게 아니라면(60점 미만이라면)...&lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍을 전혀 모르는 사람도 위 코드를 보면 &quot;아, 60점 넘으면 합격이라고 출력하는구나&quot;라고 바로 이해할 수 있습니다. 이것이 파이썬의 힘입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 인터프리터 언어: 쓰면 바로 실행된다!&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 언어들은 코드를 다 짜고 나서 컴퓨터가 이해할 수 있게 번역(컴파일)하는 복잡한 과정이 필요합니다. 하지만 파이썬은 &lt;b&gt;통역사(Interpreter)&lt;/b&gt;가 있어서, 한 줄 입력하면 바로 한 줄을 실행해 줍니다.&lt;br&gt;이 덕분에 결과를 바로바로 확인하면서 코드를 수정할 수 있어 초보자가 배우기에 아주 좋습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 즉석 계산기&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;width = 20
height = 5 * 9

area = width * height
print(area)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;900
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;width&lt;/code&gt;와 &lt;code&gt;height&lt;/code&gt;라는 상자에 값을 넣고, 둘을 곱해서 &lt;code&gt;area&lt;/code&gt;에 넣습니다.&lt;/li&gt; 
 &lt;li&gt;복잡한 설정 없이 그냥 수식만 적으면 계산기처럼 동작합니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &quot;배터리 포함&quot; (Batteries Included)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에는 &lt;b&gt;&quot;이미 다 준비되어 있으니 가져다 쓰세요&quot;&lt;/b&gt;라는 철학이 있습니다.&lt;br&gt;날짜 계산, 랜덤 숫자 뽑기, 파일 다루기 등 자주 쓰는 기능들이 &lt;b&gt;표준 라이브러리&lt;/b&gt;로 이미 내장되어 있습니다. 우리는 &lt;code&gt;import&lt;/code&gt;만 하면 됩니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 오늘 점심 메뉴 고르기&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;import random  # 랜덤 기능을 가져온다

menus = [&quot;김치찌개&quot;, &quot;돈까스&quot;, &quot;제육볶음&quot;, &quot;샐러드&quot;, &quot;초밥&quot;]

# 메뉴 리스트 중에서 하나를 무작위로 선택
lunch = random.choice(menus)

print(f&quot;오늘의 점심 메뉴는: {lunch}&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과 (매번 다름):&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;&quot;&gt;&lt;code&gt;오늘의 점심 메뉴는: 제육볶음
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  코드 설명:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;code&gt;import random&lt;/code&gt;: &quot;파이썬아, 나 무작위 기능 좀 쓸게.&quot;라고 선언합니다.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;random.choice(menus)&lt;/code&gt;: &lt;code&gt;menus&lt;/code&gt;라는 목록에서 하나를 알아서 뽑아줍니다. 복잡한 확률 계산 코드를 짤 필요가 전혀 없습니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 무엇을 할 수 있나요? (활용 분야)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬을 배우면 단순히 계산만 하는 게 아닙니다. 다음과 같은 엄청난 일들을 할 수 있습니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;데이터 분석 &amp;amp; 시각화:&lt;/b&gt; 엑셀로 처리하기 힘든 100만 건의 데이터를 순식간에 분석하고 그래프를 그립니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;웹 크롤링:&lt;/b&gt; 인터넷에 있는 뉴스 기사, 주식 정보, 최저가 정보를 자동으로 수집합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;업무 자동화:&lt;/b&gt; 매일 아침마다 보내야 하는 이메일, 엑셀 보고서 작성을 버튼 하나로 끝냅니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;인공지능(AI):&lt;/b&gt; ChatGPT 같은 AI 모델도 파이썬을 기반으로 연구되고 개발됩니다.&lt;/li&gt;&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬은 &lt;b&gt;&quot;생각하는 대로 코드를 작성할 수 있는&quot;&lt;/b&gt; 도구입니다.&lt;br&gt;문법이 어렵지 않아서, 여러분이 해결하고 싶은 &lt;b&gt;문제(What)&lt;/b&gt;에만 집중할 수 있게 도와줍니다.&lt;br&gt;&quot;나도 한번 해볼까?&quot;라는 생각이 드셨나요?&lt;br&gt;그렇다면 이미 절반은 성공입니다.&lt;br&gt;다음 포스팅에서는 &lt;b&gt;&quot;5분 컷! 파이썬 설치하고 개발 환경(VS Code) 설정하기&quot;&lt;/b&gt;를 통해 내 컴퓨터에 파이썬이라는 마법 지팡이를 쥐여드리겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1279</guid>
      <comments>https://hianna.tistory.com/1279#entry1279comment</comments>
      <pubDate>Mon, 6 Apr 2026 08:01:09 +0900</pubDate>
    </item>
    <item>
      <title>[Chrome 브라우저] 크롬 캐시 완벽하게 지우는 2가지 방법 (일반 캐시 &amp;amp; DNS 캐시)</title>
      <link>https://hianna.tistory.com/1278</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 브라우저에서 캐시를 지우는 목적에 따라 두 가지 방법이 있습니다. 웹페이지의 이미지나 레이아웃이 제대로 새로고침되지 않을 때는 &lt;b&gt;일반 캐시&lt;/b&gt;를, &lt;code&gt;hosts&lt;/code&gt; 파일을 수정했는데도 접속할 IP가 바뀌지 않을 때는 &lt;b&gt;내부 DNS 캐시&lt;/b&gt;를 지워야 합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 일반 브라우저 캐시 지우기 (단축키 활용)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트의 업데이트된 내용이 보이지 않거나 화면이 깨질 때 가장 먼저 시도하는 표준적인 방법입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;크롬 브라우저를 열고 단축키 &lt;b&gt;&lt;code&gt;Ctrl + Shift + Delete&lt;/code&gt;&lt;/b&gt;를 누릅니다. (Mac은 &lt;code&gt;Cmd + Shift + Delete&lt;/code&gt;)&lt;/li&gt; 
 &lt;li&gt;화면에 &lt;b&gt;'인터넷 사용 기록 삭제'&lt;/b&gt; 창이 나타납니다.&lt;/li&gt; 
 &lt;li&gt;상단의 &lt;b&gt;기간&lt;/b&gt;을 '전체 기간'으로 선택합니다.&lt;/li&gt; 
 &lt;li&gt;아래 항목 중 &lt;b&gt;'캐시된 이미지 및 파일'&lt;/b&gt;에 체크합니다. (로그인 상태를 유지하려면 '쿠키 및 기타 사이트 데이터'는 체크 해제해도 됩니다.)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;'데이터 삭제'&lt;/b&gt; 버튼을 클릭합니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfkgD8/dJMcaadAmwR/NRMIxA8K4A1TI2UidJfDM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfkgD8/dJMcaadAmwR/NRMIxA8K4A1TI2UidJfDM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfkgD8/dJMcaadAmwR/NRMIxA8K4A1TI2UidJfDM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfkgD8%2FdJMcaadAmwR%2FNRMIxA8K4A1TI2UidJfDM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;515&quot; height=&quot;557&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;557&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 강력한 숨겨진 기능: 크롬 내부 DNS 캐시 지우기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 수정한 &lt;code&gt;hosts&lt;/code&gt; 파일 내용이 크롬에서만 유독 적용되지 않는다면, 크롬이 브라우저 내부에 자체적으로 보관하고 있는 DNS(도메인-IP 연결 기록) 캐시를 강제로 비워야 합니다. 일반 설정 메뉴에서는 찾을 수 없는 숨겨진 기능입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;크롬 브라우저 주소창에 다음 주소를 복사하여 붙여넣고 &lt;b&gt;Enter&lt;/b&gt;를 누릅니다.&lt;br&gt;&lt;code&gt;chrome://net-internals/#dns&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;화면 왼쪽 메뉴에서 &lt;b&gt;'DNS'&lt;/b&gt;가 선택되어 있는지 확인합니다.&lt;/li&gt; 
 &lt;li&gt;우측 화면에서 &lt;b&gt;'Clear host cache'&lt;/b&gt; 버튼을 클릭합니다. (버튼을 눌러도 별도의 팝업이나 완료 메시지가 뜨지 않지만, 즉시 삭제 처리됩니다.)&lt;br&gt;
  &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;249&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8f2G4/dJMcaa5Ipco/NDhy80tli8ifKP7D5QIzyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8f2G4/dJMcaa5Ipco/NDhy80tli8ifKP7D5QIzyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8f2G4/dJMcaa5Ipco/NDhy80tli8ifKP7D5QIzyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8f2G4%2FdJMcaa5Ipco%2FNDhy80tli8ifKP7D5QIzyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;578&quot; height=&quot;249&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;249&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt; 
 &lt;li&gt;(선택 사항) 더 확실한 초기화를 위해 주소창에 &lt;code&gt;chrome://net-internals/#sockets&lt;/code&gt;를 입력하고 이동한 뒤, &lt;b&gt;'Flush socket pools'&lt;/b&gt; 버튼도 한 번 클릭해 줍니다.&lt;br&gt;
  &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czu0ET/dJMcahRhZ6x/cdp3rCBiGwhP4DoNmtLqFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czu0ET/dJMcahRhZ6x/cdp3rCBiGwhP4DoNmtLqFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czu0ET/dJMcahRhZ6x/cdp3rCBiGwhP4DoNmtLqFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fczu0ET%2FdJMcahRhZ6x%2Fcdp3rCBiGwhP4DoNmtLqFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;281&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt; 
 &lt;li&gt;열려있는 모든 크롬 창을 닫고 다시 실행합니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>생활의 발견/IT정보</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1278</guid>
      <comments>https://hianna.tistory.com/1278#entry1278comment</comments>
      <pubDate>Mon, 6 Apr 2026 00:01:10 +0900</pubDate>
    </item>
    <item>
      <title>[Windows 기초] DNS 캐시 초기화 방법 (hosts 파일 수정 후 적용이 안 될 때)</title>
      <link>https://hianna.tistory.com/1277</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;hosts&lt;/code&gt; 파일을 올바르게 수정하고 저장했는데도 웹 브라우저에서 바뀐 IP로 접속되지 않는 경우가 많습니다. 이는 윈도우 운영체제가 이전에 접속했던 기록을 임시로 기억하고 있기 때문입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이때 기존의 기억을 강제로 지우고 수정한 &lt;code&gt;hosts&lt;/code&gt; 파일을 즉각 반영하게 만드는 &lt;b&gt;DNS 캐시 초기화&lt;/b&gt; 방법을 정리해 드립니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 왜 수정한 내용이 바로 적용되지 않을까요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우는 인터넷 접속 속도를 높이기 위해, 한 번 방문했던 웹사이트의 IP 주소를 매번 새로 찾지 않고 컴퓨터 내부에 임시로 저장해 둡니다. 이를 &lt;b&gt;DNS 캐시(Cache)&lt;/b&gt;라고 부릅니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;hosts&lt;/code&gt; 파일을 방금 수정했더라도, 윈도우가 &quot;이 사이트 주소는 아까 저장해 둔 캐시에 있어!&quot;라고 판단하여 &lt;b&gt;과거의 IP 주소로 먼저 연결을 시도&lt;/b&gt;해 버리기 때문에 변경 사항이 무시되는 것입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 해결 방법:&amp;nbsp;&amp;nbsp;명령어 사용&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;명령 프롬프트를 이용해 윈도우에 쌓여 있는 기존 DNS 캐시를 깔끔하게 삭제할 수 있습니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;윈도우 &lt;b&gt;시작(검색) 메뉴&lt;/b&gt;를 엽니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;cmd&lt;/code&gt;&lt;/b&gt; 또는 &lt;b&gt;명령 프롬프트&lt;/b&gt;를 검색하여 클릭합니다. (일반 권한으로 실행해도 무방합니다.)&lt;/li&gt; 
 &lt;li&gt;나타난 검은색 창에 아래 명령어를 입력하고 &lt;b&gt;Enter&lt;/b&gt;를 누릅니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;ipconfig /flushdns&lt;/code&gt;&lt;/pre&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;정상적으로 처리되면 창에 다음과 같은 성공 메시지가 나타납니다.&lt;/li&gt;&lt;/ol&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uhB2X/dJMcafFXmZk/w4ikP0xLFuMMv8T08Jiax1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uhB2X/dJMcafFXmZk/w4ikP0xLFuMMv8T08Jiax1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uhB2X/dJMcafFXmZk/w4ikP0xLFuMMv8T08Jiax1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuhB2X%2FdJMcafFXmZk%2Fw4ikP0xLFuMMv8T08Jiax1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;979&quot; height=&quot;512&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 웹 브라우저를 새로고침(F5) 하거나 껐다가 다시 켜서 접속해 보면, 수정한 &lt;code&gt;hosts&lt;/code&gt; 파일의 내용이 정상적으로 반영된 것을 확인할 수 있습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 그래도 적용이 안 된다면? (브라우저 자체 캐시)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;명령 프롬프트에서 초기화를 마쳤는데도 여전히 예전 화면이 나온다면, 크롬(Chrome)이나 엣지(Edge) 같은 &lt;b&gt;웹 브라우저가 자체적으로 가지고 있는 내부 DNS 캐시&lt;/b&gt;가 원인일 확률이 매우 높습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>생활의 발견/IT정보</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1277</guid>
      <comments>https://hianna.tistory.com/1277#entry1277comment</comments>
      <pubDate>Sun, 5 Apr 2026 09:59:44 +0900</pubDate>
    </item>
    <item>
      <title>[Windows 기초] 윈도우 hosts 파일의 역할과 위치, 수정 방법</title>
      <link>https://hianna.tistory.com/1276</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발을 하거나 특정 웹사이트의 접속을 제어해야 할 때 반드시 알아야 하는 시스템 파일이 바로 &lt;code&gt;hosts&lt;/code&gt; 파일입니다. 이 파일이 무엇인지, 어디에 있는지, 그리고 어떻게 수정하는지 명확하게 정리해 드립니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp;&amp;nbsp;파일이란 무엇인가요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 웹 브라우저에 &lt;code&gt;google.com&lt;/code&gt;이라는 도메인 주소를 입력하면, 컴퓨터는 이 이름표에 해당하는 실제 집 주소(IP 주소, 예: &lt;code&gt;142.250.190.46&lt;/code&gt;)를 찾아야만 접속할 수 있습니다. 보통 이 변환 작업은 외부의 &lt;b&gt;DNS(Domain Name System) 서버&lt;/b&gt;가 담당합니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 컴퓨터가 외부 DNS 서버에 &quot;이 도메인의 IP가 뭐야?&quot;라고 물어보기 전에, &lt;b&gt;가장 먼저 뒤져보는 내 컴퓨터 안의 '개인 주소록'&lt;/b&gt;이 바로 &lt;code&gt;hosts&lt;/code&gt; 파일입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일에 특정 도메인과 IP 주소를 짝지어 적어두면, 컴퓨터는 외부 인터넷망을 타지 않고 내가 지정한 IP로 곧바로 연결을 시도합니다.&lt;br&gt;&lt;b&gt;주요 활용 목적:&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;웹사이트 차단:&lt;/b&gt; 특정 사이트의 도메인을 내 컴퓨터 주소(&lt;code&gt;127.0.0.1&lt;/code&gt;)로 연결해 버리면 해당 사이트 접속이 완벽하게 차단됩니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;개발 및 테스트:&lt;/b&gt; 아직 외부 DNS에 등록되지 않은 개발용 서버를 실제 도메인 이름으로 접속해 보며 테스트할 때 사용합니다.&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 윈도우&amp;nbsp;&amp;nbsp;파일 위치&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 운영체제(Windows 10, 11 공통)에서 &lt;code&gt;hosts&lt;/code&gt; 파일의 절대 경로는 다음과 같습니다. 확장자가 없는 순수 텍스트 파일 형태입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;C:\Windows\System32\drivers\etc\hosts&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.&amp;nbsp;&amp;nbsp;파일 수정 및 저장 방법 (권한 문제 해결)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일은 윈도우의 핵심 시스템 경로에 있기 때문에, 그냥 더블클릭해서 열고 수정한 뒤 저장하려고 하면 '권한이 없다'며 저장이 거부됩니다. 반드시 &lt;b&gt;관리자 권한&lt;/b&gt;으로 편집기를 실행해야 합니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;윈도우 &lt;b&gt;시작(검색) 메뉴&lt;/b&gt;를 엽니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;'메모장(Notepad)'&lt;/b&gt;을 검색합니다.&lt;/li&gt; 
 &lt;li&gt;메모장 아이콘에 마우스 우클릭을 한 뒤, &lt;b&gt;'관리자 권한으로 실행'&lt;/b&gt;을 클릭합니다.&lt;/li&gt; 
 &lt;li&gt;메모장이 열리면 좌측 상단의 &lt;b&gt;[파일] - [열기]&lt;/b&gt;를 클릭합니다.&lt;/li&gt; 
 &lt;li&gt;위에서 알려드린 경로(&lt;code&gt;C:\Windows\System32\drivers\etc&lt;/code&gt;)로 이동합니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;주의:&lt;/b&gt; 처음에는 파일이 아무것도 안 보일 수 있습니다. 열기 창 우측 하단의 파일 형식을 '텍스트 문서(&lt;i&gt;.txt)'에서 **'모든 파일(&lt;/i&gt;.&lt;i&gt;)'*&lt;/i&gt;로 변경해야 &lt;code&gt;hosts&lt;/code&gt; 파일이 나타납니다.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;hosts&lt;/code&gt; 파일을 열고 원하는 내용을 맨 아랫줄에 추가한 뒤 &lt;b&gt;저장(Ctrl + S)&lt;/b&gt; 합니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;  작성 예시&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파일 맨 아래에 다음과 같은 형식으로 띄어쓰기(또는 Tab)를 하여 &lt;code&gt;[연결할 IP 주소] [도메인]&lt;/code&gt; 순서로 작성합니다.&lt;/p&gt;&lt;pre class=&quot;accesslog&quot;&gt;&lt;code&gt;# 특정 사이트(예: facebook.com) 접속 차단하기
127.0.0.1    facebook.com
127.0.0.1    www.facebook.com

# 개발 중인 테스트 서버 IP로 강제 연결하기
192.168.0.50 my-test-website.com&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>생활의 발견/IT정보</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1276</guid>
      <comments>https://hianna.tistory.com/1276#entry1276comment</comments>
      <pubDate>Sun, 5 Apr 2026 00:59:37 +0900</pubDate>
    </item>
    <item>
      <title>[Troubleshooting] WebFlux 도입 전 꼭 알아야 할 주의사항 (BlockHound 사용법)</title>
      <link>https://hianna.tistory.com/1275</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;MVC에서는 쓰레드 하나가 막혀도(Blocking) 다른 쓰레드 199개는 잘 돌아갑니다.&lt;br&gt;하지만 WebFlux는 쓰레드가 고작 &lt;b&gt;CPU 코어 수(예: 4개)&lt;/b&gt;만큼밖에 없습니다. 하나가 막히면 &lt;b&gt;서버 성능의 25%가 날아가는 셈&lt;/b&gt;이죠.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 실수로 JDBC를 쓰거나, &lt;code&gt;Thread.sleep&lt;/code&gt;을 써도 컴파일 에러가 안 난다는 것입니다. 배포하고 나서야 서버가 죽는 걸 보게 되죠.&lt;br&gt;이걸 막아주는 수호신, &lt;b&gt;BlockHound&lt;/b&gt;를 소개합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. BlockHound: &quot;블로킹 코드가 보이면 에러를 뱉어라!&quot;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Reactor 팀이 만든 도구로, 논블로킹 쓰레드(Reactor Thread)에서 블로킹 작업이 감지되면 &lt;b&gt;즉시 예외(&lt;code&gt;BlockingOperationError&lt;/code&gt;)를 던져서 테스트를 실패하게 만듭니다.&lt;/b&gt;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 의존성 추가&lt;/h4&gt;&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;dependencies {
    testImplementation 'io.projectreactor:reactor-test' // 여기에 포함되어 있음
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 설정 ( 메서드나 테스트 설정에 추가)&lt;/h4&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    // ★ 앱 시작 시 최상단에 선언!
    BlockHound.install();

    SpringApplication.run(MyApplication.class, args);
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;③ 효과 확인&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아래 코드를 실행하면 에러가 납니다.&lt;/p&gt;&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Mono.delay(Duration.ofSeconds(1))
    .doOnNext(it -&amp;gt; {
        try {
            Thread.sleep(100); // ❌ BlockHound가 잡아서 에러 냄!
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    })
    .subscribe();
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;개발/테스트 단계에서 &lt;code&gt;Thread.sleep&lt;/code&gt;, &lt;code&gt;InputStream.read&lt;/code&gt;, JDBC 호출 등을 원천 봉쇄할 수 있습니다. &lt;b&gt;(필수 적용 권장!)&lt;/b&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 디버깅: &quot;스택 트레이스가 이상해요...&quot;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux에서 에러가 나면 스택 트레이스(Stack Trace)가 수백 줄이 나오는데, &lt;b&gt;정작 내 코드는 안 보이고 Reactor 내부 클래스만 잔뜩 보입니다.&lt;/b&gt;&lt;br&gt;비동기라서 &lt;b&gt;&quot;누가 호출했는지&quot;&lt;/b&gt; 정보가 런타임에 사라지기 때문입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  해결책 1: &lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;의심 가는 부분에 &lt;code&gt;.checkpoint(&quot;설명&quot;)&lt;/code&gt;를 붙이세요.&lt;/p&gt;&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;flux.map(x -&amp;gt; logicA(x))
    .checkpoint(&quot;logicA-after&quot;) // 에러 나면 이 지점을 알려줌
    .flatMap(x -&amp;gt; logicB(x))
    .checkpoint(&quot;logicB-after&quot;)
    .subscribe();
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;에러 로그에 &lt;code&gt;Assembly trace from producer [reactor.core.publisher.FluxMap] : ... logicA-after&lt;/code&gt;라고 친절하게 뜹니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  해결책 2: &lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경(&lt;code&gt;dev&lt;/code&gt;)에서는 앱 시작 시 이 옵션을 켜두면, 모든 연산자의 스택 트레이스를 기록합니다. (단, 운영 환경에선 성능 저하가 심하므로 끄세요!)&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 언제 WebFlux를 쓰면 안 되나요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux가 만능은 아닙니다. 오히려 &lt;b&gt;독&lt;/b&gt;이 되는 경우도 많습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 1. CPU를 많이 쓰는 작업 (암호화, 동영상 인코딩)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 루프는 &lt;b&gt;&quot;주문 받고 넘기는 일(I/O)&quot;&lt;/b&gt;에 특화되어 있습니다.&lt;br&gt;주방장(Event Loop)이 직접 요리(CPU 연산)를 하면 주문을 못 받습니다.&lt;br&gt;이런 작업은 &lt;b&gt;별도 쓰레드 풀&lt;/b&gt;이나 &lt;b&gt;MVC&lt;/b&gt;가 낫습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 2. 팀원들의 러닝 커브&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JPA도 모르는데 R2DBC와 &lt;code&gt;flatMap&lt;/code&gt;까지 배우라고 하면 퇴사자가 속출할 수 있습니다.&lt;br&gt;단순한 CRUD 서비스라면 생산성이 높은 &lt;b&gt;MVC + JPA&lt;/b&gt;가 정답입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 3. 복잡한 트랜잭션 로직&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑몰 결제처럼 &quot;A 호출하고, 실패하면 B 호출하고, C 업데이트하고...&quot; 로직이 복잡하면, WebFlux의 콜백 지옥(Callback Hell)보다 더한 &lt;b&gt;Operator Hell&lt;/b&gt;을 맛보게 됩니다. 코드를 읽을 수가 없습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1275</guid>
      <comments>https://hianna.tistory.com/1275#entry1275comment</comments>
      <pubDate>Sat, 4 Apr 2026 08:24:15 +0900</pubDate>
    </item>
    <item>
      <title>[Test] &amp;quot;비동기 코드는 어떻게 테스트해요?&amp;quot; StepVerifier로 스트림 검증하기</title>
      <link>https://hianna.tistory.com/1274</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit으로 동기 코드를 테스트할 때는 결과가 바로 나옵니다.&lt;br&gt;하지만 &lt;code&gt;Flux.interval(Duration.ofSeconds(1))&lt;/code&gt; 같은 코드는 1초 뒤에 데이터가 나옵니다.&lt;br&gt;이걸 테스트하려고 &lt;code&gt;Thread.sleep(1000)&lt;/code&gt;을 쓰는 순간, 테스트 시간은 늘어지고 코드는 지저분해집니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Project Reactor는 이런 문제를 해결하기 위해 &lt;b&gt;&lt;code&gt;StepVerifier&lt;/code&gt;&lt;/b&gt;라는 강력한 테스트 도구를 제공합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. StepVerifier가 뭔가요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;리액티브 스트림의 &lt;b&gt;구독자(Subscriber)&lt;/b&gt; 역할을 하는 테스트용 객체입니다.&lt;br&gt;&quot;첫 번째 데이터는 'A'여야 하고, 두 번째는 'B'여야 하고, 마지막엔 성공적으로 끝나야 해&quot;라는 &lt;b&gt;시나리오&lt;/b&gt;를 짜고, 실제로 그렇게 흘러가는지 검증합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 기본 사용법: 데이터 검증 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 패턴입니다.&lt;/p&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
void fluxTest() {
    Flux&amp;lt;String&amp;gt; flux = Flux.just(&quot;Apple&quot;, &quot;Banana&quot;, &quot;Cherry&quot;);

    // 1. 테스트 대상(Publisher)을 넣고 생성
    StepVerifier.create(flux)
            // 2. 예상 시나리오 작성
            .expectNext(&quot;Apple&quot;)   // 첫 번째 값은 Apple이어야 함
            .expectNext(&quot;Banana&quot;)  // 두 번째 값은 Banana여야 함
            .expectNext(&quot;Cherry&quot;)  // 세 번째 값은 Cherry여야 함
            // 3. 완료 신호 검증 및 실행 (이걸 호출해야 실제 구독 시작!)
            .verifyComplete(); 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;만약 순서가 틀리거나 값이 다르면 &lt;code&gt;AssertionError&lt;/code&gt;가 발생하며 테스트가 실패합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 에러 검증 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직에서 예외가 잘 발생하는지도 중요하죠.&lt;/p&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
void errorTest() {
    Flux&amp;lt;String&amp;gt; flux = Flux.just(&quot;A&quot;, &quot;B&quot;)
            .concatWith(Mono.error(new IllegalArgumentException(&quot;잘못된 값&quot;)));

    StepVerifier.create(flux)
            .expectNext(&quot;A&quot;)
            .expectNext(&quot;B&quot;)
            // .expectError() // 그냥 에러가 났는지만 확인
            .expectError(IllegalArgumentException.class) // 특정 에러 클래스인지 확인
            // .expectErrorMessage(&quot;잘못된 값&quot;) // 에러 메시지 확인
            .verify(); // 에러로 끝나는 건 정상 완료(Complete)가 아니므로 그냥 verify() 사용
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 개수 검증 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 100만 개인데 일일이 &lt;code&gt;expectNext&lt;/code&gt;를 100만 번 쓸 순 없습니다.&lt;/p&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
void countTest() {
    Flux&amp;lt;Integer&amp;gt; range = Flux.range(1, 100);

    StepVerifier.create(range)
            .expectNextCount(100) // 100개가 들어오는지 확인
            .verifyComplete();
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 시간의 마법사: 가상 시간 () ★핵심★&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;1시간 뒤에 알림을 보낸다&quot;는 로직을 테스트하려면 1시간을 기다려야 할까요?&lt;br&gt;&lt;b&gt;&lt;code&gt;StepVerifier.withVirtualTime&lt;/code&gt;&lt;/b&gt;을 쓰면 시간을 조작해서 &lt;b&gt;0.1초 만에 테스트&lt;/b&gt;할 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
void timeTest() {
    // 1시간 뒤에 &quot;Alarm!&quot;을 방출하는 Flux
    Mono&amp;lt;String&amp;gt; alarmMono = Mono.delay(Duration.ofHours(1))
            .map(t -&amp;gt; &quot;Alarm!&quot;);

    // withVirtualTime 사용!
    StepVerifier.withVirtualTime(() -&amp;gt; alarmMono)
            // 1. &quot;구독하자마자&quot;는 아무 일도 안 일어남
            .expectSubscription() 
            // 2. 아무런 이벤트 없이 1시간이 지났다고 쳐! (시간 점프)
            .thenAwait(Duration.ofHours(1)) 
            // 3. 그러면 알람이 와야지?
            .expectNext(&quot;Alarm!&quot;)
            .verifyComplete();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 실행 즉시 끝납니다. &lt;b&gt;&lt;code&gt;Thread.sleep&lt;/code&gt;의 완벽한 상위 호환&lt;/b&gt;입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 꿀팁: 로그 찍어보기 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 왜 실패하는지 모르겠다면, 스트림 중간에 &lt;code&gt;.log()&lt;/code&gt;를 넣어보세요.&lt;br&gt;&lt;code&gt;onNext&lt;/code&gt;, &lt;code&gt;request&lt;/code&gt;, &lt;code&gt;onComplete&lt;/code&gt; 등 모든 신호(Signal)가 콘솔에 찍혀서 디버깅이 쉬워집니다.&lt;/p&gt;&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Flux.just(&quot;A&quot;, &quot;B&quot;)
    .log() // 여기에 추가!
    .subscribe();
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;비동기 테스트에서 &lt;b&gt;&lt;code&gt;block()&lt;/code&gt;은 금지&lt;/b&gt;다. (습관 되면 망함)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;StepVerifier&lt;/code&gt;&lt;/b&gt;를 사용하면 데이터의 값, 순서, 에러, 완료 여부를 시나리오대로 검증할 수 있다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;withVirtualTime&lt;/code&gt;&lt;/b&gt;을 쓰면 긴 시간의 지연 작업도 순식간에 테스트할 수 있다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분은 비동기 코드도 자신 있게 작성하고 검증할 수 있게 되었습니다.&lt;br&gt;다음 포스팅에서는 이 시리즈의 대미를 장식할 마지막 주제, &lt;b&gt;&quot;이거 모르면 운영하다가 서버 터집니다.&quot; WebFlux 도입 전 꼭 알아야 할 주의사항 (BlockHound 사용법과 디버깅)&lt;/b&gt;에 대해 알아보겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1274</guid>
      <comments>https://hianna.tistory.com/1274#entry1274comment</comments>
      <pubDate>Sat, 4 Apr 2026 00:23:27 +0900</pubDate>
    </item>
    <item>
      <title>[R2DBC] &amp;quot;트랜잭션은 어떻게 걸죠?&amp;quot; Reactive Transactional과 R2dbcEntityTemplate</title>
      <link>https://hianna.tistory.com/1273</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux에서 R2DBC를 쓸 때 가장 불안한 점은 트랜잭션입니다.&lt;br&gt;&quot;A 계좌에서 돈을 빼고(&lt;code&gt;update&lt;/code&gt;), B 계좌에 돈을 넣어야(&lt;code&gt;update&lt;/code&gt;) 하는데, 중간에 에러 나면 롤백이 될까?&quot;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말씀드리면, &lt;b&gt;&lt;code&gt;@Transactional&lt;/code&gt; 어노테이션 하나면 완벽하게 동작합니다.&lt;/b&gt;&lt;br&gt;하지만 그 원리는 MVC와 완전히 다릅니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 트랜잭션 매니저 설정 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트가 &lt;code&gt;ConnectionFactory&lt;/code&gt;를 보고 알아서 빈을 등록해 주지만, 명시적으로 알고 있어야 합니다.&lt;br&gt;JDBC의 &lt;code&gt;PlatformTransactionManager&lt;/code&gt;가 아니라, &lt;b&gt;&lt;code&gt;R2dbcTransactionManager&lt;/code&gt;&lt;/b&gt;가 필요합니다.&lt;/p&gt;&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableTransactionManagement // 트랜잭션 관리 활성화
public class R2dbcConfig {

    @Bean
    public ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) {
        return new R2dbcTransactionManager(connectionFactory);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 선언적 트랜잭션 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사용법은 MVC와 &lt;b&gt;100% 똑같습니다.&lt;/b&gt;&lt;/p&gt;&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class MoneyTransferService {

    private final AccountRepository accountRepository;

    @Transactional // ★ 예외 발생 시 알아서 Rollback 됨!
    public Mono&amp;lt;Void&amp;gt; transfer(Long fromId, Long toId, Long amount) {
        return accountRepository.findById(fromId)
                .flatMap(from -&amp;gt; {
                    from.withdraw(amount);
                    return accountRepository.save(from);
                })
                .flatMap(savedFrom -&amp;gt; accountRepository.findById(toId))
                .flatMap(to -&amp;gt; {
                    to.deposit(amount);
                    return accountRepository.save(to);
                })
                .then(); // Mono&amp;lt;Void&amp;gt; 리턴
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작 원리 (Reactor Context):&lt;/b&gt;&lt;br&gt;트랜잭션 정보가 &lt;b&gt;쓰레드&lt;/b&gt;가 아니라, &lt;b&gt;데이터 흐름(Pipeline)의 컨텍스트(Context)&lt;/b&gt;를 타고 흘러갑니다. 그래서 중간에 쓰레드가 바뀌어도 트랜잭션은 유지됩니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. JPA가 그리울 땐:&amp;nbsp;&amp;nbsp;(DatabaseClient)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ReactiveCrudRepository&lt;/code&gt;는 너무 단순합니다. &lt;code&gt;JOIN&lt;/code&gt;도 어렵고 동적 쿼리도 안 되죠.&lt;br&gt;이때 JPA의 &lt;code&gt;EntityManager&lt;/code&gt;나 &lt;code&gt;MyBatis&lt;/code&gt;처럼 쓸 수 있는 도구가 바로 &lt;b&gt;&lt;code&gt;R2dbcEntityTemplate&lt;/code&gt;&lt;/b&gt;입니다.&lt;br&gt;(스프링 부트 최신 버전에서는 &lt;b&gt;&lt;code&gt;DatabaseClient&lt;/code&gt;&lt;/b&gt;를 더 권장합니다.)&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 복잡한 조건 검색 (Fluent API)&lt;/h4&gt;&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class UserCustomRepository {

    private final R2dbcEntityTemplate template;

    public Flux&amp;lt;User&amp;gt; findUsersByAgeAndName(int age, String name) {
        // SQL 몰라도 메서드 체이닝으로 쿼리 생성!
        return template.select(User.class)
                .from(&quot;users&quot;)
                .matching(Query.query(Criteria.where(&quot;age&quot;).is(age)
                        .and(&quot;name&quot;).is(name)))
                .all();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 직접 SQL 작성 (Native Query)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 조인이나 통계 쿼리는 그냥 SQL로 짜는 게 편합니다.&lt;/p&gt;&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class NativeQueryRepository {

    private final DatabaseClient databaseClient; // 더 로우 레벨 API

    public Flux&amp;lt;UserStatDto&amp;gt; getUserStats() {
        return databaseClient.sql(&quot;SELECT count(*) as count, role FROM users GROUP BY role&quot;)
                .map((row, metadata) -&amp;gt; new UserStatDto(
                        row.get(&quot;role&quot;, String.class),
                        row.get(&quot;count&quot;, Long.class)
                ))
                .all();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 프로그래밍 방식 트랜잭션 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Transactional&lt;/code&gt;을 못 붙이는 상황이거나, 로직 중간에 세밀하게 트랜잭션을 제어하고 싶다면 &lt;b&gt;&lt;code&gt;TransactionalOperator&lt;/code&gt;&lt;/b&gt;를 씁니다.&lt;/p&gt;&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ManualTransactionService {

    private final TransactionalOperator rxtx; // 스프링이 주입해줌
    private final UserRepository userRepository;

    public Mono&amp;lt;User&amp;gt; saveWithRollback(User user) {
        return userRepository.save(user)
                .flatMap(saved -&amp;gt; {
                    if (saved.getName().equals(&quot;BAD_USER&quot;)) {
                        return Mono.error(new RuntimeException(&quot;롤백시켜!&quot;));
                    }
                    return Mono.just(saved);
                })
                // ★ 이 체인 전체를 트랜잭션으로 감싸라!
                .as(rxtx::transactional); 
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.as(rxtx::transactional)&lt;/code&gt; 한 줄이면 끝입니다. 정말 우아하지 않나요?&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;WebFlux에서도 &lt;b&gt;&lt;code&gt;@Transactional&lt;/code&gt;&lt;/b&gt;은 완벽하게 동작한다. (쓰레드 로컬 대신 &lt;b&gt;Reactor Context&lt;/b&gt; 사용)&lt;/li&gt; 
 &lt;li&gt;복잡한 쿼리는 &lt;b&gt;&lt;code&gt;R2dbcEntityTemplate&lt;/code&gt;&lt;/b&gt;이나 &lt;b&gt;&lt;code&gt;DatabaseClient&lt;/code&gt;&lt;/b&gt;를 쓰면 된다.&lt;/li&gt; 
 &lt;li&gt;세밀한 제어는 &lt;b&gt;&lt;code&gt;TransactionalOperator&lt;/code&gt;&lt;/b&gt;를 활용하자.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 WebFlux로 API부터 DB 트랜잭션까지, 완전한 &lt;b&gt;Non-Blocking 시스템&lt;/b&gt;을 구축할 수 있게 되었습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 비동기 코드는 테스트하기가 정말 까다롭습니다.&lt;br&gt;&quot;데이터가 올 때까지 기다렸다가 검증해야 하나? &lt;code&gt;Thread.sleep&lt;/code&gt;을 써야 하나?&quot;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;비동기 코드는 어떻게 테스트해요?&quot; &lt;code&gt;block()&lt;/code&gt; 없이 리액티브 스트림을 완벽하게 검증하는 StepVerifier&lt;/b&gt;에 대해 알아보겠습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1273</guid>
      <comments>https://hianna.tistory.com/1273#entry1273comment</comments>
      <pubDate>Fri, 3 Apr 2026 08:23:42 +0900</pubDate>
    </item>
    <item>
      <title>[R2DBC] &amp;quot;WebFlux에서 JPA 쓰면 망합니다!&amp;quot; R2DBC와 Reactive DB 연결</title>
      <link>https://hianna.tistory.com/1272</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux 서버를 잘 만들어놓고 &lt;b&gt;&lt;code&gt;spring-boot-starter-data-jpa&lt;/code&gt;&lt;/b&gt; 의존성을 추가하는 순간, 그 서버의 성능은 나락으로 떨어집니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐고요? WebFlux는 적은 수의 쓰레드(이벤트 루프)로 돌아가는데, &lt;b&gt;JPA가 쿼리를 날리는 동안 그 소중한 쓰레드를 붙잡고 놔주지 않기(Blocking) 때문&lt;/b&gt;입니다.&lt;br&gt;이벤트 루프가 멈추면 서버 전체가 멈춥니다.&lt;br&gt;그래서 우리는 &lt;b&gt;R2DBC&lt;/b&gt;라는 새로운 친구를 사귀어야 합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. R2DBC가 뭔가요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Reactive Relational Database Connectivity&lt;/b&gt;의 약자입니다.&lt;br&gt;MySQL, PostgreSQL, H2 같은 관계형 DB를 &lt;b&gt;논블로킹(Non-Blocking)&lt;/b&gt; 방식으로 사용할 수 있게 해주는 표준 API입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;JDBC:&lt;/b&gt; 결과가 나올 때까지 기다린다. (&lt;code&gt;List&amp;lt;User&amp;gt;&lt;/code&gt;)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;R2DBC:&lt;/b&gt; 결과를 나중에 주겠다고 약속한다. (&lt;code&gt;Flux&amp;lt;User&amp;gt;&lt;/code&gt;)&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 의존성 추가 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JPA는 잊으세요. 이제 R2DBC 드라이버가 필요합니다.&lt;/p&gt;&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;dependencies {
    // 1. 스프링 데이터 R2DBC
    implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'

    // 2. R2DBC용 DB 드라이버 (MySQL 예시)
    // implementation 'dev.miku:r2dbc-mysql' // (구버전)
    implementation 'com.mysql:mysql-connector-j' // (최신: JDBC 드라이버가 R2DBC도 지원하거나 별도 r2dbc-mysql 사용)
    runtimeOnly 'io.asyncer:r2dbc-mysql:1.0.2' // 많이 쓰는 구현체

    // H2를 쓴다면
    // runtimeOnly 'io.r2dbc:r2dbc-h2'
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 설정 파일 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;jdbc:&lt;/code&gt;가 아니라 &lt;b&gt;&lt;code&gt;r2dbc:&lt;/code&gt;&lt;/b&gt;로 시작합니다.&lt;/p&gt;&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
  r2dbc:
    url: r2dbc:mysql://localhost:3306/mydb
    username: root
    password: password
    pool:
      initial-size: 10
      max-size: 20
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 리포지토리 만들기 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;JpaRepository&lt;/code&gt;와 거의 비슷하지만, 리턴 타입이 다릅니다.&lt;/p&gt;&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// JpaRepository 아님!
public interface UserRepository extends ReactiveCrudRepository&amp;lt;User, Long&amp;gt; {

    // 1. 이름으로 찾기 (0~N명)
    Flux&amp;lt;User&amp;gt; findByName(String name);

    // 2. 이메일로 찾기 (0~1명)
    Mono&amp;lt;User&amp;gt; findByEmail(String email);

    // 3. 쿼리 직접 짜기 (@Query)
    @Query(&quot;SELECT * FROM users WHERE age &amp;gt; :age&quot;)
    Flux&amp;lt;User&amp;gt; findByAgeGreaterThan(int age);
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;save()&lt;/code&gt;:&lt;/b&gt; &lt;code&gt;Mono&amp;lt;User&amp;gt;&lt;/code&gt; 리턴 (저장된 객체)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;delete()&lt;/code&gt;:&lt;/b&gt; &lt;code&gt;Mono&amp;lt;Void&amp;gt;&lt;/code&gt; 리턴 (결과 없음)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;findById()&lt;/code&gt;:&lt;/b&gt; &lt;code&gt;Mono&amp;lt;User&amp;gt;&lt;/code&gt; 리턴&lt;/li&gt; 
&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 엔티티(Entity) 정의하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JPA의 &lt;code&gt;@Entity&lt;/code&gt;와 비슷하지만, 여기선 &lt;b&gt;&lt;code&gt;@Table&lt;/code&gt;&lt;/b&gt;과 &lt;b&gt;&lt;code&gt;@Id&lt;/code&gt;&lt;/b&gt;만 씁니다.&lt;br&gt;(R2DBC는 JPA처럼 복잡한 ORM이 아닙니다. 심플한 매퍼에 가깝습니다.)&lt;/p&gt;&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Table(&quot;users&quot;) // DB 테이블 이름
@Getter
@NoArgsConstructor
public class User {

    @Id // PK
    private Long id;

    private String name;

    private String email;

    // 생성자 등...
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 서비스에서 사용하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;block()&lt;/code&gt; 없이 물 흐르듯 코드를 짜면 됩니다.&lt;/p&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    // 저장하고 -&amp;gt; 로그 남기고 -&amp;gt; 리턴
    public Mono&amp;lt;User&amp;gt; register(User user) {
        return userRepository.save(user)
                .doOnSuccess(u -&amp;gt; log.info(&quot;유저 저장 성공: {}&quot;, u.getId()));
    }

    // 조회해서 -&amp;gt; 이름 대문자로 바꾸기
    public Flux&amp;lt;UserDto&amp;gt; getAllUsers() {
        return userRepository.findAll()
                .map(user -&amp;gt; new UserDto(user.getName().toUpperCase()));
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 주의사항: JPA랑 뭐가 다른가요? (치명적 단점)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;R2DBC를 쓸 때 가장 당황하는 부분입니다. &lt;b&gt;JPA의 편리한 기능들이 없습니다.&lt;/b&gt;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;지연 로딩(Lazy Loading) 없음:&lt;/b&gt; &lt;code&gt;@OneToMany&lt;/code&gt; 같은 연관관계 매핑이 안 됩니다. 조인이 필요하면 쿼리를 직접 짜거나, 코드에서 두 번 조회(&lt;code&gt;flatMap&lt;/code&gt;)해야 합니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;더티 체킹(Dirty Checking) 없음:&lt;/b&gt; 객체 값을 바꾼다고 DB가 업데이트되지 않습니다. 반드시 &lt;b&gt;&lt;code&gt;repository.save(user)&lt;/code&gt;&lt;/b&gt;를 명시적으로 호출해야 합니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;1차 캐시 없음:&lt;/b&gt; 조회할 때마다 DB에 쿼리가 날아갑니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, R2DBC는 &quot;고성능&quot;을 위해 &quot;편의성&quot;을 포기한 기술입니다.&lt;/b&gt;&lt;/p&gt; 
&lt;/blockquote&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;WebFlux에서는 &lt;b&gt;JPA(Blocking)&lt;/b&gt; 대신 &lt;b&gt;R2DBC(Non-Blocking)&lt;/b&gt;를 써야 한다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;ReactiveCrudRepository&lt;/code&gt;&lt;/b&gt;를 쓰면 &lt;code&gt;Mono&lt;/code&gt;, &lt;code&gt;Flux&lt;/code&gt; 타입으로 DB를 조회할 수 있다.&lt;/li&gt; 
 &lt;li&gt;연관관계 매핑이나 더티 체킹 같은 &lt;b&gt;ORM 기능이 부족&lt;/b&gt;하므로, 쿼리 중심적인 사고가 필요하다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 DB까지 비동기로 뚫었습니다.&lt;br&gt;그런데 말이죠, R2DBC에서는 &lt;b&gt;트랜잭션(&lt;code&gt;@Transactional&lt;/code&gt;)&lt;/b&gt;이 어떻게 동작할까요? 쓰레드가 바뀌면 트랜잭션 컨텍스트도 끊기지 않을까요?&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;R2DBC 트랜잭션은 어떻게 걸죠?&quot; Reactive TransactionManager와 R2DBC Entity Template&lt;/b&gt;에 대해 알아보겠습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1272</guid>
      <comments>https://hianna.tistory.com/1272#entry1272comment</comments>
      <pubDate>Fri, 3 Apr 2026 00:21:44 +0900</pubDate>
    </item>
    <item>
      <title>[WebClient] &amp;quot;RestTemplate은 이제 Deprecated!&amp;quot; 비동기 HTTP 요청의 정석, WebClient 사용법</title>
      <link>https://hianna.tistory.com/1271</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오랫동안 사랑받아온 &lt;code&gt;RestTemplate&lt;/code&gt;이 유지보수 모드(Maintenance Mode)로 들어갔다는 사실, 알고 계셨나요?&lt;br&gt;스프링 팀은 대놓고 &lt;b&gt;&quot;앞으로는 WebClient를 쓰세요&quot;&lt;/b&gt;라고 권장합니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 단순합니다. &lt;code&gt;RestTemplate&lt;/code&gt;은 &lt;b&gt;동기(Blocking)&lt;/b&gt; 방식이라서, 외부 API가 3초 걸리면 내 서버도 3초 동안 멈춰 있어야 하기 때문입니다.&lt;br&gt;반면 &lt;b&gt;WebClient&lt;/b&gt;는 요청만 보내고 딴 일을 하러 갈 수 있습니다. &lt;b&gt;(Non-Blocking)&lt;/b&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. WebClient 만들기 (Builder 패턴)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;WebClient&lt;/code&gt;는 불변(Immutable) 객체라서 쓰레드 안전(Thread-safe)합니다. 싱글톤 빈으로 등록해서 재사용하는 것이 좋습니다.&lt;/p&gt;&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class WebClientConfig {

    @Bean
    public WebClient webClient() {
        return WebClient.builder()
                .baseUrl(&quot;http://api.external-server.com&quot;) // 기본 주소
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. GET 요청 보내기 ( vs )&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 조회 요청입니다.&lt;br&gt;응답을 &lt;b&gt;&lt;code&gt;Mono&amp;lt;String&amp;gt;&lt;/code&gt;&lt;/b&gt;이나 &lt;b&gt;&lt;code&gt;Flux&amp;lt;UserDto&amp;gt;&lt;/code&gt;&lt;/b&gt;로 받을 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ExternalApiService {

    private final WebClient webClient;

    public Mono&amp;lt;UserDto&amp;gt; getUser(String id) {
        return webClient.get()
                .uri(&quot;/users/{id}&quot;, id)
                .retrieve() // 응답 본문(Body)을 바로 가져옴
                .bodyToMono(UserDto.class); // JSON -&amp;gt; Java 객체 변환
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고:&lt;/b&gt; &lt;code&gt;exchange()&lt;/code&gt; 메서드는 메모리 누수 위험 때문에 Deprecated 되었습니다. &lt;code&gt;retrieve()&lt;/code&gt;를 쓰세요.&lt;/p&gt; 
&lt;/blockquote&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. POST 요청 보내기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 보낼 때도 똑같습니다. &lt;code&gt;bodyValue()&lt;/code&gt;만 추가하면 됩니다.&lt;/p&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public Mono&amp;lt;String&amp;gt; createUser(UserDto userDto) {
    return webClient.post()
            .uri(&quot;/users&quot;)
            .bodyValue(userDto) // 요청 본문(Body) 설정
            .retrieve()
            .bodyToMono(String.class); // &quot;생성 성공&quot; 같은 응답 받기
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. WebClient의 필살기: 병렬 호출 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이게 진짜 핵심입니다.&lt;br&gt;&quot;유저 정보(User)&quot;와 &quot;주문 내역(Order)&quot;을 각각 다른 서버에서 가져와야 한다고 칩시다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;유저 서버: 2초 소요&lt;/li&gt;&lt;li&gt;주문 서버: 3초 소요&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ RestTemplate (순차 실행: 2 + 3 = 5초)&lt;/h4&gt;&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;User user = restTemplate.getForObject(...); // 2초 대기
Order order = restTemplate.getForObject(...); // 3초 대기
// 총 5초 걸림
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ WebClient (병렬 실행: max(2, 3) = 3초)&lt;/h4&gt;&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Mono&amp;lt;User&amp;gt; userMono = webClient.get().uri(&quot;/users/1&quot;).retrieve().bodyToMono(User.class);
Mono&amp;lt;Order&amp;gt; orderMono = webClient.get().uri(&quot;/orders/1&quot;).retrieve().bodyToMono(Order.class);

// 두 요청을 동시에 쏘고(zip), 둘 다 올 때까지 기다림
return Mono.zip(userMono, orderMono)
        .map(tuple -&amp;gt; {
            User user = tuple.getT1();
            Order order = tuple.getT2();
            return new UserOrderDto(user, order);
        });
// 총 3초 걸림 (가장 느린 놈 기준)
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;API 개수가 많아질수록 성능 차이는 더 벌어집니다. &lt;b&gt;MSA 환경에서는 선택이 아닌 필수&lt;/b&gt;입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 에러 처리 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;외부 서버가 400이나 500 에러를 뱉으면 어떻게 할까요?&lt;br&gt;&lt;code&gt;retrieve()&lt;/code&gt; 뒤에 &lt;b&gt;&lt;code&gt;onStatus&lt;/code&gt;&lt;/b&gt;를 붙여서 예외 처리를 할 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;webClient.get()
        .uri(&quot;/users/1&quot;)
        .retrieve()
        .onStatus(
            status -&amp;gt; status.is4xxClientError(), // 400번대 에러면
            response -&amp;gt; Mono.error(new RuntimeException(&quot;잘못된 요청입니다.&quot;))
        )
        .onStatus(
            status -&amp;gt; status.is5xxServerError(), // 500번대 에러면
            response -&amp;gt; Mono.error(new RuntimeException(&quot;외부 서버가 죽었습니다.&quot;))
        )
        .bodyToMono(UserDto.class);
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;WebClient&lt;/b&gt;는 Non-Blocking 방식이라, 외부 서버가 느려도 내 서버는 멈추지 않는다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;Mono.zip&lt;/code&gt;&lt;/b&gt;을 사용하면 여러 API를 &lt;b&gt;병렬(Parallel)&lt;/b&gt;로 호출해서 응답 시간을 획기적으로 줄일 수 있다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;RestTemplate&lt;/b&gt;은 이제 잊고, WebClient로 갈아타자. (심지어 동기 방식인 MVC에서도 WebClient를 쓸 수 있습니다!)&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 외부 API 연동까지 완벽하게 비동기로 처리할 수 있게 되었습니다.&lt;br&gt;하지만 아직 가장 큰 관문이 남았습니다. 바로 &lt;b&gt;데이터베이스(DB)&lt;/b&gt;입니다.&lt;br&gt;WebClient로 기껏 비동기 처리를 해놓고, &lt;b&gt;JPA(Blocking)&lt;/b&gt;를 쓰면 말짱 도루묵이거든요.&lt;br&gt;다음 포스팅에서는 &lt;b&gt;&quot;WebFlux에서 JPA 쓰면 망합니다!&quot; JDBC의 대안인 R2DBC와 Reactive DB 연결&lt;/b&gt;에 대해 알아보겠습니다.&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1271</guid>
      <comments>https://hianna.tistory.com/1271#entry1271comment</comments>
      <pubDate>Thu, 2 Apr 2026 08:21:07 +0900</pubDate>
    </item>
    <item>
      <title>[WebFlux] &amp;quot;컨트롤러가 없다고?&amp;quot; 함수형 엔드포인트(Router &amp;amp; Handler) 작성법</title>
      <link>https://hianna.tistory.com/1270</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간에는 &lt;code&gt;@RestController&lt;/code&gt;를 이용한 익숙한 방식을 배웠습니다. 하지만 WebFlux의 창시자들은 조금 더 &lt;b&gt;Java 8+ 람다(Lambda)&lt;/b&gt;스러운 방식을 제안합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;b&gt;함수형 엔드포인트(Functional Endpoints)&lt;/b&gt;입니다.&lt;br /&gt;이 방식은 &lt;b&gt;&quot;요청을 어디로 보낼지(Routing)&quot;&lt;/b&gt;와 &lt;b&gt;&quot;어떻게 처리할지(Handling)&quot;&lt;/b&gt;를 완벽하게 분리합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 두 가지 핵심: Router &amp;amp; Handler&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 지하철 노선도와 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;RouterFunction (지도):&lt;/b&gt; &quot;이 URL로 오면 저기로 가세요.&quot; (길 안내)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HandlerFunction (목적지):&lt;/b&gt; &quot;오셨군요. 제가 처리해 드릴게요.&quot; (실제 업무)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC에서는 컨트롤러 안에 매핑(&lt;code&gt;@GetMapping&lt;/code&gt;)과 로직이 섞여 있었지만, 여기서는 완전히 분리됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 핸들러(Handler) 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 실제 비즈니스 로직을 수행할 일꾼, 핸들러를 만듭니다.&lt;br /&gt;MVC의 &lt;code&gt;Controller&lt;/code&gt; 메서드와 비슷하지만, 리턴 타입이 무조건 &lt;b&gt;&lt;code&gt;Mono&amp;lt;ServerResponse&amp;gt;&lt;/code&gt;&lt;/b&gt;여야 합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Component
public class UserHandler {

    private final UserRepository userRepository;

    public UserHandler(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 1. 단건 조회
    public Mono&amp;lt;ServerResponse&amp;gt; getUser(ServerRequest request) {
        // @PathVariable 대신 request.pathVariable() 사용
        Long id = Long.parseLong(request.pathVariable(&quot;id&quot;));

        return userRepository.findById(id)
                .flatMap(user -&amp;gt; ServerResponse.ok().bodyValue(user)) // 찾으면 200 OK + Body
                .switchIfEmpty(ServerResponse.notFound().build());    // 없으면 404 Not Found
    }

    // 2. 전체 조회
    public Mono&amp;lt;ServerResponse&amp;gt; getAllUsers(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(userRepository.findAll(), User.class); // Flux&amp;lt;User&amp;gt;를 바디에 넣음
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력은 &lt;code&gt;ServerRequest&lt;/code&gt;, 출력은 &lt;code&gt;ServerResponse&lt;/code&gt;로 고정됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ok()&lt;/code&gt;, &lt;code&gt;notFound()&lt;/code&gt;, &lt;code&gt;badRequest()&lt;/code&gt; 등 빌더 패턴으로 응답을 만듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 라우터(Router) 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 핸들러를 URL과 연결해 줄 지도를 그립니다. 이건 &lt;b&gt;설정 파일(&lt;code&gt;@Configuration&lt;/code&gt;)&lt;/b&gt;에 빈(Bean)으로 등록합니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Configuration
public class RouterConfig {

    @Bean
    public RouterFunction&amp;lt;ServerResponse&amp;gt; route(UserHandler userHandler) {
        return RouterFunctions
                // &quot;/users/{id}&quot;로 GET 요청이 오면 -&amp;gt; userHandler.getUser 실행
                .route(GET(&quot;/users/{id}&quot;), userHandler::getUser)

                // &quot;/users&quot;로 GET 요청이 오면 -&amp;gt; userHandler.getAllUsers 실행
                .andRoute(GET(&quot;/users&quot;), userHandler::getAllUsers);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 API 엔드포인트가 &lt;b&gt;한 눈에&lt;/b&gt; 보입니다. (MVC에서는 컨트롤러 파일을 다 뒤져야 했죠.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;andRoute()&lt;/code&gt;로 계속 체이닝해서 연결할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 어노테이션 방식 vs 함수형 방식 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그럼 뭘 써야 하나요?&quot; 정답은 없습니다. 팀의 취향 차이입니다.&lt;/p&gt;
&lt;table style=&quot;height: 108px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;어노테이션 방식(@RestController)&lt;/td&gt;
&lt;td&gt;함수형 방식 (RouterFunction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;익숙함&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;MVC와 똑같아서 매우 익숙함&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;처음엔 낯설고 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;구조&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;매핑과 로직이 컨트롤러에 섞임&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;라우팅과 로직이 분리됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;가독성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;API가 많아지면 파악하기 힘듦&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;모든 URL이 한곳에 모여있어 파악 쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;리플렉션 사용 (미세한 오버헤드)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;리플렉션 없음 (빠른 구동 속도)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추천:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 팀원들이 MVC에 익숙하다면 -&amp;gt; &lt;b&gt;어노테이션 방식&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;새로운 마이크로서비스를 작게 만들거나, 명확한 라우팅이 필요하다면 -&amp;gt; &lt;b&gt;함수형 방식&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 중첩 라우팅 (Nested Route)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형 방식의 가장 큰 장점은 &lt;b&gt;&quot;경로 그룹화&quot;&lt;/b&gt;가 쉽다는 것입니다.&lt;br /&gt;&lt;code&gt;/users&lt;/code&gt;로 시작하는 모든 요청을 묶어볼까요?&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Bean
public RouterFunction&amp;lt;ServerResponse&amp;gt; nestedRoute(UserHandler handler) {
    return RouterFunctions.route()
            .path(&quot;/users&quot;, builder -&amp;gt; builder
                .GET(&quot;/{id}&quot;, handler::getUser)
                .GET(&quot;&quot;, handler::getAllUsers)
                .POST(&quot;&quot;, handler::createUser)
            )
            .build();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 구조만 봐도 URL 구조가 보입니다. 정말 깔끔하죠?&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;함수형 엔드포인트&lt;/b&gt;는 &lt;code&gt;@Controller&lt;/code&gt; 없이 API를 만드는 모던한 방식이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Handler&lt;/b&gt;는 로직을 처리하고, &lt;b&gt;Router&lt;/b&gt;는 길을 안내한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RouterFunction&lt;/b&gt;을 쓰면 모든 API 목록을 한눈에 관리할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분은 WebFlux의 두 가지 얼굴(어노테이션, 함수형)을 모두 다룰 수 있게 되었습니다.&lt;br /&gt;다음 단계는 &lt;b&gt;&quot;남의 서버&quot;&lt;/b&gt;를 호출하는 것입니다. &lt;code&gt;RestTemplate&lt;/code&gt;은 이제 잊으세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;RestTemplate은 이제 Deprecated!&quot; 비동기 HTTP 요청의 정석, WebClient 사용법&lt;/b&gt;에 대해 알아보겠습니다. (이게 진짜 물건입니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1270</guid>
      <comments>https://hianna.tistory.com/1270#entry1270comment</comments>
      <pubDate>Thu, 2 Apr 2026 00:16:11 +0900</pubDate>
    </item>
    <item>
      <title>[Spring WebFlux] 어노테이션 기반 컨트롤러: MVC 개발자라면 10분 만에 적응 가능!</title>
      <link>https://hianna.tistory.com/1269</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux를 시작할 때 가장 큰 장벽은 &quot;새로운 문법을 배워야 한다&quot;는 두려움입니다.&lt;br /&gt;하지만 스프링 부트는 &lt;b&gt;기존 MVC 스타일의 코드를 99% 재사용&lt;/b&gt;할 수 있게 해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 보여드릴 코드를 보면 &quot;어? 이게 WebFlux라고? MVC랑 똑같은데?&quot;라고 하실 겁니다.&lt;br /&gt;하지만 그 속은 &lt;b&gt;완전히 다른 비동기 엔진(Netty)&lt;/b&gt;으로 돌아가고 있죠.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 의존성 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;spring-boot-starter-web&lt;/code&gt;이 아니라 &lt;b&gt;&lt;code&gt;spring-boot-starter-webflux&lt;/code&gt;&lt;/b&gt;가 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    // implementation 'org.springframework.boot:spring-boot-starter-web' // 이건 빼세요!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 톰캣(Tomcat) 대신 &lt;b&gt;네티(Netty)&lt;/b&gt; 서버가 뜹니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 단건 조회: &lt;code&gt;User&lt;/code&gt; 대신 &lt;code&gt;Mono&amp;lt;User&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 MVC 컨트롤러와 비교해 보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ Spring MVC (Blocking)&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@RestController
public class MvcController {
    @GetMapping(&quot;/mvc/users/{id}&quot;)
    public User getUser(@PathVariable Long id) {
        // DB에서 다 가져올 때까지 쓰레드 차단 (3초 대기)
        return userService.findById(id); 
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ Spring WebFlux (Non-Blocking)&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@RestController // 똑같음!
public class WebFluxController {

    @GetMapping(&quot;/webflux/users/{id}&quot;)
    public Mono&amp;lt;User&amp;gt; getUser(@PathVariable Long id) {
        // DB에 요청만 보내고 즉시 리턴 (0.01초)
        // 스프링이 나중에 이 Mono를 구독(subscribe)해서 결과를 클라이언트에 줌
        return userService.findById(id);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;차이점:&lt;/b&gt; 리턴 타입이 &lt;code&gt;User&lt;/code&gt;가 아니라 &lt;b&gt;&lt;code&gt;Mono&amp;lt;User&amp;gt;&lt;/code&gt;&lt;/b&gt;입니다.&lt;br /&gt;이게 답니다. 정말 쉽죠?&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 목록 조회: &lt;code&gt;List&amp;lt;User&amp;gt;&lt;/code&gt; 대신 &lt;code&gt;Flux&amp;lt;User&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 건을 조회할 때도 마찬가지입니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@GetMapping(&quot;/webflux/users&quot;)
public Flux&amp;lt;User&amp;gt; getAllUsers() {
    // List&amp;lt;User&amp;gt;를 기다리지 않고, 데이터가 흐르는 파이프(Flux)만 리턴
    return userService.findAll();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브라우저의 반응:&lt;/b&gt;&lt;br /&gt;일반적인 JSON 응답(&lt;code&gt;application/json&lt;/code&gt;)일 경우, &lt;code&gt;Flux&lt;/code&gt;의 모든 데이터가 모일 때까지 기다렸다가 &lt;code&gt;[user1, user2, ...]&lt;/code&gt; 형태의 JSON 배열로 한 방에 내려줍니다. (사실상 사용자는 차이를 못 느낍니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux의 진가를 보려면 &lt;b&gt;&quot;스트리밍&quot;&lt;/b&gt;을 해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. WebFlux의 필살기: 스트리밍 (SSE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 10,000개인데, 다 모일 때까지 기다리면 사용자는 답답합니다.&lt;br /&gt;&lt;b&gt;&quot;1개 찾을 때마다 바로바로 화면에 띄워줄 수 없을까?&quot;&lt;/b&gt;&lt;br /&gt;이때 사용하는 것이 &lt;b&gt;SSE (Server-Sent Events)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// MediaType을 꼭 지정해야 함! (text/event-stream)
@GetMapping(value = &quot;/stream/users&quot;, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux&amp;lt;User&amp;gt; streamUsers() {
    // 1초마다 유저 데이터가 하나씩 툭, 툭 전송됨
    return userService.findAll()
            .delayElements(Duration.ofSeconds(1)); // 시각적 효과를 위한 지연
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt;&lt;br /&gt;브라우저나 &lt;code&gt;curl&lt;/code&gt;로 요청해 보면, 로딩 바가 도는 게 아니라 &lt;b&gt;1초마다 데이터가 한 줄씩 찍히는 것&lt;/b&gt;을 볼 수 있습니다. 이게 바로 &lt;b&gt;Non-Blocking Streaming&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 요청 본문 받기 (&lt;code&gt;@RequestBody&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 받을 때도 &lt;code&gt;Mono&lt;/code&gt;나 &lt;code&gt;Flux&lt;/code&gt;로 받습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@PostMapping(&quot;/users&quot;)
public Mono&amp;lt;User&amp;gt; createUser(@RequestBody Mono&amp;lt;UserDto&amp;gt; userDtoMono) {
    // 요청 본문(Body)도 비동기로 들어옵니다.
    return userDtoMono.flatMap(dto -&amp;gt; userService.save(dto));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 파일 업로드처럼 큰 데이터가 들어올 때, 메모리에 다 올리지 않고 스트림으로 처리할 수 있어 효율적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 동작 원리: 누가 구독(Subscribe)하나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간에 &quot;구독(&lt;code&gt;subscribe&lt;/code&gt;) 안 하면 아무 일도 안 일어난다&quot;고 했죠?&lt;br /&gt;그런데 컨트롤러 코드에는 &lt;code&gt;subscribe()&lt;/code&gt;가 없습니다. 누가 하는 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;b&gt;스프링 프레임워크(정확히는 Netty와 Reactor Netty Bridge)&lt;/b&gt;가 대신해 줍니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요청이 들어오면 핸들러(컨트롤러)를 찾습니다.&lt;/li&gt;
&lt;li&gt;컨트롤러가 &lt;code&gt;Mono&lt;/code&gt;나 &lt;code&gt;Flux&lt;/code&gt;를 던져줍니다. (아직 실행 안 됨)&lt;/li&gt;
&lt;li&gt;스프링이 그 리턴 값을 &lt;b&gt;구독(Subscribe)&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;데이터가 발생(&lt;code&gt;onNext&lt;/code&gt;)할 때마다 HTTP 응답 버퍼에 씁니다.&lt;/li&gt;
&lt;li&gt;완료(&lt;code&gt;onComplete&lt;/code&gt;)되면 연결을 끊습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 &lt;b&gt;파이프라인 조립(&lt;code&gt;map&lt;/code&gt;, &lt;code&gt;flatMap&lt;/code&gt;)&lt;/b&gt;만 잘해놓고 리턴하면 되는 것입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;WebFlux 컨트롤러는 MVC와 &lt;b&gt;어노테이션(&lt;code&gt;@RestController&lt;/code&gt;)이 똑같다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;리턴 타입만 &lt;b&gt;&lt;code&gt;Mono&lt;/code&gt; (단건)&lt;/b&gt;, &lt;b&gt;&lt;code&gt;Flux&lt;/code&gt; (다건)&lt;/b&gt;로 바꾸면 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SSE(&lt;code&gt;text/event-stream&lt;/code&gt;)&lt;/b&gt;를 사용하면 데이터를 실시간으로 흘려보낼 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC 개발자라면 10분도 안 걸려서 적응하셨을 겁니다.&lt;br /&gt;하지만 WebFlux에는 이것 말고도 &lt;b&gt;&quot;함수형 엔드포인트(Functional Endpoints)&quot;&lt;/b&gt;라는 또 다른 스타일이 있습니다. (마치 람다식처럼 라우팅을 정의하는 방식이죠.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;컨트롤러 클래스가 없다고?&quot; 함수형 엔드포인트(Router &amp;amp; Handler) 작성법&lt;/b&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1269</guid>
      <comments>https://hianna.tistory.com/1269#entry1269comment</comments>
      <pubDate>Wed, 1 Apr 2026 09:15:47 +0900</pubDate>
    </item>
    <item>
      <title>[Reactor] &amp;quot;map과 flatMap, 도대체 뭐가 다른가요?&amp;quot; 마법의 연산자 정복하기</title>
      <link>https://hianna.tistory.com/1268</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 변환할 때 우리는 습관적으로 &lt;code&gt;.map()&lt;/code&gt;을 씁니다.&lt;br /&gt;하지만 WebFlux 세계에서 DB를 조회하거나 외부 API를 호출할 때 &lt;code&gt;.map()&lt;/code&gt;을 쓰면, 결과값으로 &lt;code&gt;Mono&amp;lt;Mono&amp;lt;String&amp;gt;&amp;gt;&lt;/code&gt; 같은 &lt;b&gt;러시아 인형(중첩 구조)&lt;/b&gt;이 튀어나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 껍질을 벗겨내고 알맹이만 쏙 빼내는 기술, &lt;b&gt;&lt;code&gt;flatMap&lt;/code&gt;&lt;/b&gt;이 필요한 순간입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 단순 변환의 제왕: &lt;code&gt;map&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;map&lt;/code&gt;은 &lt;b&gt;동기적(Synchronous)이고, 1:1로 변환&lt;/b&gt;할 때 사용합니다.&lt;br /&gt;입력 데이터 &lt;code&gt;T&lt;/code&gt;를 받아서 &lt;code&gt;U&lt;/code&gt;로 바꿉니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비유:&lt;/b&gt; 사과를 넣으면 -&amp;gt; 껍질 깎은 사과가 나옴.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징:&lt;/b&gt; 단순히 값을 가공하거나, 객체를 다른 객체로 매핑할 때 씁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 1. 문자열을 대문자로 변환
Flux.just(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)
    .map(String::toUpperCase) // &quot;a&quot; -&amp;gt; &quot;A&quot;
    .subscribe(System.out::println);

// 2. User 엔티티를 UserDto로 변환
userRepository.findById(1L) // Mono&amp;lt;User&amp;gt;
    .map(user -&amp;gt; new UserDto(user.getName())); // Mono&amp;lt;UserDto&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 Java Stream과 똑같습니다. 문제는 다음입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 비동기 연쇄 호출의 핵심: &lt;code&gt;flatMap&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;flatMap&lt;/code&gt;은 &lt;b&gt;비동기적(Asynchronous)이고, 1:N(또는 1:Publisher)으로 변환&lt;/b&gt;할 때 사용합니다.&lt;br /&gt;입력 데이터 &lt;code&gt;T&lt;/code&gt;를 받아서 &lt;b&gt;&lt;code&gt;Publisher&amp;lt;U&amp;gt;&lt;/code&gt;(Mono나 Flux)&lt;/b&gt;로 바꿉니다. 그리고 그 &lt;b&gt;Publisher를 구독해서 결과(&lt;code&gt;U&lt;/code&gt;)를 꺼내줍니다(Flatten).&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비유:&lt;/b&gt; 사과를 넣으면 -&amp;gt; &lt;b&gt;사과나무를 심고 기다렸다가&lt;/b&gt; -&amp;gt; 열린 사과들을 가져옴.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징:&lt;/b&gt; DB 조회 후에 또 DB를 조회하거나, API 호출 결과를 받아서 처리할 때 필수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 예시 (&lt;code&gt;map&lt;/code&gt; 사용)&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 유저 ID로 유저를 찾고, 그 유저의 주문 내역을 가져오고 싶다.
userRepository.findById(userId) // Mono&amp;lt;User&amp;gt; 리턴
    .map(user -&amp;gt; orderRepository.findAllByUser(user)) // Flux&amp;lt;Order&amp;gt; 리턴
    // 결과 타입: Mono&amp;lt;Flux&amp;lt;Order&amp;gt;&amp;gt; (???)
    // 껍질(Mono) 안에 껍질(Flux)이 또 들어감!
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ 올바른 예시 (&lt;code&gt;flatMap&lt;/code&gt; 사용)&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;userRepository.findById(userId) // Mono&amp;lt;User&amp;gt;
    .flatMap(user -&amp;gt; orderRepository.findAllByUser(user)) // Flux&amp;lt;Order&amp;gt;를 리턴하지만...
    // 결과 타입: Flux&amp;lt;Order&amp;gt; (!!!)
    // flatMap이 내부의 Flux를 구독해서 알맹이(Order)만 쫙 펴줌.
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 요약:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변환 결과가 일반 객체(&lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;Dto&lt;/code&gt;)다? -&amp;gt; &lt;b&gt;&lt;code&gt;map&lt;/code&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;변환 결과가 &lt;code&gt;Mono&lt;/code&gt;나 &lt;code&gt;Flux&lt;/code&gt;다? (DB 호출 등) -&amp;gt; &lt;b&gt;&lt;code&gt;flatMap&lt;/code&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 순서가 중요한가요? (&lt;code&gt;flatMap&lt;/code&gt; vs &lt;code&gt;concatMap&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;flatMap&lt;/code&gt;은 비동기적으로 동작하기 때문에 &lt;b&gt;순서를 보장하지 않습니다.&lt;/b&gt;&lt;br /&gt;1, 2, 3번 요청을 보냈는데 응답은 2, 1, 3 순서로 올 수 있습니다. (가장 빠른 놈이 먼저 나옴)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;b&gt;순서가 중요하다면&lt;/b&gt; (예: 게시글 저장 후 -&amp;gt; 댓글 저장), &lt;b&gt;&lt;code&gt;concatMap&lt;/code&gt;&lt;/b&gt;을 써야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;flatMap&lt;/code&gt;:&lt;/b&gt; 병렬 처리 (빠름, 순서 X)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;concatMap&lt;/code&gt;:&lt;/b&gt; 직렬 처리 (느림, 순서 O - 앞에게 끝나야 다음 거 실행)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. (보너스) 스케줄러: 쓰레드 갈아타기 (&lt;code&gt;subscribeOn&lt;/code&gt; vs &lt;code&gt;publishOn&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux는 기본적으로 메인 쓰레드(혹은 Netty 쓰레드) 하나로 돕니다.&lt;br /&gt;그런데 만약 &quot;이미지 리사이징&quot;처럼 CPU를 많이 쓰는 작업을 하면 서버 전체가 멈춥니다.&lt;br /&gt;이때 &lt;b&gt;&quot;이 작업은 다른 별도 쓰레드에서 해!&quot;&lt;/b&gt;라고 지정하는 게 스케줄러입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;subscribeOn&lt;/code&gt; (구독 시점 결정):&lt;/b&gt; 파이프라인의 &lt;b&gt;시작점(Source)&lt;/b&gt;이 실행될 쓰레드를 정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;데이터 가져오는 것부터 저쪽 쓰레드에서 해.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;publishOn&lt;/code&gt; (실행 시점 결정):&lt;/b&gt; 파이프라인 &lt;b&gt;중간&lt;/b&gt;에 쓰레드를 바꿉니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;여기까진 내가 했는데, 다음 &lt;code&gt;map&lt;/code&gt; 연산부터는 저쪽 쓰레드에서 해.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Flux.range(1, 10)
    .publishOn(Schedulers.boundedElastic()) // ★ 여기서부터는 별도 쓰레드풀 사용!
    .map(i -&amp;gt; {
        System.out.println(&quot;무거운 작업 수행 중...&quot;); // 메인 쓰레드 방해 안 함
        return i * 10;
    })
    .subscribe();
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 팁:&lt;/b&gt; 블로킹 작업(JDBC 등)을 어쩔 수 없이 써야 한다면 반드시 &lt;code&gt;publishOn(Schedulers.boundedElastic())&lt;/code&gt;으로 격리해야 합니다. 안 그러면 서버 멈춥니다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;map&lt;/code&gt;&lt;/b&gt;: 동기 변환 (&lt;code&gt;A -&amp;gt; B&lt;/code&gt;). 값만 바꿀 때 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;flatMap&lt;/code&gt;&lt;/b&gt;: 비동기 변환 (&lt;code&gt;A -&amp;gt; Mono&amp;lt;B&amp;gt;&lt;/code&gt;). &lt;b&gt;DB 호출, API 연동&lt;/b&gt; 등 결과를 기다려야 할 때 쓴다. (내부 Publisher를 벗겨줌)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;concatMap&lt;/code&gt;&lt;/b&gt;: 순서가 중요할 때 쓴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 데이터를 담고(&lt;code&gt;Mono/Flux&lt;/code&gt;), 가공하는 법(&lt;code&gt;Operator&lt;/code&gt;)까지 배웠습니다. 준비 운동은 끝났습니다.&lt;br /&gt;이제 진짜 서버를 띄워볼 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;MVC 개발자라면 10분 만에 적응 가능한, &quot;어노테이션 기반 WebFlux 컨트롤러 만들기&quot;&lt;/b&gt;로 실전 API를 작성해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1268</guid>
      <comments>https://hianna.tistory.com/1268#entry1268comment</comments>
      <pubDate>Wed, 1 Apr 2026 00:15:20 +0900</pubDate>
    </item>
    <item>
      <title>[Reactor] &amp;quot;List랑 뭐가 달라요?&amp;quot; Mono와 Flux, 0개 1개 그리고 N개</title>
      <link>https://hianna.tistory.com/1267</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC에서 &lt;code&gt;List&amp;lt;User&amp;gt;&lt;/code&gt;를 리턴하면 &quot;유저 목록이구나&quot;, &lt;code&gt;User&lt;/code&gt;를 리턴하면 &quot;단건 조회구나&quot;라고 바로 알 수 있죠.&lt;br /&gt;WebFlux에서도 마찬가지입니다. 리턴 타입만 봐도 &lt;b&gt;&quot;데이터가 몇 개 흐를지&quot;&lt;/b&gt; 예측할 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux의 기반 라이브러리인 &lt;b&gt;Project Reactor&lt;/b&gt;는 데이터를 담는 그릇을 딱 두 가지로 정의했습니다.&lt;br /&gt;바로 &lt;b&gt;Mono(모노)&lt;/b&gt;와 &lt;b&gt;Flux(플럭스)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Flux [0..N]: 끝없는 데이터의 강물&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flux&lt;/b&gt;는 &lt;b&gt;0개에서 N개(무한대 포함)&lt;/b&gt;의 데이터를 발행할 수 있는 Publisher입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비유:&lt;/b&gt; 컨베이어 벨트. 물건이 계속해서 지나갑니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유사한 개념:&lt;/b&gt; &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Stream&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Iterable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도:&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;findAll()&lt;/code&gt;: DB에서 여러 건 조회&lt;/li&gt;
&lt;li&gt;실시간 주식 시세 (끝없이 데이터가 들어옴)&lt;/li&gt;
&lt;li&gt;로그 스트림&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Flux 생성 예시
Flux&amp;lt;String&amp;gt; fruitFlux = Flux.just(&quot;Apple&quot;, &quot;Banana&quot;, &quot;Cherry&quot;);

Flux&amp;lt;Integer&amp;gt; rangeFlux = Flux.range(1, 5); // 1, 2, 3, 4, 5

Flux&amp;lt;String&amp;gt; fromList = Flux.fromIterable(Arrays.asList(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Mono [0..1]: 딱 하나, 혹은 없음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mono&lt;/b&gt;는 &lt;b&gt;0개 또는 1개&lt;/b&gt;의 데이터만 발행하고 종료되는 Publisher입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비유:&lt;/b&gt; 택배 상자. 열면 물건이 하나 있거나, 비어있거나(Empty).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유사한 개념:&lt;/b&gt; &lt;code&gt;Optional&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;T&lt;/code&gt; (단일 객체)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도:&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;findById()&lt;/code&gt;: ID로 단건 조회&lt;/li&gt;
&lt;li&gt;&lt;code&gt;save()&lt;/code&gt;: 저장 후 결과 반환 (성공/실패)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;count()&lt;/code&gt;: 개수 세기 (결과는 숫자 하나니까)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpClient.get()&lt;/code&gt;: API 호출 결과 (응답은 한 번 오니까)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Mono 생성 예시
Mono&amp;lt;String&amp;gt; helloMono = Mono.just(&quot;Hello World&quot;);

Mono&amp;lt;String&amp;gt; emptyMono = Mono.empty(); // 데이터 없음 (Void)

Mono&amp;lt;String&amp;gt; errorMono = Mono.error(new RuntimeException(&quot;에러 발생!&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 왜 굳이 나눴을까요? (Flux 하나로 다 하면 안 돼?)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 &lt;code&gt;Flux&lt;/code&gt;로도 1개짜리 데이터를 보낼 수 있습니다. (&lt;code&gt;Flux.just(&quot;One&quot;)&lt;/code&gt;)&lt;br /&gt;하지만 &lt;b&gt;의미(Semantics)&lt;/b&gt;가 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;Mono&amp;lt;User&amp;gt; findById(1L)&lt;/code&gt;&lt;/b&gt;: &quot;이 메서드는 유저를 1명만 찾거나 못 찾습니다.&quot; (명확함)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;Flux&amp;lt;User&amp;gt; findById(1L)&lt;/code&gt;&lt;/b&gt;: &quot;이 메서드는 유저를 여러 명 찾을 수도 있습니다.&quot; (오해의 소지)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리턴 타입을 분리함으로써, 개발자는 &lt;b&gt;API 명세서 없이도 코드만 보고 데이터의 개수(Cardinality)를 유추&lt;/b&gt;할 수 있게 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 가장 중요한 규칙: &quot;구독하기 전까진 아무 일도 안 일어난다&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초보자가 가장 많이 하는 실수입니다.&lt;br /&gt;아래 코드를 실행하면 콘솔에 무엇이 찍힐까요?&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Flux&amp;lt;String&amp;gt; flux = Flux.just(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;);
flux.map(s -&amp;gt; s.toLowerCase()); // 소문자로 변환
// 끝.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정답: 아무것도 안 찍힙니다.&lt;/b&gt;&lt;br /&gt;심지어 내부적으로 연산(&lt;code&gt;toLowerCase&lt;/code&gt;)조차 실행되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액티브 스트림즈는 &lt;b&gt;게으릅니다(Lazy Evaluation).&lt;/b&gt;&lt;br /&gt;누군가가 &lt;b&gt;&lt;code&gt;subscribe()&lt;/code&gt; (구독)&lt;/b&gt;를 해야만 비로소 수도꼭지를 틀고 데이터를 흘려보내기 시작합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Flux&amp;lt;String&amp;gt; flux = Flux.just(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;);

flux.subscribe(data -&amp;gt; System.out.println(data)); 
// 출력:
// A
// B
// C
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의:&lt;/b&gt; WebFlux 컨트롤러(&lt;code&gt;@RestController&lt;/code&gt;)에서는 우리가 직접 &lt;code&gt;subscribe()&lt;/code&gt;를 호출하지 않습니다. 리턴 타입으로 &lt;code&gt;Mono&lt;/code&gt;나 &lt;code&gt;Flux&lt;/code&gt;를 던져주면, &lt;b&gt;스프링 프레임워크가 알아서 구독&lt;/b&gt;해 줍니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Mono와 Flux는 변환 가능합니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발하다 보면 1개를 여러 개로, 여러 개를 1개로 바꿀 일이 생깁니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Flux -&amp;gt; Mono:&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flux.next()&lt;/code&gt;: 첫 번째 데이터만 가져와서 Mono로 변환.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flux.collectList()&lt;/code&gt;: 데이터를 다 모아서 &lt;code&gt;Mono&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;로 변환.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Mono -&amp;gt; Flux:&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mono.flux()&lt;/code&gt;: 그냥 Flux로 취급.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mono.flatMapMany()&lt;/code&gt;: Mono 안의 내용을 펴서 Flux로 변환.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Mono&lt;/b&gt;: 0~1개의 데이터. (&lt;code&gt;Optional&lt;/code&gt;과 비슷) -&amp;gt; 단건 조회, 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Flux&lt;/b&gt;: 0~N개의 데이터. (&lt;code&gt;List&lt;/code&gt;와 비슷) -&amp;gt; 목록 조회, 스트림&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lazy&lt;/b&gt;: &lt;code&gt;subscribe()&lt;/code&gt; 하기 전까지는 코드가 실행되지 않는다. (설계도일 뿐!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 데이터를 담는 그릇은 준비되었습니다. 하지만 그릇에 담긴 데이터를 &lt;b&gt;가공(변환, 필터링)&lt;/b&gt;해야 쓸모가 있겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;map과 flatMap, 도대체 뭐가 다른가요?&quot; 리액티브 프로그래밍의 마법 같은 연산자(Operator)&lt;/b&gt;들을 정복해 보겠습니다. (여기서부터 진짜 코딩입니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1267</guid>
      <comments>https://hianna.tistory.com/1267#entry1267comment</comments>
      <pubDate>Tue, 31 Mar 2026 08:14:48 +0900</pubDate>
    </item>
    <item>
      <title>[Reactive] &amp;quot;데이터가 강물처럼 흐른다?&amp;quot; 리액티브 스트림즈(Reactive Streams)와 Backpressure 완벽 이해</title>
      <link>https://hianna.tistory.com/1266</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 개발자에게 데이터란 무엇일까요? 보통 &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt; 같은 컬렉션에 담긴 &lt;b&gt;&quot;완성된 결과물&quot;&lt;/b&gt;을 의미했습니다.&lt;br /&gt;DB에서 100만 건을 조회하면, 100만 건이 다 메모리에 올라올 때까지 기다렸다가 한 번에 &lt;code&gt;List&lt;/code&gt;로 받아서 처리했죠. (Memory: &quot;살려줘...&quot;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;리액티브 프로그래밍(Reactive Programming)&lt;/b&gt;에서는 다릅니다.&lt;br /&gt;데이터는 고여있는 호수가 아니라, &lt;b&gt;끊임없이 흐르는 강물(Stream)&lt;/b&gt;과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 WebFlux의 뼈대가 되는 표준 사양, &lt;b&gt;리액티브 스트림즈(Reactive Streams)&lt;/b&gt;와 그 핵심 기능인 &lt;b&gt;백프레셔(Backpressure)&lt;/b&gt;를 알아보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Iterable(과거) vs Reactive Streams(미래)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 차이는 &lt;b&gt;&quot;누가 주도권을 쥐고 있는가?&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Iterable (&lt;code&gt;for-each&lt;/code&gt;):&lt;/b&gt; 소비자가 주도합니다.&lt;/li&gt;
&lt;li&gt;&quot;냉장고(List) 문 열어. 사과 하나 꺼내(&lt;code&gt;next()&lt;/code&gt;). 또 꺼내. 더 없어? 끝.&quot;&lt;/li&gt;
&lt;li&gt;데이터가 이미 준비되어 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Reactive Streams (&lt;code&gt;Publisher-Subscriber&lt;/code&gt;):&lt;/b&gt; 생산자가 주도하되, 소비자가 조절합니다.&lt;/li&gt;
&lt;li&gt;&quot;나 구독할게(&lt;code&gt;subscribe&lt;/code&gt;). 데이터 생기면 나한테 던져줘(&lt;code&gt;onNext&lt;/code&gt;).&quot;&lt;/li&gt;
&lt;li&gt;데이터가 미래의 어느 시점에 도착합니다. (비동기)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 리액티브 스트림즈의 4대장 (인터페이스)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 4가지 인터페이스가 서로 대화를 주고받으며 데이터를 처리합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Publisher (생산자):&lt;/b&gt; 데이터를 만들어내는 녀석입니다. (예: DB, 외부 API)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;subscribe(Subscriber s)&lt;/code&gt;: &quot;구독자, 들어와!&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Subscriber (소비자):&lt;/b&gt; 데이터를 받아서 처리하는 녀석입니다. (예: 우리 서버, 클라이언트)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;onSubscribe(Subscription s)&lt;/code&gt;: &quot;구독 시작됐다!&quot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onNext(T t)&lt;/code&gt;: &quot;데이터 하나 도착!&quot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onError(Throwable t)&lt;/code&gt;: &quot;에러 발생!&quot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onComplete()&lt;/code&gt;: &quot;데이터 다 보냈어. 끝!&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Subscription (구독권):&lt;/b&gt; 생산자와 소비자 사이의 연결 고리이자 &lt;b&gt;리모컨&lt;/b&gt;입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;request(long n)&lt;/code&gt;: &quot;n개만 더 줘.&quot; &lt;b&gt;(★ 핵심)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cancel()&lt;/code&gt;: &quot;구독 취소할래.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Processor (중개자):&lt;/b&gt; 생산자와 소비자 사이에서 데이터를 가공하는 녀석입니다. (Publisher이자 동시에 Subscriber)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 백프레셔(Backpressure): &quot;체할 것 같으니까 천천히 줘!&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액티브 스트림즈가 탄생한 진짜 이유는 바로 &lt;b&gt;Backpressure(배압)&lt;/b&gt; 때문입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  문제 상황: Push 모델의 비극&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생산자(Publisher)는 초당 10,000개의 데이터를 뿜어냅니다.&lt;br /&gt;그런데 소비자(Subscriber)는 1초에 100개밖에 처리를 못 합니다.&lt;br /&gt;-&amp;gt; 소비자의 메모리 큐(Queue)에 데이터가 계속 쌓이다가... &lt;b&gt;&lt;code&gt;OutOfMemoryError&lt;/code&gt; 펑!&lt;/b&gt; 서버가 죽습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  해결책: Pull 모델의 도입 (Backpressure)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소비자가 생산자에게 이렇게 말합니다.&lt;br /&gt;&lt;b&gt;&quot;나 지금 바쁘니까 딱 10개만 줘(&lt;code&gt;request(10)&lt;/code&gt;). 다 처리하면 더 달라고 할게.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 바로 &lt;b&gt;백프레셔&lt;/b&gt;입니다. 소비자가 감당할 수 있는 만큼만 데이터를 요청해서 시스템의 안정성을 보장하는 기술이죠.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 코드 흐름으로 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말로만 하면 어려우니, 실제 동작 흐름을 의사 코드(Pseudo-code)로 볼까요?&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 1. 소비자가 구독을 요청합니다.
publisher.subscribe(subscriber);

// 2. 생산자가 구독권(Subscription)을 줍니다.
// [Subscriber 내부]
public void onSubscribe(Subscription s) {
    this.subscription = s;
    // ★ 핵심: &quot;일단 1개만 줘봐.&quot; (Backpressure)
    s.request(1); 
}

// 3. 생산자가 데이터를 1개 보냅니다.
// [Subscriber 내부]
public void onNext(Data data) {
    System.out.println(&quot;데이터 처리: &quot; + data);

    // 처리 끝났으니 1개 더 줘!
    this.subscription.request(1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 생산자가 아무리 빨라도, 소비자가 &lt;code&gt;request()&lt;/code&gt;를 하지 않으면 데이터를 보내지 않기 때문에 서버가 터질 일이 없습니다. &lt;b&gt;시스템의 회복 탄력성(Resilience)&lt;/b&gt;이 엄청나게 좋아집니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. WebFlux와의 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring WebFlux는 이 &lt;b&gt;Reactive Streams&lt;/b&gt; 표준을 구현한 &lt;b&gt;Project Reactor&lt;/b&gt;라는 라이브러리를 기반으로 만들어졌습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WebFlux에서는 저 복잡한 &lt;code&gt;Subscription&lt;/code&gt;, &lt;code&gt;request()&lt;/code&gt; 코드를 직접 짤 필요가 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Mono&lt;/b&gt;와 &lt;b&gt;Flux&lt;/b&gt;라는 아주 편리한 도구가 다 알아서 해주거든요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 그저 &quot;데이터가 흐른다&quot;, &quot;소비자가 속도를 조절한다&quot;는 개념만 머릿속에 넣으면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Reactive Streams&lt;/b&gt;는 데이터를 한 번에 받지 않고 &lt;b&gt;흐름(Stream)&lt;/b&gt;으로 처리하는 표준이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Publisher(생산자)&lt;/b&gt;가 데이터를 주고, &lt;b&gt;Subscriber(소비자)&lt;/b&gt;가 받는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Backpressure&lt;/b&gt;는 소비자가 &quot;나 처리할 수 있는 만큼만 줘!&quot;라고 요청해서 &lt;b&gt;시스템 과부하를 막는 핵심 기술&lt;/b&gt;이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념은 잡혔는데, &quot;그래서 자바 코드로 어떻게 짜는데?&quot;라는 의문이 드시죠? &lt;code&gt;List&lt;/code&gt; 대신 뭘 써야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;WebFlux의 양대 산맥, 0&lt;del&gt;1개의 데이터를 다루는 &lt;code&gt;Mono&lt;/code&gt;와 0&lt;/del&gt;N개의 데이터를 다루는 &lt;code&gt;Flux&lt;/code&gt;&lt;/b&gt;에 대해 알아보며 본격적인 코딩을 시작해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1266</guid>
      <comments>https://hianna.tistory.com/1266#entry1266comment</comments>
      <pubDate>Tue, 31 Mar 2026 00:13:40 +0900</pubDate>
    </item>
    <item>
      <title>[Spring WebFlux] &amp;quot;MVC랑 뭐가 달라요?&amp;quot; 블로킹(Blocking) vs 논블로킹(Non-Blocking) 완벽 이해</title>
      <link>https://hianna.tistory.com/1265</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 지금까지 써왔던 &lt;b&gt;Spring MVC&lt;/b&gt;는 정말 훌륭한 프레임워크입니다. 안정적이고, 코짜기도 쉽고, 디버깅도 편하죠.&lt;br /&gt;하지만 서비스가 커지고 &lt;b&gt;동시 접속자가 수만 명&lt;/b&gt;을 넘어가면 문제가 생기기 시작합니다. 서버를 아무리 늘려도(Scale-out) 응답 속도가 느려지고, 메모리가 부족해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 단 하나, MVC가 &lt;b&gt;&quot;기다림의 미학(Blocking)&quot;&lt;/b&gt;을 추구하기 때문입니다.&lt;br /&gt;반면 WebFlux는 &lt;b&gt;&quot;기다리지 않는 효율성(Non-Blocking)&quot;&lt;/b&gt;을 추구합니다. 이 둘의 차이를 완벽하게 파헤쳐 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Spring MVC: &quot;손님 1명당 직원 1명&quot; (Thread per Request)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC는 전통적인 &lt;b&gt;서블릿(Servlet)&lt;/b&gt; 모델을 따릅니다.&lt;br /&gt;요청이 들어올 때마다 &lt;b&gt;쓰레드(Thread)&lt;/b&gt;라는 일꾼을 하나씩 배정합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;요청 도착:&lt;/b&gt; 쓰레드 풀에서 놀고 있는 쓰레드 A를 깨웁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작업 수행:&lt;/b&gt; 쓰레드 A가 DB에 쿼리를 날립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대기 (Blocking):&lt;/b&gt; DB가 응답을 줄 때까지 쓰레드 A는 &lt;b&gt;아무것도 안 하고 멍하니 기다립니다.&lt;/b&gt; (이게 문제입니다!)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;응답:&lt;/b&gt; DB 응답이 오면 쓰레드 A가 결과를 가지고 사용자에게 반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제점: 직원(Thread)이 부족해요!&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 DB 처리가 3초 걸린다면? 쓰레드 A는 3초 동안 마비됩니다.&lt;br /&gt;동시 접속자가 폭주해서 쓰레드 풀(기본 200개)이 꽉 차면? 201번째 사용자는 &lt;b&gt;대기열(Queue)&lt;/b&gt;에서 하염없이 기다려야 합니다. 이를 &lt;b&gt;쓰레드 지옥(Thread Hell)&lt;/b&gt;이라고 부릅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Spring WebFlux: &quot;직원 1명이 모든 주문을 처리&quot; (Event Loop)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux는 &lt;b&gt;이벤트 루프(Event Loop)&lt;/b&gt; 방식을 사용합니다. Node.js와 비슷한 구조죠.&lt;br /&gt;적은 수의 쓰레드(보통 CPU 코어 수만큼)만으로 엄청난 트래픽을 처리합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;요청 도착:&lt;/b&gt; 요청을 받는 직원(Event Loop)이 주문만 받고 바로 주방(DB)에 던집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작업 수행:&lt;/b&gt; &quot;DB야, 데이터 찾으면 나한테 알려줘(Callback)!&quot;라고 말하고, 직원은 &lt;b&gt;기다리지 않고 바로 다음 손님의 주문을 받으러 갑니다.&lt;/b&gt; (Non-Blocking)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 발생:&lt;/b&gt; DB가 일을 다 하면 &quot;다 찾았어요!&quot;라고 이벤트를 보냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;응답:&lt;/b&gt; 직원이 그 이벤트를 받아서 사용자에게 결과를 전달합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점: 직원이 놀지 않아요!&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드가 멍하니 기다리는 시간이 없습니다. 끊임없이 다음 요청을 처리하죠. 그래서 적은 리소스로도 &lt;b&gt;동시성(Concurrency)&lt;/b&gt;이 엄청나게 높아집니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 쉬운 비유: 동네 분식집 vs 스타벅스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이를 가장 쉽게 이해하는 비유가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Spring MVC (동네 분식집):&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;아주머니(Thread)가 주문을 받습니다.&lt;/li&gt;
&lt;li&gt;라면을 끓입니다. (3분 소요)&lt;/li&gt;
&lt;li&gt;라면을 손님에게 주고 나서야, 다음 손님의 주문을 받습니다.&lt;/li&gt;
&lt;li&gt;손님이 많아지면? 아주머니를 계속 고용해야 합니다. (메모리 부족)&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Spring WebFlux (스타벅스):&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;직원(Event Loop)이 주문을 받고 진동벨을 줍니다. (0.1초 소요)&lt;/li&gt;
&lt;li&gt;&quot;라떼 나왔습니다~&quot; 할 때까지 직원은 다른 손님 100명의 주문을 더 받습니다.&lt;/li&gt;
&lt;li&gt;커피 머신(DB)이 커피를 다 내리면, 직원이 진동벨을 울립니다.&lt;/li&gt;
&lt;li&gt;직원 1명으로도 수백 명을 커버할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 성능 비교: 언제 빨라지나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그럼 무조건 WebFlux가 빠른가요?&quot;&lt;br /&gt;&lt;b&gt;아니요, 그렇지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요청량이 적을 때:&lt;/b&gt; MVC가 더 빠를 수 있습니다. (WebFlux는 구조가 복잡해서 기본 오버헤드가 있음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요청량이 폭주할 때:&lt;/b&gt; MVC는 쓰레드가 고갈되어 성능이 수직 하락하지만, WebFlux는 &lt;b&gt;일정한 응답 속도&lt;/b&gt;를 유지하며 버팁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, WebFlux는 &lt;b&gt;&quot;개별 요청 속도&quot;를 빠르게 하는 게 아니라, &quot;동시 처리량(Throughput)&quot;을 늘리는 기술&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 언제 WebFlux를 써야 하나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작정 도입하면 개발 생산성만 떨어집니다. 다음 상황에서만 고려하세요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;고성능/고효율이 필요할 때:&lt;/b&gt; 적은 서버 자원으로 많은 트래픽을 감당해야 할 때.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;느린 외부 API 연동이 많을 때:&lt;/b&gt; 마이크로서비스(MSA) 환경에서 다른 서버의 응답을 기다리는 시간이 길 때.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스트리밍 서비스:&lt;/b&gt; 넷플릭스처럼 데이터를 지속적으로 흘려보내야 할 때.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실시간 통신:&lt;/b&gt; 웹소켓이나 채팅 서버.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반대로,&lt;/b&gt; 전통적인 CRUD 게시판이나 복잡한 비즈니스 로직이 많은 쇼핑몰(결제 등)은 여전히 &lt;b&gt;Spring MVC&lt;/b&gt;가 더 좋은 선택일 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;MVC (Blocking)&lt;/b&gt;는 요청당 쓰레드를 하나씩 써서, I/O 작업 시 멍하니 기다리는 시간이 많다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WebFlux (Non-Blocking)&lt;/b&gt;는 이벤트 루프를 써서, 기다리지 않고 딴 일을 하다가 완료되면 돌아온다.&lt;/li&gt;
&lt;li&gt;WebFlux는 &lt;b&gt;적은 자원으로 최대의 동시성&lt;/b&gt;을 뽑아내는 기술이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념은 잡혔지만, 막상 코드를 짜려면 막막하실 겁니다. &lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt; 대신 &lt;code&gt;Flux&amp;lt;String&amp;gt;&lt;/code&gt;이라는 이상한 녀석이 나오거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;데이터가 물처럼 흐른다?&quot; 리액티브 프로그래밍의 핵심 개념인 리액티브 스트림즈(Reactive Streams)와 Backpressure&lt;/b&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1265</guid>
      <comments>https://hianna.tistory.com/1265#entry1265comment</comments>
      <pubDate>Mon, 30 Mar 2026 08:59:18 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] &amp;quot;새로고침 없이 알림이 오네요?&amp;quot; WebSocket과 STOMP로 실시간 채팅방 만들기</title>
      <link>https://hianna.tistory.com/1264</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오톡, 슬랙, 주식 시세창의 공통점은 무엇일까요?&lt;br /&gt;내가 가만히 있어도 &lt;b&gt;새로운 정보가 화면에 뿅! 하고 나타난다&lt;/b&gt;는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP가 &lt;b&gt;무전기(단방향)&lt;/b&gt;라면, WebSocket은 &lt;b&gt;전화기(양방향)&lt;/b&gt;입니다.&lt;br /&gt;오늘은 스프링 부트에서 이 웹소켓을 이용해 간단한 &lt;b&gt;실시간 채팅방&lt;/b&gt;을 구현해 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. WebSocket만 쓰면 안 되나요? (STOMP의 필요성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹소켓은 그냥 &lt;b&gt;&quot;통신 파이프&quot;&lt;/b&gt;만 뚫어줄 뿐입니다.&lt;br /&gt;빨대만 꽂아놓고 &quot;안녕?&quot;이라고 보내면, 서버는 이게 귓속말인지, 전체 공지인지, 채팅방 1번인지 알 방법이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 규칙(프로토콜)이 필요합니다. 그게 바로 &lt;b&gt;STOMP (Simple Text Oriented Messaging Protocol)&lt;/b&gt;입니다.&lt;br /&gt;STOMP는 &lt;b&gt;&quot;주소(Topic)&quot;&lt;/b&gt; 개념을 도입해서 메시지를 배달해 줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Pub (발행):&lt;/b&gt; &quot;1번 채팅방(/topic/room/1)에 메시지 보내줘!&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Sub (구독):&lt;/b&gt; &quot;나 1번 채팅방(/topic/room/1) 듣고 있을게!&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 설정하기 (의존성 추가)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;build.gradle&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. WebSocket 설정 (&lt;code&gt;WebSocketConfig&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어디로 연결해야 대화가 시작되는지(Endpoint)&quot;와 &quot;메시지 브로커(우체부)&quot;를 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSocketMessageBroker // ★ STOMP 활성화
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 1. 연결 주소: ws://localhost:8080/ws-chat
        registry.addEndpoint(&quot;/ws-chat&quot;)
                .setAllowedOriginPatterns(&quot;*&quot;) // CORS 허용
                .withSockJS(); // 구형 브라우저 지원 (필수!)
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 2. 메시지 구독 요청(Sub) 주소 접두사
        // 클라이언트가 &quot;/sub/chat/room/1&quot;을 구독하면 브로커가 잡아서 처리함
        registry.enableSimpleBroker(&quot;/sub&quot;);

        // 3. 메시지 발행 요청(Pub) 주소 접두사
        // 클라이언트가 &quot;/pub/chat/message&quot;로 보내면 @MessageMapping이 잡아서 처리함
        registry.setApplicationDestinationPrefixes(&quot;/pub&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 메시지 DTO 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주고받을 편지지를 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;@Getter
@Setter
public class ChatMessage {
    public enum MessageType {
        ENTER, TALK
    }
    private MessageType type; // 입장인지, 대화인지
    private String roomId;    // 방 번호
    private String sender;    // 보낸 사람
    private String message;   // 내용
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 컨트롤러 만들기 (&lt;code&gt;ChatController&lt;/code&gt;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@RequestMapping&lt;/code&gt; 대신 &lt;b&gt;&lt;code&gt;@MessageMapping&lt;/code&gt;&lt;/b&gt;을 씁니다. HTTP 컨트롤러랑 비슷해서 아주 쉽습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Controller
@RequiredArgsConstructor
public class ChatController {

    private final SimpMessageSendingOperations messagingTemplate; // 메시지 보내는 도구

    // 클라이언트가 &quot;/pub/chat/message&quot;로 보내면 이 메서드가 실행됨
    @MessageMapping(&quot;/chat/message&quot;)
    public void message(ChatMessage message) {

        if (ChatMessage.MessageType.ENTER.equals(message.getType())) {
            message.setMessage(message.getSender() + &quot;님이 입장하셨습니다.&quot;);
        }

        // ★ 핵심: &quot;/sub/chat/room/{roomId}&quot;를 구독 중인 사람들에게 다 뿌리기!
        messagingTemplate.convertAndSend(&quot;/sub/chat/room/&quot; + message.getRoomId(), message);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 프론트엔드 (JavaScript) 맛보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 준비됐습니다. 클라이언트는 &lt;b&gt;SockJS&lt;/b&gt;와 &lt;b&gt;Stomp.js&lt;/b&gt; 라이브러리를 써서 연결합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 1. 연결
var sock = new SockJS(&quot;/ws-chat&quot;);
var ws = Stomp.over(sock);

// 2. 접속 시도
ws.connect({}, function(frame) {
    // 3. 구독 (Subscribe) - 1번 방
    ws.subscribe(&quot;/sub/chat/room/1&quot;, function(message) {
        var recv = JSON.parse(message.body);
        console.log(&quot;받은 메시지: &quot; + recv.message);
    });

    // 4. 메시지 전송 (Publish)
    ws.send(&quot;/pub/chat/message&quot;, {}, JSON.stringify({
        type: 'TALK', 
        roomId: '1', 
        sender: '홍길동', 
        message: '안녕하세요!'
    }));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 홍길동이 메시지를 보내면, 1번 방을 구독하고 있는 모든 사람(철수, 영희)의 콘솔창에 &quot;안녕하세요!&quot;가 동시에 뜹니다. &lt;b&gt;새로고침 없이요!&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 더 나아가기 (External Broker)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트 내장 브로커(&lt;code&gt;SimpleBroker&lt;/code&gt;)는 메모리 기반이라서, &lt;b&gt;서버가 여러 대(Scale-out)가 되면&lt;/b&gt; 1번 서버에 붙은 홍길동의 말을 2번 서버의 영희가 못 듣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 &lt;b&gt;RabbitMQ&lt;/b&gt;나 &lt;b&gt;Kafka&lt;/b&gt; 같은 &lt;b&gt;외부 브로커(External Broker)&lt;/b&gt;를 붙여서 메시지를 공유해야 합니다. (이게 바로 대규모 채팅 시스템의 핵심입니다.)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;WebSocket&lt;/b&gt;은 실시간 양방향 통신을 가능하게 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;STOMP&lt;/b&gt;를 쓰면 &quot;구독(Sub)&quot;과 &quot;발행(Pub)&quot; 구조로 쉽게 채팅방을 만들 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;@MessageMapping&lt;/code&gt;&lt;/b&gt;을 통해 HTTP 컨트롤러처럼 익숙하게 개발할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1264</guid>
      <comments>https://hianna.tistory.com/1264#entry1264comment</comments>
      <pubDate>Mon, 30 Mar 2026 01:58:30 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] &amp;quot;내 서버는 몇 명까지 버틸까?&amp;quot; nGrinder로 부하 테스트(Load Test) 하고 병목 지점 찾기</title>
      <link>https://hianna.tistory.com/1263</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 개발의 끝은 배포가 아니라 &lt;b&gt;성능 튜닝&lt;/b&gt;입니다.&lt;br /&gt;API 응답 속도가 0.1초인지 3초인지는 혼자 테스트할 땐 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버가 만들고 오픈소스로 공개한 &lt;b&gt;nGrinder&lt;/b&gt;는 엔터프라이즈급 부하 테스트 도구입니다. 스크립트 하나로 &lt;b&gt;수천 명의 가상 사용자(VUser)&lt;/b&gt;가 동시에 로그인하고, 상품을 조회하고, 결제하는 시나리오를 만들 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. nGrinder 구조 (Controller + Agent)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder는 크게 두 가지로 나뉩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Controller (지휘관):&lt;/b&gt; 웹 UI를 제공하고, 테스트 스크립트를 관리하고, 결과를 보여줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Agent (병사):&lt;/b&gt; 실제 부하(트래픽)를 발생시키는 일꾼입니다. 서버가 여러 대라면 Agent도 여러 대 띄워서 엄청난 트래픽을 만들 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 설치하기 (Docker Compose)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 까다롭기로 유명하지만, 도커를 쓰면 5분 컷입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;version: '3'
services:
  controller:
    image: ngrinder/controller
    restart: always
    ports:
      - &quot;80:80&quot;     # 웹 UI 접속 포트
      - &quot;16001:16001&quot;
      - &quot;12000-12009:12000-12009&quot;
    volumes:
      - ./ngrinder-controller:/opt/ngrinder-controller

  agent:
    image: ngrinder/agent
    restart: always
    links:
      - controller
    environment:
      - CONTROLLER_HOST=controller
    volumes:
      - ./ngrinder-agent:/opt/ngrinder-agent
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt; 후 &lt;code&gt;http://localhost:80&lt;/code&gt;에 접속합니다. (초기 ID/PW: &lt;code&gt;admin&lt;/code&gt;/&lt;code&gt;admin&lt;/code&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 테스트 스크립트 작성 (Groovy)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder는 &lt;b&gt;Groovy(자바 문법)&lt;/b&gt;로 스크립트를 짭니다. JUnit 테스트 코드와 거의 똑같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Script&lt;/b&gt; 메뉴 -&amp;gt; &lt;b&gt;Create&lt;/b&gt; -&amp;gt; &lt;b&gt;Groovy Maven Project&lt;/b&gt; 선택&lt;/li&gt;
&lt;li&gt;테스트할 URL 입력 (예: &lt;code&gt;http://192.168.0.5:8080/api/products&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주의:&lt;/b&gt; &lt;code&gt;localhost&lt;/code&gt;라고 적으면 안 됩니다! (도커 컨테이너 기준이라 자기 자신을 찾게 됨) 내 PC의 사설 IP를 적으세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptgen.HTTPRequest
import HTTPClient.NVPair
import HTTPClient.Cookie
import HTTPClient.HTTPResponse
import HTTPClient.CookieModule

@RunWith(GrinderRunner)
class TestRunner {

    public static GTest test
    public static HTTPRequest request

    @BeforeProcess
    public static void beforeProcess() {
        test = new GTest(1, &quot;Product API Test&quot;)
        request = new HTTPRequest()
    }

    @BeforeThread
    public void beforeThread() {
        // 로그인 쿠키 설정 등이 필요하면 여기서
        grinder.statistics.delayReports = true
    }

    @Test
    public void test() {
        // GET 요청 보내기
        HTTPResponse result = request.GET(&quot;http://192.168.0.5:8080/api/products&quot;)

        // 응답 코드가 200인지 검증
        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn(&quot;Warning. The response is 301 or 302.&quot;)
        } else {
            assertThat(result.statusCode, is(200))
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 테스트 실행 (Performance Test)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 시나리오를 돌려봅시다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Performance Test&lt;/b&gt; 메뉴 -&amp;gt; &lt;b&gt;Create Test&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Basic Configuration:&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Agent:&lt;/b&gt; 1 (에이전트 몇 대 쓸래?)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Vuser per Agent:&lt;/b&gt; 100 (에이전트 1대당 가상 사용자 100명 = 총 100명 동시 접속)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Duration:&lt;/b&gt; 1분 (1분 동안 공격!)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Target Host:&lt;/b&gt; 내 서버 IP&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Save and Start&lt;/b&gt; 클릭!&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 결과 분석: TPS가 떨어지면 망한 겁니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 끝나면 그래프가 나옵니다. 가장 중요한 지표는 &lt;b&gt;TPS (Transactions Per Second)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;TPS 그래프가 우상향하거나 일정하다:&lt;/b&gt; 서버가 잘 버티고 있음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TPS가 오르다가 뚝 떨어진다:&lt;/b&gt; 서버 한계 도달 (DB 커넥션 풀 부족, 메모리 부족, 스레드 풀 고갈 등).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Errors 그래프가 치솟는다:&lt;/b&gt; 서버가 500 에러를 뱉기 시작함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;튜닝 포인트 찾기:&lt;/b&gt;&lt;br /&gt;이때 우리가 배웠던 &lt;b&gt;VisualVM&lt;/b&gt;이나 &lt;b&gt;Pinpoint&lt;/b&gt;를 같이 켜두고 보면, 어디서 막히는지(병목) 정확히 알 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;어? DB 커넥션을 못 얻어서 대기하네?&quot; -&amp;gt; &lt;b&gt;HikariCP 늘리기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&quot;어? 쿼리가 느리네?&quot; -&amp;gt; &lt;b&gt;인덱스 추가 또는 Redis 도입&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;nGrinder&lt;/b&gt;는 가상의 사용자(Bot)를 만들어 내 서버를 디도스(DDoS) 공격하듯 테스트한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TPS&lt;/b&gt;가 꺾이는 지점이 내 서버의 최대 성능 한계점이다.&lt;/li&gt;
&lt;li&gt;부하 테스트 없이 오픈하는 것은 &lt;b&gt;안전벨트 없이 고속도로를 달리는 것&lt;/b&gt;과 같다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분은 &quot;이 서버는 동시 접속자 5,000명까지는 거뜬합니다!&quot;라고 숫자로 증명할 수 있는 개발자가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 이 시리즈의 마지막 주제, &lt;b&gt;&quot;새로고침 없이 알림이 오네요?&quot; HTTP가 아닌 양방향 통신, WebSocket과 STOMP로 실시간 채팅방 만들기&lt;/b&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1263</guid>
      <comments>https://hianna.tistory.com/1263#entry1263comment</comments>
      <pubDate>Sun, 29 Mar 2026 08:57:59 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] &amp;quot;RDB에서 검색하면 느려터져요...&amp;quot; Elasticsearch로 고성능 검색 엔진 구축하기</title>
      <link>https://hianna.tistory.com/1262</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 흔히 쓰는 관계형 데이터베이스(RDB)는 &lt;b&gt;&quot;저장&quot;&lt;/b&gt;과 &lt;b&gt;&quot;정합성&quot;&lt;/b&gt;에 특화되어 있지, &lt;b&gt;&quot;검색&quot;&lt;/b&gt;에는 약합니다.&lt;br /&gt;특히 본문 검색(Full-text Search)이나 오타 교정, 자동 완성 같은 기능은 RDB로 구현하기가 매우 까다롭고 느립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 &lt;b&gt;Elasticsearch&lt;/b&gt;를 도입해 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 왜 Elasticsearch 인가요? (역색인 구조)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDB는 책의 &lt;b&gt;본문&lt;/b&gt;을 처음부터 끝까지 다 읽으면서 찾는 방식(Scan)이라면, Elasticsearch는 책의 맨 뒤에 있는 &lt;b&gt;&quot;색인(Index)&quot;&lt;/b&gt;을 보고 찾는 방식입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RDB:&lt;/b&gt; &quot;강남&quot;이라는 단어가 들어간 행을 다 뒤진다. -&amp;gt; &lt;b&gt;느림&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Elasticsearch:&lt;/b&gt; &quot;강남&quot; -&amp;gt; [1번 문서, 5번 문서, 100번 문서] -&amp;gt; &lt;b&gt;즉시 리턴&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 &lt;b&gt;역색인(Inverted Index)&lt;/b&gt; 구조 덕분에 데이터가 아무리 많아도 검색 속도가 거의 일정하게 빠릅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 설치하기 (Docker Compose)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘라스틱서치(엔진)와 키바나(시각화 도구)를 함께 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;version: '3'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.10
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - &quot;9200:9200&quot;

  kibana:
    image: docker.elastic.co/kibana/kibana:7.17.10
    container_name: kibana
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - &quot;5601:5601&quot;
    depends_on:
      - elasticsearch
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt; 후 브라우저에서 &lt;code&gt;http://localhost:5601&lt;/code&gt; (Kibana)에 접속되면 성공입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 스프링 부트 설정 (Spring Data Elasticsearch)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA를 써보셨다면 아주 익숙하실 겁니다. 스프링은 &lt;b&gt;Spring Data Elasticsearch&lt;/b&gt;라는 강력한 라이브러리를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;build.gradle&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;application.yml&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
  elasticsearch:
    uris: http://localhost:9200
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 도큐먼트(Document) 정의하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDB의 &lt;code&gt;Entity&lt;/code&gt;와 비슷한 개념입니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Setter
@Document(indexName = &quot;blog_post&quot;) // 인덱스 이름 (RDB의 테이블)
public class BlogPostDocument {

    @Id
    private String id;

    @Field(type = FieldType.Text) // 형태소 분석이 필요한 필드
    private String title;

    @Field(type = FieldType.Text)
    private String content;

    @Field(type = FieldType.Keyword) // 정확히 일치해야 하는 필드 (태그 등)
    private String category;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 리포지토리(Repository) 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 하이라이트입니다. JPA처럼 인터페이스만 만들면 끝납니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;public interface BlogPostRepository extends ElasticsearchRepository&amp;lt;BlogPostDocument, String&amp;gt; {

    // 1. 제목에 검색어가 포함된 문서 찾기
    List&amp;lt;BlogPostDocument&amp;gt; findByTitleContaining(String keyword);

    // 2. 제목이나 내용에 검색어가 포함된 문서 찾기 (OR 검색)
    List&amp;lt;BlogPostDocument&amp;gt; findByTitleContainingOrContentContaining(String title, String content);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 서비스에서 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 RDB 대신 Elasticsearch를 호출하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class BlogSearchService {

    private final BlogPostRepository blogPostRepository;

    public List&amp;lt;BlogPostDocument&amp;gt; search(String keyword) {
        // RDB였다면 3초 걸릴 쿼리가 0.05초 만에 나옵니다.
        return blogPostRepository.findByTitleContaining(keyword);
    }

    // 데이터 저장 (색인)
    public void save(BlogPostDocument post) {
        blogPostRepository.save(post);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 주의사항: 데이터 동기화 (Sync)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 고민거리는 &lt;b&gt;&quot;MySQL에 글이 써졌을 때, 어떻게 Elasticsearch에도 넣지?&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Dual Write:&lt;/b&gt; 서비스 코드에서 &lt;code&gt;rdbRepository.save()&lt;/code&gt; 하고 바로 &lt;code&gt;esRepository.save()&lt;/code&gt;를 호출한다. (가장 쉽지만, 하나가 실패하면 데이터가 꼬임)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Logstash / Kafka Connect:&lt;/b&gt; MySQL의 바이너리 로그(Binlog)를 읽어서 비동기로 Elasticsearch에 밀어 넣는다. (구축이 복잡하지만 안정적)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 &lt;b&gt;Dual Write&lt;/b&gt;나 &lt;b&gt;Spring Event(@EventListener)&lt;/b&gt;를 활용해서 동기화하는 것을 추천합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;RDB&lt;/b&gt;는 저장소, &lt;b&gt;Elasticsearch&lt;/b&gt;는 검색 엔진으로 역할을 분리하자.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;역색인&lt;/b&gt; 기술 덕분에 대용량 데이터에서도 검색 속도가 압도적으로 빠르다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spring Data Elasticsearch&lt;/b&gt;를 쓰면 JPA처럼 쉽게 개발할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분의 서비스는 구글처럼 빠른 검색 속도를 자랑하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;서버가 1,000명까진 버티는데 10,000명이 오면 죽어요...&quot; 내 서버의 한계점을 찾아내는 nGrinder 부하 테스트(Load Test)&lt;/b&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1262</guid>
      <comments>https://hianna.tistory.com/1262#entry1262comment</comments>
      <pubDate>Sun, 29 Mar 2026 01:57:12 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] &amp;quot;이벤트가 1초에 10만 개?&amp;quot; 대용량 처리를 위한 Kafka 연동 기초 (Producer/Consumer)</title>
      <link>https://hianna.tistory.com/1261</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑몰에서 &lt;b&gt;&quot;주문 버튼&quot;&lt;/b&gt;을 눌렀다고 가정해 봅시다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주문 정보 DB 저장&lt;/li&gt;
&lt;li&gt;재고 감소&lt;/li&gt;
&lt;li&gt;결제 승인&lt;/li&gt;
&lt;li&gt;배송 요청&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자에게 알림톡 발송&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 분석팀에 로그 전송&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 걸 한 번의 HTTP 요청(&lt;code&gt;Controller&lt;/code&gt;) 안에서 순차적으로 처리하면 어떻게 될까요?&lt;br /&gt;알림톡 서버가 1초만 늦게 응답해도, 사용자는 주문 완료 화면을 못 보고 뱅글뱅글 도는 로딩만 보게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 이 &lt;b&gt;강한 결합(Tight Coupling)&lt;/b&gt;을 끊어내고, &lt;b&gt;초당 수십만 건의 데이터&lt;/b&gt;도 거뜬히 처리하는 메시지 큐의 제왕, &lt;b&gt;Apache Kafka&lt;/b&gt;를 스프링 부트에 연동해 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 왜 Kafka 인가요? (RabbitMQ vs Kafka)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;메시지 큐(Message Queue)라면 RabbitMQ도 있잖아요?&quot;&lt;br /&gt;맞습니다. 하지만 목적이 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RabbitMQ:&lt;/b&gt; 메시지를 안전하게 전달하고 &lt;b&gt;삭제&lt;/b&gt;하는 것이 목표. (복잡한 라우팅 가능)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Kafka:&lt;/b&gt; 메시지를 &lt;b&gt;로그(파일)로 저장&lt;/b&gt;하고, 엄청난 속도(Throughput)로 처리하는 것이 목표. (대용량 로그, 실시간 스트리밍에 특화)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카는 &lt;b&gt;&quot;생산자(Producer)&quot;&lt;/b&gt;가 데이터를 던져놓으면, &lt;b&gt;&quot;소비자(Consumer)&quot;&lt;/b&gt;가 자기가 처리할 수 있는 속도로 가져가는 구조입니다. 덕분에 서버가 터지지 않고 안정적으로 돌아갑니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 설치하기 (Docker Compose)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카는 설치가 좀 까다롭습니다. 주키퍼(Zookeeper)라는 코디네이터가 필요하기 때문인데요. Docker Compose를 쓰면 3분 컷입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;version: '3'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka:
    image: confluentinc/cp-kafka:latest
    depends_on:
      - zookeeper
    ports:
      - &quot;9092:9092&quot;
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt;로 실행하면 준비 끝!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 스프링 부트 설정 (의존성 추가)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;build.gradle&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.kafka:spring-kafka'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;application.yml&lt;/code&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;spring:
  kafka:
    bootstrap-servers: localhost:9092 # 카프카 서버 주소
    consumer:
      group-id: my-group # 소비자 그룹 ID (중요!)
      auto-offset-reset: earliest # 처음부터 다 읽을래?(earliest) 아니면 지금부터 읽을래?(latest)
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tip:&lt;/b&gt; 카프카는 모든 데이터를 &lt;b&gt;Byte 배열&lt;/b&gt;로 주고받습니다. 그래서 보낼 때(Serializer)와 받을 때(Deserializer) &quot;이거 문자열이야!&quot;라고 알려줘야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 메시지 보내기 (Producer)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문이 발생했을 때 &quot;주문 생성됨&quot;이라는 메시지를 카프카에 던져봅시다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Slf4j
public class KafkaProducerService {

    private final KafkaTemplate&amp;lt;String, String&amp;gt; kafkaTemplate;

    public void sendMessage(String topic, String message) {
        // topic: 메시지 주제 (예: &quot;order_create&quot;)
        // message: 보낼 데이터 (보통 JSON 문자열)
        kafkaTemplate.send(topic, message);
        log.info(&quot;Kafka Message Sent: topic={}, msg={}&quot;, topic, message);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Controller&lt;/b&gt;에서 호출:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;producer.sendMessage(&quot;order_create&quot;, &quot;주문번호:1234, 사용자:홍길동&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 컨트롤러는 &lt;b&gt;카프카에 던지자마자 즉시 리턴&lt;/b&gt;합니다. (0.01초 소요) 알림톡이 늦게 가든 말든 사용자는 쾌적합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 메시지 받기 (Consumer)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 누군가는 그 메시지를 받아서 뒷수습(알림 발송, 로그 저장)을 해야겠죠?&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class KafkaConsumerService {

    // 1. topics: 구독할 주제
    // 2. groupId: 내 소속 그룹 (같은 그룹끼리는 메시지를 나눠서 처리함)
    @KafkaListener(topics = &quot;order_create&quot;, groupId = &quot;my-group&quot;)
    public void consume(String message) {
        log.info(&quot;Kafka Message Received: {}&quot;, message);

        // 여기서 무거운 작업을 수행합니다.
        // ex) 알림톡 발송 (3초 걸림)
        // ex) DB에 로그 저장
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 켜두면 콘솔창에 &lt;code&gt;Kafka Message Received: 주문번호:1234...&lt;/code&gt;가 찍히는 걸 볼 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 핵심 개념: Consumer Group (확장성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 꽃은 &lt;b&gt;Consumer Group&lt;/b&gt;입니다.&lt;br /&gt;주문이 폭주해서 1초에 1,000건이 들어오는데, Consumer 서버가 1대라서 처리가 느리다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 &lt;b&gt;똑같은 Consumer 서버를 2대 더 띄우세요.&lt;/b&gt; (&lt;code&gt;groupId&lt;/code&gt;를 똑같이 해서)&lt;br /&gt;카프카가 알아서 파티션(Partition)을 나눠서 &lt;b&gt;병렬 처리&lt;/b&gt;를 시켜줍니다. 서버를 늘리는 만큼 처리 속도도 정비례해서 빨라집니다. 이게 바로 카프카가 대용량 처리에 강한 이유입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Kafka&lt;/b&gt;를 쓰면 &quot;요청&quot;과 &quot;처리&quot;를 분리(Decoupling)할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Producer&lt;/b&gt;는 던지고 잊어버리고(Fire and Forget), &lt;b&gt;Consumer&lt;/b&gt;는 자기 속도대로 처리한다.&lt;/li&gt;
&lt;li&gt;서버를 늘리는 것(Scale-out)만으로 &lt;b&gt;처리량을 무한대로 확장&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분의 시스템은 트래픽이 몰려도 터지지 않고, 메시지를 차곡차곡 쌓아뒀다가 안전하게 처리할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;b&gt;&quot;RDB에서 &lt;code&gt;LIKE %검색어%&lt;/code&gt; 쓰면 느려터져요...&quot; 1억 건 데이터에서도 0.1초 만에 검색 결과를 찾아주는 Elasticsearch 검색 엔진 구축&lt;/b&gt;에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1261</guid>
      <comments>https://hianna.tistory.com/1261#entry1261comment</comments>
      <pubDate>Sat, 28 Mar 2026 08:55:53 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] &amp;quot;사용자가 신고하기 전까지 몰랐다고?&amp;quot; 실시간 에러 추적: Sentry 연동 (feat. Slack 알림)</title>
      <link>https://hianna.tistory.com/1260</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;서버 개발자의 가장 큰 수치심은 &lt;b&gt;&quot;사용자가 먼저 에러를 발견하는 것&quot;&lt;/b&gt;입니다.&lt;br&gt;&quot;로그 파일에 다 남아있는데요?&quot;라고 변명해 봤자 소용없습니다. 이미 고객은 떠났으니까요.&lt;br&gt;우리는 &lt;b&gt;수동적(Passive)&lt;/b&gt;인 로그 파일 확인에서 벗어나, &lt;b&gt;능동적(Active)&lt;/b&gt;인 알림 시스템을 구축해야 합니다. 전 세계 개발자들이 가장 사랑하는 도구, &lt;b&gt;Sentry&lt;/b&gt;가 정답입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Sentry가 뭔가요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &quot;에러 났다&quot;고 알려주는 게 아닙니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;어떤 사용자가&lt;/b&gt; (ID, 이메일)&lt;/li&gt;&lt;li&gt;&lt;b&gt;어떤 환경에서&lt;/b&gt; (OS, 브라우저 버전)&lt;/li&gt;&lt;li&gt;&lt;b&gt;어떤 코드 몇 번째 줄에서&lt;/b&gt; (Stack Trace)&lt;/li&gt;&lt;li&gt;&lt;b&gt;무슨 값을 넣었길래&lt;/b&gt; (Request Body)&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 정보를 &lt;b&gt;스냅샷&lt;/b&gt;처럼 찍어서 예쁜 대시보드로 보여줍니다. 심지어 &lt;b&gt;Jira 이슈&lt;/b&gt;를 자동으로 생성하거나 &lt;b&gt;Slack&lt;/b&gt;으로 알림을 쏴줍니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 가입 및 프로젝트 생성 (무료)&lt;/h3&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;Sentry.io&lt;/b&gt;에 접속해서 회원가입을 합니다. (Developer 플랜은 개인에게 무료입니다.)&lt;/li&gt;&lt;li&gt;&lt;b&gt;Create Project&lt;/b&gt; -&amp;gt; &lt;b&gt;Spring Boot&lt;/b&gt; 선택 -&amp;gt; &lt;b&gt;Create Project&lt;/b&gt; 클릭.&lt;/li&gt;&lt;li&gt;화면에 나오는 &lt;b&gt;DSN (Data Source Name)&lt;/b&gt; 주소를 복사해 둡니다. (이게 우리 서버랑 Sentry를 연결하는 열쇠입니다.)&lt;/li&gt;&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 스프링 부트 연동 (3분 컷)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Sentry는 스프링 부트용 스타터를 제공해서 설정이 너무 쉽습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 의존성 추가 ()&lt;/h4&gt;&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;dependencies {
    implementation 'io.sentry:sentry-spring-boot-starter:7.3.0' // 최신 버전 확인 필요
    implementation 'io.sentry:sentry-logback:7.3.0' // Logback 연동 (선택)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 설정 파일 ()&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;복사해 둔 DSN을 붙여넣습니다.&lt;/p&gt;&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;sentry:
  dsn: &quot;https://your-public-key@o0.ingest.sentry.io/0&quot; # 복사한 DSN 주소
  # 1.0 = 100% (모든 에러 전송), 0.5 = 50% (샘플링)
  traces-sample-rate: 1.0 
  # 프로덕션 환경에서만 동작하게 설정 가능
  environment: production 
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 사용법: 자동 감지와 수동 전송&lt;/h3&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 자동 감지 (Uncaught Exception)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트가 시작되면 Sentry가 자동으로 감시를 시작합니다.&lt;br&gt;&lt;code&gt;Controller&lt;/code&gt;에서 예외(&lt;code&gt;throw new RuntimeException(&quot;테스트&quot;)&lt;/code&gt;)를 던져보세요.&lt;br&gt;5초 뒤에 Sentry 대시보드에 &lt;b&gt;&quot;New Issue Created&quot;&lt;/b&gt;라며 에러가 뜹니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 수동 전송 ()&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;try-catch&lt;/code&gt;로 잡은 에러는 밖으로 던져지지 않아서 Sentry가 모를 수 있습니다. 이럴 땐 직접 알려줘야 합니다.&lt;/p&gt;&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class OrderService {

    public void order() {
        try {
            // 위험한 로직...
        } catch (Exception e) {
            log.error(&quot;주문 실패&quot;, e); // 로그 파일엔 남지만...
            Sentry.captureException(e); // ★ Sentry에도 알려줘!
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 핵심 기능: Slack 연동 (알림봇)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이게 핵심입니다. Sentry 웹사이트를 계속 보고 있을 순 없으니까요.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;Sentry 웹 -&amp;gt; &lt;b&gt;Settings&lt;/b&gt; -&amp;gt; &lt;b&gt;Integrations&lt;/b&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;Slack&lt;/b&gt; 검색 -&amp;gt; &lt;b&gt;Add Installation&lt;/b&gt;&lt;/li&gt; 
 &lt;li&gt;내 슬랙 워크스페이스 선택 -&amp;gt; 채널 지정 (&lt;code&gt;#server-errors&lt;/code&gt;)&lt;/li&gt; 
 &lt;li&gt;이제 에러가 터지면 &lt;b&gt;슬랙으로 즉시 알림&lt;/b&gt;이 옵니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;망했다... NullPointerException 터짐 (OrderService.java:45)&quot;&lt;/b&gt;&lt;br&gt;이 메시지를 받고 바로 노트북을 열면, 사용자가 고객센터에 전화하기 전에 문제를 해결할 수 있습니다.  &lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 민감 정보 필터링 (개인정보 보호)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Sentry는 기본적으로 HTTP 요청의 헤더와 바디를 수집합니다.&lt;br&gt;하지만 &lt;b&gt;사용자의 비밀번호나 주민번호&lt;/b&gt;가 Sentry 서버에 저장되면 큰일 나겠죠?&lt;/p&gt;&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;@Bean
public SentryOptions.BeforeSendCallback beforeSendCallback() {
    return (event, hint) -&amp;gt; {
        // 특정 예외는 무시하거나
        if (event.getThrowable() instanceof IgnoredException) {
            return null;
        }

        // 민감한 정보 마스킹 처리 (User IP 제거 등)
        event.getRequest().getHeaders().remove(&quot;Authorization&quot;);
        return event;
    };
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;로그 파일은 &lt;b&gt;기록용&lt;/b&gt;이지 &lt;b&gt;알림용&lt;/b&gt;이 아니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Sentry&lt;/b&gt;를 쓰면 에러 발생 상황(Context)을 완벽하게 파악할 수 있다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Slack 연동&lt;/b&gt;을 통해 &quot;사용자보다 먼저&quot; 에러를 인지하는 개발자가 되자.&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분은 퇴근 후에도 안심할 수 있습니다. (혹은 슬랙 알림 때문에 더 불안해질 수도...   농담입니다. 빠른 대응이 서비스의 생명이니까요!)&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1260</guid>
      <comments>https://hianna.tistory.com/1260#entry1260comment</comments>
      <pubDate>Sat, 28 Mar 2026 00:52:37 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] &amp;quot;결제 버튼 두 번 눌렀어요!&amp;quot; 중복 요청 방지: 멱등성(Idempotency) 설계와 Redis</title>
      <link>https://hianna.tistory.com/1259</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;멱등성(Idempotency)&lt;/b&gt;이란 수학 용어()에서 왔지만, 개발에서는 &lt;b&gt;&quot;연산을 한 번 수행하든 여러 번 수행하든 결과가 똑같아야 한다&quot;&lt;/b&gt;는 뜻입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;GET:&lt;/b&gt; 조회는 100번 해도 데이터가 안 변하죠? (멱등하다)&lt;/li&gt;&lt;li&gt;&lt;b&gt;POST:&lt;/b&gt; 결제 요청을 2번 보내면 2번 결제되죠? (&lt;b&gt;멱등하지 않다!&lt;/b&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 &lt;b&gt;POST(생성/결제)&lt;/b&gt; 요청을 &lt;b&gt;강제로 멱등하게&lt;/b&gt; 만들어줘야 합니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 해결 원리: &quot;번호표(Key) 먼저 뽑아오세요&quot;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 클라이언트(프론트)와 서버가 약속을 하는 것입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;Client:&lt;/b&gt; 요청을 보낼 때 유니크한 ID(&lt;b&gt;Idempotency-Key&lt;/b&gt;)를 헤더에 담아 보냅니다. (예: UUID)&lt;/li&gt;&lt;li&gt;&lt;b&gt;Server:&lt;/b&gt;&lt;/li&gt;&lt;/ol&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;이 키를 &lt;b&gt;Redis&lt;/b&gt;에서 조회합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;없으면:&lt;/b&gt; 처음 온 요청이네? -&amp;gt; 처리하고 Redis에 저장.&lt;/li&gt;&lt;li&gt;&lt;b&gt;있으면:&lt;/b&gt; 이미 처리된(또는 처리 중인) 요청이네? -&amp;gt; &lt;b&gt;거부(409 Conflict)하거나 이전 결과를 그대로 반환.&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 왜 Redis인가요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 저장해도 되지만, &lt;b&gt;속도&lt;/b&gt;가 생명입니다. 모든 요청마다 검사를 해야 하는데, DB를 갔다 오면 느려집니다. 메모리 기반인 &lt;b&gt;Redis&lt;/b&gt;가 딱입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Redis의 &lt;b&gt;&lt;code&gt;SETNX&lt;/code&gt; (Set if Not Exists)&lt;/b&gt; 명령어는 &quot;값이 없을 때만 저장해라&quot;라는 원자적(Atomic) 연산이라서, 동시성 이슈까지 해결해 줍니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 구현: AOP로 깔끔하게 분리하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러마다 중복 방지 로직을 넣으면 코드가 지저분해집니다. &lt;b&gt;커스텀 어노테이션 + AOP&lt;/b&gt;로 우아하게 해결해 봅시다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 어노테이션 정의 ()&lt;/h4&gt;&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    String headerName() default &quot;Idempotency-Key&quot;; // 헤더 키 이름
    long expireTime() default 60L; // 중복 방지 시간 (초)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;② AOP Aspect 구현&lt;/h4&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Aspect
@Component
@RequiredArgsConstructor
public class IdempotencyAspect {

    private final RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate;

    @Around(&quot;@annotation(idempotent)&quot;)
    public Object checkIdempotency(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {

        // 1. 헤더에서 키 가져오기
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String key = request.getHeader(idempotent.headerName());

        if (StringUtils.isEmpty(key)) {
            throw new IllegalArgumentException(&quot;Idempotency-Key 헤더가 없습니다.&quot;);
        }

        // 2. Redis 키 생성 (유니크해야 함)
        String redisKey = &quot;idempotency:&quot; + key;

        // 3. Redis에 저장 시도 (SETNX)
        // 값이 없을 때만 true 반환, 있으면 false 반환
        Boolean isFirstRequest = redisTemplate.opsForValue()
                .setIfAbsent(redisKey, &quot;PROCESSING&quot;, Duration.ofSeconds(idempotent.expireTime()));

        if (Boolean.FALSE.equals(isFirstRequest)) {
            // 이미 키가 존재함 -&amp;gt; 중복 요청!
            throw new IllegalStateException(&quot;이미 처리 중이거나 완료된 요청입니다.&quot;);
        }

        try {
            // 4. 실제 비즈니스 로직 실행 (Controller)
            return joinPoint.proceed();
        } catch (Exception e) {
            // ★ 실패하면 키를 지워줘야 재시도 가능!
            redisTemplate.delete(redisKey);
            throw e;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 사용하기: 컨트롤러&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컨트롤러 메서드 위에 &lt;b&gt;&lt;code&gt;@Idempotent&lt;/code&gt;&lt;/b&gt; 하나만 붙이면 끝입니다.&lt;/p&gt;&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/payments&quot;)
public class PaymentController {

    private final PaymentService paymentService;

    @Idempotent // ★ 중복 결제 방지 적용!
    @PostMapping
    public ResponseEntity&amp;lt;String&amp;gt; processPayment(@RequestBody PaymentRequest dto) {
        paymentService.pay(dto);
        return ResponseEntity.ok(&quot;결제 성공&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 시나리오:&lt;/b&gt;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;&lt;b&gt;첫 번째 요청:&lt;/b&gt; (Header: &lt;code&gt;Idempotency-Key: uuid-1234&lt;/code&gt;) -&amp;gt; &lt;b&gt;성공 (200 OK)&lt;/b&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;두 번째 요청:&lt;/b&gt; (Header: &lt;code&gt;Idempotency-Key: uuid-1234&lt;/code&gt;) -&amp;gt; &lt;b&gt;실패 (500 Error: 이미 처리 중...)&lt;/b&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;다른 키 요청:&lt;/b&gt; (Header: &lt;code&gt;Idempotency-Key: uuid-5678&lt;/code&gt;) -&amp;gt; &lt;b&gt;성공 (200 OK)&lt;/b&gt;&lt;/li&gt; 
&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 더 나아가기: 결과까지 저장하기 (Advanced)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;위 방식은 단순히 &quot;막기만&quot; 합니다. 더 친절한 시스템은 &lt;b&gt;&quot;이미 처리된 결과(Response)를 저장해 뒀다가 그대로 돌려주는 것&quot;&lt;/b&gt;입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;요청 시작 시: Redis에 상태를 &lt;code&gt;PROCESSING&lt;/code&gt;으로 저장.&lt;/li&gt; 
 &lt;li&gt;처리 완료 시: Redis 값을 &lt;code&gt;COMPLETE&lt;/code&gt; + &lt;b&gt;응답 데이터(JSON)&lt;/b&gt;로 업데이트.&lt;/li&gt; 
 &lt;li&gt;중복 요청 시: Redis를 조회해서 &lt;code&gt;COMPLETE&lt;/code&gt;면 저장된 JSON을 바로 리턴.&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 사용자는 &quot;어? 에러 났나?&quot; 하고 다시 눌러도, &lt;b&gt;&quot;결제 완료 (이미 처리됨)&quot;&lt;/b&gt; 화면을 볼 수 있게 됩니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;결제, 송금&lt;/b&gt; 등 중요한 로직은 반드시 &lt;b&gt;멱등성&lt;/b&gt;을 보장해야 한다.&lt;/li&gt;&lt;li&gt;클라이언트가 생성한 &lt;b&gt;유니크 키(Request ID)&lt;/b&gt;를 기준으로 중복을 체크한다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Redis&lt;/b&gt;와 &lt;b&gt;AOP&lt;/b&gt;를 활용하면 비즈니스 로직 침범 없이 깔끔하게 구현할 수 있다.&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분의 서버는 사용자가 클릭을 연타해도 끄떡없는 &lt;b&gt;철벽 방어 시스템&lt;/b&gt;을 갖췄습니다.&lt;br&gt;다음 포스팅에서는 &lt;b&gt;&quot;에러가 났는데 사용자가 신고하기 전까지 몰랐다고?&quot; 실시간 에러 추적 플랫폼 Sentry 연동하기&lt;/b&gt;에 대해 알아보겠습니다. (슬랙으로 에러 알림 받는 법!)&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1259</guid>
      <comments>https://hianna.tistory.com/1259#entry1259comment</comments>
      <pubDate>Fri, 27 Mar 2026 09:52:05 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Cloud] 외부 서버가 죽어도 내 서버는 산다? 서킷 브레이커(Resilience4j) 패턴</title>
      <link>https://hianna.tistory.com/1258</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;집에서 드라이기랑 에어컨을 동시에 켰다가 전기가 팍! 나간 적 있으신가요?&lt;br&gt;이때 &lt;b&gt;두꺼비집(누전 차단기)&lt;/b&gt;이 내려가서 전기를 끊어주지 않았다면, 과열로 인해 집 전체에 불이 났을지도 모릅니다.&lt;br&gt;소프트웨어에서도 똑같습니다. 외부 API가 에러를 뿜어내고 있는데 계속 호출하면 내 서버까지 불이 납니다. 이때 &lt;b&gt;&quot;잠깐 연결 끊어!&quot;&lt;/b&gt;라고 해주는 기술이 바로 &lt;b&gt;서킷 브레이커&lt;/b&gt;입니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 서킷 브레이커의 3가지 상태&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 기술의 핵심은 &lt;b&gt;상태(State)&lt;/b&gt;를 관리하는 것입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;CLOSED (닫힘 - 정상):&lt;/b&gt; 전기가 통하는 상태. API 호출이 정상적으로 잘 됩니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;OPEN (열림 - 차단):&lt;/b&gt; 에러가 너무 많이 나서 회로를 끊어버린 상태. &lt;b&gt;API 호출을 아예 안 하고 바로 에러를 뱉습니다.&lt;/b&gt; (Fail Fast)&lt;/li&gt;&lt;li&gt;&lt;b&gt;HALF-OPEN (반열림 - 간보기):&lt;/b&gt; 차단된 지 일정 시간이 지나서, &quot;이제 좀 괜찮아졌나?&quot; 하고 한 번 찔러보는 상태. 성공하면 다시 CLOSED로, 실패하면 다시 OPEN으로 갑니다.&lt;/li&gt;&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 설정하기 (Resilience4j)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 Netflix Hystrix를 썼지만, 지금은 업데이트가 중단되었습니다. 요즘은 &lt;b&gt;Resilience4j&lt;/b&gt;가 표준입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 의존성 추가 ()&lt;/h4&gt;&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
    implementation 'org.springframework.boot:spring-boot-starter-aop' // AOP 필수!
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 설정 파일 ()&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;10번 호출했는데 50% 이상 실패하면 60초 동안 차단해라!&quot;라는 규칙을 정합니다.&lt;/p&gt;&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;resilience4j:
  circuitbreaker:
    instances:
      my-weather-api: # 서킷 브레이커 이름
        registerHealthIndicator: true
        slidingWindowType: COUNT_BASED # 횟수 기준 (또는 TIME_BASED)
        slidingWindowSize: 10 # 최근 10번 요청을 기록
        failureRateThreshold: 50 # 실패율 50% 넘으면 OPEN (차단)
        waitDurationInOpenState: 60s # 60초 동안은 호출하지 마!
        permittedNumberOfCallsInHalfOpenState: 3 # 반열림 상태에서 3번 테스트해봐
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실전 코드: 와 Fallback&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 서비스 코드에 어노테이션만 붙이면 됩니다.&lt;br&gt;그리고 가장 중요한 것! &lt;b&gt;차단되었을 때 대신 실행할 '대안(Fallback)'&lt;/b&gt;을 마련해야 합니다.&lt;/p&gt;&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class WeatherService {

    private final WeatherClient weatherClient;

    // 1. name: 설정 파일에 적은 인스턴스 이름
    // 2. fallbackMethod: 에러 나면 실행할 메서드 이름
    @CircuitBreaker(name = &quot;my-weather-api&quot;, fallbackMethod = &quot;getFallbackWeather&quot;)
    public String getSeoulWeather() {
        return weatherClient.getForecast(&quot;seoul&quot;); // 외부 API 호출
    }

    // ★ Fallback 메서드 (대안)
    // 파라미터와 리턴 타입이 원본 메서드와 똑같아야 함!
    // 마지막 파라미터로 Throwable을 받을 수 있음
    private String getFallbackWeather(Throwable t) {
        log.error(&quot;날씨 API가 죽었습니다: {}&quot;, t.getMessage());
        return &quot;맑음 (기본값)&quot;; // 혹은 DB에 캐시된 데이터 리턴
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;날씨 API가 죽어서 에러를 뱉습니다.&lt;/li&gt; 
 &lt;li&gt;실패율이 50%를 넘으면 서킷 브레이커가 &lt;b&gt;OPEN&lt;/b&gt; 됩니다.&lt;/li&gt; 
 &lt;li&gt;이후 요청부터는 &lt;b&gt;날씨 API를 호출조차 하지 않고&lt;/b&gt;, 바로 &lt;code&gt;getFallbackWeather&lt;/code&gt;가 실행되어 &quot;맑음 (기본값)&quot;을 리턴합니다.&lt;/li&gt; 
 &lt;li&gt;사용자는 &quot;서버 오류&quot; 화면 대신, 조금 부정확하더라도 날씨 정보를 볼 수 있습니다. &lt;b&gt;(서비스 생존!)&lt;/b&gt;&lt;/li&gt; 
&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 모니터링 (Actuator 연동)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;서킷 브레이커가 열렸는지 닫혔는지 어떻게 알까요?&lt;br&gt;스프링 부트 &lt;b&gt;Actuator&lt;/b&gt;를 통해 실시간으로 상태를 볼 수 있습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;접속 주소: &lt;code&gt;http://localhost:8080/actuator/health&lt;/code&gt;&lt;/li&gt; 
&lt;/ul&gt;&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;status&quot;: &quot;UP&quot;,
  &quot;components&quot;: {
    &quot;circuitBreakers&quot;: {
      &quot;status&quot;: &quot;UP&quot;,
      &quot;details&quot;: {
        &quot;my-weather-api&quot;: {
          &quot;status&quot;: &quot;CLOSED&quot;, // 현재 상태 (정상)
          &quot;failureRate&quot;: &quot;-1.0%&quot;,
          &quot;bufferedCalls&quot;: 0
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 프로메테우스+그라파나에 연결하면, &lt;b&gt;&quot;서킷 브레이커 열림 알림&quot;&lt;/b&gt;을 슬랙으로 받을 수도 있겠죠?&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 결론입니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;외부 API는 &lt;b&gt;언제든 죽을 수 있다&lt;/b&gt;고 가정해야 한다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;서킷 브레이커&lt;/b&gt;는 외부 장애가 내 서버로 번지는 것을 막아주는 방화벽이다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Fallback(대안)&lt;/b&gt;을 잘 준비하면, 장애 상황에서도 최소한의 서비스는 유지할 수 있다.&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러분의 서버는 주변 서버들이 다 죽어나가도 혼자 꿋꿋하게 살아남는 &lt;b&gt;생존왕&lt;/b&gt;이 되었습니다.&lt;br&gt;다음 포스팅에서는 &lt;b&gt;&quot;사용자가 실수로 결제 버튼을 두 번 눌렀어요!&quot; 중복 요청을 막아주는 멱등성(Idempotency) 설계와 Redis 활용법&lt;/b&gt;에 대해 알아보겠습니다. (돈과 관련된 중요한 문제입니다!)&lt;br&gt;도움이 되셨다면 좋아요와 댓글 부탁드립니다!  &lt;/p&gt;</description>
      <category>IT/SpringBoot</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1258</guid>
      <comments>https://hianna.tistory.com/1258#entry1258comment</comments>
      <pubDate>Fri, 27 Mar 2026 00:51:29 +0900</pubDate>
    </item>
  </channel>
</rss>