Python 기본 문법 | 변수, 연산자, 조건문, 반복문 완벽 가이드
이 글의 핵심
Python 기본 문법(변수·연산자·조건·반복)과 함께, CPython이 소스를 바이트코드로 컴파일하고 ceval 루프로 실행하는 내부 구조를 전문가 수준으로 정리했습니다.
들어가며: Python 문법의 특징
”Python은 왜 이렇게 간단한가요?”
Python은 가독성을 최우선으로 설계되었습니다. 들여쓰기로 블록을 구분하고, 간결한 문법으로 빠르게 배울 수 있습니다. 이 글에서 다루는 것:
- 변수와 타입
- 연산자 (산술, 비교, 논리)
- 조건문 (if/elif/else)
- 반복문 (for/while)
- 들여쓰기 규칙
실무 활용 사례: 데이터 분석, 웹 개발, 자동화 프로젝트에서 실제로 사용한 패턴과 코드를 바탕으로 정리했습니다. 초보자가 흔히 겪는 오류와 해결법을 포함합니다.
실무에서 느낀 Python의 매력
처음 Python을 배울 때는 “이게 정말 프로그래밍 언어인가?” 싶을 정도로 간결했습니다. C++에서 10줄로 작성하던 코드가 Python에서는 2~3줄로 끝나는 경우가 많았죠. 특히 데이터 분석 프로젝트를 진행하면서 Pandas와 NumPy의 강력함을 체감했습니다. 엑셀로 몇 시간 걸리던 작업이 Python 스크립트로는 몇 초 만에 끝나는 걸 보고 동료들이 놀라워했던 기억이 납니다. 하지만 처음부터 순탄하지만은 않았습니다. 들여쓰기 하나 잘못해서 몇 시간을 헤맨 적도 있고, 가상환경 설정이 꼬여서 프로젝트 전체를 다시 시작한 적도 있습니다. 이런 시행착오를 겪으며 깨달은 건, 환경 설정을 처음부터 제대로 하는 것이 얼마나 중요한지였습니다. 이 글에서는 제가 겪은 실수들을 바탕으로, 여러분이 같은 시행착오를 겪지 않도록 실전 팁을 담았습니다.
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!
6. CPython 내부: 바이트코드와 인터프리터 루프
앞 절까지는 문법을 다뤘습니다. 이 절에서는 같은 코드가 CPython(공식 구현) 안에서 어떻게 컴파일되고 실행되는지, 구현 관점에서 짚습니다. 다른 Python 구현(예: PyPy, Jython)은 단계나 최적화가 다를 수 있으나, 대부분의 운영·학습 환경은 CPython이므로 여기서는 CPython을 기준으로 설명합니다.
6.1 소스에서 바이트코드까지
CPython은 사용자가 작성한 .py 소스를 한 번에 기계어로 컴파일하지 않습니다. 대략 다음 순서를 거칩니다.
- 파싱·구문 분석: 소스 문자열을 토큰으로 나누고, 문법 규칙에 맞는지 확인합니다.
- AST(Abstract Syntax Tree): 구문 트리 형태로 프로그램 구조를 표현합니다.
- 컴파일: AST를
code객체(코드 객체)로 변환합니다. 이때 바이트코드(bytecode) 인스트럭션 시퀀스가 만들어집니다.
바이트코드는 CPU가 직접 실행하는 명령이 아니라, 가상 머신이 해석할 중간 표현입니다. 한 줄의 Python 문은 여러 바이트코드 연산으로 펼쳐질 수 있습니다.
확인 방법으로 표준 라이브러리 dis 모듈이 있습니다. 예를 들어 간단한 산술은 다음과 같이 펼쳐집니다.
import dis
def add_one(x):
return x + 1
dis.dis(add_one)
# 예시 출력(버전에 따라 세부 연산 코드·인자는 다를 수 있음):
# LOAD_FAST 0 (x)
# LOAD_CONST 1 (1)
# BINARY_OP 0 (+)
# RETURN_VALUE
dis.dis는 함수·메서드·코드 객체에 대해 연산자 이름, 인자(상수 인덱스·로컬 슬롯 번호 등) 를 사람이 읽을 수 있는 형태로 보여 줍니다. Python 버전이 바뀌면 연산 코드(opcode) 집합이 달라질 수 있으므로, 출력은 참고용으로 두고, 패턴(스택에 올리고, 연산하고, 반환한다)을 이해하는 것이 중요합니다.
6.2 평가 스택과 스택 머신 모델
CPython의 바이트코드 실행은 스택 머신(stack machine) 모델에 가깝습니다. 대부분의 연산은 평가 스택(eval stack) 위에서 이뤄집니다.
LOAD_FAST등: 로컬 변수·상수 값을 스택에 푸시- 이항 연산: 스택에서 두 개를 팝해 연산 후 결과를 푸시
RETURN_VALUE: 스택 꼭대기 값을 반환
조건문·반복문도 점프(jump) 연산과 조건부 점프로 제어 흐름을 표현합니다. 그래서 소스 레벨에서 보기엔 단순한 if 한 줄이, 바이트코드에서는 비교·점프·블록 경계가 분리되어 보이는 것이 자연스럽습니다.
6.3 인터프리터 루프(ceval)와 프레임
바이트코드를 실제로 한 줄씩 읽어 실행하는 핵심 루프는 CPython 소스 트리에서 Python/ceval.c 의 _PyEval_EvalFrameDefault 등으로 구현되어 있습니다(이름·세부 구조는 버전마다 조금씩 바뀔 수 있음). 흔히 이 부분을 ceval 루프 또는 인터프리터 루프라고 부릅니다.
실행 단위로 중요한 것이 frame 객체(프레임) 입니다. 함수 호출마다(및 특정 실행 컨텍스트마다) 프레임이 쌓이며, 여기에는 대략 다음 정보가 들어갑니다.
- 실행 중인 코드 객체(바이트코드·상수 테이블·자유 변수 정보 등)
- 로컬·글로벌 네임스페이스에 대한 참조
- instruction pointer(다음에 실행할 바이트코드 위치)
- 평가 스택 등 실행 상태
재귀 호출이 깊어지면 프레임이 많이 쌓여 스택 오버플로에 가까운 한계에 다가가는 이유도, 이 프레임 스택 모델과 연결해서 이해할 수 있습니다.
6.4 GIL과 바이트코드 실행(개념만)
CPython의 GIL(Global Interpreter Lock) 은 “한 시점에 한 스레드만 바이트코드를 실행한다”는 규칙을 둡니다(일부 I/O·C 확장 지점에서 해제될 수 있음). 따라서 CPU 바운드 작업을 스레드 여러 개로 나누어도 한 프로세스 안에서는 기대만큼 확장되지 않는 경우가 많습니다. 이는 문법과는 별개이지만, “Python 코드가 실제로 어떻게 도는가”를 논할 때 자주 따라붙는 주제이므로 이름만이라도 짚어 둡니다.
6.5 정리: 문법과 구현을 함께 보면 좋은 이유
dis로 바이트코드를 보면, “왜 이 문법이 이렇게 동작하나?”를 연산 순서 수준에서 설명할 수 있습니다.- 프레임·스택 개념은 이후 함수·클로저·예외(
python-series-04-functions등)를 읽을 때 그대로 이어집니다. - 성능을 다룰 때는 “한 줄이 저수준에서 얼마나 무거운가”를 대략 짐작하는 데 도움이 됩니다.
실전 예제
예제 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” 출력
정답 보기
```python 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) ```정답 보기
```python def is_prime(n): if n < 2: return Falsefor 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
</details>
### 문제 3: 리스트 역순 출력
리스트를 역순으로 출력하세요 (reversed() 사용 금지).
<details>
<summary>정답 보기</summary>
```python
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
Python 기본 문법은 간결하고 직관적입니다. 들여쓰기만 잘 지키면 누구나 쉽게 배울 수 있습니다! 다음 글에서는 Python의 다양한 자료형(리스트, 딕셔너리, 튜플 등)을 배워보겠습니다.
관련 글
- [Go 2주 완성 #01] Day 1~2: Go 언어의 철학과 기본 문법 - C++ 개발자의 첫인상
- Python 환경 설정 | Windows/Mac에서 Python 설치하고 시작하기
- Python 자료형 | 리스트, 딕셔너리, 튜플, 세트 완벽 가이드
- 배열과 리스트 | 코딩 테스트 필수 자료구조 완벽 정리
- 정렬 문제 풀이 | 코딩 테스트 정렬 패턴 완벽 정리
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「Python 기본 문법 | 변수, 연산자, 조건문, 반복문 완벽 가이드」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「Python 기본 문법 | 변수, 연산자, 조건문, 반복문 완벽 가이드」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Python 기본 문법과 CPython 바이트코드·ceval 인터프리터 루프까지: 소스가 실행될 때 컴파일·스택 머신·프레임 객체 관점을 한글로 정리합니다. 변수·조건문·반복문 실무와 내부 동작을 함께 다룹니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. Python 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Expression Template 완벽 가이드 | 지연 평가와 수학 라이브러리 최적화
- [Go 2주 완성 #01] Day 1~2: Go 언어의 철학과 기본 문법 - C++ 개발자의 첫인상
- C++ Decorator Pattern 완벽 가이드 | 기능 동적 추가와 조합
이 글에서 다루는 키워드 (관련 검색어)
Python, 기본문법, CPython, 바이트코드, 인터프리터, ceval, 입문 등으로 검색하시면 이 글이 도움이 됩니다.