Python 기본 문법 | 변수, 연산자, 조건문, 반복문 완벽 가이드

Python 기본 문법 | 변수, 연산자, 조건문, 반복문 완벽 가이드

이 글의 핵심

Python 기본 문법에 대한 실전 가이드입니다. 변수, 연산자, 조건문, 반복문 완벽 가이드 등을 예제와 함께 상세히 설명합니다.

들어가며: Python 문법의 특징

”Python은 왜 이렇게 간단한가요?”

Python은 가독성을 최우선으로 설계되었습니다. 들여쓰기로 블록을 구분하고, 간결한 문법으로 빠르게 배울 수 있습니다.

이 글에서 다루는 것:

  • 변수와 타입
  • 연산자 (산술, 비교, 논리)
  • 조건문 (if/elif/else)
  • 반복문 (for/while)
  • 들여쓰기 규칙

목차

  1. 변수와 타입
  2. 연산자
  3. 조건문
  4. 반복문
  5. 들여쓰기 규칙
  6. 정리

1. 변수와 타입

변수 선언: 타입 없이 바로 사용

Python은 동적 타입 언어(실행할 때 값에 따라 타입이 정해지는 방식)입니다. 변수 선언 시 타입을 명시하지 않아도 됩니다. 변수 이름은 값을 담아 두는 서랍 라벨이라고 생각하시면 됩니다. 같은 라벨에 처음에는 숫자를 넣었다가 나중에 문자열을 넣을 수 있지만, 읽는 사람을 위해 한 변수에는 한 종류의 의미를 유지하는 편이 좋습니다.

# 변수 선언 (타입 선언 없음)
name = "홍길동"        # 문자열
age = 25              # 정수
height = 175.5        # 실수
is_student = True     # 불린

print(name, age, height, is_student)
# 출력: 홍길동 25 175.5 True

# 변수 타입은 값에 따라 자동 결정
x = 10        # int
x = "Hello"   # 이제 str (타입 변경 가능)
x = [1, 2, 3] # 이제 list

다른 언어와 비교:

# Python: 타입 선언 없음
age = 25

# Java: 타입 선언 필수
# int age = 25;

# C++: 타입 선언 필수 (또는 auto)
# int age = 25;
# auto age = 25;

# TypeScript: 타입 선언 선택
# let age: number = 25;
# let age = 25;  // 타입 추론

타입 확인과 변환

# 타입 확인
print(type(name))        # <class 'str'>
print(type(age))         # <class 'int'>
print(type(height))      # <class 'float'>
print(type(is_student))  # <class 'bool'>

# isinstance로 타입 체크
print(isinstance(age, int))     # True
print(isinstance(name, str))    # True
print(isinstance(height, int))  # False

# 타입 변환
x = "123"
print(type(x))        # <class 'str'>

y = int(x)            # 문자열 → 정수
print(type(y))        # <class 'int'>
print(y)              # 123

z = float(x)          # 문자열 → 실수
print(z)              # 123.0

# 변환 실패 시 에러
# int("abc")  # ValueError: invalid literal for int()

# 안전한 변환
def safe_int(value, default=0):
    try:
        return int(value)
    except ValueError:
        return default

print(safe_int("123"))   # 123
print(safe_int("abc"))   # 0 (기본값)

여러 변수 동시 할당 (언패킹)

# 동시 할당 (튜플 언패킹)
x, y, z = 1, 2, 3
print(x, y, z)  # 1 2 3

# 같은 값 할당
a = b = c = 0
print(a, b, c)  # 0 0 0

# 값 교환 (Python의 강력한 기능!)
x, y = 10, 20
print(f"교환 전: x={x}, y={y}")  # x=10, y=20

x, y = y, x  # 한 줄로 교환!
print(f"교환 후: x={x}, y={y}")  # x=20, y=10

# 다른 언어에서는 임시 변수 필요:
# temp = x
# x = y
# y = temp

# 리스트 언패킹
numbers = [1, 2, 3]
a, b, c = numbers
print(a, b, c)  # 1 2 3

# 일부만 받기
first, *rest = [1, 2, 3, 4, 5]
print(first)  # 1
print(rest)   # [2, 3, 4, 5]

*beginning, last = [1, 2, 3, 4, 5]
print(beginning)  # [1, 2, 3, 4]
print(last)       # 5

변수 명명 규칙 (PEP 8)

# ✅ 좋은 예 (snake_case)
user_name = "홍길동"
total_count = 100
MAX_SIZE = 1000      # 상수는 대문자 + 언더스코어
_private_var = 42    # 비공개 변수 (관례)

# ❌ 나쁜 예
userName = "홍길동"  # camelCase (Python에서는 비권장)
TotalCount = 100     # PascalCase (클래스명에만 사용)
2nd_value = 10       # 숫자로 시작 (문법 에러)
my-var = 10          # 하이픈 사용 (문법 에러)

# 예약어는 변수명으로 사용 불가
# if = 10      # SyntaxError
# for = 20     # SyntaxError
# class = 30   # SyntaxError

# 예약어 확인
import keyword
print(keyword.kwlist)
# ['False', 'None', 'True', 'and', 'as', 'assert', ...]

타입 힌트 (Type Hints) - 선택사항

# 타입 힌트 (Python 3.5+)
name: str = "홍길동"
age: int = 25
height: float = 175.5
is_student: bool = True

# 함수 타입 힌트
def greet(name: str) -> str:
    return f"안녕하세요, {name}님!"

result = greet("홍길동")
print(result)  # 안녕하세요, 홍길동님!

# 주의: 타입 힌트는 강제가 아님 (런타임 체크 안 함)
result = greet(123)  # 에러 없음 (경고만)
print(result)  # 안녕하세요, 123님!

# mypy로 정적 타입 체크 가능
# pip install mypy
# mypy script.py

2. 연산자

산술 연산자

a, b = 10, 3

print(a + b)   # 13 (덧셈)
print(a - b)   # 7  (뺄셈)
print(a * b)   # 30 (곱셈)
print(a / b)   # 3.3333333333333335 (나눗셈, 항상 float 반환)
print(a // b)  # 3  (몫, floor division)
print(a % b)   # 1  (나머지, modulo)
print(a ** b)  # 1000 (거듭제곱, 10^3)

# 음수 나눗셈
print(-10 // 3)  # -4 (내림)
print(-10 % 3)   # 2

# 복합 할당 연산자
x = 10
x += 5   # x = x + 5
print(x)  # 15

x -= 3   # x = x - 3
print(x)  # 12

x *= 2   # x = x * 2
print(x)  # 24

x //= 5  # x = x // 5
print(x)  # 4

x **= 2  # x = x ** 2
print(x)  # 16

Python vs 다른 언어:

# Python: / 는 항상 float
print(10 / 3)    # 3.3333... (float)
print(10 // 3)   # 3 (int)

# C++/Java: / 는 정수 나눗셈 (피연산자가 정수면)
# 10 / 3 → 3 (int)
# 10.0 / 3 → 3.333... (double)

# Python: ** 거듭제곱
print(2 ** 10)  # 1024

# C++/Java: pow() 함수 사용
# pow(2, 10) → 1024

비교 연산자

x, y = 5, 10

print(x == y)  # False (같음)
print(x != y)  # True  (다름)
print(x < y)   # True  (작음)
print(x > y)   # False (큼)
print(x <= y)  # True  (작거나 같음)
print(x >= y)  # False (크거나 같음)

# 체이닝 비교 (Python의 독특한 기능)
x = 5
print(1 < x < 10)  # True (1 < 5 < 10)
print(x == 5 == 5)  # True

# 다른 언어에서는:
# (1 < x) && (x < 10)  # Java/C++

논리 연산자

a, b = True, False

print(a and b)  # False (AND)
print(a or b)   # True  (OR)
print(not a)    # False (NOT)

# 실전 예제
age = 25
is_student = True

if age >= 18 and is_student:
    print("성인 학생입니다")

# 단축 평가 (Short-circuit Evaluation)
x = 0
y = 10

# and: 첫 번째가 False면 두 번째 평가 안 함
result = (x != 0) and (y / x > 5)  # x != 0이 False → y / x 평가 안 함 (에러 방지)
print(result)  # False

# or: 첫 번째가 True면 두 번째 평가 안 함
result = (x == 0) or (y / x > 5)  # x == 0이 True → y / x 평가 안 함
print(result)  # True

# 기본값 설정에 활용
name = ""
display_name = name or "익명"  # name이 빈 문자열(False)이면 "익명"
print(display_name)  # 익명

멤버십 연산자 (in, not in)

# 리스트에서 검색
fruits = ["apple", "banana", "cherry"]

print("apple" in fruits)      # True
print("grape" not in fruits)  # True

# 문자열에서 검색
text = "Hello, Python!"
print("Python" in text)       # True
print("Java" not in text)     # True

# 딕셔너리에서 키 검색
user = {"name": "홍길동", "age": 25}
print("name" in user)         # True (키 검색)
print("홍길동" in user)       # False (값은 검색 안 됨)
print("홍길동" in user.values())  # True (값 검색)

# 범위 검색
print(5 in range(10))         # True (0~9)
print(15 in range(10))        # False

# 성능: in 연산자는 자료구조에 따라 다름
# list: O(n)
# set: O(1)
# dict: O(1) (키 검색)

비트 연산자

a, b = 5, 3  # 0b101, 0b011

print(a & b)   # 1  (AND, 0b001)
print(a | b)   # 7  (OR, 0b111)
print(a ^ b)   # 6  (XOR, 0b110)
print(~a)      # -6 (NOT, 2의 보수)
print(a << 1)  # 10 (왼쪽 시프트, 0b1010)
print(a >> 1)  # 2  (오른쪽 시프트, 0b010)

# 실전 활용: 플래그 관리
READ = 1    # 0b001
WRITE = 2   # 0b010
EXECUTE = 4 # 0b100

permission = READ | WRITE  # 0b011 (읽기 + 쓰기)
print(permission & READ)   # 1 (읽기 권한 있음)
print(permission & EXECUTE)  # 0 (실행 권한 없음)

3. 조건문

if 문

age = 20

if age >= 18:
    print("성인입니다")
# 성인입니다

if-else 문

age = 15

if age >= 18:
    print("성인입니다")
else:
    print("미성년자입니다")
# 미성년자입니다

if-elif-else 문

score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

print(f"학점: {grade}")  # 학점: B

중첩 if 문

age = 25
has_license = True

if age >= 18:
    if has_license:
        print("운전 가능")
    else:
        print("면허 필요")
else:
    print("나이 미달")

삼항 연산자 (Ternary Operator)

Python의 조건 표현식으로 한 줄로 if-else를 작성할 수 있습니다.

age = 20
status = "성인" if age >= 18 else "미성년자"
print(status)  # 성인

# 일반 if문과 비교
if age >= 18:
    status = "성인"
else:
    status = "미성년자"

# 중첩 삼항 연산자
score = 85
grade = "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 70 else "F"
print(grade)  # B

# 가독성을 위해 괄호 사용
grade = (
    "A" if score >= 90 else
    "B" if score >= 80 else
    "C" if score >= 70 else
    "F"
)

# 실전 활용
# 리스트에서 짝수만 2배
numbers = [1, 2, 3, 4, 5]
doubled = [x * 2 if x % 2 == 0 else x for x in numbers]
print(doubled)  # [1, 4, 3, 8, 5]

Truthy와 Falsy 값

Python에서 조건문은 불린이 아닌 값도 평가할 수 있습니다.

# Falsy 값 (False로 평가)
# - False, None, 0, 0.0, "", [], {}, ()

# Truthy 값 (True로 평가)
# - 위를 제외한 모든 값

# 예시
if "":
    print("실행 안 됨")  # 빈 문자열은 False

if "Hello":
    print("실행됨")  # 비어있지 않은 문자열은 True

if []:
    print("실행 안 됨")  # 빈 리스트는 False

if [1, 2, 3]:
    print("실행됨")  # 비어있지 않은 리스트는 True

# 실전 활용: 빈 값 체크
data = []
if not data:
    print("데이터가 비어있습니다")

# None 체크
value = None
if value is None:
    print("값이 None입니다")

# is vs ==
# is: 객체 동일성 (같은 메모리 주소)
# ==: 값 동일성
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)  # True (값이 같음)
print(a is b)  # False (다른 객체)

4. 반복문

for 문: 반복 가능한 객체 순회

Python의 for 문은 반복 가능한 객체(iterable)를 순회합니다.

# 리스트 순회
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)
# 출력:
# apple
# banana
# cherry

# 인덱스와 값 동시 접근
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry

# enumerate 시작 인덱스 변경
for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry

# range() 사용 (숫자 범위)
for i in range(5):
    print(i, end=" ")
# 0 1 2 3 4
print()

# range(시작, 끝, 간격)
for i in range(1, 11, 2):
    print(i, end=" ")
# 1 3 5 7 9
print()

# 역순
for i in range(10, 0, -1):
    print(i, end=" ")
# 10 9 8 7 6 5 4 3 2 1
print()

# 문자열 순회
for char in "Python":
    print(char, end=" ")
# P y t h o n
print()

# 딕셔너리 순회
user = {"name": "홍길동", "age": 25, "city": "서울"}

# 키만
for key in user:
    print(key, end=" ")
# name age city
print()

# 키와 값
for key, value in user.items():
    print(f"{key}: {value}")
# name: 홍길동
# age: 25
# city: 서울

while 문: 조건 기반 반복

# 기본 while
count = 0
while count < 5:
    print(count, end=" ")
    count += 1
# 0 1 2 3 4
print()

# while vs for 선택
# for: 반복 횟수가 정해진 경우
for i in range(5):
    print(i)

# while: 조건이 만족될 때까지
count = 0
while count < 5:
    print(count)
    count += 1

# 무한 루프 (서버, 게임 루프 등)
while True:
    user_input = input("종료하려면 'q' 입력: ")
    if user_input == 'q':
        break
    print(f"입력: {user_input}")

# 실전 예시: 입력 검증
while True:
    age = input("나이를 입력하세요 (1-120): ")
    if age.isdigit() and 1 <= int(age) <= 120:
        age = int(age)
        break
    print("올바른 나이를 입력하세요")

print(f"입력된 나이: {age}")

break와 continue: 루프 제어

# break: 루프 즉시 종료
for i in range(10):
    if i == 5:
        break  # i가 5일 때 루프 종료
    print(i, end=" ")
# 0 1 2 3 4
print()

# continue: 현재 반복 건너뛰고 다음 반복으로
for i in range(10):
    if i % 2 == 0:
        continue  # 짝수는 건너뛰기
    print(i, end=" ")
# 1 3 5 7 9
print()

# 실전 예시: 소수 찾기
for num in range(2, 20):
    is_prime = True
    for i in range(2, int(num ** 0.5) + 1):
        if num % i == 0:
            is_prime = False
            break  # 약수 발견 시 더 이상 확인 불필요
    
    if is_prime:
        print(num, end=" ")
# 2 3 5 7 11 13 17 19
print()

# break vs continue 비교
for i in range(10):
    if i == 5:
        break  # 5에서 루프 종료
    print(i, end=" ")
# 0 1 2 3 4
print()

for i in range(10):
    if i == 5:
        continue  # 5만 건너뛰고 계속
    print(i, end=" ")
# 0 1 2 3 4 6 7 8 9
print()

else절: 루프 완료 시 실행 (Python의 독특한 기능)

# for-else: break 없이 정상 완료되면 else 실행
for i in range(5):
    print(i, end=" ")
else:
    print("\n루프 정상 완료")
# 0 1 2 3 4
# 루프 정상 완료

# break로 중단되면 else 실행 안 됨
for i in range(5):
    if i == 3:
        break
    print(i, end=" ")
else:
    print("\n루프 정상 완료")  # 실행 안 됨
# 0 1 2

# 실전 활용: 검색
numbers = [1, 3, 5, 7, 9]
target = 4

for num in numbers:
    if num == target:
        print(f"{target} 찾음!")
        break
else:
    print(f"{target} 없음")  # break 안 됨 → else 실행
# 4 없음

# while-else도 동일하게 동작
count = 0
while count < 5:
    print(count, end=" ")
    count += 1
else:
    print("\nwhile 완료")
# 0 1 2 3 4
# while 완료

중첩 루프

# 구구단 (2중 루프)
for i in range(2, 10):  # 2단부터 9단
    for j in range(1, 10):  # 1부터 9까지
        print(f"{i} x {j} = {i*j}")
    print()  # 단 구분 빈 줄

# 2차원 리스트 순회
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

for row in matrix:
    for num in row:
        print(num, end=" ")
    print()
# 1 2 3
# 4 5 6
# 7 8 9

# 인덱스와 함께
for i, row in enumerate(matrix):
    for j, num in enumerate(row):
        print(f"[{i}][{j}] = {num}")

중첩 루프 탈출 (Python에는 레이블 없음)

# 방법 1: 플래그 사용
found = False
for i in range(5):
    for j in range(5):
        if i * j > 10:
            found = True
            break
    if found:
        break

# 방법 2: 함수로 감싸기 (권장)
def find_pair():
    for i in range(5):
        for j in range(5):
            if i * j > 10:
                return (i, j)  # 즉시 반환
    return None

result = find_pair()
print(result)  # (3, 4)

# 방법 3: 예외 사용 (복잡한 경우)
class BreakLoop(Exception):
    pass

try:
    for i in range(5):
        for j in range(5):
            if i * j > 10:
                raise BreakLoop
except BreakLoop:
    pass

5. 들여쓰기 규칙

들여쓰기의 중요성

Python은 들여쓰기로 블록을 구분합니다:

# ✅ 올바른 들여쓰기
if True:
    print("Hello")
    print("World")

# ❌ 들여쓰기 에러
if True:
print("Hello")  # IndentationError!

들여쓰기 표준 (PEP 8)

# ✅ 스페이스 4개 (권장)
def my_function():
    if True:
        print("Hello")

# ❌ 탭 사용 (비권장)
def my_function():
	if True:
		print("Hello")

# ❌ 스페이스 2개 (비표준)
def my_function():
  if True:
    print("Hello")

혼합 사용 금지

# ❌ 탭과 스페이스 혼용
def my_function():
    if True:  # 스페이스 4개
	    print("Hello")  # 탭 1개 → TabError!

실전 예제

예제 1: 짝수/홀수 판별

number = int(input("숫자 입력: "))

if number % 2 == 0:
    print(f"{number}는 짝수입니다")
else:
    print(f"{number}는 홀수입니다")

예제 2: 1부터 N까지 합

n = 10
total = 0

for i in range(1, n + 1):
    total += i

print(f"1부터 {n}까지의 합: {total}")
# 1부터 10까지의 합: 55

예제 3: 구구단

dan = int(input("단 입력: "))

for i in range(1, 10):
    print(f"{dan} x {i} = {dan * i}")

예제 4: 최댓값 찾기

numbers = [3, 7, 2, 9, 1, 5]
max_num = numbers[0]

for num in numbers:
    if num > max_num:
        max_num = num

print(f"최댓값: {max_num}")  # 최댓값: 9

6. 자주 하는 실수와 해결법

실수 1: 들여쓰기 에러

# ❌ IndentationError
if True:
print("Hello")  # 들여쓰기 없음

# ✅ 수정
if True:
    print("Hello")  # 4칸 들여쓰기

# ❌ 불일치
if True:
    print("Hello")  # 4칸
      print("World")  # 6칸 (에러!)

# ✅ 일관성 유지
if True:
    print("Hello")  # 4칸
    print("World")  # 4칸

실수 2: range() 범위 착각

# ❌ 1~10이 아님!
for i in range(10):
    print(i)  # 0~9 (10개)

# ✅ 1~10
for i in range(1, 11):
    print(i)  # 1~10 (10개)

# range(n): 0부터 n-1까지
# range(a, b): a부터 b-1까지
# range(a, b, step): a부터 b-1까지 step 간격

# 예시
print(list(range(5)))        # [0, 1, 2, 3, 4]
print(list(range(1, 6)))     # [1, 2, 3, 4, 5]
print(list(range(0, 10, 2))) # [0, 2, 4, 6, 8]

실수 3: 무한 루프

# ❌ 무한 루프 (Ctrl+C로 강제 종료)
while True:
    print("Hello")  # 영원히 실행!

# ✅ 탈출 조건 추가
count = 0
while True:
    print("Hello")
    count += 1
    if count >= 5:
        break

# ✅ 조건식 사용 (더 명확)
count = 0
while count < 5:
    print("Hello")
    count += 1

실수 4: 변수 타입 혼동

# ❌ 문자열 + 정수
age = 25
# print("나이: " + age)  # TypeError: can only concatenate str

# ✅ 타입 변환
print("나이: " + str(age))  # 나이: 25

# ✅ f-string 사용 (권장)
print(f"나이: {age}")  # 나이: 25

# ❌ 정수 나눗셈 착각
result = 10 / 3
print(type(result))  # <class 'float'> (항상 float!)

# ✅ 정수 나눗셈
result = 10 // 3
print(type(result))  # <class 'int'>

실수 5: 리스트 인덱스 범위 초과

numbers = [1, 2, 3]

# ❌ IndexError
# print(numbers[3])  # 인덱스는 0, 1, 2만 존재

# ✅ 범위 체크
if len(numbers) > 3:
    print(numbers[3])
else:
    print("인덱스 범위 초과")

# ✅ 예외 처리
try:
    print(numbers[3])
except IndexError:
    print("인덱스 범위 초과")

7. 연습 문제

문제 1: FizzBuzz

1부터 30까지 출력하되, 3의 배수는 “Fizz”, 5의 배수는 “Buzz”, 15의 배수는 “FizzBuzz” 출력

정답 보기
for i in range(1, 31):
    if i % 15 == 0:
        print("FizzBuzz")
    elif i % 3 == 0:
        print("Fizz")
    elif i % 5 == 0:
        print("Buzz")
    else:
        print(i)

문제 2: 소수 판별

주어진 숫자가 소수인지 판별하세요.

정답 보기
def is_prime(n):
    if n < 2:
        return False
    
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    
    return True

print(is_prime(7))   # True
print(is_prime(10))  # False

문제 3: 리스트 역순 출력

리스트를 역순으로 출력하세요 (reversed() 사용 금지).

정답 보기
numbers = [1, 2, 3, 4, 5]

# 방법 1: 인덱스 역순
for i in range(len(numbers) - 1, -1, -1):
    print(numbers[i], end=" ")
# 5 4 3 2 1

# 방법 2: 슬라이싱
for num in numbers[::-1]:
    print(num, end=" ")
# 5 4 3 2 1

정리

핵심 요약

  1. 변수: 타입 선언 없이 name = "홍길동", 동적 타입
  2. 연산자: 산술(+, -, *, /, //, %, **), 비교, 논리, 멤버십
  3. 조건문: if-elif-else, 들여쓰기 필수, 삼항 연산자
  4. 반복문: for (iterable 순회), while (조건 기반), break, continue
  5. 들여쓰기: 스페이스 4개 (PEP 8), 탭 혼용 금지
  6. Python 특징: Truthy/Falsy, for-else, 체이닝 비교

Python 문법을 마스터하면

  • 간결하고 읽기 쉬운 코드 작성
  • 다른 언어보다 빠른 개발 속도
  • 프로토타이핑에 최적

다음 단계

  • Python 자료형 - 리스트, 딕셔너리, 세트, 튜플
  • Python 함수 - 함수 정의, 매개변수, 람다
  • Python 모듈 - import, 패키지, pip

마치며

Python 기본 문법은 간결하고 직관적입니다. 들여쓰기만 잘 지키면 누구나 쉽게 배울 수 있습니다!

다음 글에서는 Python의 다양한 자료형(리스트, 딕셔너리, 튜플 등)을 배워보겠습니다.


관련 글

  • [Go 2주 완성 #01] Day 1~2: Go 언어의 철학과 기본 문법 - C++ 개발자의 첫인상
  • Python 환경 설정 | Windows/Mac에서 Python 설치하고 시작하기
  • Python 자료형 | 리스트, 딕셔너리, 튜플, 세트 완벽 가이드
  • 배열과 리스트 | 코딩 테스트 필수 자료구조 완벽 정리
  • 정렬 문제 풀이 | 코딩 테스트 정렬 패턴 완벽 정리