<?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>Fri, 7 Aug 2026 22:54:24 +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>clsx로 React 조건부 클래스 깔끔하게 관리하기</title>
      <link>https://hianna.tistory.com/1342</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며: 어떤 문제를 해결하는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트를 만들다 보면 props나 state 값에 따라 className을 다르게 줘야 하는 경우가 정말 많습니다. 처음에는 템플릿 리터럴이나 삼항 연산자로 충분해 보이지만, 조건이 두세 개만 넘어가도 코드가 금방 지저분해집니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;// 조건이 늘어날수록 읽기 힘들어지는 예
function Button({ variant, disabled, fullWidth }) {
  return (
    &amp;lt;button
      className={
        'px-4 py-2 rounded-lg font-medium ' +
        (variant === 'primary' ? 'bg-blue-500 text-white ' : '') +
        (variant === 'secondary' ? 'bg-gray-200 text-gray-800 ' : '') +
        (disabled ? 'opacity-50 cursor-not-allowed ' : '') +
        (fullWidth ? 'w-full' : '')
      }
    &amp;gt;
      버튼
    &amp;lt;/button&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 연결 사이에 공백을 빠뜨리거나, 조건이 &lt;code&gt;false&lt;/code&gt;일 때 빈 문자열을 제대로 처리하는지 신경 쓰다 보면 정작 스타일 로직보다 문자열 조립에 더 많은 신경을 쓰게 됩니다. &lt;code&gt;clsx&lt;/code&gt;는 바로 이 문제를 해결하기 위한, 아주 작지만 실용적인 유틸리티 라이브러리입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 설치하기&lt;/h2&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install clsx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들 크기가 300바이트 남짓으로 매우 작아서 부담 없이 추가할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기본 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;clsx&lt;/code&gt;는 다양한 타입의 인자를 받아서, 유효한(truthy) 값만 골라 공백으로 이어 붙인 하나의 문자열을 반환합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자열과 조건식&lt;/h3&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import clsx from 'clsx'

clsx('base', true &amp;amp;&amp;amp; 'active', false &amp;amp;&amp;amp; 'hidden')
// &amp;rarr; 'base active'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 연산자를 활용하면 조건이 &lt;code&gt;false&lt;/code&gt;일 때 자동으로 무시됩니다. &lt;code&gt;undefined&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;''&lt;/code&gt; 같은 falsy 값도 마찬가지로 걸러집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체 문법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건이 여러 개일 때는 객체 형태가 더 읽기 좋습니다. key가 클래스명, value가 조건(boolean)입니다.&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;clsx({
  'bg-blue-500': variant === 'primary',
  'bg-gray-200': variant === 'secondary',
  'opacity-50 cursor-not-allowed': disabled,
})&lt;/code&gt;&lt;/pre&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;clsx(['base', 'flex'], { 'w-full': fullWidth }, disabled &amp;amp;&amp;amp; 'opacity-50')&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. React 컴포넌트에서 활용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 예로 든 지저분한 버튼 컴포넌트를 &lt;code&gt;clsx&lt;/code&gt;로 다시 작성하면 이렇게 정리됩니다.&lt;/p&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;import clsx from 'clsx'

function Button({ variant, disabled, fullWidth, children }) {
  return (
    &amp;lt;button
      className={clsx(
        'px-4 py-2 rounded-lg font-medium transition',
        variant === 'primary' &amp;amp;&amp;amp; 'bg-blue-500 text-white hover:bg-blue-600',
        variant === 'secondary' &amp;amp;&amp;amp; 'bg-gray-200 text-gray-800 hover:bg-gray-300',
        disabled &amp;amp;&amp;amp; 'opacity-50 cursor-not-allowed',
        fullWidth &amp;amp;&amp;amp; 'w-full'
      )}
      disabled={disabled}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건 하나하나가 한 줄씩 명확하게 나열되기 때문에, 어떤 상황에 어떤 클래스가 적용되는지 한눈에 파악할 수 있습니다. 조건을 추가하거나 삭제할 때도 해당 줄만 건드리면 되어 유지보수가 쉬워집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. classnames 라이브러리와의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;clsx&lt;/code&gt; 이전부터 널리 쓰이던 비슷한 라이브러리로 &lt;code&gt;classnames&lt;/code&gt;가 있습니다. API는 거의 동일해서 대부분의 코드는 import문만 바꾸면 그대로 호환됩니다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;// classnames
import classNames from 'classnames'
classNames('base', { active: true })

// clsx
import clsx from 'clsx'
clsx('base', { active: true })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 차이는 번들 크기와 실행 속도입니다. &lt;code&gt;clsx&lt;/code&gt;는 기능을 최소한으로 좁히는 대신 훨씬 가볍고 빠르게 동작하도록 설계되어 있어서, 신규 프로젝트라면 &lt;code&gt;clsx&lt;/code&gt;가 더 널리 권장되는 선택지입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. tailwind-merge와 함께 쓰기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;clsx&lt;/code&gt;는 조건에 따라 클래스를 조합하는 역할만 할 뿐, Tailwind 클래스끼리의 충돌(예: &lt;code&gt;p-4&lt;/code&gt;와 &lt;code&gt;p-8&lt;/code&gt;이 동시에 존재하는 경우)은 해결해주지 않습니다. 이 부분은 &lt;code&gt;tailwind-merge&lt;/code&gt;의 역할이라, 둘을 함께 묶어 쓰는 &lt;code&gt;cn&lt;/code&gt; 헬퍼 함수 패턴이 실무에서 널리 쓰입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'

function cn(...inputs) {
  return twMerge(clsx(...inputs))
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Card({ className, children }) {
  return (
    &amp;lt;div className={cn('p-4 bg-white rounded-lg shadow-md', className)}&amp;gt;
      {children}
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;clsx&lt;/code&gt;가 조건부 조합을 만들고, &lt;code&gt;twMerge&lt;/code&gt;가 그 결과에서 충돌하는 클래스를 정리해주는 구조입니다. 이 조합은 shadcn/ui를 비롯한 많은 컴포넌트 라이브러리에서 표준처럼 사용되고 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;clsx&lt;/code&gt;는 크기도 작고 배우는 데 5분도 걸리지 않는 라이브러리지만, React + Tailwind 조합으로 개발할 때 조건부 스타일링 코드를 눈에 띄게 깔끔하게 만들어줍니다. 문자열 템플릿이나 중첩 삼항 연산자로 className을 조립하고 있다면, &lt;code&gt;clsx&lt;/code&gt; 도입만으로도 가독성이 크게 개선될 겁니다. 여기에 &lt;code&gt;tailwind-merge&lt;/code&gt;까지 더한 &lt;code&gt;cn&lt;/code&gt; 헬퍼 패턴을 프로젝트 초반에 만들어두면, 이후 컴포넌트를 작성할 때마다 매번 재사용할 수 있습니다.&lt;/p&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1342</guid>
      <comments>https://hianna.tistory.com/1342#entry1342comment</comments>
      <pubDate>Fri, 7 Aug 2026 14:51:26 +0900</pubDate>
    </item>
    <item>
      <title>tailwind-merge로 Tailwind 클래스 충돌 깔끔하게 해결하기</title>
      <link>https://hianna.tistory.com/1341</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며: 어떤 문제를 해결하는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailwind CSS로 컴포넌트를 만들다 보면 이런 상황을 자주 마주칩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Box({ className }) {
  return &amp;lt;div className={`p-4 bg-white rounded-lg ${className}`}&amp;gt;내용&amp;lt;/div&amp;gt;
}

// 사용하는 쪽에서 padding을 다르게 주고 싶어서
&amp;lt;Box className=&quot;p-8&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 문자열을 단순히 이어 붙이면 최종 className은 &lt;code&gt;p-4 bg-white rounded-lg p-8&lt;/code&gt;이 됩니다. 문제는 CSS에서 &lt;code&gt;p-4&lt;/code&gt;와 &lt;code&gt;p-8&lt;/code&gt;이 같은 우선순위를 가진 클래스라서, &lt;b&gt;어떤 게 이길지는 CSS가 실제로 로드되는 순서에 달려있다&lt;/b&gt;는 점입니다. Tailwind가 빌드 시 클래스를 어떤 순서로 정렬하느냐에 따라 &lt;code&gt;p-4&lt;/code&gt;가 이길 수도, &lt;code&gt;p-8&lt;/code&gt;이 이길 수도 있어서 예측이 어렵고 버그로 이어지기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;tailwind-merge&lt;/code&gt;는 바로 이 문제, 즉 &lt;b&gt;같은 CSS 속성을 건드리는 Tailwind 클래스끼리 충돌할 때 뒤에 오는 값으로 안전하게 덮어쓰는&lt;/b&gt; 역할을 해주는 라이브러리입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 설치하기&lt;/h2&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;npm install tailwind-merge&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기본 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 함수는 &lt;code&gt;twMerge&lt;/code&gt; 하나입니다. 문자열로 된 클래스 목록을 인자로 넘기면, 충돌하는 클래스를 정리한 하나의 문자열을 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;import { twMerge } from 'tailwind-merge'

twMerge('p-4 bg-white', 'p-8')
// &amp;rarr; 'bg-white p-8'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;p-4&lt;/code&gt;와 &lt;code&gt;p-8&lt;/code&gt;은 둘 다 padding을 지정하는 클래스라서 충돌로 인식되고, 뒤에 온 &lt;code&gt;p-8&lt;/code&gt;이 살아남습니다. 반면 서로 다른 속성을 건드리는 클래스는 충돌 없이 그대로 유지됩니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;twMerge('p-4 text-black', 'bg-white')
// &amp;rarr; 'p-4 text-black bg-white'  (충돌 없음, 그대로 합쳐짐)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 문자열 템플릿과 비교하면 차이가 명확합니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;// 문자열 템플릿: 충돌 클래스가 그대로 둘 다 남음
`p-4 bg-white rounded-lg ${className}` 
// &amp;rarr; 'p-4 bg-white rounded-lg p-8'  (어느 쪽이 이길지 예측 어려움)

// twMerge: 충돌 클래스를 인식해서 뒤엣것만 남김
twMerge('p-4 bg-white rounded-lg', className)
// &amp;rarr; 'bg-white rounded-lg p-8'  (항상 p-8이 적용됨)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. React 컴포넌트에서 활용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔한 활용 패턴은, 재사용 가능한 컴포넌트를 만들 때 &lt;b&gt;기본 스타일은 컴포넌트가 갖고, 사용하는 쪽에서 필요하면 일부를 덮어쓸 수 있게&lt;/b&gt; 열어두는 것입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;import { twMerge } from 'tailwind-merge'

function Card({ className, children }) {
  return (
    &amp;lt;div className={twMerge('p-4 bg-white rounded-lg shadow-md', className)}&amp;gt;
      {children}
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;// 기본 스타일 그대로 사용
&amp;lt;Card&amp;gt;기본 카드&amp;lt;/Card&amp;gt;

// padding과 그림자만 다르게 덮어쓰기
&amp;lt;Card className=&quot;p-8 shadow-xl&quot;&amp;gt;강조 카드&amp;lt;/Card&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 &lt;code&gt;Card&lt;/code&gt; 컴포넌트를 쓰는 쪽에서 &lt;code&gt;className&lt;/code&gt;을 넘기기만 하면, 나머지 스타일은 유지한 채 원하는 부분만 안전하게 교체됩니다. &lt;code&gt;props&lt;/code&gt;로 스타일 오버라이드를 허용하는 디자인 시스템 컴포넌트를 만들 때 특히 유용합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. clsx와 함께 쓰기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건부로 클래스를 조합해야 하는 경우가 많기 때문에, 실무에서는 &lt;code&gt;clsx&lt;/code&gt;와 &lt;code&gt;tailwind-merge&lt;/code&gt;를 함께 쓰는 경우가 대부분입니다. &lt;code&gt;clsx&lt;/code&gt;로 조건에 따른 클래스 조합을 만들고, 그 결과를 &lt;code&gt;twMerge&lt;/code&gt;로 한 번 더 정리하는 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'

function cn(...inputs) {
  return twMerge(clsx(...inputs))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만든 &lt;code&gt;cn&lt;/code&gt; 헬퍼 함수는 shadcn/ui를 비롯한 많은 React 컴포넌트 라이브러리에서 표준처럼 사용되는 패턴입니다.&lt;/p&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;function Button({ variant, className, disabled, children }) {
  return (
    &amp;lt;button
      className={cn(
        'px-4 py-2 rounded-lg font-medium transition',
        variant === 'primary' &amp;amp;&amp;amp; 'bg-blue-500 text-white',
        variant === 'secondary' &amp;amp;&amp;amp; 'bg-gray-200 text-gray-800',
        disabled &amp;amp;&amp;amp; 'opacity-50 cursor-not-allowed',
        className // 외부에서 넘긴 클래스가 항상 안전하게 마지막에 병합됨
      )}
      disabled={disabled}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건부 로직은 &lt;code&gt;clsx&lt;/code&gt;가 처리하고, 그 결과에서 발생할 수 있는 클래스 충돌은 &lt;code&gt;twMerge&lt;/code&gt;가 정리해주기 때문에 두 라이브러리의 역할이 명확하게 나뉩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 동작 원리 간단히 살펴보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;tailwind-merge&lt;/code&gt;는 Tailwind의 클래스 이름을 분석해서 어떤 CSS 속성 그룹에 속하는지 내부적으로 매핑하고 있습니다. 예를 들어 &lt;code&gt;p-*&lt;/code&gt;, &lt;code&gt;px-*&lt;/code&gt;, &lt;code&gt;pt-*&lt;/code&gt;는 모두 padding 관련 그룹으로 인식되기 때문에, 서로 겹치는 범위라면 충돌로 판단합니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;twMerge('px-4 py-2', 'p-8')
// &amp;rarr; 'p-8'  (px, py를 모두 포함하는 p로 통합)

twMerge('p-4', 'px-8')
// &amp;rarr; 'p-4 px-8'  (다른 축이므로 병합해서 유지: 세로는 p-4, 가로는 px-8 적용)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 반응형 접두사(&lt;code&gt;md:&lt;/code&gt;, &lt;code&gt;lg:&lt;/code&gt;)나 상태 접두사(&lt;code&gt;hover:&lt;/code&gt;, &lt;code&gt;focus:&lt;/code&gt;)가 붙은 클래스는 접두사가 같을 때만 충돌로 판단하기 때문에, 아래처럼 브레이크포인트별로 다른 값을 안전하게 유지할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;twMerge('p-4 md:p-8', 'p-6')
// &amp;rarr; 'md:p-8 p-6'  (md: 접두사가 없는 p-6만 기본 p-4를 덮어씀)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;tailwind-merge&lt;/code&gt;는 화려한 기능을 가진 라이브러리는 아니지만, Tailwind로 재사용 가능한 컴포넌트를 만들 때 발생하는 클래스 충돌 문제를 근본적으로 해결해줍니다. 컴포넌트 라이브러리를 만들거나, &lt;code&gt;className&lt;/code&gt; prop으로 스타일 오버라이드를 지원하는 컴포넌트가 하나라도 있다면 도입을 고려해볼 만합니다. 특히 &lt;code&gt;clsx&lt;/code&gt;와 함께 &lt;code&gt;cn&lt;/code&gt; 헬퍼로 묶어서 쓰는 패턴은 실무에서 사실상 표준처럼 자리 잡았으니, 프로젝트 초기에 유틸 함수로 미리 만들어두는 것을 추천합니다.&lt;/p&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1341</guid>
      <comments>https://hianna.tistory.com/1341#entry1341comment</comments>
      <pubDate>Fri, 7 Aug 2026 14:51:10 +0900</pubDate>
    </item>
    <item>
      <title>React에서 Tailwind CSS 제대로 활용하기 (설치, 활용)</title>
      <link>https://hianna.tistory.com/1340</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 들어가며&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tailwind CSS란 무엇인가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailwind CSS는 미리 정의된 유틸리티 클래스를 조합해서 스타일을 만드는 &lt;b&gt;utility-first CSS 프레임워크&lt;/b&gt;입니다. Bootstrap처럼 완성된 컴포넌트(버튼, 카드 등)를 제공하는 대신, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;p-4&lt;/code&gt;, &lt;code&gt;text-center&lt;/code&gt;, &lt;code&gt;bg-blue-500&lt;/code&gt; 같은 작은 단위의 클래스를 조합해서 원하는 디자인을 직접 만들어가는 방식이에요.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;&amp;lt;button className=&quot;px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600&quot;&amp;gt;
  버튼
&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 CSS 파일을 오가지 않고 마크업 안에서 스타일을 완결할 수 있다는 게 가장 큰 특징입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 React와 함께 많이 쓰이는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 컴포넌트 단위로 UI를 쪼개서 개발합니다. 이때 CSS도 컴포넌트 단위로 캡슐화되어야 관리가 편한데, Tailwind는 클래스 자체가 컴포넌트의 스타일 명세서 역할을 하기 때문에 궁합이 좋습니다.&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;.btn-primary-active-large&lt;/code&gt; 같은 네이밍 지옥 탈출)&lt;/li&gt;
&lt;li&gt;컴포넌트 파일 하나만 봐도 스타일까지 한눈에 파악 가능&lt;/li&gt;
&lt;li&gt;컴포넌트를 재사용할 때 스타일도 함께 따라옴&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Tailwind CSS 설치 및 설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Vite 프로젝트에 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 널리 쓰이는 Vite 기준 설치 과정입니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;npm create vite@latest my-app -- --template react
cd my-app
npm install tailwindcss @tailwindcss/vite&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;vite.config.js&lt;/code&gt;에 플러그인을 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;src/index.css&lt;/code&gt;에 아래 한 줄만 추가하면 준비 끝입니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@import &quot;tailwindcss&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Next.js 프로젝트에 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 &lt;code&gt;create-next-app&lt;/code&gt; 실행 시 Tailwind 사용 여부를 물어보는 옵션이 있어 처음부터 설정할 수 있습니다. 이미 만든 프로젝트라면 위와 비슷하게 패키지를 설치하고 &lt;code&gt;globals.css&lt;/code&gt;에 &lt;code&gt;@import &quot;tailwindcss&quot;;&lt;/code&gt;를 추가하면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기본 사용법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유틸리티 클래스 개념 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailwind의 클래스는 대부분 CSS 속성 하나에 대응합니다.&lt;/p&gt;
&lt;table style=&quot;height: 135px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&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: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;flex&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;display: flex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;p-4&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;padding: 1rem&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;text-lg&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;font-size: 1.125rem&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;bg-red-500&lt;/code&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;code&gt;rounded-md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;border-radius&lt;/code&gt; 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자(&lt;code&gt;4&lt;/code&gt;, &lt;code&gt;8&lt;/code&gt; 등)는 대부분 &lt;code&gt;0.25rem&lt;/code&gt; 단위의 간격 스케일을 따릅니다. &lt;code&gt;p-4&lt;/code&gt;는 &lt;code&gt;1rem&lt;/code&gt;, &lt;code&gt;p-8&lt;/code&gt;은 &lt;code&gt;2rem&lt;/code&gt;이 되는 식입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자주 쓰는 클래스 조합 예시&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Card() {
  return (
    &amp;lt;div className=&quot;max-w-sm mx-auto p-6 bg-white rounded-xl shadow-md&quot;&amp;gt;
      &amp;lt;h2 className=&quot;text-xl font-bold text-gray-800 mb-2&quot;&amp;gt;카드 제목&amp;lt;/h2&amp;gt;
      &amp;lt;p className=&quot;text-gray-600 leading-relaxed&quot;&amp;gt;
        카드 본문 내용이 들어가는 자리입니다.
      &amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃(&lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;grid&lt;/code&gt;), 간격(&lt;code&gt;p-&lt;/code&gt;, &lt;code&gt;m-&lt;/code&gt;, &lt;code&gt;gap-&lt;/code&gt;), 타이포그래피(&lt;code&gt;text-&lt;/code&gt;, &lt;code&gt;font-&lt;/code&gt;), 색상(&lt;code&gt;bg-&lt;/code&gt;, &lt;code&gt;text-&lt;/code&gt;, &lt;code&gt;border-&lt;/code&gt;) 이 네 가지 카테고리만 익혀도 웬만한 화면은 만들 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. React 컴포넌트에 Tailwind 적용하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JSX에서 className 다루기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 &lt;code&gt;class&lt;/code&gt; 대신 &lt;code&gt;className&lt;/code&gt;을 사용한다는 점만 주의하면 HTML과 동일하게 사용할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조건부 스타일링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props나 state에 따라 클래스를 다르게 주고 싶을 때가 많습니다. 삼항 연산자로도 가능하지만, 조건이 늘어나면 가독성이 떨어지기 때문에 &lt;code&gt;clsx&lt;/code&gt; 같은 라이브러리를 함께 쓰는 걸 추천합니다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install clsx&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;import clsx from 'clsx'

function Button({ variant, disabled, children }) {
  return (
    &amp;lt;button
      className={clsx(
        // 항상 적용되는 기본 스타일
        'px-4 py-2 rounded-lg font-medium transition',
        // variant가 'primary'일 때만 적용
        variant === 'primary' &amp;amp;&amp;amp; 'bg-blue-500 text-white hover:bg-blue-600',
        // variant가 'secondary'일 때만 적용
        variant === 'secondary' &amp;amp;&amp;amp; 'bg-gray-200 text-gray-800 hover:bg-gray-300',
        // disabled가 true일 때만 추가로 적용 (falsy면 무시됨)
        disabled &amp;amp;&amp;amp; 'opacity-50 cursor-not-allowed'
      )}
      disabled={disabled}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;clsx&lt;/code&gt;는 인자로 전달된 값들을 순서대로 검사해서, &lt;code&gt;false&lt;/code&gt;나 &lt;code&gt;undefined&lt;/code&gt;처럼 falsy한 값은 무시하고 문자열만 골라 하나의 className으로 합쳐줍니다. 조건마다 삼항 연산자를 겹겹이 쓰는 대신 &lt;code&gt;조건 &amp;amp;&amp;amp; '클래스명'&lt;/code&gt; 형태로 나열만 하면 되기 때문에 조건이 많아져도 가독성이 유지됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;재사용 가능한 컴포넌트로 스타일 추출하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 스타일 조합이 여러 곳에서 반복된다면, 그때는 컴포넌트로 분리하는 게 Tailwind에서 코드 중복을 관리하는 정석적인 방법입니다. CSS를 추출하기보다 &lt;b&gt;컴포넌트를 추출&lt;/b&gt;한다고 생각하면 이해가 쉽습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 반응형 디자인 구현&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브레이크포인트 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailwind는 모바일 우선(mobile-first) 방식입니다. 접두사가 없는 클래스는 모든 화면에 적용되고, &lt;code&gt;sm:&lt;/code&gt;, &lt;code&gt;md:&lt;/code&gt;, &lt;code&gt;lg:&lt;/code&gt;, &lt;code&gt;xl:&lt;/code&gt;, &lt;code&gt;2xl:&lt;/code&gt; 접두사를 붙이면 해당 크기 이상에서만 적용됩니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;접두사&lt;/th&gt;
&lt;th&gt;최소 너비&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;640px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;768px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1024px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1280px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2xl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1536px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실전 예제&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function ResponsiveGrid() {
  return (
    &amp;lt;div className=&quot;grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4&quot;&amp;gt;
      &amp;lt;div className=&quot;bg-blue-100 p-4 rounded-lg&quot;&amp;gt;아이템 1&amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;bg-blue-100 p-4 rounded-lg&quot;&amp;gt;아이템 2&amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;bg-blue-100 p-4 rounded-lg&quot;&amp;gt;아이템 3&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일에서는 1열, 태블릿(&lt;code&gt;sm&lt;/code&gt; 이상)에서는 2열, 데스크톱(&lt;code&gt;lg&lt;/code&gt; 이상)에서는 3열로 자동 변경됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 다크 모드 지원&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dark: 접두사 활용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;dark:&lt;/code&gt; 접두사를 클래스 앞에 붙이면 다크 모드일 때만 적용되는 스타일을 지정할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;div className=&quot;bg-white dark:bg-gray-900 text-gray-900 dark:text-white p-6&quot;&amp;gt;
  다크모드 대응 컴포넌트
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;React state와 연동한 토글 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 시스템 설정을 따르지만, 사용자가 직접 토글할 수 있게 하려면 &lt;code&gt;class&lt;/code&gt; 전략을 사용하고 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 태그에 &lt;code&gt;dark&lt;/code&gt; 클래스를 붙였다 뗐다 하는 방식으로 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useEffect } from 'react'

function DarkModeToggle() {
  const [isDark, setIsDark] = useState(false)

  useEffect(() =&amp;gt; {
    document.documentElement.classList.toggle('dark', isDark)
  }, [isDark])

  return (
    &amp;lt;button
      onClick={() =&amp;gt; setIsDark(!isDark)}
      className=&quot;px-3 py-1.5 rounded-md bg-gray-200 dark:bg-gray-700 dark:text-white&quot;
    &amp;gt;
      {isDark ? '라이트 모드' : '다크 모드'}
    &amp;lt;/button&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 커스터마이징&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커스텀 색상&amp;middot;폰트&amp;middot;간격 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 버전에서는 CSS 파일 안에서 &lt;code&gt;@theme&lt;/code&gt; 블록으로 디자인 토큰을 직접 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;@import &quot;tailwindcss&quot;;

@theme {
  --color-brand: #6366f1;
  --font-display: &quot;Pretendard&quot;, sans-serif;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정의하면 &lt;code&gt;bg-brand&lt;/code&gt;, &lt;code&gt;font-display&lt;/code&gt; 같은 클래스를 바로 사용할 수 있습니다. &lt;code&gt;--color-brand&lt;/code&gt;처럼 &lt;code&gt;--color-*&lt;/code&gt; 네임스페이스로 정의한 변수는 &lt;code&gt;color-brand&lt;/code&gt;라는 클래스가 생기는 게 아니라, &lt;code&gt;bg-&lt;/code&gt;, &lt;code&gt;text-&lt;/code&gt;, &lt;code&gt;border-&lt;/code&gt; 등 색상 관련 유틸리티 뒤에 이름을 붙여 &lt;code&gt;bg-brand&lt;/code&gt;, &lt;code&gt;text-brand&lt;/code&gt;, &lt;code&gt;border-brand&lt;/code&gt; 형태로 사용됩니다. &lt;code&gt;--font-display&lt;/code&gt;도 같은 방식으로 &lt;code&gt;font-display&lt;/code&gt; 유틸리티 클래스에 연결됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@apply로 반복 스타일 관리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 자주 반복되는 조합은 &lt;code&gt;@apply&lt;/code&gt;로 별도 클래스에 묶어둘 수 있습니다. 다만 남용하면 utility-first의 장점이 사라지므로 꼭 필요한 곳에만 제한적으로 사용하는 걸 권장합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;.btn-primary {
  @apply px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만든 클래스는 일반 CSS 클래스와 똑같이 React 컴포넌트의 &lt;code&gt;className&lt;/code&gt;에 그대로 가져다 쓸 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function SubmitButton() {
  return &amp;lt;button className=&quot;btn-primary&quot;&amp;gt;제출하기&amp;lt;/button&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼, 인풋, 배지처럼 프로젝트 전반에서 반복되는 UI 요소의 기본 스타일을 이런 식으로 하나의 클래스에 묶어두면, 마크업이 짧아지고 디자인이 바뀔 때도 CSS 한 곳만 수정하면 되는 장점이 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 다른 스타일링 방식과 비교&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; 방식 &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Tailwind CSS&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;빠른 개발 속도, 일관된 디자인 시스템, 별도 파일 불필요&lt;/td&gt;
&lt;td&gt;초기 학습 곡선, JSX가 다소 길어짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;CSS Modules&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;순수 CSS 문법, 클래스 충돌 없음&lt;/td&gt;
&lt;td&gt;파일을 오가야 함, 네이밍 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;styled-components&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;JS 안에서 동적 스타일링 자유도 높음&lt;/td&gt;
&lt;td&gt;런타임 비용, 번들 크기 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답은 없습니다. 빠른 프로토타이핑이나 디자인 시스템 일관성이 중요하다면 Tailwind가, 컴포넌트 단위로 완전히 격리된 스타일이 필요하다면 CSS Modules가 더 유리할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 실전 팁 &amp;amp; 자주 하는 실수&lt;/h2&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;clsx&lt;/code&gt;나 &lt;code&gt;tailwind-merge&lt;/code&gt;를 활용해 조건부 로직을 분리하고, 반복되는 조합은 컴포넌트로 추출하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동적 클래스명 조합 주의&lt;/b&gt;: &lt;code&gt;`text-${color}-500`&lt;/code&gt; 처럼 문자열을 동적으로 조합하면 Tailwind가 해당 클래스를 스캔하지 못해 스타일이 누락될 수 있습니다. 가능하면 완전한 클래스명을 그대로 사용하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VSCode 확장 프로그램&lt;/b&gt;: &lt;code&gt;Tailwind CSS IntelliSense&lt;/code&gt;를 설치하면 클래스 자동완성, 색상 미리보기, 실시간 값 확인이 가능해 생산성이 크게 올라갑니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불필요한 CSS 최소화&lt;/b&gt;: 최신 버전은 사용된 클래스만 자동으로 스캔해서 빌드하므로, 별도 purge 설정 없이도 최종 번들이 가볍게 유지됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailwind CSS는 React의 컴포넌트 기반 철학과 잘 맞아떨어지는 스타일링 도구입니다. 처음에는 클래스명이 낯설고 마크업이 복잡해 보일 수 있지만, 익숙해지면 별도 CSS 파일을 오가지 않고도 빠르게 UI를 완성할 수 있다는 장점이 크게 느껴질 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에 적용할 때는 팀의 디자인 시스템, 프로젝트 규모, 협업 방식을 고려해서 CSS Modules나 styled-components와 비교해보고 선택하는 것을 추천합니다.&lt;/p&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1340</guid>
      <comments>https://hianna.tistory.com/1340#entry1340comment</comments>
      <pubDate>Fri, 7 Aug 2026 14:50:47 +0900</pubDate>
    </item>
    <item>
      <title>[ React ] useReducer 사용법: 복잡한 상태 로직을 체계적으로 관리하기</title>
      <link>https://hianna.tistory.com/1339</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useState&lt;/code&gt;를 쓰다 보면 상태가 점점 복잡해지는 순간이 옵니다. 상태 값이 여러 개로 늘어나고, 상태를 바꾸는 로직이 컴포넌트 곳곳에 흩어지기 시작하죠. 이럴 때 상태 관리를 한곳으로 모아 체계적으로 다룰 수 있게 해주는 것이 바로 &lt;b&gt;useReducer&lt;/b&gt; Hook입니다. 이 글에서는 useReducer가 무엇인지, useState와 어떻게 다른지, 언제 쓰면 좋은지 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useReducer란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useReducer는 &lt;b&gt;상태 업데이트 로직을 컴포넌트 바깥으로 분리&lt;/b&gt;해서 관리할 수 있게 해주는 Hook입니다. Redux를 써본 적이 있다면 익숙할 텐데, 사실 useReducer는 Redux의 핵심 개념을 React 내장 기능으로 가져온 것입니다.&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;state&lt;/b&gt;: 현재 상태 값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dispatch&lt;/b&gt;: &quot;이런 일이 일어났다&quot;고 알리는 함수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;reducer&lt;/b&gt;: 현재 상태와 action을 받아 새로운 상태를 계산하는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 셋이 어떻게 맞물리는지 예제로 살펴보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useState vs useReducer 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 익숙한 &lt;code&gt;useState&lt;/code&gt;로 만든 카운터를 봅시다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
      {/* 상태 변경 로직이 각 버튼에 흩어져 있음 */}
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;+1&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count - 1)}&amp;gt;-1&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(0)}&amp;gt;리셋&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 같은 기능을 &lt;code&gt;useReducer&lt;/code&gt;로 바꿔보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useReducer } from 'react';

// reducer 함수: 현재 상태(state)와 action을 받아 새 상태를 반환
// 상태를 어떻게 바꿀지에 대한 모든 로직이 여기 한곳에 모임
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      // 정의되지 않은 action이 들어오면 에러로 잡아냄
      throw new Error('알 수 없는 action입니다: ' + action.type);
  }
}

function Counter() {
  // useReducer(reducer, 초기상태)
  // state: 현재 상태 / dispatch: action을 보내는 함수
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{state.count}&amp;lt;/p&amp;gt;
      {/* dispatch로 &quot;무슨 일이 일어났는지&quot;만 알림 */}
      {/* 실제 상태 변경 방법은 reducer가 알아서 처리 */}
      &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'increment' })}&amp;gt;+1&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'decrement' })}&amp;gt;-1&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'reset' })}&amp;gt;리셋&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼핏 코드가 더 길어 보이지만, 중요한 차이가 있습니다. &lt;b&gt;상태를 바꾸는 모든 로직이 &lt;code&gt;reducer&lt;/code&gt; 함수 한곳에 모여 있다&lt;/b&gt;는 점입니다. 컴포넌트는 그저 &quot;increment 액션이 일어났어&quot;라고 &lt;code&gt;dispatch&lt;/code&gt;로 알리기만 하면 되고, 실제로 어떻게 값을 바꿀지는 reducer가 책임집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 흐름 이해하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useReducer의 데이터 흐름은 다음과 같습니다.&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;dispatch({ type: 'increment' })&lt;/code&gt;로 action을 보냄&lt;/li&gt;
&lt;li&gt;React가 &lt;code&gt;reducer(현재상태, action)&lt;/code&gt;을 호출&lt;/li&gt;
&lt;li&gt;reducer가 새로운 상태를 반환&lt;/li&gt;
&lt;li&gt;새 상태로 컴포넌트가 리렌더링&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;action&lt;/b&gt;은 &quot;무슨 일이 일어났는지&quot;를 설명하는 객체입니다. 보통 &lt;code&gt;type&lt;/code&gt; 속성으로 종류를 구분하고, 추가 데이터가 필요하면 &lt;code&gt;payload&lt;/code&gt;에 담습니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;// action 객체의 일반적인 형태
dispatch({ type: 'increment' });              // 데이터가 필요 없는 경우
dispatch({ type: 'setCount', payload: 10 });  // 추가 데이터가 필요한 경우&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전 예제: 할 일 목록(Todo) 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카운터처럼 단순한 상태라면 굳이 useReducer를 쓸 필요가 없습니다. useReducer의 진가는 &lt;b&gt;상태 구조가 복잡하고 업데이트 종류가 많을 때&lt;/b&gt; 드러납니다. 할 일 목록을 예로 들어보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useReducer } from 'react';

// 할 일 목록에 대한 모든 상태 변경 로직을 한곳에 정의
function todoReducer(state, action) {
  switch (action.type) {
    case 'add':
      // 새 할 일을 배열에 추가
      return [
        ...state,
        { id: Date.now(), text: action.payload, done: false }
      ];

    case 'toggle':
      // 특정 id의 할 일 완료 상태를 반전
      return state.map(todo =&amp;gt;
        todo.id === action.payload
          ? { ...todo, done: !todo.done }
          : todo
      );

    case 'delete':
      // 특정 id의 할 일을 목록에서 제거
      return state.filter(todo =&amp;gt; todo.id !== action.payload);

    default:
      throw new Error('알 수 없는 action: ' + action.type);
  }
}

function TodoApp() {
  // 초기 상태는 빈 배열
  const [todos, dispatch] = useReducer(todoReducer, []);

  const addTodo = (text) =&amp;gt; {
    // &quot;할 일 추가&quot; 액션과 함께 입력한 텍스트를 payload로 전달
    dispatch({ type: 'add', payload: text });
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; addTodo('React 공부하기')}&amp;gt;
        할 일 추가
      &amp;lt;/button&amp;gt;

      &amp;lt;ul&amp;gt;
        {todos.map(todo =&amp;gt; (
          &amp;lt;li key={todo.id}&amp;gt;
            {/* 클릭하면 해당 id의 완료 상태를 토글 */}
            &amp;lt;span
              onClick={() =&amp;gt; dispatch({ type: 'toggle', payload: todo.id })}
              style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
            &amp;gt;
              {todo.text}
            &amp;lt;/span&amp;gt;
            {/* 삭제 버튼: 해당 id를 payload로 전달 */}
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'delete', payload: todo.id })}&amp;gt;
              삭제
            &amp;lt;/button&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 로직을 전부 &lt;code&gt;useState&lt;/code&gt;로 작성했다면, 추가/토글/삭제 로직이 컴포넌트 여기저기에 흩어져 코드를 읽기 어려웠을 것입니다. useReducer를 쓰면 &quot;상태가 어떤 방식으로 바뀔 수 있는가&quot;를 reducer 함수만 보면 한눈에 파악할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useState vs useReducer, 언제 무엇을 쓸까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 Hook의 차이를 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;table style=&quot;height: 108px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; useState &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; useReducer &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&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;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;컴포넌트 안에 흩어짐&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;reducer 함수 한곳에 모임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&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;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;reducer만 따로 테스트하기 쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 신호가 보이면 useReducer 도입을 고려해보세요.&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;/li&gt;
&lt;li&gt;&lt;code&gt;useState&lt;/code&gt;가 너무 많아져서 관리하기 힘들 때&lt;/li&gt;
&lt;li&gt;상태 변경 방식(action)이 여러 종류일 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 단순한 토글이나 입력값 하나 정도라면 &lt;code&gt;useState&lt;/code&gt;가 더 간결하고 적절합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Context와 함께 쓰기: 미니 상태 관리 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useReducer는 &lt;a href=&quot;#&quot;&gt;Context API&lt;/a&gt;와 결합하면 진짜 강력해집니다. Context로 상태와 dispatch를 앱 전체에 뿌리면, Redux 없이도 전역 상태 관리를 구현할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { createContext, useContext, useReducer } from 'react';

const TodoContext = createContext();

// Provider 컴포넌트: reducer로 만든 상태와 dispatch를 하위에 공유
function TodoProvider({ children }) {
  const [todos, dispatch] = useReducer(todoReducer, []);

  return (
    // state와 dispatch를 함께 value로 전달
    &amp;lt;TodoContext.Provider value={{ todos, dispatch }}&amp;gt;
      {children}
    &amp;lt;/TodoContext.Provider&amp;gt;
  );
}

// 커스텀 Hook으로 편하게 꺼내 쓰기
function useTodos() {
  return useContext(TodoContext);
}

// 이제 어느 컴포넌트에서든 아래처럼 사용 가능
function AddButton() {
  const { dispatch } = useTodos();
  return (
    &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'add', payload: '새 할 일' })}&amp;gt;
      추가
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴은 소규모~중규모 앱에서 Redux 같은 외부 라이브러리 없이도 충분히 훌륭한 전역 상태 관리 역할을 해냅니다. 실제로 많은 프로젝트가 &quot;Context + useReducer&quot; 조합으로 상태 관리를 해결합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;reducer 작성 시 주의할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reducer 함수를 작성할 때는 몇 가지 원칙을 지켜야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 순수 함수로 작성하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reducer는 &lt;b&gt;같은 입력에 대해 항상 같은 출력&lt;/b&gt;을 반환하는 순수 함수여야 합니다. API 호출, 랜덤 값 생성, 타이머 같은 부수 효과(side effect)를 reducer 안에 넣으면 안 됩니다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;// ❌ reducer 안에서 부수 효과를 일으키면 안 됨
function reducer(state, action) {
  fetch('/api/save'); // API 호출은 reducer 밖에서!
  return { ...state };
}&lt;/code&gt;&lt;/pre&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;pre class=&quot;pf&quot;&gt;&lt;code&gt;// ❌ 기존 state를 직접 수정 (불변성 위반)
function reducer(state, action) {
  state.count += 1; // 직접 변경하면 안 됨
  return state;
}

// ✅ 새로운 객체를 만들어 반환
function reducer(state, action) {
  return { ...state, count: state.count + 1 };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 상태 객체의 참조가 바뀌었는지로 변경을 감지하기 때문에, 불변성을 지키지 않으면 화면이 제대로 갱신되지 않을 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useReducer는 복잡한 상태 로직을 reducer 함수 한곳으로 모아 체계적으로 관리하게 해주는 Hook입니다. &lt;code&gt;useState&lt;/code&gt;가 감당하기 버거워지는 순간, 즉 상태가 복잡하고 업데이트 종류가 많아질 때 진가를 발휘합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심을 정리하면 이렇습니다. &lt;b&gt;dispatch로 &quot;무슨 일이 일어났는지&quot;를 알리면, reducer가 &quot;상태를 어떻게 바꿀지&quot;를 책임진다.&lt;/b&gt; 이 역할 분리 덕분에 상태 관리가 예측 가능해지고, reducer만 따로 테스트하기도 쉬워집니다. 여기에 Context를 더하면 외부 라이브러리 없이도 훌륭한 전역 상태 관리 시스템을 만들 수 있으니, useState에 익숙해졌다면 다음 단계로 꼭 익혀두길 추천합니다.&lt;/p&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1339</guid>
      <comments>https://hianna.tistory.com/1339#entry1339comment</comments>
      <pubDate>Fri, 7 Aug 2026 14:50:05 +0900</pubDate>
    </item>
    <item>
      <title>[ React ] useCallback 사용법: 함수를 메모이제이션해 리렌더링 최적화하기</title>
      <link>https://hianna.tistory.com/1338</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 &quot;useCallback은 useMemo로 함수를 감싼 것과 같다&quot;고 잠깐 언급했었죠. 이번 글에서는 그 이야기를 이어서 useCallback을 자세히 다뤄보겠습니다. useCallback은 함수를 메모이제이션하는 Hook으로, &lt;code&gt;React.memo&lt;/code&gt;와 함께 쓰일 때 진가를 발휘합니다. 이 글에서는 useCallback이 왜 필요한지, 언제 써야 하는지 예제와 함께 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제의 시작: 함수는 매번 새로 만들어진다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 자바스크립트의 기본 성질부터 짚고 가야 합니다. 함수도 객체이기 때문에, 컴포넌트가 렌더링될 때마다 &lt;b&gt;함수는 매번 새로 생성&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Parent() {
  // 이 handleClick 함수는 Parent가 렌더링될 때마다 새로 만들어짐
  // 내용은 똑같지만, 자바스크립트 입장에서는 매번 &quot;다른 함수&quot;
  const handleClick = () =&amp;gt; {
    console.log('클릭!');
  };

  return &amp;lt;Child onClick={handleClick} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용이 완전히 같아도, 자바스크립트에서 함수는 새로 만들면 서로 다른 참조로 취급됩니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const fn1 = () =&amp;gt; console.log('hi');
const fn2 = () =&amp;gt; console.log('hi');
console.log(fn1 === fn2); // false! 내용은 같지만 다른 함수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소에는 이게 문제가 되지 않지만, &lt;code&gt;React.memo&lt;/code&gt;와 만나면 이야기가 달라집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;React.memo와 함께 보는 문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;React.memo&lt;/code&gt;는 props가 바뀌지 않으면 컴포넌트의 리렌더링을 건너뛰게 해주는 최적화 도구입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { memo } from 'react';

// React.memo로 감싸면, props가 바뀌지 않는 한 리렌더링을 건너뜀
const Child = memo(function Child({ onClick }) {
  console.log('Child 렌더링'); // 언제 렌더링되는지 확인용
  return &amp;lt;button onClick={onClick}&amp;gt;버튼&amp;lt;/button&amp;gt;;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 부모에 다른 상태가 있어서 리렌더링이 일어난다고 가정해봅시다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Parent() {
  const [count, setCount] = useState(0);

  // ❌ Parent가 리렌더링될 때마다 handleClick이 새로 생성됨
  //    Child에 넘기는 onClick의 참조값이 매번 달라짐
  const handleClick = () =&amp;gt; {
    console.log('클릭!');
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;카운트 증가&amp;lt;/button&amp;gt;

      {/* count만 바뀌었는데 Child도 매번 리렌더링됨 */}
      {/* 왜냐면 handleClick의 참조가 매번 새롭기 때문 */}
      &amp;lt;Child onClick={handleClick} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;count&lt;/code&gt;만 증가시켰을 뿐인데, &lt;code&gt;Child&lt;/code&gt;도 함께 리렌더링됩니다. &lt;code&gt;React.memo&lt;/code&gt;로 감쌌는데도 말이죠. 이유는 &lt;code&gt;handleClick&lt;/code&gt;이 매 렌더링마다 새로 생성되어 참조값이 달라지고, &lt;code&gt;React.memo&lt;/code&gt;가 &quot;props가 바뀌었다&quot;고 판단하기 때문입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useCallback으로 해결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 이 지점에서 &lt;b&gt;useCallback&lt;/b&gt;이 등장합니다. useCallback은 함수를 메모이제이션해서, 의존성이 바뀌지 않는 한 &lt;b&gt;같은 함수(같은 참조)를 재사용&lt;/b&gt;하게 해줍니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useCallback, memo } from 'react';

function Parent() {
  const [count, setCount] = useState(0);

  // ✅ useCallback으로 함수를 감쌈
  //    의존성 배열([])이 바뀌지 않는 한 항상 같은 함수를 반환
  //    &amp;rarr; Child에 넘기는 onClick의 참조가 유지됨
  const handleClick = useCallback(() =&amp;gt; {
    console.log('클릭!');
  }, []); // 의존성이 없으므로 함수는 최초 1회만 생성

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;카운트 증가&amp;lt;/button&amp;gt;

      {/* 이제 handleClick의 참조가 유지되므로 */}
      {/* count가 바뀌어도 Child는 리렌더링되지 않음 */}
      &amp;lt;Child onClick={handleClick} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;count&lt;/code&gt;를 아무리 증가시켜도 &lt;code&gt;Child&lt;/code&gt;는 리렌더링되지 않습니다. &lt;code&gt;handleClick&lt;/code&gt;의 참조가 유지되어 &lt;code&gt;React.memo&lt;/code&gt;가 &quot;props가 그대로다&quot;라고 판단하기 때문입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useCallback의 기본 문법&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const memoizedFn = useCallback(() =&amp;gt; {
  // 실행할 함수 내용
  doSomething(a, b);
}, [a, b]); // 의존성 배열&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;li&gt;&lt;b&gt;두 번째 인자&lt;/b&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 배열의 규칙은 useMemo, useEffect와 완전히 동일합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의존성 배열 다루기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 내부에서 사용하는 값은 의존성 배열에 넣어줘야 합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function SearchBox({ onSearch }) {
  const [keyword, setKeyword] = useState('');

  // keyword를 함수 내부에서 사용하므로 의존성 배열에 포함
  // keyword가 바뀌면 함수도 새로 생성됨 (최신 keyword를 반영하기 위해)
  const handleSearch = useCallback(() =&amp;gt; {
    onSearch(keyword);
  }, [keyword, onSearch]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        value={keyword}
        onChange={(e) =&amp;gt; setKeyword(e.target.value)}
      /&amp;gt;
      &amp;lt;button onClick={handleSearch}&amp;gt;검색&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 의존성 배열에서 &lt;code&gt;keyword&lt;/code&gt;를 빼먹으면, 함수가 처음 만들어질 때의 오래된 &lt;code&gt;keyword&lt;/code&gt;(빈 문자열)만 참조하는 버그가 생깁니다. 이를 &lt;b&gt;stale closure(오래된 클로저)&lt;/b&gt; 문제라고 부릅니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useCallback vs useMemo&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 다뤘듯, 이 둘은 형제 같은 관계입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// useMemo: 함수를 &quot;실행한 결과값&quot;을 메모이제이션
const value = useMemo(() =&amp;gt; computeValue(a, b), [a, b]);

// useCallback: 함수 &quot;그 자체&quot;를 메모이제이션
const fn = useCallback(() =&amp;gt; doSomething(a, b), [a, b]);

// 사실 useCallback(fn, deps)는 useMemo(() =&amp;gt; fn, deps)와 동일함
const fn = useMemo(() =&amp;gt; () =&amp;gt; doSomething(a, b), [a, b]);&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;height: 104px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; text-align: center;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center;&quot;&gt;useMemo&lt;/td&gt;
&lt;td style=&quot;height: 17px; text-align: center;&quot;&gt;&lt;b&gt; useCallback &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;메모이제이션 대상&lt;/td&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;b&gt;함수&lt;/b&gt; 자체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&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;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;무거운 계산 결과 캐싱&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;자식에 함수 props 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 문장으로: &lt;b&gt;결과값을 기억하고 싶으면 useMemo, 함수를 기억하고 싶으면 useCallback&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최적화 3종 세트: memo + useMemo + useCallback&lt;/h2&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;React.memo&lt;/b&gt;: 컴포넌트를 감싸서, props가 그대로면 리렌더링을 건너뜀&lt;/li&gt;
&lt;li&gt;&lt;b&gt;useMemo&lt;/b&gt;: 자식에게 넘기는 &lt;b&gt;객체/배열&lt;/b&gt;의 참조를 유지 (또는 무거운 계산 캐싱)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;useCallback&lt;/b&gt;: 자식에게 넘기는 &lt;b&gt;함수&lt;/b&gt;의 참조를 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;code&gt;React.memo&lt;/code&gt;로 자식의 리렌더링을 막고 싶다면, 그 자식에게 넘기는 &lt;b&gt;함수는 useCallback으로, 객체는 useMemo로&lt;/b&gt; 참조를 유지시켜줘야 비로소 최적화가 완성됩니다. 셋 중 하나라도 빠지면 최적화가 깨질 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const Child = memo(function Child({ data, onClick }) {
  return &amp;lt;div onClick={onClick}&amp;gt;{data.title}&amp;lt;/div&amp;gt;;
});

function Parent() {
  // 함수는 useCallback으로 참조 유지
  const handleClick = useCallback(() =&amp;gt; { /* ... */ }, []);

  // 객체는 useMemo로 참조 유지
  const data = useMemo(() =&amp;gt; ({ title: &quot;제목&quot; }), []);

  // 이제 세 요소가 맞물려 Child의 불필요한 리렌더링을 막음
  return &amp;lt;Child data={data} onClick={handleClick} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;남용하지 말 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo와 마찬가지로, useCallback도 공짜가 아닙니다. 함수를 저장하고 의존성을 비교하는 비용이 들기 때문에, &lt;b&gt;모든 함수에 붙이는 것은 오히려 손해&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// ❌ React.memo로 감싸지 않은 일반 컴포넌트에 넘기는 함수라면
//    useCallback을 써도 최적화 효과가 없고 오버헤드만 늘어남
const handleClick = useCallback(() =&amp;gt; {}, []);
&amp;lt;NormalChild onClick={handleClick} /&amp;gt;

// ✅ 그냥 일반 함수로 충분함
const handleClick = () =&amp;gt; {};
&amp;lt;NormalChild onClick={handleClick} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback이 실제로 의미가 있는 경우는 다음과 같습니다.&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;React.memo&lt;/code&gt;로 감싼 자식에게 함수를 props로 넘길 때&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;useEffect&lt;/code&gt;의 의존성 배열에 함수가 들어갈 때&lt;/b&gt; (함수 참조가 유지되어야 effect가 불필요하게 재실행되지 않음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커스텀 Hook이 반환하는 함수를 안정적으로 유지하고 싶을 때&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건에 해당하지 않는다면 굳이 useCallback을 쓸 필요가 없습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback은 함수를 메모이제이션해서 참조 동일성을 유지하는 Hook입니다. 그 자체로는 마법을 부리지 않고, &lt;b&gt;&lt;code&gt;React.memo&lt;/code&gt;나 &lt;code&gt;useEffect&lt;/code&gt;와 짝을 이룰 때&lt;/b&gt; 비로소 의미가 생긴다는 점이 핵심입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 이렇습니다. 함수는 렌더링마다 새로 생성되어 참조가 달라지고, 이것이 &lt;code&gt;React.memo&lt;/code&gt;의 최적화를 무력화시킵니다. useCallback은 이 참조를 붙잡아둠으로써 불필요한 리렌더링을 막아줍니다. 다만 아무 데나 붙이면 오히려 손해이니, &quot;이 함수가 정말 참조 유지가 필요한 곳으로 전달되는가?&quot;를 확인한 뒤 적용하는 것이 좋은 습관입니다.&lt;/p&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;Tip&lt;/b&gt;: useMemo 글에서도 언급했듯, React 19의 React Compiler는 이런 수동 메모이제이션을 상당 부분 자동화해줍니다. 하지만 원리를 이해하고 있어야 컴파일러가 없는 환경이나 예외 상황에 대응할 수 있으니, useCallback의 동작 방식은 확실히 익혀두는 것을 추천합니다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1338</guid>
      <comments>https://hianna.tistory.com/1338#entry1338comment</comments>
      <pubDate>Fri, 7 Aug 2026 14:49:35 +0900</pubDate>
    </item>
    <item>
      <title>[React] useRef 사용법: 렌더링 없이 값을 기억하고 DOM에 접근하기</title>
      <link>https://hianna.tistory.com/1337</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;React를 배우다 보면 &lt;code&gt;useState&lt;/code&gt;만으로는 해결하기 애매한 상황을 만나게 됩니다. &quot;값은 기억하고 싶은데 화면은 다시 그리고 싶지 않을 때&quot;, 혹은 &quot;특정 input에 직접 포커스를 주고 싶을 때&quot; 같은 경우죠. 이럴 때 사용하는 것이 바로 &lt;b&gt;useRef&lt;/b&gt; Hook입니다. 이 글에서는 useRef의 두 가지 핵심 용도를 예제와 함께 알아보겠습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;useRef란?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useRef는 &lt;b&gt;변경 가능한(mutable) 값을 담는 상자&lt;/b&gt;를 만들어주는 Hook입니다. 이 상자는 &lt;code&gt;.current&lt;/code&gt;라는 프로퍼티를 가지며, 여기에 어떤 값이든 담아둘 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

function Component() {
  // 초기값 0을 담은 ref 객체를 생성
  // ref는 { current: 0 } 형태의 객체
  const countRef = useRef(0);

  // 값에 접근할 때는 .current 사용
  console.log(countRef.current); // 0
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useRef의 가장 중요한 특징은 두 가지입니다.&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;p data-ke-size=&quot;size16&quot;&gt;이 특징 때문에 useRef는 크게 두 가지 용도로 쓰입니다. 하나씩 살펴보겠습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;용도 1: 렌더링과 무관한 값 저장하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useState와 useRef의 가장 큰 차이는 &lt;b&gt;리렌더링 여부&lt;/b&gt;입니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useRef } from 'react';

function Counter() {
  // useState: 값이 바뀌면 화면이 다시 그려짐
  const [stateCount, setStateCount] = useState(0);

  // useRef: 값이 바뀌어도 화면은 다시 그려지지 않음
  const refCount = useRef(0);

  const handleClick = () =&amp;gt; {
    // ref 값은 직접 .current를 수정 (setState 같은 함수 없음)
    refCount.current += 1;
    console.log('ref 값:', refCount.current); // 콘솔에는 증가한 값이 찍힘

    // 하지만 화면의 refCount는 갱신되지 않음 (리렌더링이 안 되니까)
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;state: {stateCount}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;ref: {refCount.current}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;ref 증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 버튼을 눌러 &lt;code&gt;refCount&lt;/code&gt;를 증가시켜도 화면의 &lt;code&gt;ref&lt;/code&gt; 값은 바뀌지 않습니다. 콘솔에는 증가한 값이 찍히지만, 리렌더링이 일어나지 않으니 화면은 그대로입니다. &lt;code&gt;stateCount&lt;/code&gt;가 바뀌어 리렌더링이 일어나야 비로소 갱신된 ref 값이 화면에 반영됩니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 특성 덕분에 useRef는 &lt;b&gt;화면에 보여줄 필요는 없지만 계속 기억해야 하는 값&lt;/b&gt;을 저장하는 데 유용합니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Timer() {
  // setInterval이 반환하는 타이머 ID를 저장
  // 이 값은 화면에 보여줄 필요가 없고, 리렌더링을 유발할 필요도 없음
  const timerId = useRef(null);

  const startTimer = () =&amp;gt; {
    // 타이머 ID를 ref에 보관
    timerId.current = setInterval(() =&amp;gt; {
      console.log('1초마다 실행');
    }, 1000);
  };

  const stopTimer = () =&amp;gt; {
    // 저장해둔 ID로 타이머를 정지
    clearInterval(timerId.current);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={startTimer}&amp;gt;시작&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={stopTimer}&amp;gt;정지&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;만약 타이머 ID를 &lt;code&gt;useState&lt;/code&gt;로 관리했다면, ID가 저장될 때마다 불필요한 리렌더링이 발생했을 것입니다. useRef를 쓰면 이런 낭비 없이 값을 관리할 수 있습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;용도 2: DOM 요소에 직접 접근하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useRef의 또 다른 대표적인 용도는 &lt;b&gt;실제 DOM 요소에 접근&lt;/b&gt;하는 것입니다. React에서는 보통 DOM을 직접 다루지 않지만, 포커스 설정이나 스크롤 이동처럼 어쩔 수 없이 직접 접근해야 하는 경우가 있습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

function SearchInput() {
  // DOM 요소를 담을 ref 생성 (초기값은 null)
  const inputRef = useRef(null);

  const focusInput = () =&amp;gt; {
    // .current에 실제 &amp;lt;input&amp;gt; DOM 요소가 담겨 있음
    // 순수 자바스크립트처럼 focus() 메서드 호출 가능
    inputRef.current.focus();
  };

  return (
    &amp;lt;div&amp;gt;
      {/* ref 속성에 inputRef를 연결하면 */}
      {/* React가 이 input의 실제 DOM을 inputRef.current에 넣어줌 */}
      &amp;lt;input ref={inputRef} type=&quot;text&quot; placeholder=&quot;검색어 입력&quot; /&amp;gt;
      &amp;lt;button onClick={focusInput}&amp;gt;검색창에 포커스&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ref&lt;/code&gt; 속성에 ref 객체를 연결하면, React가 렌더링 후 해당 DOM 요소를 자동으로 &lt;code&gt;.current&lt;/code&gt;에 넣어줍니다. 그 후에는 &lt;code&gt;inputRef.current.focus()&lt;/code&gt;처럼 순수 자바스크립트 DOM API를 그대로 사용할 수 있습니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;실전 예제: 마운트 시 자동 포커스&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 화면에 나타나자마자 input에 포커스를 주는 것도 자주 쓰이는 패턴입니다.&lt;/p&gt;&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;import { useRef, useEffect } from 'react';

function LoginForm() {
  const inputRef = useRef(null);

  // 컴포넌트가 처음 마운트될 때 한 번 실행
  useEffect(() =&amp;gt; {
    // 화면에 나타나자마자 이메일 입력창에 자동 포커스
    inputRef.current.focus();
  }, []); // 빈 배열: 최초 1회만 실행

  return (
    &amp;lt;form&amp;gt;
      &amp;lt;input ref={inputRef} type=&quot;email&quot; placeholder=&quot;이메일&quot; /&amp;gt;
      &amp;lt;input type=&quot;password&quot; placeholder=&quot;비밀번호&quot; /&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;useRef vs useState: 언제 무엇을 쓸까?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;두 Hook은 비슷해 보이지만 목적이 다릅니다. 아래 표로 정리했습니다.&lt;/p&gt;&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt; 
   &lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; useState &lt;/b&gt;&lt;/td&gt; 
   &lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; useRef &lt;/b&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;값 변경 시 리렌더링&lt;/td&gt; 
   &lt;td&gt;O (일어남)&lt;/td&gt; 
   &lt;td&gt;X (일어나지 않음)&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;값 접근 방법&lt;/td&gt; 
   &lt;td&gt;변수 직접 사용&lt;/td&gt; 
   &lt;td&gt;&lt;code&gt;.current&lt;/code&gt;로 접근&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;값 변경 방법&lt;/td&gt; 
   &lt;td&gt;&lt;code&gt;setState&lt;/code&gt; 함수&lt;/td&gt; 
   &lt;td&gt;&lt;code&gt;.current&lt;/code&gt; 직접 수정&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;주요 용도&lt;/td&gt; 
   &lt;td&gt;화면에 반영되는 상태&lt;/td&gt; 
   &lt;td&gt;DOM 접근, 렌더링 무관한 값 저장&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 기준은 간단합니다. &lt;b&gt;그 값이 바뀌었을 때 화면이 다시 그려져야 하는가?&lt;/b&gt; 그렇다면 &lt;code&gt;useState&lt;/code&gt;, 아니라면 &lt;code&gt;useRef&lt;/code&gt;입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의할 점&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 렌더링 중에 ref를 읽거나 쓰지 말 것&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.current&lt;/code&gt; 값은 렌더링 과정(컴포넌트 함수 본문)에서 직접 읽거나 수정하지 않는 것이 원칙입니다. 이벤트 핸들러나 &lt;code&gt;useEffect&lt;/code&gt; 안에서 다루는 것이 안전합니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Component() {
  const ref = useRef(0);

  // ❌ 렌더링 중에 ref를 수정하는 것은 예측 불가능한 동작을 유발
  ref.current += 1;

  // ✅ 이벤트 핸들러나 useEffect 안에서 다루기
  const handleClick = () =&amp;gt; {
    ref.current += 1;
  };

  return &amp;lt;button onClick={handleClick}&amp;gt;클릭&amp;lt;/button&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. ref 값 변경은 화면에 반영되지 않는다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 강조했듯, ref를 수정해도 화면은 자동으로 갱신되지 않습니다. 화면에 보여줘야 하는 값이라면 반드시 &lt;code&gt;useState&lt;/code&gt;를 사용해야 합니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useRef는 React에서 두 가지 뚜렷한 역할을 합니다. 첫째, &lt;b&gt;리렌더링을 유발하지 않으면서 값을 기억&lt;/b&gt;하고 싶을 때(타이머 ID, 이전 값 저장 등), 둘째, &lt;b&gt;실제 DOM 요소에 직접 접근&lt;/b&gt;해야 할 때(포커스, 스크롤 등)입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기억할 핵심은 이것입니다. &lt;code&gt;useState&lt;/code&gt;는 화면을 위한 상태이고, &lt;code&gt;useRef&lt;/code&gt;는 화면 뒤에서 조용히 값을 기억하거나 DOM을 다루기 위한 도구입니다. 이 둘의 차이를 명확히 이해하면 React 컴포넌트를 훨씬 정교하게 제어할 수 있습니다.&lt;/p&gt;</description>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1337</guid>
      <comments>https://hianna.tistory.com/1337#entry1337comment</comments>
      <pubDate>Mon, 3 Aug 2026 22:17:23 +0900</pubDate>
    </item>
    <item>
      <title>[React] JSX 기초 완벽 가이드: React의 화면을 그리는 문법</title>
      <link>https://hianna.tistory.com/1336</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React를 처음 배울 때 가장 먼저 마주치는 낯선 문법이 바로 &lt;b&gt;JSX&lt;/b&gt;입니다. 자바스크립트 안에 HTML 같은 코드가 섞여 있는 모습을 보고 당황하기 쉽죠. 이 글에서는 JSX가 무엇인지, 어떤 규칙을 지켜야 하는지 기초부터 차근차근 알아보겠습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;JSX란?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 &lt;b&gt;JavaScript XML&lt;/b&gt;의 줄임말로, 자바스크립트 안에서 HTML과 비슷한 문법으로 UI를 표현할 수 있게 해주는 문법 확장입니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 이것이 바로 JSX
const element = &amp;lt;h1&amp;gt;안녕하세요, React!&amp;lt;/h1&amp;gt;;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;한 줄로 정리하면, JSX는 &quot;자바스크립트 코드 안에 마크업을 자연스럽게 작성하는 방법&quot;입니다. 브라우저는 JSX를 직접 이해하지 못하기 때문에, Babel 같은 도구가 JSX를 일반 자바스크립트로 변환해줍니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 우리가 작성하는 JSX
const element = &amp;lt;h1&amp;gt;안녕하세요&amp;lt;/h1&amp;gt;;

// Babel이 변환한 실제 자바스크립트
// React.createElement(태그, 속성, 자식) 형태로 바뀜
const element = React.createElement('h1', null, '안녕하세요');&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;즉, JSX는 필수는 아니지만 &lt;code&gt;React.createElement&lt;/code&gt;를 매번 쓰는 번거로움을 없애주는 편리한 문법입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;JSX 안에서 자바스크립트 사용하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSX의 진짜 강력함은 자바스크립트 표현식을 자유롭게 넣을 수 있다는 점입니다. &lt;b&gt;중괄호 &lt;code&gt;{}&lt;/code&gt;&lt;/b&gt; 안에 자바스크립트 표현식을 넣으면 됩니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Greeting() {
  const name = &quot;홍길동&quot;;

  return (
    // 중괄호 안에 변수, 연산, 함수 호출 등 표현식을 넣을 수 있음
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{name}님 환영합니다&amp;lt;/h1&amp;gt;        {/* 변수 */}
      &amp;lt;p&amp;gt;{2 + 3}개의 알림이 있습니다&amp;lt;/p&amp;gt;    {/* 연산 결과: 5 */}
      &amp;lt;p&amp;gt;{name.toUpperCase()}&amp;lt;/p&amp;gt;          {/* 함수 호출 */}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;단, 중괄호 안에는 &lt;b&gt;표현식(expression)&lt;/b&gt;만 들어갈 수 있습니다. &lt;code&gt;if&lt;/code&gt;문이나 &lt;code&gt;for&lt;/code&gt;문 같은 &lt;b&gt;문(statement)&lt;/b&gt;은 사용할 수 없습니다.&lt;/p&gt;&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;// ❌ if문은 표현식이 아니라서 중괄호 안에 못 넣음
&amp;lt;p&amp;gt;{if (isLogin) { ... }}&amp;lt;/p&amp;gt;

// ✅ 삼항 연산자는 표현식이라서 사용 가능
&amp;lt;p&amp;gt;{isLogin ? &quot;로그아웃&quot; : &quot;로그인&quot;}&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;JSX의 핵심 규칙&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSX에는 반드시 지켜야 하는 몇 가지 규칙이 있습니다. 하나씩 살펴보겠습니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 하나의 부모 요소로 감싸야 한다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 반드시 &lt;b&gt;하나의 최상위 요소&lt;/b&gt;를 반환해야 합니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ❌ 두 개의 요소가 나란히 있으면 에러
function App() {
  return (
    &amp;lt;h1&amp;gt;제목&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;내용&amp;lt;/p&amp;gt;
  );
}

// ✅ 하나의 div로 감싸기
function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;제목&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;내용&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 &lt;code&gt;div&lt;/code&gt;를 추가하고 싶지 않다면 &lt;b&gt;Fragment&lt;/b&gt;를 사용할 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Fragment는 실제 DOM에 아무 요소도 추가하지 않으면서 감싸주는 역할
// &amp;lt;&amp;gt;&amp;lt;/&amp;gt; 는 &amp;lt;React.Fragment&amp;gt;&amp;lt;/React.Fragment&amp;gt;의 축약형
function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;제목&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;내용&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 모든 태그는 닫아야 한다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTML에서는 &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;처럼 닫지 않아도 되는 태그가 있지만, JSX에서는 &lt;b&gt;모든 태그를 반드시 닫아야&lt;/b&gt; 합니다.&lt;/p&gt;&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;// ❌ HTML에서는 되지만 JSX에서는 에러
&amp;lt;img src=&quot;photo.jpg&quot;&amp;gt;
&amp;lt;br&amp;gt;
&amp;lt;input type=&quot;text&quot;&amp;gt;

// ✅ 셀프 클로징(/)으로 닫아줘야 함
&amp;lt;img src=&quot;photo.jpg&quot; /&amp;gt;
&amp;lt;br /&amp;gt;
&amp;lt;input type=&quot;text&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. camelCase로 속성을 작성한다&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSX 속성은 대부분 &lt;b&gt;camelCase(카멜 케이스)&lt;/b&gt;로 작성합니다. HTML 속성명과 다른 경우가 있으니 주의해야 합니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ❌ HTML 방식의 속성명
&amp;lt;div class=&quot;box&quot; onclick={handleClick}&amp;gt;&amp;lt;/div&amp;gt;

// ✅ JSX 방식 (camelCase)
&amp;lt;div className=&quot;box&quot; onClick={handleClick}&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&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;class&lt;/code&gt; → &lt;code&gt;className&lt;/code&gt; (class는 자바스크립트 예약어라서)&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;for&lt;/code&gt; → &lt;code&gt;htmlFor&lt;/code&gt; (label의 for 속성, for도 예약어)&lt;/li&gt; 
&lt;/ul&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;label htmlFor=&quot;email&quot;&amp;gt;이메일&amp;lt;/label&amp;gt;
&amp;lt;input id=&quot;email&quot; className=&quot;input-field&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;조건부 렌더링&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSX에서는 조건에 따라 다른 화면을 보여주는 패턴이 자주 쓰입니다.&lt;/p&gt;&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;function UserStatus({ isLogin }) {
  return (
    &amp;lt;div&amp;gt;
      {/* 삼항 연산자: 조건에 따라 둘 중 하나를 렌더링 */}
      {isLogin ? &amp;lt;p&amp;gt;환영합니다!&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;로그인이 필요합니다&amp;lt;/p&amp;gt;}

      {/* &amp;amp;&amp;amp; 연산자: 조건이 true일 때만 렌더링 */}
      {/* isLogin이 true면 뒤의 JSX가, false면 아무것도 안 나옴 */}
      {isLogin &amp;amp;&amp;amp; &amp;lt;button&amp;gt;로그아웃&amp;lt;/button&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 연산자를 쓸 때 주의할 점은, 앞의 값이 &lt;code&gt;0&lt;/code&gt;이면 화면에 &lt;code&gt;0&lt;/code&gt;이 그대로 출력된다는 것입니다.&lt;/p&gt;&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;// ❌ count가 0이면 화면에 숫자 0이 그대로 표시됨
{count &amp;amp;&amp;amp; &amp;lt;p&amp;gt;메시지가 있습니다&amp;lt;/p&amp;gt;}

// ✅ 명확하게 boolean으로 변환하거나 삼항 연산자 사용
{count &amp;gt; 0 &amp;amp;&amp;amp; &amp;lt;p&amp;gt;메시지가 있습니다&amp;lt;/p&amp;gt;}&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;리스트 렌더링&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 화면에 나열할 때는 &lt;code&gt;map()&lt;/code&gt; 메서드를 사용합니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function FruitList() {
  const fruits = [&quot;사과&quot;, &quot;바나나&quot;, &quot;포도&quot;];

  return (
    &amp;lt;ul&amp;gt;
      {/* map으로 배열을 JSX 요소 배열로 변환 */}
      {/* 각 요소에는 반드시 고유한 key를 지정해야 함 */}
      {fruits.map((fruit, index) =&amp;gt; (
        &amp;lt;li key={index}&amp;gt;{fruit}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;code&gt;key&lt;/code&gt;는 React가 각 항목을 구별하기 위한 고유 식별자입니다. &lt;b&gt;리스트를 렌더링할 때 key는 필수&lt;/b&gt;이며, 가능하면 배열의 index보다 데이터의 고유 id를 사용하는 것이 좋습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ✅ 고유 id가 있다면 index 대신 id 사용 권장
{users.map(user =&amp;gt; (
  &amp;lt;li key={user.id}&amp;gt;{user.name}&amp;lt;/li&amp;gt;
))}&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;스타일 적용하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSX에서 인라인 스타일은 &lt;b&gt;객체 형태&lt;/b&gt;로 전달하며, CSS 속성명도 camelCase로 작성합니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function StyledBox() {
  // 스타일을 객체로 정의
  // background-color → backgroundColor 처럼 camelCase로 변환
  const boxStyle = {
    backgroundColor: &quot;skyblue&quot;,
    fontSize: &quot;18px&quot;,
    padding: &quot;10px&quot;
  };

  return (
    // style 속성에 객체를 전달 (중괄호가 두 번인 이유:
    // 바깥 {}는 JSX 표현식, 안쪽 {}는 객체 리터럴)
    &amp;lt;div style={boxStyle}&amp;gt;스타일이 적용된 박스&amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 처음엔 낯설지만, 몇 가지 규칙만 익히면 HTML보다 훨씬 강력하고 유연하게 UI를 표현할 수 있습니다. 핵심을 정리하면 다음과 같습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;JSX는 자바스크립트 안에서 마크업을 작성하는 문법이며, 내부적으로 &lt;code&gt;React.createElement&lt;/code&gt;로 변환된다&lt;/li&gt; 
 &lt;li&gt;중괄호 &lt;code&gt;{}&lt;/code&gt; 안에 자바스크립트 &lt;b&gt;표현식&lt;/b&gt;을 넣을 수 있다&lt;/li&gt; 
 &lt;li&gt;하나의 부모 요소로 감싸고, 모든 태그를 닫으며, 속성은 camelCase로 작성한다&lt;/li&gt; 
 &lt;li&gt;조건부 렌더링은 삼항 연산자와 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, 리스트 렌더링은 &lt;code&gt;map()&lt;/code&gt;과 &lt;code&gt;key&lt;/code&gt;를 사용한다&lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 기초를 탄탄히 다져두면 앞으로 React 컴포넌트를 만드는 일이 훨씬 수월해질 것입니다.&lt;/p&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1336</guid>
      <comments>https://hianna.tistory.com/1336#entry1336comment</comments>
      <pubDate>Mon, 3 Aug 2026 22:17:12 +0900</pubDate>
    </item>
    <item>
      <title>[React] useMemo 사용법: 불필요한 연산을 줄이는 메모이제이션</title>
      <link>https://hianna.tistory.com/1335</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 애플리케이션이 커지다 보면 렌더링 성능이 신경 쓰이기 시작합니다. 특히 무거운 계산이 매 렌더링마다 반복되면 앱이 느려질 수 있죠. 이럴 때 사용하는 것이 바로 &lt;b&gt;useMemo&lt;/b&gt; Hook입니다. 이 글에서는 useMemo가 무엇인지, 언제 쓰고 언제 쓰지 말아야 하는지 예제와 함께 알아보겠습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모이제이션이란?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo를 이해하려면 먼저 &lt;b&gt;메모이제이션(Memoization)&lt;/b&gt; 개념을 알아야 합니다. 메모이제이션은 이미 계산한 결과를 저장해 두었다가, 같은 입력이 들어오면 다시 계산하지 않고 저장된 값을 재사용하는 최적화 기법입니다.&lt;br&gt;쉽게 말해, &quot;한 번 계산한 건 기억해뒀다가 그대로 꺼내 쓰자&quot;는 아이디어입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황: 매 렌더링마다 반복되는 무거운 계산&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 useMemo가 없을 때 어떤 문제가 생기는지 살펴보겠습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function ProductList({ products, filter }) {
  // 이 계산은 컴포넌트가 렌더링될 때마다 매번 실행됨
  // products가 1만 개라면 매 렌더링마다 1만 번 순회 → 성능 저하
  const filteredProducts = products.filter(product =&amp;gt;
    product.name.includes(filter)
  );

  return (
    &amp;lt;ul&amp;gt;
      {filteredProducts.map(p =&amp;gt; &amp;lt;li key={p.id}&amp;gt;{p.name}&amp;lt;/li&amp;gt;)}
    &amp;lt;/ul&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 컴포넌트는 &lt;code&gt;filter&lt;/code&gt;가 바뀌지 않았더라도, 부모 컴포넌트가 리렌더링되면 함께 리렌더링되면서 &lt;code&gt;filter()&lt;/code&gt; 연산을 매번 다시 실행합니다. 목록이 크거나 계산이 복잡하다면 불필요한 낭비가 발생합니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;useMemo로 해결하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo를 사용하면 의존성이 바뀔 때만 계산을 다시 하도록 만들 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useMemo } from 'react';

function ProductList({ products, filter }) {
  // useMemo(계산 함수, 의존성 배열) 형태로 사용
  // products나 filter가 바뀔 때만 필터링을 다시 실행
  // 둘 다 그대로면 이전에 계산해둔 결과를 그대로 재사용
  const filteredProducts = useMemo(() =&amp;gt; {
    return products.filter(product =&amp;gt;
      product.name.includes(filter)
    );
  }, [products, filter]); // 이 배열의 값이 바뀌어야만 재계산

  return (
    &amp;lt;ul&amp;gt;
      {filteredProducts.map(p =&amp;gt; &amp;lt;li key={p.id}&amp;gt;{p.name}&amp;lt;/li&amp;gt;)}
    &amp;lt;/ul&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;filter&lt;/code&gt;와 &lt;code&gt;products&lt;/code&gt;가 그대로라면, 다른 이유로 리렌더링이 일어나도 필터링 연산은 다시 실행되지 않습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;useMemo의 기본 문법&lt;/h2&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const memoizedValue = useMemo(() =&amp;gt; {
  // 무겁거나 반복하고 싶지 않은 계산
  return computeExpensiveValue(a, b);
}, [a, b]); // 의존성 배열&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;li&gt;&lt;b&gt;두 번째 인자&lt;/b&gt;: 의존성 배열(dependency array) 
  &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;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 배열은 &lt;code&gt;useEffect&lt;/code&gt;와 동일한 규칙으로 동작합니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;또 다른 활용: 참조 동일성(Referential Equality) 유지&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo는 무거운 계산뿐 아니라, &lt;b&gt;객체나 배열의 참조값을 유지&lt;/b&gt;하는 데도 유용합니다. 이전 글에서 다뤘던 Context 예제가 좋은 사례입니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function App() {
  const [user, setUser] = useState({ name: &quot;홍길동&quot; });

  // ❌ 매 렌더링마다 새로운 객체가 생성됨
  //    참조값이 매번 달라져서, 이 값을 받는 컴포넌트가 불필요하게 리렌더링됨
  const value = { user, setUser };

  return &amp;lt;UserContext.Provider value={value}&amp;gt;...&amp;lt;/UserContext.Provider&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function App() {
  const [user, setUser] = useState({ name: &quot;홍길동&quot; });

  // ✅ user가 바뀔 때만 새 객체를 생성하고, 그 외엔 같은 객체를 재사용
  //    참조값이 유지되므로 불필요한 리렌더링을 방지
  const value = useMemo(() =&amp;gt; ({ user, setUser }), [user]);

  return &amp;lt;UserContext.Provider value={value}&amp;gt;...&amp;lt;/UserContext.Provider&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 객체는 내용이 같아도 새로 만들면 서로 다른 참조로 취급됩니다. &lt;code&gt;React.memo&lt;/code&gt;로 감싼 자식 컴포넌트나 Context에 객체를 넘길 때, 이 참조 동일성을 유지하는 것이 성능 최적화의 핵심입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;useMemo vs useCallback&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo와 자주 헷갈리는 것이 &lt;code&gt;useCallback&lt;/code&gt;입니다. 둘의 관계를 간단히 정리하면 이렇습니다.&lt;/p&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// useMemo는 &quot;값&quot;을 메모이제이션
const memoizedValue = useMemo(() =&amp;gt; computeValue(a, b), [a, b]);

// useCallback은 &quot;함수&quot;를 메모이제이션
const memoizedFn = useCallback(() =&amp;gt; doSomething(a, b), [a, b]);

// 사실 useCallback은 useMemo로 함수를 감싼 것과 동일함
const memoizedFn = useMemo(() =&amp;gt; () =&amp;gt; doSomething(a, b), [a, b]);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse;&quot; border=&quot;1&quot; data-ke-style=&quot;style15&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;구분&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;useMemo&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; useCallback &lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;메모이제이션 대상&lt;/td&gt;&lt;td&gt;계산된 &lt;b&gt;값&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;함수&lt;/b&gt; 자체&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;반환값&lt;/td&gt;&lt;td&gt;함수 실행 결과&lt;/td&gt;&lt;td&gt;함수 그 자체&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;주요 용도&lt;/td&gt;&lt;td&gt;무거운 계산, 참조 유지&lt;/td&gt;&lt;td&gt;자식에 함수를 props로 전달할 때&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;남용하지 말아야 하는 이유&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo는 공짜가 아닙니다. React는 의존성 배열을 비교하고, 이전 값을 메모리에 저장하는 비용을 치릅니다. 따라서 &lt;b&gt;모든 값에 useMemo를 붙이는 것은 오히려 손해&lt;/b&gt;입니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ❌ 이런 단순한 계산에는 useMemo가 오히려 오버헤드
//    덧셈보다 useMemo 자체의 비교 비용이 더 클 수 있음
const sum = useMemo(() =&amp;gt; a + b, [a, b]);

// ✅ 그냥 계산하는 게 더 빠르고 깔끔함
const sum = a + b;&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 useMemo를 사용해야 할까?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다음 상황에 해당할 때 useMemo 도입을 고려하세요.&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;React.memo&lt;/code&gt;로 감싼 컴포넌트나 Context에 전달하는 값, &lt;code&gt;useEffect&lt;/code&gt;의 의존성으로 쓰이는 객체&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;측정을 통해 실제 병목을 확인했을 때&lt;/b&gt;: 성능 문제가 실제로 존재하는지 먼저 확인하는 것이 중요&lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 단순 계산이나 성능 문제가 없는 곳에서는 useMemo를 붙이지 않는 것이 더 좋습니다. &quot;일단 붙이고 보자&quot;가 아니라, &lt;b&gt;측정 후 필요한 곳에만&lt;/b&gt; 적용하는 것이 좋은 습관입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo는 불필요한 재계산을 막아 성능을 최적화하는 강력한 도구입니다. 무거운 계산을 캐싱하거나 객체의 참조값을 유지하는 두 가지 용도로 주로 쓰입니다. 다만 모든 곳에 남용하면 오히려 코드가 복잡해지고 성능에 악영향을 줄 수 있으니, &lt;b&gt;&quot;이 계산이 정말 비싼가?&quot;, &quot;참조 동일성이 필요한가?&quot;&lt;/b&gt;를 스스로 물어본 뒤 적용하는 것이 핵심입니다.&lt;/p&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;: React 19부터는 React Compiler가 이런 메모이제이션을 자동으로 처리해주는 방향으로 발전하고 있습니다. 하지만 원리를 이해하는 것은 여전히 중요하며, 수동 최적화가 필요한 상황도 많으니 useMemo의 동작 방식을 확실히 익혀두는 것을 추천합니다.&lt;/p&gt; 
&lt;/blockquote&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1335</guid>
      <comments>https://hianna.tistory.com/1335#entry1335comment</comments>
      <pubDate>Mon, 3 Aug 2026 22:17:00 +0900</pubDate>
    </item>
    <item>
      <title>[React] Context API 완벽 가이드: Props Drilling 없이 상태 공유하기</title>
      <link>https://hianna.tistory.com/1334</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React로 애플리케이션을 개발하다 보면 여러 컴포넌트가 같은 데이터를 필요로 하는 상황을 자주 마주치게 됩니다. 이럴 때 사용하는 것이 바로 &lt;b&gt;Context API&lt;/b&gt;입니다. 이 글에서는 Context API가 왜 필요한지, 어떻게 사용하는지, 그리고 언제 사용해야 하는지 알아보겠습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Props Drilling 문제&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;React는 기본적으로 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 데이터를 전달합니다. 하지만 컴포넌트 트리가 깊어지면 문제가 생깁니다.&lt;/p&gt;&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;function App() {
  const user = { name: &quot;홍길동&quot;, theme: &quot;dark&quot; };
  // App에서 만든 user 데이터를 Layout에 props로 전달
  return &amp;lt;Layout user={user} /&amp;gt;;
}

function Layout({ user }) {
  // Layout은 user를 직접 쓰지 않지만, 자식에게 넘기려고 받아둠
  return &amp;lt;Sidebar user={user} /&amp;gt;;
}

function Sidebar({ user }) {
  // Sidebar 역시 user를 사용하지 않고 그냥 아래로 전달만 함
  return &amp;lt;UserProfile user={user} /&amp;gt;;
}

function UserProfile({ user }) {
  // 실제로 user가 필요한 곳은 여기 하나뿐
  return &amp;lt;p&amp;gt;{user.name}님 환영합니다&amp;lt;/p&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Layout&lt;/code&gt;과 &lt;code&gt;Sidebar&lt;/code&gt;는 &lt;code&gt;user&lt;/code&gt;라는 데이터를 실제로 사용하지 않지만, 하위 컴포넌트에 전달하기 위해 어쩔 수 없이 props를 받아 넘겨줘야 합니다. 이런 현상을 &lt;b&gt;Props Drilling(프롭스 드릴링)&lt;/b&gt;이라고 부릅니다. 컴포넌트가 많아질수록 코드가 지저분해지고 유지보수가 어려워집니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Context API란?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Context API는 컴포넌트 트리 전체에 데이터를 전달할 수 있는 방법을 제공합니다. 중간 컴포넌트를 거치지 않고, 필요한 컴포넌트에서 바로 데이터를 꺼내 쓸 수 있습니다.&lt;br&gt;Context API는 크게 세 단계로 사용합니다.&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;Context 생성&lt;/b&gt; — &lt;code&gt;createContext&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;Provider로 데이터 제공&lt;/b&gt; — &lt;code&gt;&amp;lt;Context.Provider&amp;gt;&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;Consumer에서 데이터 사용&lt;/b&gt; — &lt;code&gt;useContext&lt;/code&gt;&lt;/li&gt; 
&lt;/ol&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 사용법&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Context 생성하기&lt;/h3&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { createContext } from 'react';

// createContext로 새로운 Context 객체를 생성
// 인자로 넘긴 null은 Provider가 없을 때 사용될 기본값
const UserContext = createContext(null);&lt;/code&gt;&lt;/pre&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Provider로 감싸기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Provider&lt;/code&gt;는 하위 컴포넌트들에게 데이터를 전달하는 역할을 합니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function App() {
  const user = { name: &quot;홍길동&quot;, theme: &quot;dark&quot; };

  return (
    // Provider의 value에 담긴 데이터가 하위 컴포넌트 전체에 공유됨
    // 이 안에 있는 어떤 컴포넌트든 user 데이터에 접근 가능
    &amp;lt;UserContext.Provider value={user}&amp;gt;
      &amp;lt;Layout /&amp;gt;
    &amp;lt;/UserContext.Provider&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. useContext로 데이터 꺼내 쓰기&lt;/h3&gt;&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;import { useContext } from 'react';

function Layout() {
  // 더 이상 user props를 받지 않아도 됨
  return &amp;lt;Sidebar /&amp;gt;;
}

function Sidebar() {
  // 중간 컴포넌트는 데이터 전달 책임에서 완전히 해방됨
  return &amp;lt;UserProfile /&amp;gt;;
}

function UserProfile() {
  // useContext에 Context 객체를 넘기면 가장 가까운 Provider의 value를 반환
  const user = useContext(UserContext);
  return &amp;lt;p&amp;gt;{user.name}님 환영합니다&amp;lt;/p&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;Layout&lt;/code&gt;과 &lt;code&gt;Sidebar&lt;/code&gt;는 더 이상 &lt;code&gt;user&lt;/code&gt; props를 알 필요가 없습니다. &lt;code&gt;UserProfile&lt;/code&gt;에서 직접 &lt;code&gt;UserContext&lt;/code&gt;에 접근해 데이터를 가져오기 때문입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전 예제: 테마 전환 기능 만들기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다크 모드/라이트 모드를 전환하는 기능은 Context API가 자주 쓰이는 대표적인 예시입니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { createContext, useContext, useState } from 'react';

// 테마 관련 데이터를 공유할 Context 생성
const ThemeContext = createContext();

// Provider와 상태 로직을 하나로 묶은 컴포넌트
// children으로 감싼 모든 컴포넌트가 테마에 접근할 수 있게 됨
function ThemeProvider({ children }) {
  // 현재 테마 상태를 관리 (기본값은 'light')
  const [theme, setTheme] = useState('light');

  // 테마를 반대로 전환하는 함수
  const toggleTheme = () =&amp;gt; {
    // 이전 상태값(prev)을 기준으로 light ↔ dark 전환
    setTheme(prev =&amp;gt; (prev === 'light' ? 'dark' : 'light'));
  };

  return (
    // theme 값과 toggleTheme 함수를 함께 value로 전달
    &amp;lt;ThemeContext.Provider value={{ theme, toggleTheme }}&amp;gt;
      {children}
    &amp;lt;/ThemeContext.Provider&amp;gt;
  );
}

function ThemeButton() {
  // Context에서 theme 상태와 toggleTheme 함수를 한 번에 꺼내옴
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    // 버튼 클릭 시 테마 전환 함수 실행
    &amp;lt;button onClick={toggleTheme}&amp;gt;
      현재 테마: {theme} (클릭해서 전환)
    &amp;lt;/button&amp;gt;
  );
}

function App() {
  return (
    // ThemeProvider로 감싸면 내부의 모든 컴포넌트가 테마 사용 가능
    &amp;lt;ThemeProvider&amp;gt;
      &amp;lt;ThemeButton /&amp;gt;
    &amp;lt;/ThemeProvider&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Provider와 상태 로직을 하나의 컴포넌트로 묶으면, 어디서든 &lt;code&gt;useContext(ThemeContext)&lt;/code&gt; 한 줄로 테마 상태와 전환 함수에 접근할 수 있습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;커스텀 Hook으로 더 깔끔하게&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;매번 &lt;code&gt;useContext(ThemeContext)&lt;/code&gt;를 호출하는 대신, 커스텀 Hook으로 감싸면 재사용성과 가독성이 좋아집니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Context 사용 로직을 감싼 커스텀 Hook
function useTheme() {
  const context = useContext(ThemeContext);

  // Provider 바깥에서 호출하면 context가 undefined가 됨
  // 이 경우 명확한 에러를 던져 실수를 빠르게 잡아냄
  if (!context) {
    throw new Error('useTheme은 ThemeProvider 안에서 사용해야 합니다');
  }

  return context;
}

// 사용하는 쪽 - useContext 대신 useTheme만 호출하면 됨
function ThemeButton() {
  // 어떤 Context를 쓰는지 신경 쓸 필요 없이 깔끔하게 사용
  const { theme, toggleTheme } = useTheme();
  return &amp;lt;button onClick={toggleTheme}&amp;gt;{theme}&amp;lt;/button&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Provider 외부에서 실수로 사용했을 때 명확한 에러 메시지를 보여줄 수 있다는 것도 큰 장점입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 Context API를 사용해야 할까?&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Context 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;전역적으로 필요한 데이터&lt;/b&gt;: 로그인한 사용자 정보, 테마, 언어 설정 등&lt;/li&gt;&lt;li&gt;&lt;b&gt;여러 depth를 거쳐 전달되는 데이터&lt;/b&gt;: props drilling이 3~4단계 이상 발생할 때&lt;/li&gt;&lt;li&gt;&lt;b&gt;자주 바뀌지 않는 데이터&lt;/b&gt;: 너무 자주 변경되는 값은 불필요한 리렌더링을 유발할 수 있습니다&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의할 점: 불필요한 리렌더링&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Context의 &lt;code&gt;value&lt;/code&gt;가 변경되면 해당 Context를 구독하는 모든 컴포넌트가 리렌더링됩니다. 특히 아래처럼 객체를 매 렌더링마다 새로 생성하면 문제가 될 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;// ❌ 컴포넌트가 렌더링될 때마다 { name: &quot;홍길동&quot; } 객체가 새로 만들어짐
//    참조값이 매번 달라지므로 React는 value가 바뀌었다고 판단 → 불필요한 리렌더링
&amp;lt;UserContext.Provider value={{ name: &quot;홍길동&quot; }}&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ✅ useMemo로 객체를 메모이제이션해서 참조값을 유지
//    의존성 배열([])이 변하지 않는 한 같은 객체를 재사용 → 리렌더링 방지
const value = useMemo(() =&amp;gt; ({ name: &quot;홍길동&quot; }), []);
&amp;lt;UserContext.Provider value={value}&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;또한 자주 바뀌는 값(예: 마우스 위치, 스크롤 위치)을 Context로 관리하면 성능 저하가 발생할 수 있으므로, 이런 경우에는 Redux나 Zustand 같은 상태 관리 라이브러리를 고려하는 것이 좋습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Context API vs 상태 관리 라이브러리&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Context API는 Redux 같은 전역 상태 관리 라이브러리를 완전히 대체하지는 않습니다. 둘의 차이를 간단히 정리하면 다음과 같습니다.&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse;&quot; border=&quot;1&quot; data-ke-style=&quot;style15&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; Context API &lt;/b&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; Redux / Zustand 등 &lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;설정 복잡도&lt;/td&gt;&lt;td&gt;낮음 (React 내장)&lt;/td&gt;&lt;td&gt;상대적으로 높음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;적합한 규모&lt;/td&gt;&lt;td&gt;소~중규모 앱, 단순한 전역 상태&lt;/td&gt;&lt;td&gt;대규모 앱, 복잡한 상태 로직&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;성능 최적화&lt;/td&gt;&lt;td&gt;직접 관리 필요 (memo, useMemo)&lt;/td&gt;&lt;td&gt;라이브러리 차원에서 최적화 제공&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;디버깅 도구&lt;/td&gt;&lt;td&gt;별도 없음&lt;/td&gt;&lt;td&gt;Redux DevTools 등 지원&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 테마 설정이나 인증 정보 정도라면 Context API만으로 충분하지만, 상태 로직이 복잡하고 자주 변경된다면 전용 상태 관리 라이브러리를 검토해보는 것이 좋습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Context API는 React 애플리케이션에서 props drilling 문제를 해결하는 훌륭한 도구입니다. &lt;code&gt;createContext&lt;/code&gt;, &lt;code&gt;Provider&lt;/code&gt;, &lt;code&gt;useContext&lt;/code&gt; 세 가지 개념만 이해하면 전역 상태를 손쉽게 공유할 수 있습니다. 다만 모든 상태를 Context로 관리하려 하기보다는, 정말 전역적으로 필요한 데이터에 한해 사용하는 것이 좋은 설계 습관입니다.&lt;/p&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1334</guid>
      <comments>https://hianna.tistory.com/1334#entry1334comment</comments>
      <pubDate>Mon, 3 Aug 2026 22:16:45 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript 비동기 처리 완전 정복: async/await 사용법</title>
      <link>https://hianna.tistory.com/1333</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript로 개발을 하다 보면 서버에서 데이터를 가져오거나, 파일을 읽거나, 타이머를 다루는 등 시간이 걸리는 작업을 자주 만나게 됩니다. 이런 작업들을 효율적으로 처리하기 위한 핵심 도구가 바로 &lt;b&gt;async/await&lt;/b&gt;입니다. 이 글에서는 비동기 처리가 왜 필요한지부터, async/await를 실전에서 어떻게 활용하는지까지 차근차근 살펴보겠습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 비동기 처리가 필요한 이유&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript는 기본적으로 &lt;b&gt;싱글 스레드&lt;/b&gt;로 동작합니다. 즉, 한 번에 하나의 작업만 처리할 수 있습니다. 그런데 만약 네트워크 요청처럼 오래 걸리는 작업이 끝날 때까지 다음 코드 실행을 멈추고 기다린다면 어떻게 될까요? 화면이 멈추고, 사용자는 아무것도 할 수 없는 답답한 경험을 하게 될 것입니다.&lt;br&gt;이 문제를 해결하기 위해 JavaScript는 &lt;b&gt;비동기(asynchronous)&lt;/b&gt; 방식을 사용합니다. 오래 걸리는 작업은 백그라운드에 맡겨두고, 그동안 다른 코드를 계속 실행하다가 작업이 끝나면 결과를 받아 처리하는 방식입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 콜백에서 Promise, 그리고 async/await로&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 처리 방식은 시간이 지나면서 계속 발전해왔습니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 콜백 함수 (Callback)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 초기 방식은 콜백 함수였습니다.&lt;/p&gt;&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;function getUser(id, callback) {
  setTimeout(() =&amp;gt; {
    callback({ id, name: &quot;홍길동&quot; });
  }, 1000);
}

getUser(1, (user) =&amp;gt; {
  console.log(user);
  getUser(2, (user2) =&amp;gt; {
    console.log(user2);
    getUser(3, (user3) =&amp;gt; {
      console.log(user3);
      // 콜백이 계속 중첩되면서 코드가 옆으로 길어짐
    });
  });
});&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;콜백이 중첩될수록 코드가 오른쪽으로 계속 깊어지는 이른바 &lt;b&gt;&quot;콜백 지옥(Callback Hell)&quot;&lt;/b&gt; 현상이 발생해 가독성이 크게 떨어집니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. Promise의 등장&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이를 개선하기 위해 ES6에서 &lt;b&gt;Promise&lt;/b&gt;가 도입되었습니다.&lt;/p&gt;&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;function getUser(id) {
  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve({ id, name: &quot;홍길동&quot; });
    }, 1000);
  });
}

getUser(1)
  .then((user) =&amp;gt; {
    console.log(user);
    return getUser(2);
  })
  .then((user2) =&amp;gt; {
    console.log(user2);
    return getUser(3);
  })
  .then((user3) =&amp;gt; {
    console.log(user3);
  })
  .catch((error) =&amp;gt; {
    console.error(error);
  });&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.then()&lt;/code&gt; 체이닝 덕분에 콜백 지옥은 어느 정도 해결됐지만, 로직이 복잡해지면 여전히 코드가 길어지고 흐름을 한눈에 파악하기 어려운 경우가 많습니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3. async/await의 등장&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;ES2017(ES8)부터 도입된 async/await는 Promise를 기반으로 하면서도, &lt;b&gt;마치 동기 코드를 작성하듯&lt;/b&gt; 비동기 코드를 작성할 수 있게 해줍니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function getUser(id) {
  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve({ id, name: &quot;홍길동&quot; });
    }, 1000);
  });
}

async function main() {
  const user1 = await getUser(1);
  console.log(user1);

  const user2 = await getUser(2);
  console.log(user2);

  const user3 = await getUser(3);
  console.log(user3);
}

main();&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;훨씬 직관적이고 읽기 쉬운 코드가 되었습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. async/await 기본 문법&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. async 키워드&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;함수 앞에 &lt;code&gt;async&lt;/code&gt;를 붙이면 해당 함수는 항상 &lt;b&gt;Promise를 반환&lt;/b&gt;하는 비동기 함수가 됩니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function sayHello() {
  return &quot;안녕하세요&quot;;
}

sayHello().then((msg) =&amp;gt; console.log(msg)); // &quot;안녕하세요&quot;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;함수 안에서 일반 값을 &lt;code&gt;return&lt;/code&gt;해도, 자동으로 그 값을 담은 Promise로 감싸져서 반환됩니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. await 키워드&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;await&lt;/code&gt;는 &lt;b&gt;async 함수 내부에서만&lt;/b&gt; 사용할 수 있으며, Promise가 처리(resolve 또는 reject)될 때까지 함수 실행을 잠시 멈추고 기다립니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function fetchData() {
  const response = await fetch(&quot;https://api.example.com/data&quot;);
  const data = await response.json();
  console.log(data);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;await&lt;/code&gt;를 만나면 해당 Promise가 완료될 때까지 기다린 후, 그 결과값을 반환합니다. 이때 전체 프로그램이 멈추는 것이 아니라, 해당 async 함수의 실행만 잠시 대기 상태가 되고 다른 코드는 계속 실행될 수 있습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 에러 처리: try/catch&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Promise에서는 &lt;code&gt;.catch()&lt;/code&gt;로 에러를 처리했다면, async/await에서는 익숙한 &lt;b&gt;try/catch&lt;/b&gt; 문법을 그대로 사용할 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function getUserData(id) {
  try {
    const response = await fetch(`https://api.example.com/users/${id}`);

    if (!response.ok) {
      throw new Error(`요청 실패: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error(&quot;사용자 정보를 가져오는 중 오류 발생:&quot;, error.message);
    return null;
  } finally {
    console.log(&quot;요청 처리가 완료되었습니다.&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;try&lt;/code&gt; 블록 안에서 &lt;code&gt;await&lt;/code&gt;한 Promise가 reject되면 즉시 &lt;code&gt;catch&lt;/code&gt; 블록으로 넘어가기 때문에, 동기 코드와 동일한 감각으로 에러를 처리할 수 있다는 점이 큰 장점입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 여러 비동기 작업 병렬로 처리하기&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1. 순차 처리의 함정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드처럼 서로 관련 없는 작업을 &lt;code&gt;await&lt;/code&gt;로 하나씩 순서대로 처리하면 불필요하게 시간이 오래 걸립니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function badExample() {
  const user = await getUser();      // 1초 소요
  const posts = await getPosts();    // 1초 소요
  const comments = await getComments(); // 1초 소요
  // 총 소요 시간: 약 3초
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;세 작업이 서로 의존 관계가 없다면, 굳이 순서대로 기다릴 필요가 없습니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-2. Promise.all로 병렬 처리하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때는 &lt;code&gt;Promise.all()&lt;/code&gt;을 사용해 여러 작업을 동시에 시작하고, 모두 끝날 때까지 한 번에 기다리는 것이 효율적입니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function goodExample() {
  const [user, posts, comments] = await Promise.all([
    getUser(),
    getPosts(),
    getComments(),
  ]);
  // 총 소요 시간: 약 1초 (가장 오래 걸리는 작업 기준)

  console.log(user, posts, comments);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;세 요청이 동시에 시작되기 때문에, 전체 소요 시간이 가장 오래 걸리는 작업 하나의 시간으로 줄어듭니다.&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;code&gt;Promise.all()&lt;/code&gt;은 하나라도 실패(reject)하면 즉시 전체가 실패로 처리됩니다. 일부가 실패해도 나머지 결과를 모두 받고 싶다면 &lt;code&gt;Promise.allSettled()&lt;/code&gt;를 사용하면 됩니다.&lt;/p&gt; 
&lt;/blockquote&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 실전 예제: API 데이터 가져오기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로젝트에서 자주 쓰이는 패턴을 정리한 예제입니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function fetchUserProfile(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);

    if (!response.ok) {
      throw new Error(`HTTP 에러: ${response.status}`);
    }

    const profile = await response.json();
    return profile;
  } catch (error) {
    if (error instanceof TypeError) {
      console.error(&quot;네트워크 연결을 확인해주세요.&quot;);
    } else {
      console.error(&quot;프로필 조회 실패:&quot;, error.message);
    }
    throw error; // 필요하다면 호출한 쪽으로 에러를 다시 전달
  }
}

// 사용하는 쪽
async function displayProfile() {
  try {
    const profile = await fetchUserProfile(123);
    console.log(&quot;프로필:&quot;, profile);
  } catch (error) {
    console.log(&quot;프로필을 표시할 수 없습니다.&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 자주 하는 실수와 주의사항&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) forEach 안에서 await를 사용하는 경우&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;forEach&lt;/code&gt;는 async/await와 함께 사용해도 기다려주지 않습니다.&lt;/p&gt;&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 잘못된 예: 순서를 보장하지 않고 기다리지도 않음
items.forEach(async (item) =&amp;gt; {
  await processItem(item);
});

// 올바른 예 1: for...of 사용 (순차 처리)
for (const item of items) {
  await processItem(item);
}

// 올바른 예 2: Promise.all + map 사용 (병렬 처리)
await Promise.all(items.map((item) =&amp;gt; processItem(item)));&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 최상위 레벨에서 await 사용&lt;/b&gt;&lt;br&gt;모던 모듈 환경(ES 모듈)에서는 top-level await가 가능하지만, 일반 스크립트나 함수 밖에서는 async 함수로 감싸야 합니다.&lt;/p&gt;&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// async 함수로 감싸서 실행
(async () =&amp;gt; {
  const data = await fetchData();
  console.log(data);
})();&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) try/catch를 생략하지 않기&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;await&lt;/code&gt;한 Promise가 실패했을 때 try/catch가 없으면 처리되지 않은 에러(unhandled rejection)로 이어질 수 있으므로, 실패 가능성이 있는 비동기 작업은 항상 에러 처리를 함께 고려하는 것이 좋습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 정리&lt;/h2&gt;&lt;table style=&quot;height: 108px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 개념 &lt;/b&gt;&lt;/td&gt; 
   &lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 설명 &lt;/b&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr style=&quot;height: 21px;&quot;&gt; 
   &lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;async&lt;/code&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 21px;&quot;&gt;함수를 비동기 함수로 선언, 항상 Promise 반환&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr style=&quot;height: 21px;&quot;&gt; 
   &lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;await&lt;/code&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 21px;&quot;&gt;Promise가 처리될 때까지 대기, async 함수 내부에서만 사용 가능&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr style=&quot;height: 21px;&quot;&gt; 
   &lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;try/catch&lt;/code&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;code&gt;Promise.all&lt;/code&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;async/await는 복잡한 비동기 로직을 간결하고 읽기 쉬운 코드로 바꿔주는 강력한 도구입니다. 콜백 지옥이나 복잡한 &lt;code&gt;.then()&lt;/code&gt; 체이닝 대신, 마치 동기 코드를 작성하듯 자연스러운 흐름으로 비동기 처리를 구현할 수 있다는 것이 가장 큰 장점입니다. 다만 병렬로 처리 가능한 작업까지 무조건 순차적으로 &lt;code&gt;await&lt;/code&gt;하지 않도록 주의하고, 상황에 맞게 &lt;code&gt;Promise.all&lt;/code&gt; 같은 도구도 함께 활용하는 것이 좋은 비동기 코드를 작성하는 핵심입니다.&lt;/p&gt;</description>
      <category>IT/Javascript</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1333</guid>
      <comments>https://hianna.tistory.com/1333#entry1333comment</comments>
      <pubDate>Mon, 3 Aug 2026 12:30:25 +0900</pubDate>
    </item>
    <item>
      <title>[React] props와 state 차이 이해하기, 상태 끌어올리기</title>
      <link>https://hianna.tistory.com/1332</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트는 레고 블록과 같습니다. 조각조각 만들어진 컴포넌트들을 조합하여 하나의 완성된 웹 페이지를 구축합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &quot;컴포넌트에 전달되는 데이터&quot;와 &quot;컴포넌트 내부에서 변하는 데이터&quot;를 관리하기 위해 사용하는 핵심 개념이 바로 &lt;b&gt;Props&lt;/b&gt;와 &lt;b&gt;State&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 &lt;code&gt;Props&lt;/code&gt;와 &lt;code&gt;State&lt;/code&gt;의 각각의 개념과 특징, 한눈에 보는 비교표, 그리고 두 개념을 함께 활용하는 &lt;b&gt;상태 끌어올리기(Lifting State Up)&lt;/b&gt; 패턴까지 알아보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Props (Properties)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Props&lt;/code&gt;는 &lt;b&gt;부모 컴포넌트가 자식 컴포넌트에게 전달하는 데이터&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에 전달하는 매개변수(Parameter)와 유사한 개념으로 생각하면 이해하기 쉽습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Props의 핵심 특징&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;읽기 전용 (Read-Only)&lt;/b&gt;: 자식 컴포넌트는 전달받은 &lt;code&gt;Props&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;h3 data-ke-size=&quot;size23&quot;&gt;  Props 활용 예제 코드&lt;/h3&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;import React from 'react';

// [자식 컴포넌트]
// 부모로부터 props 객체를 전달받습니다.
// 구조 분해 할당({ name, role, age = 20 })을 사용하여 깔끔하게 가져올 수 있습니다.
function UserCard({ name, role, age = 20 }) {
  return (
    &amp;lt;div style={{ border: '1px solid #ccc', padding: '10px', margin: '10px 0' }}&amp;gt;
      {/* 전달받은 props 값을 화면에 렌더링합니다. */}
      &amp;lt;h3&amp;gt;이름: {name}&amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt;역할: {role}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;나이: {age}세&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

// [부모 컴포넌트]
function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;팀원 목록&amp;lt;/h2&amp;gt;
      {/* 
        자식 컴포넌트에 속성(Attribute) 형태로 데이터를 전달합니다.
        문자열은 &quot;&quot;로, 숫자/객체/함수 등은 {}로 감싸서 전달합니다.
      */}
      &amp;lt;UserCard name=&quot;김철수&quot; role=&quot;프론트엔드 개발자&quot; age={25} /&amp;gt;
      &amp;lt;UserCard name=&quot;이영희&quot; role=&quot;백엔드 개발자&quot; age={28} /&amp;gt;
      {/* age를 전달하지 않으면 자식 컴포넌트의 기본값(20)이 적용됩니다. */}
      &amp;lt;UserCard name=&quot;박민수&quot; role=&quot;디자이너&quot; /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. State란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;State&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;h3 data-ke-size=&quot;size23&quot;&gt;  State의 핵심 특징&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;가변성 (Mutable)&lt;/b&gt;: &lt;code&gt;useState&lt;/code&gt;에서 제공하는 상태 변경 함수(&lt;code&gt;setState&lt;/code&gt;)를 통해 변경할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리렌더링 유발&lt;/b&gt;: &lt;code&gt;State&lt;/code&gt; 값이 변경되면, React는 해당 컴포넌트와 자식 컴포넌트를 &lt;b&gt;자동으로 리렌더링&lt;/b&gt;하여 화면을 갱신합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비공개 (Private)&lt;/b&gt;: 기본적으로 해당 State가 선언된 컴포넌트 내부에서만 접근할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  State 활용 예제 코드&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState } from 'react';

function LikeButton() {
  // useState Hook으로 likeCount 상태를 선언합니다.
  // isLiked: 현재 좋아요 여부 (Boolean)
  // setIsLiked: isLiked 상태를 변경하는 함수
  const [isLiked, setIsLiked] = useState(false);

  // likeCount: 좋아요 개수
  const [likeCount, setLikeCount] = useState(0);

  // 좋아요 토글 핸들러
  const handleToggleLike = () =&amp;gt; {
    if (isLiked) {
      // 이미 좋아요 상태라면 해제 (-1)
      setIsLiked(false);
      setLikeCount((prev) =&amp;gt; prev - 1);
    } else {
      // 좋아요 안 한 상태라면 설정 (+1)
      setIsLiked(true);
      setLikeCount((prev) =&amp;gt; prev + 1);
    }
  };

  return (
    &amp;lt;div style={{ textAlign: 'center', margin: '20px' }}&amp;gt;
      &amp;lt;button 
        onClick={handleToggleLike}
        style={{ 
          backgroundColor: isLiked ? '#ff4d4f' : '#e6e6e6', 
          color: isLiked ? '#fff' : '#000',
          padding: '10px 20px',
          border: 'none',
          borderRadius: '5px',
          cursor: 'pointer'
        }}
      &amp;gt;
        {isLiked ? '❤️ 좋아요 취소' : '  좋아요'} ({likeCount})
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default LikeButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Props vs State 한눈에 비교하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개념을 비교 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;table style=&quot;height: 140px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Props&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;State&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;부모 컴포넌트&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;해당 State를 정의한 컴포넌트&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;b&gt;불가능 (Read-Only)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;가능 (setter 함수 사용)&lt;/b&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;code&gt;useState(초기값)&lt;/code&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;UI 변화를 일으키는 동적 데이터 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;Props&lt;/code&gt;는 컴포넌트 &lt;b&gt;외부에서 들어오는 설정값&lt;/b&gt;이고, &lt;code&gt;State&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;h2 data-ke-size=&quot;size26&quot;&gt;4. Props와 State 함께 사용하기 (상태 끌어올리기)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 &quot;부모의 State를 자식에게 Props로 전달&quot;하거나, &quot;자식에서 발생한 이벤트를 부모의 State에 반영&quot;하는 패턴을 정말 자주 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 React에서는 상태 끌어올리기 (Lifting State Up)라고 부릅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  상태 끌어올리기 예제 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트가 &lt;code&gt;State&lt;/code&gt;를 가지고 있고, 자식 컴포넌트에게 &lt;b&gt;상태 값&lt;/b&gt;과 &lt;b&gt;상태 변경 함수&lt;/b&gt;를 각각 &lt;code&gt;Props&lt;/code&gt;로 전달하는 형태입니다.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;import React, { useState } from 'react';

// [자식 컴포넌트 1: 입력 폼]
// 부모로부터 text 상태와 setText 변경 함수를 props로 전달받습니다.
function TodoInput({ inputText, onInputChange, onAddTodo }) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input 
        type=&quot;text&quot; 
        value={inputText} 
        onChange={(e) =&amp;gt; onInputChange(e.target.value)} // 부모의 state를 변경 요청
        placeholder=&quot;할 일을 입력하세요&quot;
      /&amp;gt;
      &amp;lt;button onClick={onAddTodo}&amp;gt;추가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

// [자식 컴포넌트 2: 리스트 표시]
// 부모로부터 todos 배열을 props로 전달받아 렌더링만 전담합니다.
function TodoList({ todos }) {
  return (
    &amp;lt;ul&amp;gt;
      {todos.map((todo, index) =&amp;gt; (
        &amp;lt;li key={index}&amp;gt;{todo}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}

// [부모 컴포넌트: 모든 State 통합 관리]
function TodoApp() {
  // 1. 전체 할 일 목록 State
  const [todos, setTodos] = useState(['리액트 공부하기', 'State와 Props 정리하기']);

  // 2. 현재 입력 중인 텍스트 State
  const [inputText, setInputText] = useState('');

  // 할 일 추가 핸들러
  const handleAddTodo = () =&amp;gt; {
    if (inputText.trim() === '') return;
    setTodos((prevTodos) =&amp;gt; [...prevTodos, inputText]); // 불변성 유지하며 배열 추가
    setInputText(''); // 입력창 초기화
  };

  return (
    &amp;lt;div style={{ padding: '20px' }}&amp;gt;
      &amp;lt;h2&amp;gt;  나의 Todo 앱&amp;lt;/h2&amp;gt;
      {/* 자식 컴포넌트에게 State와 핸들러 함수를 Props로 내려줍니다. */}
      &amp;lt;TodoInput 
        inputText={inputText} 
        onInputChange={setInputText} 
        onAddTodo={handleAddTodo} 
      /&amp;gt;
      &amp;lt;TodoList todos={todos} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default TodoApp;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 핵심 요약&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;Props&lt;/code&gt;는 외부(부모)에서 받는 데이터이며, &lt;b&gt;자식 쪽에서 절대 변경할 수 없는 읽기 전용 값&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;State&lt;/code&gt;는 컴포넌트 내부에서 생성/변경되는 데이터이며, 값이 바뀌면 &lt;b&gt;화면이 자동으로 리렌더링&lt;/b&gt;됩니다.&lt;/li&gt;
&lt;li&gt;자식 컴포넌트가 부모의 데이터 상태를 바꾸고 싶다면, 부모가 &lt;b&gt;상태 변경 함수(Setter Function)를 Props로 넘겨주어 호출&lt;/b&gt;하게 만듭니다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1332</guid>
      <comments>https://hianna.tistory.com/1332#entry1332comment</comments>
      <pubDate>Sun, 2 Aug 2026 20:14:09 +0900</pubDate>
    </item>
    <item>
      <title>[React] useEffect 사용법 (개념, 의존성 배열, Clean-up 함수)</title>
      <link>https://hianna.tistory.com/1331</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트를 작성하다 보면 서버에서 데이터를 불러오거나(Data Fetching), 타이머를 설정하거나, 브라우저 DOM을 직접 조작하는 등의 작업이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 컴포넌트의 순수한 렌더링 과정 외에 &lt;b&gt;외부 시스템과 동기화하거나 발생하는 부수 효과&lt;/b&gt;를 React에서는 Side Effect(사이드 이펙트)라고 부릅니다. 그리고 이러한 Side Effect를 안전하게 처리하도록 도와주는 대표적인 Hook이 바로 &lt;code&gt;useEffect&lt;/code&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. useEffect 기본 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt;는 [실행할 콜백 함수, 의존성 배열]이라는 두 개의 인자를 받습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useEffect } from 'react';

useEffect(() =&amp;gt; {
  // 1. 실행할 Side Effect 로직 (컴포넌트 렌더링 후 실행됨)

  return () =&amp;gt; {
    // 2. Clean-up 함수 (선택 사항)
    // 컴포넌트가 언마운트되거나 Effect가 재실행되기 직전에 실행됨
  };
}, [/* 3. 의존성 배열 (Dependency Array) */]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 기본적으로 &lt;b&gt;컴포넌트가 화면에 렌더링(또는 리렌더링)된 이후&lt;/b&gt;에 &lt;code&gt;useEffect&lt;/code&gt; 안의 콜백 함수를 실행합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 의존성 배열(Dependency Array)의 3가지 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt;의 두 번째 인자인 의존성 배열(&lt;code&gt;deps&lt;/code&gt;)을 어떻게 넘겨주느냐에 따라 Effect가 실행되는 시점이 완전히 달라집니다.&lt;/p&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;처음 나타날 때(Mount)와 리렌더링될 때마다&lt;/b&gt; 실행됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function AlwaysRun() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // ❌ 두 번째 인자(배열)를 넘기지 않음
  useEffect(() =&amp;gt; {
    // count가 바뀌든, text가 바뀌든 매 렌더링 후 항상 실행됩니다.
    console.log('컴포넌트가 렌더링될 때마다 실행됩니다!');
  });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;Count: {count}&amp;lt;/button&amp;gt;
      &amp;lt;input value={text} onChange={(e) =&amp;gt; setText(e.target.value)} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 빈 배열(&lt;code&gt;[]&lt;/code&gt;)을 전달하는 경우: 마운트 시 1회만 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 배열을 넘기면 컴포넌트가 &lt;b&gt;화면에 처음 그려질 때(Mount) 단 한 번만&lt;/b&gt; 실행됩니다. API 호출이나 초기 설정 시 가장 많이 사용되는 형태입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function FetchUserData() {
  const [user, setUser] = useState(null);

  // ✅ 빈 배열([])을 두 번째 인자로 전달
  useEffect(() =&amp;gt; {
    // 컴포넌트가 처음 화면에 나타날 때 딱 1번만 실행됩니다.
    console.log('데이터를 서버에서 가져옵니다.');

    fetch('https://jsonplaceholder.typicode.com/users/1')
      .then((res) =&amp;gt; res.json())
      .then((data) =&amp;gt; setUser(data));
  }, []); // 의존하는 값이 없으므로 최초 마운트 시 1회만 동작

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;사용자 정보&amp;lt;/h3&amp;gt;
      {user ? &amp;lt;p&amp;gt;이름: {user.name}&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;로딩 중...&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&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;초기 마운트 시 1회 실행 + 배열에 지정한 값이 변경되어 리렌더링될 때마다&lt;/b&gt; 실행됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function UserDetail({ userId }) {
  const [userInfo, setUserInfo] = useState(null);

  // ✅ userId 변경을 감지하도록 배열에 포함
  useEffect(() =&amp;gt; {
    console.log(`User ID ${userId} 번 데이터 조회 중...`);

    fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then((res) =&amp;gt; res.json())
      .then((data) =&amp;gt; setUserInfo(data));

    // userId props가 변경될 때마다 이 콜백 함수가 다시 실행됩니다.
  }, [userId]); 

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h4&amp;gt;선택된 사용자: {userInfo?.name}&amp;lt;/h4&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 정리 함수 (Clean-up Function)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt; 내부에서 &lt;code&gt;return&lt;/code&gt;으로 함수를 반환하면, 이를 &lt;b&gt;정리(Clean-up) 함수&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;: 컴포넌트가 화면에서 사라질 때(Unmount) 또는 &lt;b&gt;Effect가 재실행되기 직전&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;: 타이머 해제(&lt;code&gt;clearInterval&lt;/code&gt;), 이벤트 리스너 제거(&lt;code&gt;removeEventListener&lt;/code&gt;), WebSocket 연결 종료 등 메모리 누수(Memory Leak)를 방지하는 작업을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() =&amp;gt; {
    // 1초마다 seconds 상태를 1씩 증가시키는 타이머 생성
    const timerId = setInterval(() =&amp;gt; {
      setSeconds((prev) =&amp;gt; prev + 1);
    }, 1000);

    console.log('타이머가 시작되었습니다.');

    // ✅ Clean-up 함수 반환
    return () =&amp;gt; {
      // 컴포넌트가 사라지거나(Unmount) Effect가 재실행될 때
      // 이전에 등록된 타이머를 제거하여 메모리 누수를 막습니다.
      clearInterval(timerId);
      console.log('타이머가 정리(Clean-up)되었습니다.');
    };
  }, []); // 빈 배열: 최초 마운트 시 타이머 시작, 언마운트 시 클린업 실행

  return &amp;lt;div&amp;gt;경과 시간: {seconds}초&amp;lt;/div&amp;gt;;
}

export default Timer;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. useEffect 사용 시 실무 필수 주의사항 3가지&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 무한 루프(Infinite Loop)를 주의하세요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt; 내부에서 &lt;code&gt;state&lt;/code&gt;를 변경하고, 그 &lt;code&gt;state&lt;/code&gt;를 다시 의존성 배열에 넣으면 &lt;b&gt;무한 루프&lt;/b&gt;가 발생합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// ❌ 무한 루프 발생 예시
const [count, setCount] = useState(0);

useEffect(() =&amp;gt; {
  // count 상태를 업데이트 -&amp;gt; 컴포넌트 리렌더링 -&amp;gt; useEffect 재실행 -&amp;gt; 또 setCount -&amp;gt; 무한 반복!
  setCount(count + 1); 
}, [count]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② &lt;code&gt;useEffect&lt;/code&gt; 내부에서 &lt;code&gt;async/await&lt;/code&gt; 직접 사용 금지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt;의 첫 번째 인자인 콜백 함수는 &lt;b&gt;반환값으로 오직 정리(Clean-up) 함수만&lt;/b&gt; 가질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 &lt;code&gt;async&lt;/code&gt; 함수는 기본적으로 &lt;code&gt;Promise&lt;/code&gt; 객체를 반환하므로 문법적으로 올바르지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ❌ 잘못된 사용법 (async 키워드를 직접 붙임)
useEffect(async () =&amp;gt; {
  const response = await fetch('/api/data');
}, []);

// ✅ 올바른 사용법 (내부에 async 함수를 정의하고 호출)
useEffect(() =&amp;gt; {
  // 내부에서 비동기 함수 선언
  const fetchData = async () =&amp;gt; {
    try {
      const response = await fetch('/api/data');
      const data = await response.json();
      console.log(data);
    } catch (error) {
      console.error('에러 발생:', error);
    }
  };

  fetchData(); // 비동기 함수 실행
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 단순 데이터 계산은 &lt;code&gt;useEffect&lt;/code&gt; 대신 일반 로직으로 처리하세요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State나 Props가 변경되었을 때 &lt;b&gt;단순히 값을 재계산하거나 변환하는 작업&lt;/b&gt;은 &lt;code&gt;useEffect&lt;/code&gt;를 사용하는 것보다 &lt;b&gt;컴포넌트 바디(렌더링 과정)에서 바로 계산&lt;/b&gt;하는 것이 성능과 코드 가독성에 훨씬 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ❌ 불필요한 useEffect 사용 (불필요한 추가 리렌더링 발생)
function BadFullName({ firstName, lastName }) {
  const [fullName, setFullName] = useState('');

  useEffect(() =&amp;gt; {
    // props가 바뀔 때마다 상태를 업데이트하므로 렌더링이 2번 일어남
    setFullName(`${firstName} ${lastName}`);
  }, [firstName, lastName]);

  return &amp;lt;div&amp;gt;{fullName}&amp;lt;/div&amp;gt;;
}

// ✅ 올바른 접근 (렌더링 도중 바로 계산)
function GoodFullName({ firstName, lastName }) {
  // 별도의 State나 useEffect 없이 렌더링 타임에 파생값을 계산합니다.
  const fullName = `${firstName} ${lastName}`;

  return &amp;lt;div&amp;gt;{fullName}&amp;lt;/div&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 핵심 요약&lt;/h2&gt;
&lt;table 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;실행 시점&lt;/td&gt;
&lt;td&gt;주요 사용 목적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;생략 (&lt;code&gt;useEffect(fn)&lt;/code&gt;)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최초 마운트 + &lt;b&gt;모든 리렌더링 시&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;로깅, 매번 실행되어야 하는 조작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;빈 배열 (&lt;code&gt;useEffect(fn, [])&lt;/code&gt;)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;최초 마운트 시 1회만&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;초기 API 데이터 로딩, 글로벌 이벤트 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;값 포함 (&lt;code&gt;useEffect(fn, [a, b])&lt;/code&gt;)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최초 마운트 + &lt;b&gt;의존성(a, b) 변경 시&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;특정 props/state 변화에 따른 동기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Clean-up 함수 (&lt;code&gt;return () =&amp;gt; {}&lt;/code&gt;)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;컴포넌트 언마운트 또는 &lt;b&gt;Effect 재실행 직전&lt;/b&gt;&lt;/td&gt;
&lt;td&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;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1331</guid>
      <comments>https://hianna.tistory.com/1331#entry1331comment</comments>
      <pubDate>Sun, 2 Aug 2026 20:10:11 +0900</pubDate>
    </item>
    <item>
      <title>[React] useState 사용법 (개념, 흔히 하는 실수, 주의할 점)</title>
      <link>https://hianna.tistory.com/1330</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React로 웹 애플리케이션을 만들 때 가장 핵심이 되는 개념은 '상태(State)'입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 자바스크립트 변수는 값이 바뀌어도 화면에 즉시 반영되지 않지만, React의 State 값이 변경되면 컴포넌트가 리렌더링(Re-render)되어 화면이 자동으로 업데이트됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 함수형 컴포넌트에서 상태를 관리하게 해주는 &lt;b&gt;&lt;code&gt;useState&lt;/code&gt; Hook의 기초 사용법&lt;/b&gt;과 &lt;b&gt;실무에서 꼭 알고 있어야 할 주의사항 3가지&lt;/b&gt;를 알아보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. useState 기본 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useState&lt;/code&gt;는 호출 시 &lt;b&gt;[현재 상태 값, 상태를 변경하는 함수]&lt;/b&gt; 형태의 배열을 반환합니다. 이를 구조 분해 할당(Destructuring)을 통해 가져와 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState } from 'react';

function Counter() {
  // useState(초기값): count 변수의 초기값을 0으로 설정합니다.
  // count: 현재 상태 값
  // setCount: count 상태를 변경하는 함수
  const [count, setCount] = useState(0);

  // 카운트 1 증가 핸들러
  const handleIncrease = () =&amp;gt; {
    // setCount를 호출하여 상태를 변경하면 React가 컴포넌트를 리렌더링합니다.
    setCount(count + 1);
  };

  // 카운트 1 감소 핸들러
  const handleDecrease = () =&amp;gt; {
    setCount(count - 1);
  };

  return (
    &amp;lt;div style={{ padding: '20px', textAlign: 'center' }}&amp;gt;
      &amp;lt;h2&amp;gt;현재 카운트: {count}&amp;lt;/h2&amp;gt;
      {/* 버튼 클릭 시 각각의 이벤트 핸들러가 실행됩니다. */}
      &amp;lt;button onClick={handleIncrease}&amp;gt;+1 증가&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleDecrease}&amp;gt;-1 감소&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Counter;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. useState 사용 시 꼭 주의해야 할 점 3가지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useState&lt;/code&gt;는 단순해 보이지만, 동작 원리를 제대로 이해하지 못하면 예측하지 못한 버그가 발생할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 상태 업데이트는 비동기적으로 처리됩니다 (함수형 업데이트)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 성능 최적화를 위해 여러 상태 변경 요청을 모아서 한 번에 처리(Batching)합니다.&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;livescript&quot;&gt;&lt;code&gt;import React, { useState } from 'react';

function BadCounter() {
  const [count, setCount] = useState(0);

  const handleTripleAdd = () =&amp;gt; {
    // count가 0인 상태에서 실행될 경우:
    setCount(count + 1); // setCount(0 + 1) -&amp;gt; 1로 변경 요청
    setCount(count + 1); // setCount(0 + 1) -&amp;gt; 여전히 1로 변경 요청
    setCount(count + 1); // setCount(0 + 1) -&amp;gt; 여전히 1로 변경 요청

    // 결과적으로 3이 더해지는 것이 아니라 1만 증가합니다.
    // 비동기 처리 특성상 이 함수가 실행되는 동안 count는 여전히 0이기 때문입니다.
  };

  return &amp;lt;button onClick={handleTripleAdd}&amp;gt;+3 증가 (잘못된 예시)&amp;lt;/button&amp;gt;;
}
&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;콜백 함수&lt;/b&gt;를 넘겨주어야 합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState } from 'react';

function GoodCounter() {
  const [count, setCount] = useState(0);

  const handleTripleAdd = () =&amp;gt; {
    // 이전 상태(prev)를 인자로 받는 함수를 전달합니다.
    // React는 이 함수들을 큐에 넣고 순서대로 최신 값을 적용합니다.
    setCount((prev) =&amp;gt; prev + 1); // prev = 0 -&amp;gt; 반환값 1
    setCount((prev) =&amp;gt; prev + 1); // prev = 1 -&amp;gt; 반환값 2
    setCount((prev) =&amp;gt; prev + 1); // prev = 2 -&amp;gt; 반환값 3

    // 한 번의 클릭으로 정확히 3이 증가합니다!
  };

  return &amp;lt;button onClick={handleTripleAdd}&amp;gt;+3 증가 (올바른 예시)&amp;lt;/button&amp;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;② 객체와 배열은 '불변성(Immutability)'을 유지해야 합니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 이전 상태와 다음 상태의 참조 주소(Reference)를 비교하여 변경 여부를 감지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체나 배열 내부의 값을 직접 수정하면, 메모리 주소가 바뀌지 않아 React가 상태 변경을 감지하지 못하고 리렌더링을 건너뜁니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 예시 (원본 직접 수정)&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const [user, setUser] = useState({ name: '홍길동', age: 20 });

const handleUpdateAge = () =&amp;gt; {
  // ❌ 객체의 속성을 직접 변경 (메모리 참조 주소는 동일함)
  user.age = 21; 

  // ❌ 동일한 객체 주소를 전달했으므로 React는 변경 사항이 없다고 판단해 리렌더링하지 않음!
  setUser(user); 
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ 올바른 작성법 (새로운 객체/배열 생성)&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState } from 'react';

function UserProfile() {
  const [user, setUser] = useState({ name: '홍길동', age: 20 });

  const handleUpdateAge = () =&amp;gt; {
    // ✅ 전개 연산자(...)를 사용해 기존 값을 복사하고, 새로운 객체를 만들어 전달합니다.
    setUser((prevUser) =&amp;gt; ({
      ...prevUser,            // 기존 객체의 속성들을 복사 (name: '홍길동')
      age: prevUser.age + 1,  // age 속성만 새로운 값으로 덮어쓰기
    }));
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;이름: {user.name}, 나이: {user.age}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={handleUpdateAge}&amp;gt;나이 +1&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 경우에도 &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;splice&lt;/code&gt; 같은 원본을 변형하는 메서드 대신, &lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, 또는 &lt;code&gt;[...]&lt;/code&gt; 전개 연산자를 사용해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const [items, setItems] = useState(['사과', '바나나']);

const handleAddItem = () =&amp;gt; {
  // ✅ 불변성을 유지하며 새 요소 추가
  setItems((prevItems) =&amp;gt; [...prevItems, '포도']); 
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 무거운 연산이 포함된 초기값은 '게으른 초기화(Lazy Initialization)'를 사용하세요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useState(초기값)&lt;/code&gt; 형태는 컴포넌트가 &lt;b&gt;리렌더링될 때마다 초기값 인자 계산식을 다시 호출&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값 계산에 연산량이 많거나 &lt;code&gt;localStorage&lt;/code&gt; 조회 같은 작업이 포함된다면 낭비가 발생합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;❌ 매 리렌더링마다 불필요한 연산 수행&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// ❌ 컴포넌트가 리렌더링될 때마다 getExpensiveCalculatedValue() 함수가 계속 재실행됩니다.
// (실제 초기값은 첫 번째 렌더링에만 사용됨에도 불구하고 연산이 반복됨)
const [data, setData] = useState(getExpensiveCalculatedValue());
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ 올바른 작성법 (게으른 초기화)&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState } from 'react';

// 연산 비용이 많이 드는 함수 예시
function getExpensiveCalculatedValue() {
  console.log('복잡하고 무거운 계산 수행 중...');
  return 10000;
}

function LazyInitExample() {
  // ✅ 함수 자체를 콜백 형태로 전달하면 첫 렌더링 시에만 단 한 번 실행됩니다.
  const [data, setData] = useState(() =&amp;gt; getExpensiveCalculatedValue());

  const [count, setCount] = useState(0);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;계산된 초기값: {data}&amp;lt;/p&amp;gt;
      {/* 이 버튼을 눌러 리렌더링이 발생해도 무거운 계산 함수는 재실행되지 않습니다. */}
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;리렌더링 유발 ({count})&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;&lt;br /&gt;React 개발에서 &lt;/span&gt;&lt;code style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;useState&lt;/code&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;의 이 세 가지 주의사항만 잘 지켜도 상태 관련 버그의 대부분을 예방할 수 있습니다!&lt;/span&gt;&lt;/h2&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1330</guid>
      <comments>https://hianna.tistory.com/1330#entry1330comment</comments>
      <pubDate>Sun, 2 Aug 2026 19:48:21 +0900</pubDate>
    </item>
    <item>
      <title>[React] AboutController로 비동기 처리하기</title>
      <link>https://hianna.tistory.com/1329</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 애플리케이션을 개발하다 보면 &lt;code&gt;useEffect&lt;/code&gt; 내부에서 API 호출 등 비동기 작업을 수행하는 경우가 매우 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 컴포넌트가 빠르게 재렌더링되거나 유저가 다른 페이지로 이동(언마운트)했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 보낸 API 요청이 뒤늦게 응답을 반환하여 &lt;b&gt;메모리 누수&lt;/b&gt;나 레이스 컨디션(Race Condition)을 유발하곤 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 깔끔하게 해결해 주는 표준 Web API가 바로 &lt;code&gt;AbortController&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅에서는 &lt;code&gt;AbortController&lt;/code&gt;의 기본 개념부터 React에서 활용하는 3가지 실전 패턴&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;h2 data-ke-size=&quot;size26&quot;&gt;1. AbortController란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AbortController&lt;/code&gt;는 하나 이상의 DOM 요청(예: &lt;code&gt;fetch&lt;/code&gt; API)을 원하는 시점에 취소(Abort)할 수 있게 해주는 JavaScript 표준 Web 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;code&gt;const controller = new AbortController();&lt;/code&gt;로 인스턴스를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;controller.signal&lt;/code&gt;을 &lt;code&gt;fetch&lt;/code&gt; 옵션으로 전달합니다.&lt;/li&gt;
&lt;li&gt;요청 중단이 필요한 시점에 &lt;code&gt;controller.abort()&lt;/code&gt;를 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 패턴 1: 컴포넌트 언마운트 시 요청 취소하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 데이터를 불러오는 도중에 페이지를 이탈하면, 응답이 돌아와도 상태를 업데이트할 컴포넌트가 사라진 상태입니다. &lt;code&gt;useEffect&lt;/code&gt;의 클린업 함수(Cleanup Function)에서 &lt;code&gt;controller.abort()&lt;/code&gt;를 호출하면 불필요한 네트워크 리소스 소비와 상태 업데이트 에러를 방지할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() =&amp;gt; {
    // 1. 새로운 AbortController 인스턴스 생성
    const controller = new AbortController();
    // 2. 컨트롤러로부터 signal 객체를 추출
    const signal = controller.signal;

    async function fetchUserData() {
      setLoading(true);
      setError(null);

      try {
        // 3. fetch 옵션에 signal 전달
        const response = await fetch(`https://api.example.com/users/${userId}`, {
          signal: signal // 또는 단축 속성명으로 signal 만 작성 가능
        });

        if (!response.ok) {
          throw new Error('데이터를 불러오는데 실패했습니다.');
        }

        const data = await response.json();

        // 4. 요청이 정상 완료되었을 때만 상태 업데이트
        setUser(data);
      } catch (err) {
        // 5. 취소 요청으로 발생한 에러(AbortError)인지 구분 처리
        if (err.name === 'AbortError') {
          // abort() 호출에 의한 에러는 의도된 취소이므로 콘솔 로그만 남기거나 무시
          console.log('API 요청이 성공적으로 취소되었습니다.');
        } else {
          // 일반적인 네트워크/서버 에러는 사용자에게 표시
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    }

    fetchUserData();

    // 6. useEffect 클린업 함수: 컴포넌트가 언마운트되거나 userId가 변경될 때 실행
    return () =&amp;gt; {
      // 진행 중인 API 요청 중단
      controller.abort();
    };
  }, [userId]); // userId가 바뀔 때마다 기존 요청을 취소하고 새 요청 시작

  if (loading) return &amp;lt;div&amp;gt;로딩 중...&amp;lt;/div&amp;gt;;
  if (error) return &amp;lt;div&amp;gt;에러 발생: {error}&amp;lt;/div&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;{user?.name}님의 프로필&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;이메일: {user?.email}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default UserProfile;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 패턴 2: 레이스 컨디션(Race Condition) 방지 - 실시간 검색&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 검색창에 글자를 빠르게 칠 때(&lt;code&gt;r&lt;/code&gt;, &lt;code&gt;re&lt;/code&gt;, &lt;code&gt;rea&lt;/code&gt;, &lt;code&gt;react&lt;/code&gt;), 각 글자마다 API 요청이 발송됩니다. 만약 네트워크 상황에 의해 이전 요청(&lt;code&gt;re&lt;/code&gt;)이 마지막 요청(&lt;code&gt;react&lt;/code&gt;)보다 나중에 응답되면, &lt;b&gt;잘못된 이전 검색 결과가 화면에 노출&lt;/b&gt;되는 &lt;b&gt;레이스 컨디션&lt;/b&gt;이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AbortController&lt;/code&gt;를 사용하면 새로운 입력이 들어오는 순간 이전 요청을 바로 취소하여 항상 &lt;b&gt;최신 응답만 적용&lt;/b&gt;되도록 보장합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from 'react';

function SearchAutoComplete() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  useEffect(() =&amp;gt; {
    // 검색어가 비어있으면 API 요청을 보내지 않고 결과 초기화
    if (!query.trim()) {
      setResults([]);
      return;
    }

    // 새로운 컨트롤러 생성
    const controller = new AbortController();

    async function searchApi() {
      try {
        const response = await fetch(
          `https://api.example.com/search?q=${encodeURIComponent(query)}`,
          { signal: controller.signal } // signal 연결
        );
        const data = await response.json();
        setResults(data.items);
      } catch (err) {
        // 의도적인 취소는 에러로 다루지 않음
        if (err.name !== 'AbortError') {
          console.error('검색 중 에러 발생:', err);
        }
      }
    }

    searchApi();

    // query가 변경되면 다음 effect가 실행되기 전 클린업이 작동하여 '이전 요청'을 취소함
    return () =&amp;gt; {
      controller.abort();
    };
  }, [query]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        value={query}
        onChange={(e) =&amp;gt; setQuery(e.target.value)}
        placeholder=&quot;검색어를 입력하세요...&quot;
      /&amp;gt;
      &amp;lt;ul&amp;gt;
        {results.map((item) =&amp;gt; (
          &amp;lt;li key={item.id}&amp;gt;{item.title}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default SearchAutoComplete;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 패턴 3: Axios에서의 AbortController 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 React 프로젝트에서 &lt;code&gt;fetch&lt;/code&gt; 대신 &lt;code&gt;Axios&lt;/code&gt;를 사용합니다. Axios v0.22.0부터는 기존의 &lt;code&gt;CancelToken&lt;/code&gt;(현재는 deprecated) 대신 &lt;b&gt;표준 &lt;code&gt;AbortController&lt;/code&gt; signal을 지원&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { useEffect, useState } from 'react';
import axios from 'axios';

function AxiosExample() {
  const [data, setData] = useState(null);

  useEffect(() =&amp;gt; {
    const controller = new AbortController();

    axios
      .get('https://api.example.com/data', {
        // Axios 요청 설정에 signal 객체 전달
        signal: controller.signal
      })
      .then((response) =&amp;gt; {
        setData(response.data);
      })
      .catch((error) =&amp;gt; {
        // Axios에서는 axios.isCancel(error) 또는 error.name으로 취소 여부 확인
        if (axios.isCancel(error)) {
          console.log('Axios 요청이 취소되었습니다:', error.message);
        } else {
          console.error('Axios 에러:', error);
        }
      });

    return () =&amp;gt; {
      // 클린업 시 중단
      controller.abort();
    };
  }, []);

  return &amp;lt;div&amp;gt;{data ? JSON.stringify(data) : '불러오는 중...'}&amp;lt;/div&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 패턴 4: 재사용 가능한 커스텀 훅 &lt;code&gt;useFetch&lt;/code&gt; 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 &lt;code&gt;useEffect&lt;/code&gt; 안에서 &lt;code&gt;AbortController&lt;/code&gt;를 선언하고 클린업하는 코드가 번거롭다면, 커스텀 훅(Custom Hook)으로 로직을 분리하여 깔끔하게 재사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useEffect } from 'react';

/**
 * URL 기반으로 데이터를 가져오며, 
 * URL 변경이나 언마운트 시 자동 요청 취소를 지원하는 커스텀 훅
 */
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() =&amp;gt; {
    // URL이 유효하지 않으면 실행 방지
    if (!url) return;

    const controller = new AbortController();

    const fetchData = async () =&amp;gt; {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch(url, { signal: controller.signal });
        if (!response.ok) {
          throw new Error(`HTTP 에러 발생: ${response.status}`);
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        // 취소가 아닐 때만 에러 상태 업데이트
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    // 클린업 함수에서 자동으로 요청 중단 처리
    return () =&amp;gt; {
      controller.abort();
    };
  }, [url]);

  return { data, loading, error };
}

// --- 커스텀 훅 사용 예시 ---
function CustomHookDemo({ productId }) {
  // 사용처에서는 abort 신경 쓸 필요 없이 단 한 줄로 안전한 비동기 통신 구현
  const { data, loading, error } = useFetch(`https://api.example.com/products/${productId}`);

  if (loading) return &amp;lt;p&amp;gt;상품 정보를 불러오는 중...&amp;lt;/p&amp;gt;;
  if (error) return &amp;lt;p&amp;gt;에러 발생: {error}&amp;lt;/p&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;{data?.name}&amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt;가격: {data?.price}원&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default CustomHookDemo;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1329</guid>
      <comments>https://hianna.tistory.com/1329#entry1329comment</comments>
      <pubDate>Sun, 2 Aug 2026 19:09:52 +0900</pubDate>
    </item>
    <item>
      <title>React StrictMode 완벽 가이드: 왜 내 코드는 두 번 실행될까?</title>
      <link>https://hianna.tistory.com/1328</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 프로젝트를 진행하다 보면 한 번쯤 이런 경험을 하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&quot;어? 분명 &lt;code&gt;useEffect&lt;/code&gt;에 &lt;code&gt;console.log&lt;/code&gt;를 하나만 찍었는데 왜 터미널과 브라우저 콘솔에 두 번씩 나올까?&quot;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&quot;API 요청을 한 번만 보내야 하는데 왜 두 번이나 호출되는 거지?&quot;&lt;/i&gt;&lt;/p&gt;
&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;React StrictMode&lt;/b&gt;입니다. 이번 글에서는 StrictMode가 무엇인지, 왜 개발 환경에서 코드를 두 번 실행하는지, 그리고 이를 통해 어떤 이점을 얻을 수 있는지 정리해 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. React StrictMode란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;React.StrictMode&lt;/code&gt;는 React 애플리케이션 내의 잠재적인 문제를 감지하기 위한 &lt;b&gt;개발 전용 도구&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);&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;UI 영향 없음&lt;/b&gt;: &lt;code&gt;StrictMode&lt;/code&gt;는 별도의 DOM 요소를 생성하지 않으며, visual한 UI를 변경하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발 모드 전용&lt;/b&gt;: &lt;b&gt;Development 빌드에서만 동작&lt;/b&gt;합니다. Production 빌드에서는 이 모든 검사 로직이 자동으로 비활성화되어 성능이나 동작에 아무런 영향을 주지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. StrictMode의 핵심 기능 3가지&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 순수성(Purity) 검사를 위한 이중 렌더링 (Double Rendering)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 컴포넌트 렌더링 함수는 순수 함수(Pure Function)여야 합니다. 동일한 입력(props, state)이 주어지면 항상 동일한 JSX를 반환해야 하며, 렌더링 도중에 외부 변수를 수정하는 등의 사이드 이펙트(Side Effect)가 없어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StrictMode는 컴포넌트가 순수한지 확인하기 위해 &lt;b&gt;렌더링 단계를 의도적으로 두 번 실행&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// ❌ 잘못된 작성법: 렌더링 중에 외부 변수를 직접 수정 (Impure)
let count = 0;

function Counter() {
  count++; // 사이드 이펙트 발생!
  return &amp;lt;h1&amp;gt;Count: {count}&amp;lt;/h1&amp;gt;;
}&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;StrictMode가 없다면:&lt;/b&gt; 1, 2, 3...처럼 동작하는 것처럼 보여 버그를 놓치기 쉽습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;StrictMode가 있다면:&lt;/b&gt; 첫 렌더링 시 &lt;code&gt;count&lt;/code&gt;가 바로 2가 되는 이상 현상이 관찰되어, 코드가 비순수(Impure)하다는 것을 빠르게 감지할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Effect의 Cleanup 검사 (React 18 이상)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18부터 StrictMode는 컴포넌트가 마운트될 때 &lt;code&gt;useEffect&lt;/code&gt;를 다음과 같은 순서로 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\text{Mount (Setup)} \longrightarrow \text{Unmount (Cleanup)} \longrightarrow \text{Remount (Setup)}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;[Setup $\rightarrow$ Cleanup $\rightarrow$ Setup]&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;미래의 React 기능(예: Offscreen API, 화면 전환 시 이전 상태 보존 등)은 컴포넌트를 DOM에서 제거했다가 나중에 이전 상태 그대로 다시 복원(Remount)할 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;code&gt;useEffect&lt;/code&gt;에 &lt;b&gt;정리(Cleanup) 로직이 빠져 있다면&lt;/b&gt;, 컴포넌트가 재마운트될 때 메모리 누수나 중복 이벤트 리스너가 등록되는 버그가 발생합니다. StrictMode는 이 문제를 미리 찾아낼 수 있도록 강제로 재마운트 시뮬레이션을 수행합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 레거시 API 사용 감지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래된 React 코드베이스나 구버전 라이브러리를 사용할 때 발생할 수 있는 위험 요소들을 감지하여 콘솔 경고를 띄워줍니다.&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;String Ref 사용 경고&lt;/b&gt;: &lt;code&gt;ref=&quot;myInput&quot;&lt;/code&gt; 대신 &lt;code&gt;useRef()&lt;/code&gt; 또는 콜백 Ref 사용 권장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;findDOMNode 사용 경고&lt;/b&gt;: 캡슐화를 깨뜨리는 &lt;code&gt;findDOMNode&lt;/code&gt; 대신 Ref 사용 권장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구버전 라이프사이클 메서드 경고&lt;/b&gt;: &lt;code&gt;componentWillMount&lt;/code&gt; 등 안전하지 않은 메서드 감지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 자주 묻는 질문: &quot;API 두 번 호출되는 건 어떻게 해결하나요?&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔한 실수는 &lt;b&gt;&quot;API가 두 번 호출되니까 StrictMode를 꺼버려야지!&quot;&lt;/b&gt; 라고 생각하는 것입니다. 이는 문제를 해결하는 것이 아니라 버그를 숨기는 것입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 잘못된 해결 방법 (StrictMode 제거)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;index.jsx&lt;/code&gt;에서 &lt;code&gt;&amp;lt;React.StrictMode&amp;gt;&lt;/code&gt;를 지워버리면 개발 모드에서 더 이상 두 번 실행되지 않지만, 클린업이 누락된 잠재적 버그는 그대로 남게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 올바른 해결 방법 (Cleanup 로직 작성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt;에서 비동기 데이터 패칭 시 &lt;b&gt;AbortController&lt;/b&gt; 또는 클린업 플래그를 활용하여 언마운트 시 취소 처리 로직을 작성해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() =&amp;gt; {
    // ① 네트워크 요청을 취소할 수 있는 AbortController 인스턴스 생성
    const controller = new AbortController();

    async function fetchUser() {
      try {
        // fetch 옵션의 signal에 controller.signal을 전달하여 연동
        const response = await fetch(`/api/users/${userId}`, {
          signal: controller.signal 
        });
        const data = await response.json();
        setUser(data);
      } catch (error) {
        // 의도적인 요청 취소(AbortError)인 경우 콘솔에 에러를 출력하지 않고 무시
        if (error.name !== 'AbortError') {
          console.error('Fetch error:', error);
        }
      }
    }

    // 비동기 요청 함수 실행
    fetchUser();

    // ② Cleanup 함수 반환:
    // 컴포넌트가 언마운트되거나 userId가 바뀔 때 실행됩니다.
    // StrictMode가 [Setup -&amp;gt; Cleanup -&amp;gt; Setup]을 시뮬레이션할 때, 
    // 첫 번째 Setup에서 발생한 요청을 안전하게 취소(abort)하여 데이터 유실 및 중복 요청 문제를 방지합니다.
    return () =&amp;gt; {
      controller.abort(); 
    };
  }, [userId]); // userId 변경 시 Effect 재실행

  return &amp;lt;div&amp;gt;{user ? user.name : 'Loading...'}&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 클린업 함수를 올바르게 구현하면, StrictMode가 첫 번째 실행 후 즉시 클린업을 호출하므로 첫 번째 네트워크 요청이 취소되고 두 번째 요청만 정상 반영됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 요약: Development vs Production&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;개발 환경 (Development)&lt;/th&gt;
&lt;th&gt;프로덕션 (Production)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;렌더링 횟수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2회 실행 (사이드 이펙트 검사)&lt;/td&gt;
&lt;td&gt;1회 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;useEffect 실행&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Setup $\rightarrow$ Cleanup $\rightarrow$ Setup&lt;/td&gt;
&lt;td&gt;Setup만 실행 (마운트 시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;성능 영향&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;검사 로직으로 인한 약간의 부하&lt;/td&gt;
&lt;td&gt;&lt;b&gt;영향 없음 (0%)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;콘솔 경고&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;레거시 API 및 이펙트 경고 출력&lt;/td&gt;
&lt;td&gt;출력 안 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React StrictMode는 개발자를 괴롭히기 위한 도구가 아니라, &lt;b&gt;더 안전하고 예측 가능한 코드를 작성할 수 있도록 돕는 든든한 가드레일&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/React</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1328</guid>
      <comments>https://hianna.tistory.com/1328#entry1328comment</comments>
      <pubDate>Sun, 2 Aug 2026 18:55:30 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 여러 텍스트 파일을 하나의 딕셔너리로 병합하기</title>
      <link>https://hianna.tistory.com/1327</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;여러 파일을 다룰 때는 &lt;b&gt;1) 어떤 파일들을 읽을지 목록을 만들고, 2) 각 파일을 열어 데이터를 추출한 뒤, 3) 메인 딕셔너리에 추가&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.&amp;nbsp;&amp;nbsp;라이브러리로 파일 목록 한 번에 가져오기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;glob&lt;/code&gt; 모듈을 사용하면 특정 폴더 안에 있는 모든 &lt;code&gt;.txt&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 glob
import os

# 1. 'data' 폴더 안의 모든 .txt 파일 경로를 가져옵니다.
file_list = glob.glob(&quot;data/*.txt&quot;)

all_data = {}

for file_path in file_list:
    # 2. 파일 이름만 추출해서 딕셔너리의 키(Key)로 사용 (예: 'student1')
    file_name = os.path.basename(file_path).replace(&quot;.txt&quot;, &quot;&quot;)

    with open(file_path, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
        # 3. 파일 내용을 읽어 딕셔너리의 값(Value)으로 저장
        # 여기서는 파일 전체 내용을 저장하거나, 줄별로 파싱할 수 있습니다.
        all_data[file_name] = f.read().strip()

print(f&quot;총 {len(all_data)}개의 파일을 합쳤습니다.&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;2. 파일별 데이터를 '중첩 딕셔너리'로 합치기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 내용 전체를 읽는 게 아니라, 각 파일 안에 들어있는 &lt;code&gt;Key: Value&lt;/code&gt; 쌍들을 모아 하나의 큰 중첩 딕셔너리로 만드는 방식입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 상세 데이터 병합&lt;/h4&gt;&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;import glob

# 예: 학생별 성적 파일들이 여러 개 있을 때
all_students_scores = {}

for file_path in glob.glob(&quot;scores/*.txt&quot;):
    student_name = os.path.basename(file_path).split(&quot;.&quot;)[0]

    current_scores = {}
    with open(file_path, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
        for line in f:
            if &quot;:&quot; in line:
                subject, score = line.strip().split(&quot;:&quot;)
                current_scores[subject.strip()] = int(score.strip())

    # 학생 이름을 키로, 성적 딕셔너리를 값으로 저장
    all_students_scores[student_name] = current_scores

# 결과: {'철수': {'국어': 90, '수학': 85}, '영희': {'국어': 95, '수학': 100}}
print(all_students_scores)
&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;리스트 형태로 쌓이게&lt;/b&gt; 하려면 &lt;code&gt;defaultdict&lt;/code&gt;가 가장 좋습니다.&lt;/p&gt;&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;from collections import defaultdict
import glob

# 날짜별 판매 기록 파일을 합칠 때
total_sales = defaultdict(list)

for file_path in glob.glob(&quot;sales/*.txt&quot;):
    with open(file_path, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
        for line in f:
            item, amount = line.strip().split(&quot;,&quot;)
            # 같은 품목이 나와도 리스트에 계속 추가됨
            total_sales[item].append(int(amount))

# 결과: {'사과': [10, 5, 12], '포도': [3, 8]}
&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;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;glob.glob(&quot;경로/*.txt&quot;)&lt;/code&gt;&lt;/b&gt;: 원하는 확장자 파일만 쏙쏙 골라 리스트로 만든다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;os.path.basename()&lt;/code&gt;&lt;/b&gt;: 파일의 전체 경로에서 파일 이름만 추출할 때 유용하다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;병합 전략&lt;/b&gt;: 파일 이름을 키로 쓸지, 파일 내부의 데이터를 누적할지에 따라 일반 딕셔너리 또는 &lt;code&gt;defaultdict&lt;/code&gt;를 선택한다.&lt;/li&gt; 
&lt;/ol&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/1327</guid>
      <comments>https://hianna.tistory.com/1327#entry1327comment</comments>
      <pubDate>Sat, 18 Jul 2026 09:43:58 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 텍스트 파일을 읽어 딕셔너리로 변환하는 3가지 방법</title>
      <link>https://hianna.tistory.com/1326</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. JSON 형식으로 저장된 텍스트 읽기 (가장 추천)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파일 내용이 &lt;code&gt;{ &quot;key&quot;: &quot;value&quot; }&lt;/code&gt; 형태라면 &lt;code&gt;json&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 json

# 파일 읽기
with open(&quot;user.txt&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    # 파일 전체 내용을 딕셔너리로 바로 변환
    data_dict = json.load(f)

print(data_dict)
print(type(data_dict)) # &amp;lt;class 'dict'&amp;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. &quot;Key: Value&quot; 형태로 한 줄씩 기록된 경우&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;  예제 코드:&amp;nbsp;&amp;nbsp;활용&lt;/h4&gt;&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;# scores.txt 내용 예시:
# 수학: 90
# 영어: 85

result_dict = {}

with open(&quot;scores.txt&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    for line in f:
        # 1. 줄바꿈 문자 제거 및 ' : ' 기준으로 쪼개기
        line = line.strip()
        if &quot;:&quot; in line:
            key, value = line.split(&quot;:&quot;)
            # 2. 앞뒤 공백 제거 후 딕셔너리에 추가
            result_dict[key.strip()] = value.strip()

print(result_dict)
# 출력: {'수학': '90', '영어': '85'}
&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;ID, 이름, 지역&lt;/code&gt;이 한 줄씩 길게 나열된 텍스트 파일을 딕셔너리 리스트로 만드는 방법입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 실무형 데이터 파싱&lt;/h4&gt;&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;# data.txt 내용 예시:
# 1,철수,서울
# 2,영희,부산

user_list = []
keys = [&quot;id&quot;, &quot;name&quot;, &quot;city&quot;]

with open(&quot;data.txt&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    for line in f:
        values = line.strip().split(&quot;,&quot;)
        # zip을 사용해 key와 value를 매칭한 뒤 딕셔너리 생성
        user_dict = dict(zip(keys, values))
        user_list.append(user_dict)

print(user_list)
# 출력: [{'id': '1', 'name': '철수', 'city': '서울'}, ...]
&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;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;.strip()&lt;/code&gt; 사용&lt;/b&gt;: 파일을 읽으면 줄 끝에 눈에 보이지 않는 줄바꿈 문자(&lt;code&gt;\n&lt;/code&gt;)가 포함되므로 반드시 제거해야 합니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;구분자 확인&lt;/b&gt;: 데이터가 &lt;code&gt;:&lt;/code&gt;로 나뉘어 있는지, &lt;code&gt;,&lt;/code&gt;로 나뉘어 있는지 확인하고 &lt;code&gt;split()&lt;/code&gt; 안에 넣어주세요.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;데이터 타입&lt;/b&gt;: 파일에서 읽어온 모든 값은 기본적으로 &lt;b&gt;문자열(str)&lt;/b&gt;입니다. 숫자가 필요하다면 &lt;code&gt;int(value)&lt;/code&gt;로 직접 바꿔주어야 합니다.&lt;/li&gt; 
&lt;/ol&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/1326</guid>
      <comments>https://hianna.tistory.com/1326#entry1326comment</comments>
      <pubDate>Sat, 13 Jun 2026 12:38:50 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 파이썬 딕셔너리를 TXT 파일로 저장하는 법</title>
      <link>https://hianna.tistory.com/1325</link>
      <description>&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;code&gt;json&lt;/code&gt;&lt;/b&gt; 모듈이나 &lt;b&gt;&lt;code&gt;str()&lt;/code&gt;&lt;/b&gt; 함수를 사용하는 것입니다. 텍스트 파일이지만 딕셔너리의 구조(&lt;code&gt;{ }&lt;/code&gt;)를 그대로 유지하여 저장합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  방법 1:&amp;nbsp;&amp;nbsp;모듈 활용 (가장 표준적인 방식)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.txt&lt;/code&gt; 확장자로 저장하더라도 내부 형식을 JSON으로 유지하면 나중에 데이터를 다시 불러와서 딕셔너리로 복구하기가 매우 쉽습니다.&lt;/p&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import json

user_data = {&quot;name&quot;: &quot;철수&quot;, &quot;age&quot;: 25, &quot;city&quot;: &quot;서울&quot;}

# txt 파일로 저장
with open(&quot;user.txt&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    # indent를 주면 보기 좋게 저장됩니다.
    json.dump(user_data, f, ensure_ascii=False, indent=4)

print(&quot;txt 파일에 딕셔너리 구조로 저장 완료!&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;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;  방법 2: 반복문을 활용한 커스텀 저장&lt;/h4&gt;&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;scores = {&quot;수학&quot;: 90, &quot;영어&quot;: 85, &quot;과학&quot;: 95}

with open(&quot;scores.txt&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    f.write(&quot;--- 성적표 ---\n&quot;)
    for subject, score in scores.items():
        # &quot;과목: 점수&quot; 형태로 한 줄씩 기록
        f.write(f&quot;{subject}: {score}점\n&quot;)
    f.write(&quot;--------------&quot;)

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;3. 복잡한 객체 그대로 저장하기:&amp;nbsp;&amp;nbsp;모듈&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리 안에 튜플이나 커스텀 클래스 객체처럼 JSON으로 담기 힘든 파이썬 전용 데이터가 섞여 있다면 &lt;b&gt;&lt;code&gt;pickle&lt;/code&gt;&lt;/b&gt;을 사용합니다. 단, 사람이 읽을 수는 없는 바이너리 형태로 저장됩니다.&lt;/p&gt;&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;import pickle

complex_data = {&quot;id&quot;: 1, &quot;coord&quot;: (10, 20), &quot;tags&quot;: {&quot;python&quot;, &quot;coding&quot;}}

# 'wb'는 Write Binary의 약자입니다.
with open(&quot;data.txt&quot;, &quot;wb&quot;) as f:
    pickle.dump(complex_data, f)

# 다시 불러올 때
with open(&quot;data.txt&quot;, &quot;rb&quot;) as f:
    loaded_data = pickle.load(f)
    print(loaded_data)
&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;table style=&quot;height: 105px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt; 
 &lt;tbody&gt; 
  &lt;tr style=&quot;height: 16px;&quot;&gt; 
   &lt;td style=&quot;height: 16px;&quot;&gt;&lt;b&gt;저장 방식&lt;/b&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 16px;&quot;&gt;확장자&lt;/td&gt; 
   &lt;td style=&quot;height: 16px;&quot;&gt;특징&lt;/td&gt; 
   &lt;td style=&quot;height: 16px;&quot;&gt;&lt;b&gt;추천 용도&lt;/b&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr style=&quot;height: 22px;&quot;&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;JSON 방식&lt;/b&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;code&gt;.txt&lt;/code&gt; / &lt;code&gt;.json&lt;/code&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;구조 유지, 범용성 높음&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;가장 권장 (데이터 보관용)&lt;/b&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr style=&quot;height: 22px;&quot;&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;반복문 방식&lt;/b&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;code&gt;.txt&lt;/code&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;사람이 읽기 가장 편함&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;로그 기록, 간단한 리포트&lt;/b&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr style=&quot;height: 22px;&quot;&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;Pickle 방식&lt;/b&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;code&gt;.txt&lt;/code&gt; / &lt;code&gt;.pkl&lt;/code&gt;&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;파이썬 전용 데이터 완벽 보존&lt;/td&gt; 
   &lt;td style=&quot;height: 22px;&quot;&gt;&lt;b&gt;복잡한 파이썬 객체 저장&lt;/b&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&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;code&gt;encoding=&quot;utf-8&quot;&lt;/code&gt;을 빼먹으면 한글이 깨져서 저장될 수 있습니다. 윈도우와 맥/리눅스 어디서든 잘 보이게 하려면 항상 이 옵션을 넣어주는 습관을 가지세요!&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 딕셔너리를 &lt;b&gt;JSON, CSV, TXT&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/1325</guid>
      <comments>https://hianna.tistory.com/1325#entry1325comment</comments>
      <pubDate>Wed, 10 Jun 2026 11:53:38 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 파이썬 딕셔너리를 CSV 파일로 저장하는 법</title>
      <link>https://hianna.tistory.com/1324</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 여러 개의 딕셔너리(리스트 형태) 저장하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔한 경우입니다. 리스트 안에 여러 개의 딕셔너리가 들어 있는 형태(JSON 배열 형태)를 CSV로 저장할 때는 파이썬 내장 &lt;code&gt;csv&lt;/code&gt; 모듈의 &lt;code&gt;DictWriter&lt;/code&gt;를 사용합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  실전 예제:&amp;nbsp;&amp;nbsp;활용&lt;/h4&gt;&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;import csv

# 저장할 데이터 (리스트 안의 딕셔너리 형태)
data = [
    {&quot;이름&quot;: &quot;철수&quot;, &quot;나이&quot;: 25, &quot;직업&quot;: &quot;개발자&quot;},
    {&quot;이름&quot;: &quot;영희&quot;, &quot;나이&quot;: 30, &quot;직업&quot;: &quot;디자이너&quot;},
    {&quot;이름&quot;: &quot;민수&quot;, &quot;나이&quot;: 28, &quot;직업&quot;: &quot;데이터 분석가&quot;}
]

# 1. 파일 열기 (newline=''은 윈도우에서 줄바꿈 중복 방지)
with open(&quot;members.csv&quot;, &quot;w&quot;, encoding=&quot;utf-8-sig&quot;, newline=&quot;&quot;) as f:
    # 2. 필드 이름(컬럼명) 설정
    fieldnames = [&quot;이름&quot;, &quot;나이&quot;, &quot;직업&quot;]

    # 3. DictWriter 객체 생성
    writer = csv.DictWriter(f, fieldnames=fieldnames)

    # 4. 헤더(컬럼명) 쓰기
    writer.writeheader()

    # 5. 데이터 쓰기 (리스트 전체를 한 번에 저장)
    writer.writerows(data)

print(&quot;CSV 파일 저장이 완료되었습니다.&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;utf-8-sig&lt;/code&gt;&lt;/b&gt;: 엑셀에서 한글이 깨지지 않도록 인코딩하는 팁입니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;writeheader()&lt;/code&gt;&lt;/b&gt;: 딕셔너리의 키를 CSV의 첫 번째 줄(제목)로 만듭니다.&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. 단일 딕셔너리(Key-Value 쌍) 저장하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 딕셔너리에 담긴 정보를 '항목명-값'의 형태로 저장하고 싶을 때 사용합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  실전 예제: Key와 Value를 두 컬럼으로 저장&lt;/h4&gt;&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;import csv

user_info = {
    &quot;ID&quot;: &quot;python_user&quot;,
    &quot;Level&quot;: 99,
    &quot;Status&quot;: &quot;Active&quot;,
    &quot;Point&quot;: 1500
}

with open(&quot;user_info.csv&quot;, &quot;w&quot;, encoding=&quot;utf-8-sig&quot;, newline=&quot;&quot;) as f:
    writer = csv.writer(f)
    # 제목 줄 추가
    writer.writerow([&quot;항목&quot;, &quot;값&quot;])

    # 딕셔너리의 데이터를 한 줄씩 쓰기
    for key, value in user_info.items():
        writer.writerow([key, value])
&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. 실무 끝판왕: Pandas 활용하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 양이 많고 복잡하다면 &lt;b&gt;Pandas&lt;/b&gt; 라이브러리를 쓰는 것이 가장 빠르고 강력합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  실전 예제:&amp;nbsp;&amp;nbsp;활용&lt;/h4&gt;&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;import pandas as pd

data = [
    {&quot;name&quot;: &quot;Alice&quot;, &quot;age&quot;: 25},
    {&quot;name&quot;: &quot;Bob&quot;, &quot;age&quot;: 30}
]

# 1. 딕셔너리 리스트를 데이터프레임으로 변환
df = pd.DataFrame(data)

# 2. 바로 CSV로 저장 (index=False는 왼쪽 번호 생략)
df.to_csv(&quot;data.csv&quot;, index=False, encoding=&quot;utf-8-sig&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;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;csv.DictWriter&lt;/code&gt;를 사용하여 Key를 헤더로, Value를 데이터로 저장한다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;한글 깨짐 방지&lt;/b&gt;: &lt;code&gt;encoding=&quot;utf-8-sig&quot;&lt;/code&gt;를 사용하면 엑셀에서 바로 열어도 한글이 깨지지 않는다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;편의성&lt;/b&gt;: 복잡한 가공이 필요하다면 &lt;code&gt;Pandas&lt;/code&gt;를 사용하는 것이 훨씬 효율적이다.&lt;/li&gt; 
&lt;/ol&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/1324</guid>
      <comments>https://hianna.tistory.com/1324#entry1324comment</comments>
      <pubDate>Mon, 8 Jun 2026 23:33:45 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 파이썬 딕셔너리를 파일로 저장하는 법: JSON 활용 가이드</title>
      <link>https://hianna.tistory.com/1323</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. JSON이란 무엇인가요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSON은 데이터를 저장하거나 전송할 때 사용하는 경량의 데이터 형식입니다. 파이썬의 &lt;b&gt;딕셔너리(&lt;code&gt;{}&lt;/code&gt;)&lt;/b&gt; 및 &lt;b&gt;리스트(&lt;code&gt;[]&lt;/code&gt;)&lt;/b&gt; 구조와 거의 1:1로 매칭되기 때문에 변환이 매우 쉽습니다.&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. 딕셔너리를 JSON 파일로 저장하기 ()&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 내장 모듈인 &lt;code&gt;json&lt;/code&gt;을 임포트하여 사용합니다. &lt;code&gt;dump()&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;import json

# 저장할 딕셔너리 데이터
config = {
    &quot;user_id&quot;: &quot;gemini_dev&quot;,
    &quot;theme&quot;: &quot;dark_mode&quot;,
    &quot;notifications&quot;: True,
    &quot;recent_projects&quot;: [&quot;AI_bot&quot;, &quot;Web_scraper&quot;]
}

# 1. 파일 열기 (w: 쓰기 모드)
with open(&quot;user_config.json&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    # 2. 딕셔너리를 JSON 파일로 저장
    # indent: 들여쓰기 칸수 (가독성 향상)
    # ensure_ascii=False: 한글 깨짐 방지
    json.dump(config, f, indent=4, ensure_ascii=False)

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;3. JSON 파일을 딕셔너리로 불러오기 ()&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;python&quot;&gt;&lt;code&gt;import json

# 1. 파일 열기 (r: 읽기 모드)
with open(&quot;user_config.json&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    # 2. JSON 파일을 딕셔너리로 변환
    loaded_config = json.load(f)

print(f&quot;사용자 ID: {loaded_config['user_id']}&quot;)
print(f&quot;최근 프로젝트: {loaded_config['recent_projects'][0]}&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;vs ,&amp;nbsp;&amp;nbsp;vs&amp;nbsp;&amp;nbsp;차이점&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;초보자들이 가장 헷갈려 하는 부분입니다. 뒤에 &lt;b&gt;'s'&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;dump()&lt;/code&gt; / `load()&lt;/b&gt;`: &lt;b&gt;파일(File)&lt;/b&gt;을 직접 다룰 때 사용 (f 같은 파일 객체 필요)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;dumps()&lt;/code&gt; / `loads()&lt;/b&gt;`: &lt;b&gt;문자열(String)&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;# 딕셔너리를 한 줄짜리 JSON 문자열로 변환
json_string = json.dumps(config)
print(type(json_string)) # &amp;lt;class 'str'&amp;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;  핵심 요약&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;모듈 임포트&lt;/b&gt;: &lt;code&gt;import json&lt;/code&gt;은 필수입니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;한글 설정&lt;/b&gt;: 한글이 들어있다면 &lt;code&gt;ensure_ascii=False&lt;/code&gt; 옵션을 꼭 넣어주세요.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;가독성&lt;/b&gt;: &lt;code&gt;indent=4&lt;/code&gt; 옵션을 사용하면 텍스트 편집기에서 파일을 볼 때 구조가 명확하게 보입니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;파일 닫기&lt;/b&gt;: &lt;code&gt;with open(...)&lt;/code&gt; 구문을 사용하면 작업이 끝난 후 파일이 자동으로 안전하게 닫힙니다.&lt;/li&gt; 
&lt;/ol&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/1323</guid>
      <comments>https://hianna.tistory.com/1323#entry1323comment</comments>
      <pubDate>Mon, 8 Jun 2026 06:46:43 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] 딕셔너리로 코드 속도 10배 올리기: 메모이제이션(Memoization)</title>
      <link>https://hianna.tistory.com/1322</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 메모이제이션(Memoization)이란?&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;복잡한 연산을 매번 처음부터 다시 하지 않고, &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;2. 왜 딕셔너리를 사용하나요?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;메모이제이션의 핵심은 &lt;b&gt;검색 속도&lt;/b&gt;입니다. 파이썬의 &lt;b&gt;딕셔너리(Dictionary)&lt;/b&gt;는 내부적으로 해시 테이블(Hash Table) 구조를 사용하기 때문에, 데이터가 아무리 많아도 내가 원하는 값을 찾는 속도가 거의 일정하고 매우 빠릅니다. (시간 복잡도 $O(1)$)&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. 실전 예제: 피보나치 수열 (Fibonacci)&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;code&gt;n=40&lt;/code&gt;만 되어도 연산 횟수가 수억 번에 달해 컴퓨터가 버벅이기 시작합니다.&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;def fib(n):
    if n &amp;lt;= 1:
        return n
    return fib(n-1) + fib(n-2)
&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;code&gt;memo&lt;/code&gt; 딕셔너리에 저장하므로, 동일한 계산은 단 한 번만 수행됩니다.&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 결과를 저장할 캐시 딕셔너리
memo = {}

def fib_memo(n):
    # 1. 이미 메모에 적혀있다면 바로 반환
    if n in memo:
        return memo[n]

    # 2. 기본 조건 처리
    if n &amp;lt;= 1:
        return n

    # 3. 계산 후 결과를 메모에 저장
    memo[n] = fib_memo(n-1) + fib_memo(n-2)
    return memo[n]

print(fib_memo(100)) # 354224848179261915075 (0.001초 만에 결과 출력)
&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;functools&lt;/code&gt; 라이브러리의 &lt;b&gt;&lt;code&gt;@lru_cache&lt;/code&gt;&lt;/b&gt; 데코레이터를 함수 위에 붙여주기만 하면 됩니다.&lt;/p&gt;&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;from functools import lru_cache

@lru_cache(maxsize=None)
def fib_smart(n):
    if n &amp;lt;= 1:
        return n
    return fib_smart(n-1) + fib_smart(n-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;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;code&gt;Key&lt;/code&gt;는 입력값, &lt;code&gt;Value&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1322</guid>
      <comments>https://hianna.tistory.com/1322#entry1322comment</comments>
      <pubDate>Sun, 7 Jun 2026 22:44:47 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] &amp;quot;복잡한 JSON도 척척!&amp;quot; 중첩 딕셔너리 데이터 추출 기술</title>
      <link>https://hianna.tistory.com/1321</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 중첩 구조 접근의 정석 (계단식 접근)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 데이터는 보통 딕셔너리와 리스트가 층층이 쌓인 구조입니다. 대괄호&lt;code&gt;[]&lt;/code&gt;를 연달아 써서 안쪽으로 들어갈 수 있습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 기상청 API 데이터 가공하기 (가상)&lt;/h4&gt;&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;weather_json = {
    &quot;status&quot;: &quot;OK&quot;,
    &quot;result&quot;: {
        &quot;location&quot;: &quot;Seoul&quot;,
        &quot;forecast&quot;: [
            {&quot;date&quot;: &quot;2026-02-25&quot;, &quot;temp&quot;: 12, &quot;desc&quot;: &quot;Sunny&quot;},
            {&quot;date&quot;: &quot;2026-02-26&quot;, &quot;temp&quot;: 8, &quot;desc&quot;: &quot;Rainy&quot;}
        ]
    }
}

# '내일(26일)의 기온'을 가져오고 싶다면?
# 딕셔너리(result) -&amp;gt; 딕셔너리(forecast) -&amp;gt; 리스트의 두 번째[1] -&amp;gt; 딕셔너리(temp)
tomorrow_temp = weather_json[&quot;result&quot;][&quot;forecast&quot;][1][&quot;temp&quot;]

print(f&quot;내일 기온: {tomorrow_temp}도&quot;) # 8도
&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;중첩 구조에서 가장 큰 문제는 중간에 키가 하나라도 없으면 코드가 멈춘다는 것입니다. 이를 방지하기 위해 &lt;code&gt;get()&lt;/code&gt;을 연속으로 사용하거나 기본값을 빈 딕셔너리로 주는 팁이 있습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  팁 1: 안전하게 파고들기&lt;/h4&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 'description' 키가 없을 수도 있는 상황이라면?
# 중간에 None이 반환되면 다음 get에서 에러가 날 수 있으므로 빈 딕셔너리{}를 기본값으로 줍니다.
desc = weather_json.get(&quot;result&quot;, {}).get(&quot;forecast&quot;, [{}])[0].get(&quot;desc&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. 고수의 도구:&amp;nbsp;&amp;nbsp;라이브러리 (강력 추천)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;만약 구조가 너무 깊고 복잡하다면, 파이썬 기본 기능만으로는 코드가 매우 지저분해집니다. 이때 &lt;b&gt;&lt;code&gt;glom&lt;/code&gt;&lt;/b&gt;이라는 라이브러리를 사용하면 마치 파일 경로를 적듯 데이터를 추출할 수 있습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: glom으로 한 줄에 추출하기&lt;/h4&gt;&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;# pip install glom
from glom import glom

target_path = 'result.forecast.1.temp'
temp = glom(weather_json, target_path)

print(temp) # 8
&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;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 모든 날짜의 기온만 리스트로 뽑기&lt;/h4&gt;&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;# forecast 리스트 안의 모든 딕셔너리에서 'temp'만 추출
all_temps = [f[&quot;temp&quot;] for f in weather_json[&quot;result&quot;][&quot;forecast&quot;]]

print(all_temps) # [12, 8]
&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;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;code&gt;{}&lt;/code&gt;&lt;/b&gt;이고 어디가 &lt;b&gt;리스트&lt;code&gt;[]&lt;/code&gt;&lt;/b&gt;인지 구분하세요.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;안전 우선&lt;/b&gt;: 실무에서는 &lt;code&gt;json[&quot;key&quot;]&lt;/code&gt;보다는 &lt;code&gt;json.get(&quot;key&quot;, {})&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;/ol&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/1321</guid>
      <comments>https://hianna.tistory.com/1321#entry1321comment</comments>
      <pubDate>Sun, 7 Jun 2026 08:48:16 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] &amp;quot;KeyError는 이제 안녕!&amp;quot; 딕셔너리 안전 삭제 및 병합 가이드</title>
      <link>https://hianna.tistory.com/1320</link>
      <description>&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;del dic[&quot;key&quot;]&lt;/code&gt;를 먼저 배우지만, 이 방식은 키가 없으면 즉시 에러를 발생시킵니다. 실무에서는 &lt;b&gt;&lt;code&gt;.pop()&lt;/code&gt;&lt;/b&gt;을 사용하여 기본값을 설정하는 방식을 훨씬 선호합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  팁 1: 의 기본값(Default) 활용&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;키가 없어도 에러를 내지 않고 &lt;code&gt;None&lt;/code&gt;이나 지정한 메시지를 반환하게 할 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;nsis&quot;&gt;&lt;code&gt;user = {&quot;name&quot;: &quot;제미니&quot;, &quot;level&quot;: 10}

# 키가 없어도 에러 없이 안전하게 삭제 시도
# pop(키, 키가 없을 때 반환할 값)
removed_value = user.pop(&quot;email&quot;, &quot;해당 키가 없습니다&quot;)

print(removed_value)  # 해당 키가 없습니다.
print(user)           # 원본은 그대로 유지됨
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  팁 2: 조건문을 활용한 삭제&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 키를 삭제해야 한다면 &lt;b&gt;딕셔너리 컴프리헨션&lt;/b&gt;으로 '삭제할 것만 빼고' 새로 만드는 것이 더 안전하고 깔끔합니다.&lt;/p&gt;&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;data = {&quot;a&quot;: 1, &quot;b&quot;: 2, &quot;c&quot;: 3, &quot;d&quot;: 4}
remove_keys = {&quot;b&quot;, &quot;d&quot;}

# b와 d를 제외한 나머지만 모아서 새 딕셔너리 생성
clean_data = {k: v for k, v in data.items() if k not in remove_keys}
&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;여러 곳에서 수집된 데이터를 하나로 합칠 때, 단순히 덮어쓰는 것이 아니라 효율적으로 병합하는 방법입니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  팁 3: 파이썬 3.9+ '병합 연산자' 사용하기&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;최신 파이썬 버전에서는 &lt;code&gt;|&lt;/code&gt; 기호 하나로 딕셔너리를 합칠 수 있습니다. 가독성이 매우 좋아집니다.&lt;/p&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;dict_a = {&quot;apple&quot;: 1, &quot;banana&quot;: 2}
dict_b = {&quot;banana&quot;: 3, &quot;cherry&quot;: 4}

# 병합 (뒤에 오는 dict_b의 값이 우선순위를 가집니다)
merged = dict_a | dict_b

print(merged) 
# {'apple': 1, 'banana': 3, 'cherry': 4}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  팁 4: 원본을 유지하며 합치기 vs 수정하기&lt;/h4&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;.update()&lt;/code&gt;&lt;/b&gt;: &lt;code&gt;dict_a&lt;/code&gt; 자체를 수정하여 합칩니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;`{&lt;/b&gt;dict_a, &lt;b&gt;dict_b}`&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. 중복 키가 걱정될 때: &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;병합할 때 같은 키가 있으면 값이 덮어씌워지는 게 일반적입니다. 하지만 &lt;b&gt;&quot;기존 값을 유지하면서 새로운 값을 추가&quot;&lt;/b&gt;하고 싶다면(예: 한 사람의 여러 취미를 모으는 경우), &lt;code&gt;defaultdict&lt;/code&gt;가 정답입니다.&lt;/p&gt;&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;from collections import defaultdict

# 리스트를 기본값으로 가지는 딕셔너리 생성
hobby_map = defaultdict(list)

# 데이터 합치기 시도 (Key 존재 여부를 확인할 필요가 없음!)
data = [(&quot;철수&quot;, &quot;축구&quot;), (&quot;영희&quot;, &quot;독서&quot;), (&quot;철수&quot;, &quot;농구&quot;)]

for name, hobby in data:
    hobby_map[name].append(hobby)

print(dict(hobby_map))
# {'철수': ['축구', '농구'], '영희': ['독서']}
&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;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;dic.pop(&quot;key&quot;, None)&lt;/code&gt;을 써라.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;병합할 때&lt;/b&gt;: 최신 파이썬이라면 &lt;code&gt;dict_a | dict_b&lt;/code&gt;가 가장 깔끔하다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;데이터를 누적할 때&lt;/b&gt;: &lt;code&gt;defaultdict(list)&lt;/code&gt;를 쓰면 &lt;code&gt;KeyError&lt;/code&gt; 없이 데이터를 쌓을 수 있다.&lt;/li&gt; 
&lt;/ol&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/1320</guid>
      <comments>https://hianna.tistory.com/1320#entry1320comment</comments>
      <pubDate>Thu, 4 Jun 2026 11:30:03 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] &amp;quot;딕셔너리 고수되는 법&amp;quot; 정렬과 컴프리헨션 완벽 마스터</title>
      <link>https://hianna.tistory.com/1319</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 딕셔너리 정렬 (Sorting)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리는 기본적으로 순서가 중요하지 않은 자료형이지만, 출력하거나 분석할 때는 &lt;b&gt;Key(이름표)나 Value(값)&lt;/b&gt;를 기준으로 정렬해야 할 때가 많습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  Key(이름표) 기준 정렬&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;sorted()&lt;/code&gt; 함수를 사용하면 기본적으로 Key를 기준으로 정렬된 Key 리스트를 반환합니다.&lt;/p&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;scores = {&quot;철수&quot;: 85, &quot;영희&quot;: 90, &quot;민수&quot;: 75}

# 1. Key 기준 오름차순 (가나다순)
sorted_keys = sorted(scores)
print(sorted_keys) # ['민수', '영희', '철수']

# 2. Key 기준 정렬된 새로운 딕셔너리 만들기
sorted_dict = {k: scores[k] for k in sorted(scores)}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  Value(값) 기준 정렬 (★실무 필수)&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;점수가 높은 순서대로 정렬해 줘!&quot;라고 할 때 쓰는 방식입니다. &lt;code&gt;lambda&lt;/code&gt; 함수를 활용합니다.&lt;/p&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 점수가 높은 순(내림차순)으로 정렬
# x[1]은 딕셔너리의 (Key, Value) 튜플에서 1번 인덱스인 Value를 의미합니다.
sorted_by_score = sorted(scores.items(), key=lambda x: x[1], reverse=True)

print(sorted_by_score)
# 출력: [('영희', 90), ('철수', 85), ('민수', 75)]
&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. 딕셔너리 컴프리헨션 (Dictionary Comprehension)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&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;모든 학생의 점수에 가산점 5점을 더한 새로운 딕셔너리를 만든다면?&lt;/p&gt;&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;# { Key: Value for 반복문 }
plus_scores = {name: score + 5 for name, score in scores.items()}
print(plus_scores) 
# {'철수': 90, '영희': 95, '민수': 80}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 2: 특정 조건으로 필터링하기&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;80점 이상인 '우수 학생'만 따로 골라내고 싶다면?&lt;/p&gt;&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;# 뒤에 if 조건을 붙여줍니다.
top_students = {name: score for name, score in scores.items() if score &amp;gt;= 80}
print(top_students) 
# {'철수': 85, '영희': 90}
&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. 실전 팁: Key와 Value 뒤집기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 찾다 보면 가끔 &quot;점수를 넣으면 이름이 나오게&quot; 구조를 바꿔야 할 때가 있습니다.&lt;/p&gt;&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;# { Value: Key for Key, Value in 딕셔너리.items() }
reverse_scores = {v: k for k, v in scores.items()}

print(reverse_scores)
# {85: '철수', 90: '영희', 75: '민수'}
&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;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;sorted(scores.items(), key=lambda x: x[1])&lt;/code&gt; 공식을 외워두면 점수/가격순 정렬이 아주 쉬워집니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;컴프리헨션&lt;/b&gt;: 대량의 딕셔너리 데이터를 정제할 때 &lt;code&gt;if&lt;/code&gt;문과 함께 사용하면 코드가 획기적으로 줄어듭니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;순서 유지&lt;/b&gt;: 파이썬 3.7부터는 딕셔너리에 데이터를 넣은 순서가 기본적으로 유지되므로 안심하고 사용하세요!&lt;/li&gt; 
&lt;/ol&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/1319</guid>
      <comments>https://hianna.tistory.com/1319#entry1319comment</comments>
      <pubDate>Wed, 3 Jun 2026 06:03:30 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] 딕셔너리(Dictionary) 완벽 가이드</title>
      <link>https://hianna.tistory.com/1318</link>
      <description>&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;code&gt;Key: Value&lt;/code&gt; 형태로 짝을 지어줍니다.&lt;/p&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 1. 딕셔너리 생성
user = {
    &quot;name&quot;: &quot;제미니&quot;,
    &quot;level&quot;: 10,
    &quot;job&quot;: &quot;AI 메이트&quot;
}

# 2. 새로운 데이터 추가
user[&quot;location&quot;] = &quot;Seoul&quot;

# 3. 값 수정 (기존에 있는 Key에 새 값을 넣으면 덮어쓰기 됩니다)
user[&quot;level&quot;] = 11

print(user)
# 출력: {'name': '제미니', 'level': 11, 'job': 'AI 메이트', 'location': 'Seoul'}
&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;code&gt;[]&lt;/code&gt;)를 쓰면 존재하지 않는 키를 넣었을 때 에러가 나지만, &lt;code&gt;.get()&lt;/code&gt;을 쓰면 안전하게 처리할 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 방법 1: 대괄호 (키가 없으면 에러 발생 후 프로그램 종료)
print(user[&quot;name&quot;]) 

# 방법 2: .get() 함수 (권장! 키가 없으면 None을 반환함)
email = user.get(&quot;email&quot;) 
print(email) # None

# 키가 없을 때 기본값 지정하기
email = user.get(&quot;email&quot;, &quot;이메일 없음&quot;)
print(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;3. 반복문에서 활용하기 (items, keys, values)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리의 내용물을 싹 훑어야 할 때 사용하는 삼총사입니다.&lt;/p&gt;&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;# 1. 이름표(Key)와 내용(Value) 둘 다 한 번에 꺼내기
for key, value in user.items():
    print(f&quot;[{key}] : {value}&quot;)

# 2. 이름표(Key)만 꺼내기
for key in user.keys():
    print(key)

# 3. 내용(Value)만 꺼내기
for value in user.values():
    print(value)
&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. 데이터 삭제 및 합치기 (pop, update)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 지우거나 두 딕셔너리를 하나로 합칠 때 유용합니다.&lt;/p&gt;&lt;pre class=&quot;nsis&quot;&gt;&lt;code&gt;# 1. 특정 키 삭제하기 (.pop은 지운 값을 반환해 줍니다)
job = user.pop(&quot;job&quot;)
print(f&quot;삭제된 직업: {job}&quot;)

# 2. 두 딕셔너리 합치기 (파이썬 3.9+ 에서는 | 연산자 사용 가능)
extra_info = {&quot;skill&quot;: &quot;Python&quot;, &quot;hobby&quot;: &quot;Coding&quot;}
user.update(extra_info)

# 최신 파이썬 한 줄 합치기 팁
# user = user | extra_info 

print(user)
&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;  딕셔너리 사용 꿀팁 (Q&amp;amp;A)&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;Q: 딕셔너리 키로 리스트를 쓸 수 있나요?&lt;/b&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;A:&lt;/b&gt; 아니요! 딕셔너리 키는 '변하지 않는 값'이어야 합니다. 리스트는 내용이 변할 수 있으므로 키로 쓸 수 없고, 대신 &lt;b&gt;튜플(&lt;code&gt;tuple&lt;/code&gt;)&lt;/b&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;Q: 키가 중복되면 어떻게 되나요?&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;A:&lt;/b&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/1318</guid>
      <comments>https://hianna.tistory.com/1318#entry1318comment</comments>
      <pubDate>Tue, 26 May 2026 11:00:45 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;리스트를 하나의 글로!&amp;quot; 파이썬 리스트 문자열 변환</title>
      <link>https://hianna.tistory.com/1317</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;['Hello', 'World']&lt;/code&gt;라는 리스트를 &lt;code&gt;&quot;Hello World&quot;&lt;/code&gt;라는 문자열로 바꾸고 싶을 때, 초보자들은 흔히 &lt;code&gt;for&lt;/code&gt;문을 돌리며 글자를 더하곤 합니다. 하지만 파이썬에는 이를 위한 전용 마법인 &lt;b&gt;&lt;code&gt;join()&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. 가장 정석적인 방법: &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;join()&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;words = [&quot;Apple&quot;, &quot;Banana&quot;, &quot;Orange&quot;]

# 1. 공백(&quot; &quot;)으로 합치기
result1 = &quot; &quot;.join(words)
print(result1)  # 출력: Apple Banana Orange

# 2. 쉼표(&quot;,&quot;)로 합치기
result2 = &quot;,&quot;.join(words)
print(result2)  # 출력: Apple,Banana,Orange

# 3. 아무 구분 없이 붙이기
result3 = &quot;&quot;.join(words)
print(result3)  # 출력: AppleBananaOrange
&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. 리스트에 숫자가 섞여 있을 때 (TypeError 해결법)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;join()&lt;/code&gt; 함수는 &lt;b&gt;리스트 안의 모든 데이터가 '문자열'일 때만&lt;/b&gt; 작동합니다. 숫자가 하나라도 섞여 있으면 에러가 발생하죠. 이때는 우리가 배웠던 &lt;b&gt;리스트 컴프리헨션&lt;/b&gt;이나 &lt;b&gt;&lt;code&gt;map()&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;nums = [1, 2, 3, 4, 5]

# 오류 발생: &quot;&quot;.join(nums) -&amp;gt; TypeError

# 해결법: 모든 요소를 str()로 먼저 변환한 뒤 합칩니다.
# 1) 리스트 컴프리헨션 방식
result = &quot;-&quot;.join([str(n) for n in nums])

# 2) map 함수 방식 (더 세련된 방식!)
result_map = &quot;-&quot;.join(map(str, nums))

print(result) # 출력: 1-2-3-4-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;3. 실전 응용: 2차원 리스트를 문자열로 변환하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;여러 줄로 된 데이터(2차원 리스트)를 줄바꿈(&lt;code&gt;\n&lt;/code&gt;)을 넣어 하나의 텍스트로 만들 때 유용합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 2차원 리스트 ➔ 텍스트 파일 형식&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;data = [
    [&quot;이름&quot;, &quot;나이&quot;],
    [&quot;철수&quot;, &quot;20&quot;],
    [&quot;영희&quot;, &quot;22&quot;]
]

# 각 행을 탭(&quot;\t&quot;)으로 합치고, 행끼리는 줄바꿈(&quot;\n&quot;)으로 합칩니다.
lines = [&quot;\t&quot;.join(row) for row in data]
final_text = &quot;\n&quot;.join(lines)

print(final_text)
# 출력:
# 이름    나이
# 철수    20
# 영희    22
&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;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;&quot;&quot;.join(list)&lt;/code&gt;&lt;/b&gt;: 리스트의 문자열들을 하나로 매끄럽게 잇는다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;&quot; &quot;.join(list)&lt;/code&gt;&lt;/b&gt;: 단어 사이에 공백을 넣어 문장을 만든다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;숫자가 있다면&lt;/b&gt;: 반드시 &lt;code&gt;map(str, list)&lt;/code&gt;를 사용해 문자로 먼저 바꾼다.&lt;/li&gt; 
&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&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/1317</guid>
      <comments>https://hianna.tistory.com/1317#entry1317comment</comments>
      <pubDate>Mon, 25 May 2026 23:02:57 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] &amp;quot;가로 세로 싹 다 더해라!&amp;quot; 2차원 리스트 합계와 평균 구하기</title>
      <link>https://hianna.tistory.com/1316</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. 각 행(Row)의 합계와 평균 구하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;행의 합계는 매우 쉽습니다. 2차원 리스트에서 한 줄씩 꺼내면 그 자체가 1차원 리스트이므로, 파이썬 내장 함수인 &lt;code&gt;sum()&lt;/code&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;scores = [
    [80, 90, 100], # 1행
    [70, 60, 50],  # 2행
    [100, 100, 90] # 3행
]

# 각 행의 합계 구하기
row_sums = [sum(row) for row in scores]

# 각 행의 평균 구하기 (합계 / 개수)
row_averages = [sum(row) / len(row) for row in scores]

print(&quot;각 행의 합계:&quot;, row_sums)      # [270, 180, 290]
print(&quot;각 행의 평균:&quot;, row_averages)  # [90.0, 60.0, 96.66...]
&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. 각 열(Column)의 합계와 평균 구하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;열의 합계는 행보다 조금 까다롭습니다. 같은 인덱스에 있는 세로 데이터들을 모아야 하기 때문이죠. 이때 우리가 이전에 배웠던 &lt;b&gt;전치(Transpose)&lt;/b&gt; 기술인 &lt;code&gt;zip(*matrix)&lt;/code&gt;를 쓰면 아주 우아하게 해결됩니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: zip()을 활용한 열 합산&lt;/h4&gt;&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;scores = [
    [80, 90, 100],
    [70, 60, 50],
    [100, 100, 90]
]

# 1. zip(*scores)로 행과 열을 뒤바꿉니다. 
# (80, 70, 100), (90, 60, 100)... 이런 식으로 묶입니다.

# 2. 묶인 데이터들의 합계를 구합니다.
col_sums = [sum(col) for col in zip(*scores)]

# 3. 열의 평균 구하기
col_averages = [sum(col) / len(col) for col in zip(*scores)]

print(&quot;각 열의 합계:&quot;, col_sums)      # [250, 250, 240]
print(&quot;각 열의 평균:&quot;, col_averages)  # [83.33, 83.33, 80.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;3. 실무형 끝판왕: Pandas 사용하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 양이 많아지면 위와 같은 리스트 방식은 코드가 복잡해집니다. &lt;b&gt;Pandas&lt;/b&gt;를 쓰면 &lt;code&gt;axis&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;code&gt;axis=1&lt;/code&gt;: 행(가로) 방향으로 계산&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;axis=0&lt;/code&gt;: 열(세로) 방향으로 계산&lt;/li&gt; 
&lt;/ul&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: Pandas의 강력한 한 줄&lt;/h4&gt;&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;import pandas as pd

df = pd.DataFrame([
    [80, 90, 100],
    [70, 60, 50],
    [100, 100, 90]
])

# 열(세로) 합계
col_sum = df.sum(axis=0)

# 행(가로) 평균
row_avg = df.mean(axis=1)

print(&quot;열 합계:\n&quot;, col_sum.tolist())
print(&quot;행 평균:\n&quot;, row_avg.tolist())
&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;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;[sum(row) for row in matrix]&lt;/code&gt; 처럼 리스트에서 한 줄씩 꺼내서 바로 계산한다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;열(세로) 계산&lt;/b&gt;: &lt;code&gt;zip(*matrix)&lt;/code&gt;를 사용해 세로 줄을 가로 줄로 변신시킨 뒤 계산한다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;데이터가 크다면&lt;/b&gt;: 무조건 &lt;b&gt;Pandas&lt;/b&gt;나 &lt;b&gt;NumPy&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;p data-ke-size=&quot;size16&quot;&gt;이제 2차원 데이터를 합치고, 뒤집고, 계산하는 모든 핵심 기술을 섭렵하셨습니다!&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1316</guid>
      <comments>https://hianna.tistory.com/1316#entry1316comment</comments>
      <pubDate>Mon, 25 May 2026 15:59:50 +0900</pubDate>
    </item>
    <item>
      <title>[Python 실전] &amp;quot;좌표를 찍어라!&amp;quot; 2차원 리스트 데이터 위치 찾기</title>
      <link>https://hianna.tistory.com/1315</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;데이터가 바둑판처럼 나열된 2차원 리스트에서 우리가 원하는 값을 찾으려면 &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;1. 가장 정석적인 방법: 중첩 문과 &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;가장 직관적이고 가독성이 좋은 방법입니다. &lt;code&gt;enumerate()&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;grid = [
    [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;],
    [&quot;D&quot;, &quot;Target&quot;, &quot;F&quot;],
    [&quot;G&quot;, &quot;H&quot;, &quot;Target&quot;]
]

target = &quot;Target&quot;
locations = []

# r: 행 번호, row: 해당 행의 리스트
for r, row in enumerate(grid):
    # c: 열 번호, val: 실제 값
    for c, val in enumerate(row):
        if val == target:
            locations.append((r, c)) # 좌표를 튜플 형태로 저장

print(f&quot;'{target}'의 위치 목록 (행, 열):&quot;, locations)
# 출력: 'Target'의 위치 목록 (행, 열): [(1, 1), (2, 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;위의 복잡한 5줄 코드를 단 한 줄로 줄일 수 있습니다. 코드가 간결해져서 실무에서 위치를 빠르게 뽑아낼 때 자주 쓰입니다.&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;grid = [
    [10, 20, 30],
    [40, 50, 60],
    [70, 20, 90]
]

target_value = 20

# (r, c) 좌표를 담는데, r행의 c열 값이 target_value와 같을 때만 담아라!
coords = [(r, c) for r, row in enumerate(grid) for c, val in enumerate(row) if val == target_value]

print(f&quot;값 {target_value}의 위치:&quot;, coords)
# 출력: 값 20의 위치: [(0, 1), (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;3. 조건에 맞는 첫 번째 위치만 빠르게 찾기&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;  예제 코드:&amp;nbsp;&amp;nbsp;함수와 제너레이터 활용&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;grid = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# 5라는 숫자의 위치를 찾자마자 종료
target = 5

# next()는 조건에 맞는 첫 번째 결과가 나오면 즉시 반환하고 멈춥니다.
# 만약 없다면 &quot;찾을 수 없음&quot;을 반환합니다.
found = next(((r, c) for r, row in enumerate(grid) for c, val in enumerate(row) if val == target), &quot;찾을 수 없음&quot;)

print(f&quot;숫자 {target}의 첫 번째 위치:&quot;, found)
# 출력: 숫자 5의 첫 번째 위치: (1, 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;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;enumerate()&lt;/code&gt;를 쓴 리스트 컴프리헨션이 가장 깔끔합니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;가장 먼저 나오는 하나만 필요할 때:&lt;/b&gt; &lt;code&gt;next()&lt;/code&gt; 함수를 써서 속도를 높이세요.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;성능 팁:&lt;/b&gt; 리스트가 수만 행 이상으로 크다면, 리스트 대신 &lt;b&gt;NumPy&lt;/b&gt; 라이브러리의 &lt;code&gt;np.where()&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;p data-ke-size=&quot;size16&quot;&gt;2차원 리스트에서 위치를 찾는 법까지 익히셨으니, 이제 격자 형태의 데이터를 자유자재로 다루실 수 있겠네요!&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1315</guid>
      <comments>https://hianna.tistory.com/1315#entry1315comment</comments>
      <pubDate>Mon, 25 May 2026 07:01:21 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;가로를 세로로!&amp;quot; 2차원 리스트 전치(Transpose) 완벽 가이드</title>
      <link>https://hianna.tistory.com/1314</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;전치 행렬이란 말 그대로 &lt;b&gt;'행과 열의 인덱스를 서로 맞바꾼 행렬'&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. 가장 파이썬다운 방법: &lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 고수들이 가장 즐겨 쓰는 방식입니다. 우리가 앞서 배운 &lt;b&gt;언패킹(&lt;code&gt;*&lt;/code&gt;)&lt;/b&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;  예제 코드: zip을 활용한 전치&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;matrix = [
    [1, 2, 3],
    [4, 5, 6]
]

# 1. *matrix로 행들을 해체한 뒤
# 2. zip으로 같은 인덱스끼리(열끼리) 묶어줍니다.
transposed = [list(row) for row in zip(*matrix)]

print(transposed)
# 출력: [[1, 4], [2, 5], [3, 6]]
&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;zip(*matrix)&lt;/code&gt;는 &lt;code&gt;zip([1, 2, 3], [4, 5, 6])&lt;/code&gt;과 같습니다. &lt;code&gt;zip&lt;/code&gt;은 각 리스트의 첫 번째 요소인 &lt;code&gt;(1, 4)&lt;/code&gt;를 묶고, 그다음 &lt;code&gt;(2, 5)&lt;/code&gt;, &lt;code&gt;(3, 6)&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;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;prolog&quot;&gt;&lt;code&gt;matrix = [
    [1, 2, 3],
    [4, 5, 6]
]

# 바깥쪽 루프는 '열(column)'을 돌고, 안쪽 루프는 '행(row)'을 돕니다.
# &quot;i번째 열의 값들을 모아서 새로운 행을 만들어라!&quot;
transposed = [[matrix[row][col] for row in range(len(matrix))] for col in range(len(matrix[0]))]

print(transposed)
# 출력: [[1, 4], [2, 5], [3, 6]]
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;[col] vs matrix[col][row] to demonstrate the logic of matrix transposition]&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. 실무 끝판왕: 넘파이(NumPy) 활용&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여러분이 데이터 과학이나 머신러닝을 공부하고 있다면, 사실 위의 방법들을 직접 짜기보다 전용 라이브러리인 &lt;b&gt;NumPy&lt;/b&gt;를 사용하는 것이 훨씬 빠르고 강력합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: NumPy의 .T 속성&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import numpy as np

matrix = np.array([[1, 2, 3], [4, 5, 6]])

# .T 속성 하나면 전치 행렬이 완성됩니다.
transposed = matrix.T

print(transposed)
# 출력:
# [[1 4]
#  [2 5]
#  [3 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;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;zip(*matrix)&lt;/code&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;NumPy&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;p data-ke-size=&quot;size16&quot;&gt;행과 열을 뒤바꾸는 법까지 익히셨으니 이제 2차원 데이터를 다루는 데 있어 두려울 게 없으실 것 같네요!&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1314</guid>
      <comments>https://hianna.tistory.com/1314#entry1314comment</comments>
      <pubDate>Sun, 24 May 2026 23:26:47 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;표 형태의 데이터도 한 줄로!&amp;quot; 2차원 리스트 컴프리헨션</title>
      <link>https://hianna.tistory.com/1313</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;2차원 리스트를 만들 때 가장 흔하게 저지르는 실수가 바로 리스트 곱하기(&lt;code&gt;*&lt;/code&gt;)를 사용하는 것입니다. 하지만 앞서 배웠듯이 이는 &lt;b&gt;참조 복사 버그&lt;/b&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;2차원 리스트는 '리스트 안에 리스트가 들어있는 구조'입니다. 따라서 컴프리헨션 안에 또 다른 컴프리헨션을 넣어주면 됩니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 3x4 크기의 0으로 채워진 행렬 만들기&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;rows = 3
cols = 4

# [ [안쪽 리스트] for 바깥쪽 반복문 ]
# &quot;0을 4번 채운 리스트를 3번 만들어라!&quot;
matrix = [[0 for _ in range(cols)] for _ in range(rows)]

print(matrix)
# 출력: [[0, 0, 0, 0], [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;2. 값에 규칙 부여하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 번호를 활용하여 2차원 배열 안에 특정한 값을 채울 수도 있습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 구구단 2단부터 3단까지 리스트로 만들기&lt;/h4&gt;&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;# [[결과값 for i 인덱스] for j 인덱스]
gugudan = [[j * i for i in range(1, 10)] for j in range(2, 4)]

print(&quot;2단:&quot;, gugudan[0])
print(&quot;3단:&quot;, gugudan[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;3. 고수들의 기술: 2차원 리스트 '평탄화' (Flatten)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 &lt;b&gt;2차원 리스트를 다시 1차원 리스트로 쭉 펴야 할 때&lt;/b&gt;가 있습니다. 이때도 리스트 컴프리헨션을 쓰면 &lt;code&gt;for&lt;/code&gt;문을 두 번 겹쳐 쓰는 것보다 훨씬 간결합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 2차원 -&amp;gt; 1차원 변환&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# &quot;행(row)에서 값(val)을 꺼내는데, 그 행은 matrix에서 하나씩 가져와라&quot;
# 주의: 바깥쪽 for문이 앞에 옵니다!
flatten = [val for row in matrix for val in row]

print(flatten)
# 출력: [1, 2, 3, 4, 5, 6, 7, 8, 9]
&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. 조건문이 포함된 2차원 컴프리헨션&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;특정 조건에 맞는 데이터만 골라내어 2차원 리스트를 정제할 수도 있습니다.&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 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 각 행에서 짝수만 골라내어 새로운 2차원 리스트 생성
evens_only = [[x for x in row if x % 2 == 0] for row in data]

print(evens_only)
# 출력: [[2], [4, 6], [8]]
&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;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;: 리스트 컴프리헨션으로 만든 2차원 리스트는 각 행이 독립적이라서 한 곳을 수정해도 다른 곳에 영향을 주지 않습니다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;순서&lt;/b&gt;: 평탄화(Flatten) 시에는 &lt;b&gt;바깥쪽 반복문&lt;/b&gt;을 먼저 쓰고 &lt;b&gt;안쪽 반복문&lt;/b&gt;을 뒤에 씁니다. (&lt;code&gt;[val for row in matrix for val in row]&lt;/code&gt;)&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;가독성&lt;/b&gt;: 2차원 이상의 깊이(3차원 등)로 들어가면 코드가 너무 복잡해질 수 있으니, 그때는 일반적인 &lt;code&gt;for&lt;/code&gt;문을 쓰는 것이 나을 수도 있습니다.&lt;/li&gt; 
&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2차원 리스트 컴프리헨션까지 정복하셨군요! 이제 데이터를 구조적으로 생성하고 가공하는 데 거침이 없으실 겁니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/Python</category>
      <author>hi.anna</author>
      <guid isPermaLink="true">https://hianna.tistory.com/1313</guid>
      <comments>https://hianna.tistory.com/1313#entry1313comment</comments>
      <pubDate>Sun, 24 May 2026 18:36:05 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;세 줄 코드를 한 줄로!&amp;quot; 리스트 컴프리헨션 완벽 이해</title>
      <link>https://hianna.tistory.com/1312</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;for&lt;/code&gt;문과 &lt;code&gt;if&lt;/code&gt;문을 직접 집어넣어 새로운 리스트를 생성하는 기법입니다. 처음엔 생소할 수 있지만, 익숙해지면 &lt;b&gt;가독성&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. 기본 구조: 문 압축하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 방식은 빈 리스트를 만들고, &lt;code&gt;for&lt;/code&gt;문을 돌리면서 하나씩 &lt;code&gt;append&lt;/code&gt;를 해줘야 했습니다. 리스트 컴프리헨션은 이를 한 줄로 해결합니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 1부터 5까지의 제곱 구하기&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 1. 기존 방식 (3줄)
squares = []
for x in range(1, 6):
    squares.append(x**2)

# 2. 리스트 컴프리헨션 (1줄)
# [ 표현식 for 항목 in 반복가능객체 ]
squares_comp = [x**2 for x in range(1, 6)]

print(squares_comp) 
# 출력: [1, 4, 9, 16, 25]
&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;for&lt;/code&gt;문 뒤에 &lt;code&gt;if&lt;/code&gt;문을 붙여주면 됩니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;]&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;numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# &quot;n을 담을 건데, numbers에서 꺼낸 n이 2로 나누어 떨어질 때만 담아라!&quot;
evens = [n for n in numbers if n % 2 == 0]

print(evens)
# 출력: [2, 4, 6, 8, 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. 조건부 표현식() 활용하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;조건에 따라 값을 다르게 저장하고 싶을 때는 &lt;code&gt;if-else&lt;/code&gt; 문을 &lt;code&gt;for&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;sas&quot;&gt;&lt;code&gt;data = [1, 2, 3, 4, 5]

# &quot;짝수면 'Even', 홀수면 'Odd'라고 적어서 리스트를 만들어라!&quot;
labels = [&quot;Even&quot; if x % 2 == 0 else &quot;Odd&quot; for x in data]

print(labels)
# 출력: ['Odd', 'Even', 'Odd', 'Even', 'Odd']
&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;/p&gt;&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;raw_names = [&quot;  김철수  &quot;, &quot;이영희 &quot;, &quot;  박민수&quot;]

# 양쪽 공백을 제거(.strip())한 이름들의 리스트 만들기
clean_names = [name.strip() for name in raw_names]

print(clean_names)
# 출력: ['김철수', '이영희', '박민수']
&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;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;code&gt;for&lt;/code&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;p data-ke-size=&quot;size16&quot;&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/1312</guid>
      <comments>https://hianna.tistory.com/1312#entry1312comment</comments>
      <pubDate>Sun, 24 May 2026 09:28:38 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;따로 노는 리스트를 하나로 묶자!&amp;quot; zip() 함수 활용법</title>
      <link>https://hianna.tistory.com/1311</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;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. 기본 사용법: 리스트 짝꿍 만들기&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;makefile&quot;&gt;&lt;code&gt;names = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;]
scores = [85, 90, 100]

# zip으로 묶은 뒤 리스트로 변환해서 확인
zipped = list(zip(names, scores))

print(zipped)
# 출력: [('철수', 85), ('영희', 90), ('민수', 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;2. 실무 1등 문법: 딕셔너리로 즉시 변환&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;zip()&lt;/code&gt;의 가장 강력한 용도는 두 리스트를 &lt;b&gt;Key와 Value의 관계&lt;/b&gt;로 만드는 것입니다. &lt;code&gt;dict()&lt;/code&gt; 함수와 결합하면 1초 만에 데이터베이스 형태가 완성됩니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 리스트 ➔ 딕셔너리&lt;/h4&gt;&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;keys = [&quot;id&quot;, &quot;name&quot;, &quot;email&quot;]
values = [101, &quot;Kim&quot;, &quot;kim@test.com&quot;]

# zip을 dict로 감싸면 앞의 리스트는 Key, 뒤는 Value가 됩니다.
user_dict = dict(zip(keys, values))

print(user_dict)
# 출력: {'id': 101, 'name': 'Kim', 'email': 'kim@test.com'}
&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;range(len(list))&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;fruits = [&quot;Apple&quot;, &quot;Banana&quot;, &quot;Orange&quot;]
prices = [1000, 500, 700]

for fruit, price in zip(fruits, prices):
    print(f&quot;{fruit}의 가격은 {price}원입니다.&quot;)

# 출력:
# Apple의 가격은 1000원입니다.
# Banana의 가격은 500원입니다.
# Orange의 가격은 700원입니다.
&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;code&gt;zip()&lt;/code&gt;은 아주 냉정합니다. 리스트들의 길이가 서로 다르면 &lt;b&gt;가장 짧은 리스트의 길이에 맞춰서&lt;/b&gt; 남는 데이터는 그냥 버려버립니다.&lt;/p&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;a = [1, 2, 3]
b = [&quot;A&quot;, &quot;B&quot;]

print(list(zip(a, b)))
# 출력: [(1, 'A'), (2, 'B')]  &amp;lt;-- 3은 짝이 없어서 사라집니다!
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(  꿀팁: 만약 남는 데이터도 다 챙기고 싶다면 &lt;code&gt;itertools.zip_longest&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;  요약&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;&lt;code&gt;zip(list1, list2)&lt;/code&gt;&lt;/b&gt;: 같은 순서의 데이터를 튜플로 묶어준다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;&lt;code&gt;dict(zip(keys, values))&lt;/code&gt;&lt;/b&gt;: 두 리스트를 딕셔너리로 변환할 때 최고다.&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;순회&lt;/b&gt;: 여러 리스트를 동시에 &lt;code&gt;for&lt;/code&gt;문으로 돌릴 때 가독성이 좋다.&lt;/li&gt; 
&lt;/ol&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&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/1311</guid>
      <comments>https://hianna.tistory.com/1311#entry1311comment</comments>
      <pubDate>Sun, 24 May 2026 06:34:23 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;누가 빠졌지?&amp;quot; 리스트 차집합 찾기 및 데이터 대조</title>
      <link>https://hianna.tistory.com/1310</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;/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;set&lt;/code&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;makefile&quot;&gt;&lt;code&gt;all_students = [&quot;철수&quot;, &quot;영희&quot;, &quot;민수&quot;, &quot;지훈&quot;, &quot;수진&quot;]
submitted = [&quot;영희&quot;, &quot;수진&quot;]

# 1. 집합으로 변환
set_all = set(all_students)
set_sub = set(submitted)

# 2. 전체 명단에서 제출자 명단을 뺍니다.
not_submitted = set_all - set_sub

print(&quot;미제출자:&quot;, list(not_submitted))
# 출력: ['철수', '민수', '지훈'] (순서는 바뀔 수 있음)
&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;기호 대신 이름을 사용하는 방식입니다. 역시나 협업 환경에서 &quot;차집합을 구한다&quot;는 의도를 명확히 전달할 때 좋습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: difference 함수 활용&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;list1 = [1, 2, 3, 4, 5]
list2 = [4, 5, 6, 7, 8]

# list1에는 있지만 list2에는 없는 요소들
diff = list(set(list1).difference(list2))

print(&quot;차집합 결과:&quot;, diff)
# 출력: [1, 2, 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. 순서를 유지해야 할 때: 리스트 컴프리헨션&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;차집합 역시 &lt;code&gt;set&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;makefile&quot;&gt;&lt;code&gt;total_list = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;E&quot;]
remove_list = [&quot;B&quot;, &quot;D&quot;]

# remove_list를 set으로 미리 바꿔두면 검색 속도가 훨씬 빨라집니다 (필수 꿀팁!)
set_remove = set(remove_list)

# total_list에서 하나씩 꺼내는데, 그게 remove_list에 '없는(not in)' 경우만 모으기
result = [item for item in total_list if item not in set_remove]

print(&quot;순서 유지 결과:&quot;, result)
# 출력: ['A', 'C', '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;  요약 및 실무 팁&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;set_a - set_b&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;원본 순서가 중요할 때:&lt;/b&gt; &lt;code&gt;[x for x in list_a if x not in set_b]&lt;/code&gt;&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;대소문자&lt;/b&gt;가 섞여 있으면 파이썬은 서로 다른 데이터로 인식합니다 (&lt;code&gt;&quot;Apple&quot;&lt;/code&gt;과 &lt;code&gt;&quot;apple&quot;&lt;/code&gt;은 다름). 이럴 땐 리스트를 집합으로 만들기 전에 모든 요소를 소문자로 바꾸는 과정이 필요합니다.&lt;/p&gt;&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# 모든 요소를 소문자로 통일한 뒤 차집합 구하기
list_a = [x.lower() for x in list_a]
list_b = [x.lower() for x in list_b]
diff = set(list_a) - set(list_b)
&lt;/code&gt;&lt;/pre&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/1310</guid>
      <comments>https://hianna.tistory.com/1310#entry1310comment</comments>
      <pubDate>Fri, 22 May 2026 19:16:38 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;겹치는 데이터만 골라내자!&amp;quot; 리스트 교집합 찾기 3가지 비법</title>
      <link>https://hianna.tistory.com/1309</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;/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;set&lt;/code&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;  예제 코드: &amp;amp; 연산자 활용&lt;/h4&gt;&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;list_a = [&quot;사과&quot;, &quot;바나나&quot;, &quot;포도&quot;, &quot;딸기&quot;]
list_b = [&quot;포도&quot;, &quot;딸기&quot;, &quot;수박&quot;, &quot;참외&quot;]

# 1. 리스트를 집합으로 변환
set_a = set(list_a)
set_b = set(list_b)

# 2. '&amp;amp;' 기호로 교집합 구하기
common = set_a &amp;amp; set_b

# 3. 다시 리스트로 변환 (필요한 경우)
result = list(common)

print(&quot;공통 요소:&quot;, result)
# 출력: ['포도', '딸기'] (순서는 바뀔 수 있습니다)
&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;기호(&lt;code&gt;&amp;amp;&lt;/code&gt;) 대신 이름을 직접 부르는 함수를 사용하는 방법입니다. 코드가 더 명시적이어서 다른 사람이 읽을 때 &quot;아, 교집합을 구하고 있구나!&quot;라고 바로 이해하기 좋습니다.&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: intersection 함수 활용&lt;/h4&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;list1 = [1, 2, 3, 4, 5]
list2 = [4, 5, 6, 7, 8]

# 한 줄로 요약해서 쓰기
common = list(set(list1).intersection(list2))

print(&quot;겹치는 숫자:&quot;, common)
# 출력: [4, 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;3. 순서를 유지해야 할 때: 리스트 컴프리헨션&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;집합(&lt;code&gt;set&lt;/code&gt;)을 쓰면 중복 제거와 속도 면에서 유리하지만, &lt;b&gt;데이터의 순서가 뒤죽박죽 섞인다&lt;/b&gt;는 단점이 있습니다. 만약 &lt;b&gt;&quot;list_a에 들어있는 순서대로&quot;&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;list_a = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;]
list_b = [&quot;C&quot;, &quot;E&quot;, &quot;A&quot;, &quot;F&quot;]

# list_a에서 하나씩 꺼내는데, 그게 list_b에도 들어있는 경우만 모으기
# (이때 list_b를 set으로 미리 바꿔두면 속도가 훨씬 빨라집니다!)
set_b = set(list_b)
common = [item for item in list_a if item in set_b]

print(&quot;순서 유지 교집합:&quot;, common)
# 출력: ['A', 'C'] (list_a에 나타난 순서대로 'A'가 'C'보다 먼저 나옵니다)
&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;table data-ke-align=&quot;alignLeft&quot;&gt; 
 &lt;thead&gt; 
  &lt;tr&gt; 
   &lt;th&gt;방법&lt;/th&gt; 
   &lt;th&gt;특징&lt;/th&gt; 
   &lt;th&gt;추천 상황&lt;/th&gt; 
  &lt;/tr&gt; 
 &lt;/thead&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td&gt;&lt;b&gt;&lt;code&gt;set_a &amp;amp; set_b&lt;/code&gt;&lt;/b&gt;&lt;/td&gt; 
   &lt;td&gt;가장 짧고 빠름&lt;/td&gt; 
   &lt;td&gt;순서가 상관없을 때 (가장 권장)&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;&lt;b&gt;&lt;code&gt;.intersection()&lt;/code&gt;&lt;/b&gt;&lt;/td&gt; 
   &lt;td&gt;의미가 명확함&lt;/td&gt; 
   &lt;td&gt;가독성이 중요한 협업 프로젝트&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;&lt;b&gt;리스트 컴프리헨션&lt;/b&gt;&lt;/td&gt; 
   &lt;td&gt;순서 유지 가능&lt;/td&gt; 
   &lt;td&gt;원본의 순서가 중요할 때&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&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;set&lt;/code&gt;은 강력합니다.&lt;br&gt;&lt;code&gt;set(list1) &amp;amp; set(list2) &amp;amp; set(list3)&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/1309</guid>
      <comments>https://hianna.tistory.com/1309#entry1309comment</comments>
      <pubDate>Fri, 22 May 2026 10:00:39 +0900</pubDate>
    </item>
    <item>
      <title>[Python 기초] &amp;quot;흩어진 데이터를 하나로!&amp;quot; 파이썬 리스트 합치기 4가지 방법</title>
      <link>https://hianna.tistory.com/1308</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;1. 가장 직관적인 방법: 더하기() 연산자&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&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;angelscript&quot;&gt;&lt;code&gt;list_a = [1, 2, 3]
list_b = [4, 5, 6]

# 두 리스트를 합쳐 새로운 리스트 생성
combined = list_a + list_b

print(combined) 
# 출력: [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;2. 원본에 그대로 덧붙이기:&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;h4 data-ke-size=&quot;size20&quot;&gt;  예제 코드: 원본 확장&lt;/h4&gt;&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;fruits = [&quot;사과&quot;, &quot;바나나&quot;]
new_fruits = [&quot;포도&quot;, &quot;딸기&quot;]

# fruits 리스트 뒤에 그대로 이어 붙입니다.
fruits.extend(new_fruits)

print(fruits)
# 출력: ['사과', '바나나', '포도', '딸기']
&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;파이썬 3.5 버전부터 도입된 강력한 기능입니다. 리스트 앞에 별표(&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;a = [1, 2]
b = [3, 4]
c = [5, 6]

# 여러 리스트를 자유롭게 배치하여 합칠 수 있습니다.
all_in_one = [*a, *b, *c, 7, 8]

print(all_in_one)
# 출력: [1, 2, 3, 4, 5, 6, 7, 8]
&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;만약 합쳐야 할 리스트가 수만 개이고, 데이터 양이 엄청나게 많다면 실제로 리스트를 합치는 대신 &quot;하나의 리스트처럼 보이게 연결&quot;해주는 방식이 좋습니다. 메모리를 절약할 수 있는 고급 기술입니다.&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;import itertools

list1 = [1, 2]
list2 = [3, 4]
list3 = [5, 6]

# 실제로 합치지 않고 연결된 '상태'만 만듭니다 (메모리 절약)
combined_iter = itertools.chain(list1, list2, list3)

# 사용할 때 리스트로 변환하거나 반복문에서 씁니다.
print(list(combined_iter))
# 출력: [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;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_a + list_b&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;기존 리스트를 확장하고 싶을 때:&lt;/b&gt; &lt;code&gt;list_a.extend(list_b)&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;3개 이상의 리스트를 우아하게 합칠 때:&lt;/b&gt; &lt;code&gt;[*a, *b, *c]&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;b&gt;엄청난 대용량 데이터를 다룰 때:&lt;/b&gt; &lt;code&gt;itertools.chain()&lt;/code&gt;&lt;/li&gt; 
&lt;/ol&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/1308</guid>
      <comments>https://hianna.tistory.com/1308#entry1308comment</comments>
      <pubDate>Thu, 21 May 2026 07:04:27 +0900</pubDate>
    </item>
    <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>
  </channel>
</rss>