Python은 값을 대입하는 순간 그 값을 보고 자료형을 자동으로 결정합니다(동적 타이핑). 같은 변수라도 새 값을 대입하면 자료형이 바뀔 수 있습니다.
기본 자료형 6종
| 분류 | 자료형 | 설명 | 예시 |
|---|---|---|---|
| 숫자형 | int | 정수 (소수점 없음, 크기 제한 없음) | 100, -5, 0 |
| 숫자형 | float | 실수 (소수점이 있는 수) | 3.14, 100.0 |
| 논리형 | bool | 참 / 거짓 | True, False |
| 문자열 | str | 문자들의 묶음 | "hello", "100.0" |
| 시퀀스 | list | 변경 가능한 묶음 (mutable) | [1, 2, 3] |
| 시퀀스 | tuple | 변경 불가능한 묶음 (immutable) | (1, 2, 3) |
변수와 동적 타이핑
x = 5 # int (정수) y = 3.14 # float (실수) z = "감자" # str (문자열) t = (1, 2) # tuple (튜플) x = 5 # x는 int x = "감자" # x는 str로 바뀜 (재대입하면 자료형도 바뀜)
int와 float의 차이는 점(.) 하나
같아 보이는 두 숫자라도 점이 있으면 float, 없으면 int입니다. 시험 단골 함정입니다.
a = 100 # int b = 100.0 # float (점이 있어서 실수) print(type(a)) # <class 'int'> print(type(b)) # <class 'float'> print(type(100) == type(100.0)) # False (값은 같아 보여도 자료형이 다름)
bool은 int의 하위 자료형
True는 1, False는 0으로 동작해 산술 연산에 쓸 수 있습니다.
print(True + True) # 2 (1 + 1) print(True + 1) # 2 print(False * 100) # 0
type() — 자료형 확인
type(x)는 항상 <class '자료형'> 형태로 결과를 보여줍니다.
print(type(100)) # <class 'int'> print(type(100.0)) # <class 'float'> print(type("100")) # <class 'str'> print(type((1, 2))) # <class 'tuple'> print(type([1, 2])) # <class 'list'>
type(x) == type(샘플) 형태로 두 값이 같은 자료형인지 비교하는 관용구가 기출에 나옵니다.
x = 100 print(type(x) == type(100)) # True (둘 다 int) print(type(x) == type("")) # False (int vs str)
len() — 요소 개수
문자열·리스트·튜플·딕셔너리 등의 요소 개수를 반환합니다. 문자열에서는 공백·점·특수기호도 한 글자로 셉니다.
print(len("hello")) # 5 print(len("a b")) # 3 (공백도 한 글자) print(len("100.0")) # 5 (문자 '1','0','0','.','0') print(len("")) # 0 (빈 문자열) print(len([1, 2, 3])) # 3 print(len((100, 200))) # 2 print(len({"a": 1, "b": 2})) # 2 (키-값 쌍의 개수)
산술 연산자
| 연산자 | 이름 | 예시 | 결과 |
|---|---|---|---|
+ | 덧셈 | 5 + 3 | 8 |
- | 뺄셈 | 5 - 3 | 2 |
* | 곱셈 | 5 * 3 | 15 |
/ | 실수 나눗셈 (항상 float) | 10 / 3 | 3.333... |
// | 정수 나눗셈 (소수점 버림) | 10 // 3 | 3 |
% | 나머지 (모듈로) | 5 % 3 | 2 |
** | 거듭제곱 | 2 ** 10 | 1024 |
Python의 /는 항상 실수를 반환합니다. C/Java와 달리 10 / 3은 3.333...입니다. 정수 몫이 필요하면 //를 씁니다.
print(10 / 3) # 3.3333333333333335 print(10 // 3) # 3 print(2 ** 10) # 1024
나머지 연산 활용 (자릿수·짝홀·배수)
# 짝수/홀수 판별 if n % 2 == 0: print("짝수") else: print("홀수") # 자릿수 추출 n = 123 ones = n % 10 # 3 (1의 자리) tens = (n // 10) % 10 # 2 (10의 자리) # 순환 (0 → 1 → 2 → 0 → ...) index = (index + 1) % 3
비교 연산자 — 결과는 True / False
| 연산자 | 의미 | 예시 | 결과 |
|---|---|---|---|
== | 같다 (값 비교) | 5 == 5 | True |
!= | 같지 않다 | 5 != 3 | True |
< > <= >= | 대소 비교 | 3 < 5 | True |
is / is not | 같은 객체인가 (주소 비교) | None is None | True |
in / not in | 포함되어 있는가 | 3 in [1, 2, 3] | True |
is / in
==는 값이 같은지, is는 같은 객체(주소)인지를 확인합니다. None과 비교할 땐 is None이 관례입니다.
x = None if x is None: # 권장 print("값 없음") nums = [1, 2, 3] print(3 in nums) # True print(10 not in nums) # True print("py" in "python") # True grades = {"철수": 90} print("철수" in grades) # True (in은 key만 확인) print(90 in grades) # False (value는 확인 대상 아님)
논리 연산자
| 연산자 | 설명 | 예시 | 결과 |
|---|---|---|---|
and | 두 조건 모두 True이면 True | True and False | False |
or | 하나라도 True이면 True | True or False | True |
not | 참/거짓 반전 | not True | False |
input() — 입력은 항상 문자열
input()은 키보드 입력을 문자열로 반환합니다. 숫자로 쓰려면 int()로 변환합니다.
name = input("이름을 입력하세요: ") # 문자열 age = int(input()) # 정수로 변환
print() 기본 동작 — 자동 줄바꿈
print("A") print("B") # 출력: # A # B
여러 인자와 sep (사이에 공백 한 칸 자동 삽입)
쉼표로 값을 나열하면 각 값 사이에 공백 한 칸이 자동으로 들어갑니다.
print("x", 1, "y", 2) # x 1 y 2 print('a=', 20) # a= 20 (등호와 20 사이에 공백!) print("a", "b", "c") # a b c (기본 공백) print("a", "b", "c", sep="") # abc (구분 없음) print("a", "b", "c", sep="/") # a/b/c
end (끝 문자 변경) + 빈 print()
print("A", end="") print("B", end="") # 출력: AB (같은 줄) # 중첩 for에서 안쪽 끝나면 줄바꿈 lol = [[1, 2, 3], [4, 5], [6, 7, 8, 9]] for sub in lol: for item in sub: print(item, end="") print() # 출력: # 123 # 45 # 6789
f-string — 중괄호 안 값이 치환
name = "감자" age = 20 print(f"이름: {name}, 나이: {age}") # 이름: 감자, 나이: 20 print(f"합계: {3 + 4}") # 합계: 7 (계산식도 가능)
f-string 포맷 지정자 — {값:포맷}
| 포맷 | 의미 | 예시 → 결과 |
|---|---|---|
:5d | 정수, 최소 폭 5 (오른쪽 정렬) | f"{7:5d}" → " 7" |
:04d | 폭 4, 빈자리 0으로 채움 | f"{7:04d}" → "0007" |
:.2f | 소수점 이하 2자리 (반올림) | f"{3.14159:.2f}" → "3.14" |
:<5 | 왼쪽 정렬, 폭 5 | f"{'A':<5}" → "A " |
:>5 | 오른쪽 정렬, 폭 5 | f"{'A':>5}" → " A" |
:^5 | 가운데 정렬, 폭 5 | f"{'A':^5}" → " A " |
pi = 3.14159 print(f"{pi:.2f}") # 3.14 print(f"{pi:.0f}") # 3 (반올림) print(f"{pi:8.2f}") # " 3.14" (폭 8, 소수점 2자리)
% 포맷팅 (옛 방식, 기출에 함께 출제)
| 자리표시자 | 받는 값 |
|---|---|
%s | 문자열 (string) |
%d | 정수 (digit) |
%f | 실수 (float) |
print("%s" % "hi") # hi print("%d점" % 100) # 100점 print("%s %d점" % ("감자", 100)) # 감자 100점 (값 여러 개는 튜플로)
인접 문자열 자동 결합
문자열 리터럴이 공백으로 나란히 있으면 + 없이 자동으로 이어 붙습니다(f-string도 가능). 변수에는 적용되지 않습니다.
s = "abc" "def" print(s) # abcdef print(f'ab{2+1}' f'ca{3}') # ab3ca3 (각 f-string 계산 후 결합)
문자열 만들기
홑/쌍/삼중 따옴표 모두 동일한 str입니다. Python에는 문자(char) 타입이 따로 없습니다.
name = "감자" greeting = '안녕하세요' long_text = """여러 줄에 걸친 문자열입니다""" s1 = "it's fine" # 쌍따옴표 밖, 홑따옴표 안 (이스케이프 불필요) s2 = 'say "hello"' # 홑따옴표 밖, 쌍따옴표 안
함정: 따옴표로 감싸면 숫자처럼 보여도 str입니다.
a = "100" # str c = "100.0" # str print(type(a)) # <class 'str'>
인덱싱 (0부터, 음수는 뒤에서)
s = "PYTHON" print(s[0]) # P (첫 번째) print(s[5]) # N (여섯 번째) print(s[-1]) # N (마지막) print(s[-2]) # O (뒤에서 두 번째)
슬라이싱 s[시작:끝:step] — 끝 인덱스 미포함
s = "PYTHON" print(s[0:3]) # PYT (인덱스 0, 1, 2) print(s[:3]) # PYT (처음부터 인덱스 2까지) print(s[3:]) # HON (인덱스 3부터 끝까지) print(s[1:4]) # YTH (인덱스 1, 2, 3 — 4는 미포함) print(s[::2]) # PTO (2칸 간격) print(s[::-1]) # NOHTYP (역순 = 뒤집기)
split() — 문자열을 리스트로 쪼갬
s = "hello world" parts = s.split(' ') print(parts) # ['hello', 'world'] print(parts[0]) # hello
구분자는 결과에 포함되지 않습니다(시험 함정).
join() — 이터러블을 문자열로 합침 (split의 반대)
print(' '.join(['hello', 'world'])) # "hello world" print(','.join(['A', 'B', 'C'])) # "A,B,C" print(''.join(['a', 'b', 'c'])) # "abc" # 숫자는 str()로 변환 후 넣어야 함 (안 하면 TypeError) print(','.join(str(n) for n in [1, 2, 3])) # "1,2,3" # 구분자 교체: split + join print(','.join('hello world'.split(' '))) # "hello,world"
기타 문자열 메서드
| 메서드 | 동작 | 예시 → 결과 |
|---|---|---|
strip() | 양 끝 공백/문자 제거 (안쪽은 그대로) | " hi ".strip() → "hi" |
replace(old, new) | 모든 old를 new로 치환 | "hello".replace("l","L") → "heLLo" |
find(x) | x가 처음 나오는 인덱스 | "hello".find("l") → 2 |
upper() / lower() | 대문자 / 소문자로 | "py".upper() → "PY" |
startswith(x) | x로 시작하는지 | "hello".startswith("he") → True |
split()만 리스트를 돌려주고 나머지는 문자열/정수 1개를 돌려줍니다. 그래서 a, b = ... 다중 할당 빈칸에는 split()만 들어갈 수 있습니다.
리스트와 튜플의 가장 큰 차이는 변경 가능 여부입니다.
| 항목 | 리스트 | 튜플 |
|---|---|---|
| 괄호 | 대괄호 [ ] | 소괄호 ( ) |
| 변경 가능 여부 | 변경 가능 (mutable) | 변경 불가 (immutable) |
| 요소 추가/삭제 | append, pop 등 가능 | 불가 |
| 메서드 | 다양 | count, index만 |
nums = [1, 2, 3] nums[0] = 100 # 리스트: 가능 point = (1, 2, 3) # point[0] = 100 # TypeError! 튜플은 변경 불가
튜플 만들기 — 쉼표가 핵심
t = 10, 20, 30 # 괄호 없이 콤마만으로도 튜플 a = (100) # int! (그냥 괄호 친 정수) b = (100,) # tuple (끝에 쉼표 필수) empty = () # 빈 튜플 print(type(a)) # <class 'int'> print(type(b)) # <class 'tuple'>
함정: (100)은 튜플이 아닙니다. 요소 1개 튜플은 (100,)처럼 쉼표를 붙여야 합니다.
tuple() / list() 변환
print(tuple([1, 2, 3])) # (1, 2, 3) print(tuple("abc")) # ('a', 'b', 'c') (한 글자씩) print(tuple(range(5))) # (0, 1, 2, 3, 4) print(list((10, 20))) # [10, 20]
슬라이싱 (리스트·튜플 공통, 끝 인덱스 미포함)
t = (0, 1, 2, 3, 4, 5) print(t[1:4]) # (1, 2, 3) print(t[:3]) # (0, 1, 2) print(t[3:]) # (3, 4, 5) print(t[::2]) # (0, 2, 4) print(t[::-1]) # (5, 4, 3, 2, 1, 0) (역순) lst = list(range(10)) print(lst[2:6]) # [2, 3, 4, 5] print(lst[::2]) # [0, 2, 4, 6, 8] print(lst[::-2]) # [9, 7, 5, 3, 1] (음수 간격: 끝에서 앞으로)
연산과 포함 여부
print((1, 2) + (3, 4)) # (1, 2, 3, 4) (연결, 새 튜플) print((0,) * 5) # (0, 0, 0, 0, 0) (반복) print((1, 2) * 3) # (1, 2, 1, 2, 1, 2) print("바나나" in ("사과", "바나나")) # True
튜플 메서드 (count, index)
t = (1, 2, 3, 2, 4, 2) print(t.count(2)) # 3 (2가 3개) print(t.index(2)) # 1 (2가 처음 나오는 인덱스, 없으면 ValueError)
딕셔너리 — 키-값 쌍
d = {"name": "홍길동", "age": 25} print(d["name"]) # 홍길동 d["age"] = 30 # 수정 d["score"] = 95 # 새 키 추가 print(d.keys()) # dict_keys(['name', 'age', 'score']) print(d.values()) # dict_values(['홍길동', 30, 95]) print(d.items()) # dict_items([('name','홍길동'), ...])
순회 + 집계
score = {"국어": 90, "영어": 85, "수학": 95} for k, v in score.items(): # items()는 (키, 값) 튜플 print(k, v) print(sum(score.values())) # 270 (값의 합) print(len(score)) # 3 (키-값 쌍의 개수)
get(key, default) — 키가 없을 때 기본값
d["x"]는 키가 없으면 KeyError, get()은 기본값을 돌려줍니다.
d = {"a": 1, "b": 2} print(d.get("a")) # 1 print(d.get("x")) # None (기본값 생략 시) print(d.get("x", 0)) # 0
빈도수 카운팅 (최빈출 패턴)
s = "banana" freq = {} for ch in s: freq[ch] = freq.get(ch, 0) + 1 print(freq) # {'b': 1, 'a': 3, 'n': 2} print(sum(freq.values())) # 6 (전체 글자 수) print(len(freq)) # 3 (서로 다른 글자 수)
get(ch, 0)이 첫 등장도 0에서 출발시켜 if-else 분기 없이 한 줄로 처리됩니다. d.get(ch)처럼 기본값을 생략하면 None + 1에서 TypeError가 납니다.
집합 (set) — 중복 없음, 순서 없음
s = {1, 2, 2, 3, 3, 3} print(s) # {1, 2, 3} (중복 자동 제거) empty = set() # 빈 집합 ({}는 빈 딕셔너리!)
함정: 빈 {}는 집합이 아니라 빈 딕셔너리입니다. 빈 집합은 set().
집합 메서드와 연산
| 연산 | 기호 | 설명 |
|---|---|---|
| 교집합 | & | 두 집합에 모두 있는 요소 |
| 합집합 | | | 두 집합의 모든 요소 |
| 차집합 | - | 앞 집합에만 있는 요소 |
| 대칭 차집합 | ^ | 한쪽에만 있는 요소 |
a = {1, 2, 3, 4, 5} b = {3, 4, 5, 6, 7} print(a & b) # {3, 4, 5} print(a | b) # {1, 2, 3, 4, 5, 6, 7} print(a - b) # {1, 2} print(a ^ b) # {1, 2, 6, 7} s = {1, 2, 3} s.add(4) # {1, 2, 3, 4} s.remove(2) # {1, 3, 4} (없으면 KeyError) s.discard(10) # 없어도 오류 없음 s.update([5, 6, 7]) # 여러 개 한 번에 추가
if / elif / else
Python은 중괄호 대신 콜론(:)과 들여쓰기로 블록을 구분합니다.
if score >= 90: print("A") elif score >= 80: print("B") else: print("C")
for + range
for i in range(5): print(i, end=" ") # 출력: 0 1 2 3 4 fruits = ["사과", "바나나", "체리"] for i in range(len(fruits)): print(i, fruits[i])
while
while은 조건이 참인 동안 반복합니다. 변수 변경(i += 1)을 빠뜨리면 무한 루프가 됩니다.
i = 0 while i < 3: print(i, end=" ") i += 1 # 출력: 0 1 2
break / continue
for i in range(10): if i == 3: break # 즉시 종료 (3은 출력 안 됨) print(i, end=" ") # 출력: 0 1 2 for i in range(5): if i == 2: continue # 건너뛰고 다음 반복 print(i, end=" ") # 출력: 0 1 3 4
def — 함수 정의
Python은 main 함수가 필수가 아니며, 함수는 호출 전에 정의되어야 합니다(아래에 있으면 NameError).
def gamja(a, b):
return a + b
result = gamja(3, 5)
print(result) # 8
return이 없으면 None을 반환합니다.
def greet():
print("hi")
print(greet()) # hi 다음 줄에 None
lambda — 이름 없는 익명 함수 (한 줄)
lambda 매개변수: 반환식 형태로, 함수 이름과 return을 생략합니다.
| 일반 함수 (def) | lambda |
|---|---|
def add(x): return x + 100 | lambda x: x + 100 |
| 여러 줄 가능 | 단일 표현식만 가능 |
add_100 = lambda num: num + 100 print(add_100(5)) # 105 multiply = lambda x, y: x * y # 매개변수 여러 개 print(multiply(3, 4)) # 12 clamp = lambda x: x if x > 0 else 0 # 삼항 연산 가능 print(clamp(-3)) # 0
if/for 같은 복잡한 로직은 lambda로 쓸 수 없습니다. 그럴 땐 def를 씁니다.
range() — 정수 범위 (끝 미포함)
list(range(5)) # [0, 1, 2, 3, 4] list(range(2, 6)) # [2, 3, 4, 5] list(range(0, 10, 3)) # [0, 3, 6, 9] list(range(5, 0, -1)) # [5, 4, 3, 2, 1] (역순) print(range(10)) # range(0, 10) (그대로 출력하면 범위 정보)
range(5)는 0~4를 생성하고 5는 포함하지 않습니다(C/Java의 i < 5와 동일).
sum() — 숫자 요소의 합
print(sum([1, 2, 3, 4, 5])) # 15 scores = [80, 90, 70, 100] print(sum(scores) / len(scores)) # 85.0 (평균 패턴)
sorted() — 정렬된 새 리스트 (원본 유지)
nums = [3, 1, 4, 1, 5] print(sorted(nums)) # [1, 1, 3, 4, 5] print(nums) # [3, 1, 4, 1, 5] (원본 그대로) print(sorted(nums, reverse=True)) # [5, 4, 3, 1, 1]
sorted(x)는 새 리스트 반환, x.sort()는 원본을 정렬하고 None 반환.
key=lambda — 정렬 기준 바꾸기
data = [("A", 90), ("B", 70), ("C", 85)] print(sorted(data, key=lambda x: x[1])) # [('B', 70), ('C', 85), ('A', 90)] (두 번째 원소 기준) # 딕셔너리를 값 기준 정렬 (최빈값 뽑기) freq = {'b': 1, 'a': 3, 'n': 2} top = sorted(freq.items(), key=lambda x: x[1], reverse=True)[0] print(top) # ('a', 3)
key에는 값이 아니라 함수를 넘겨야 합니다(key=x[1]은 오류).
enumerate() — 인덱스와 값 동시에
for i, v in enumerate(["사과", "바나나"]): print(i, v) # 0 사과 # 1 바나나 for i, v in enumerate(["a", "b"], start=1): # 시작 번호 변경 print(i, v) # 1 a 그리고 2 b
map() — 각 요소에 함수 적용 (map 객체 반환)
nums = ["1", "2", "3"] print(list(map(int, nums))) # [1, 2, 3] print(list(map(lambda x: x + 100, [1, 2, 3]))) # [101, 102, 103] print(map(str, [1, 2])) # <map object at 0x...> (list()로 감싸야 함)
all() / any()
all은 모두 참이면 True, any는 하나라도 참이면 True. 숫자 0은 False, 0이 아니면 True.
print(all([1, 2, 3])) # True print(all([1, 0, 3])) # False (0은 False) print(any([0, 0, 1])) # True print(all([])) # True (거짓인 요소가 없음) print(any([])) # False (참인 요소가 없음)
같은 공식을 괄호 모양만 바꿔 4종의 자료구조를 만듭니다.
| 종류 | 형식 | 반환 타입 |
|---|---|---|
| 리스트 | [식 for 변수 in 반복] | list |
| 딕셔너리 | {키: 값 for ...} | dict |
| 집합 | {식 for ...} (콜론 없음) | set |
| 제너레이터 | (식 for ...) | generator |
괄호 모양으로 자료형이 갈립니다
nums = [1, 2, 3] a = [x * 2 for x in nums] # 대괄호 → list b = {x: x * 2 for x in nums} # 중괄호 + : → dict c = {x * 2 for x in nums} # 중괄호 (콜론 없음) → set d = (x * 2 for x in nums) # 소괄호 → generator print(type(a)) # <class 'list'> print(type(b)) # <class 'dict'> print(type(c)) # <class 'set'> print(type(d)) # <class 'generator'>
딕셔너리와 집합은 같은 중괄호를 쓰므로 콜론(:) 유무가 결정적 차이입니다. 빈 {}는 딕셔너리입니다.
조건 필터링 + 예시
x = [1, 2, 3, 4, 5, 6] print([n for n in x if n % 2 == 0]) # [2, 4, 6] print([n ** 2 for n in [1, 2, 3, 4]]) # [1, 4, 9, 16] print({i: i * 2 for i in [1, 2, 3]}) # {1: 2, 2: 4, 3: 6} print({n for n in [1, 2, 2, 3, 3]}) # {1, 2, 3} (중복 제거)
제너레이터와 게으른 평가 + next()
제너레이터는 값을 미리 만들지 않고 next()/for로 요청할 때마다 하나씩 계산합니다(메모리 절약).
gen = (x * 2 for x in [1, 2, 3]) print(next(gen)) # 2 print(next(gen)) # 4 print(next(gen)) # 6 # 한 번 더 next(gen) → StopIteration 예외 # sum/any/all/join 인자로 넣을 땐 괄호 생략 가능 print(sum(x for x in [1, 2, 3, 4, 5] if x % 2 == 0)) # 6 (2+4) print(''.join(c for c in "Hello" if c.islower())) # "ello"
제너레이터는 한 번 소비하면 빈 상태가 됩니다. 재사용하려면 리스트 컴프리헨션을 쓰세요.
기본 구조와 init 생성자
__init__은 인스턴스 생성 시 자동 호출되는 생성자입니다. 첫 매개변수는 반드시 self(인스턴스 자신, Python이 자동 전달).
class Node:
def __init__(self, v):
self.v = v # 인스턴스 속성
self.c = [] # 자식 목록
n = Node(10) # __init__(self, 10) 자동 호출
print(n.v) # 10
print(n.c) # []
Node(10) 호출 시 self는 자동으로 넘어가고 사용자는 v만 전달합니다.
클래스 속성 vs 인스턴스 속성
| 구분 | 정의 위치 | 공유 여부 |
|---|---|---|
| 클래스 속성 | 클래스 블록 (메서드 밖) | 모든 인스턴스가 공유 |
| 인스턴스 속성 | __init__ 안 (self.속성) | 인스턴스마다 독립 |
class Gamja:
li = [1, 2, 3] # 클래스 속성 (공유)
print(Gamja().li) # [1, 2, 3]
print(Gamja.li) # [1, 2, 3] (클래스명으로도 접근)
함정: 클래스 속성으로 리스트를 두면 모든 인스턴스가 같은 리스트를 공유합니다. 독립이 필요하면 __init__ 안에서 self.c = []로 정의합니다.
인스턴스 메서드 (첫 인자 self)
class Bag:
def __init__(self):
self.items = []
def add(self, x):
self.items.append(x) # self로 속성 조작
def total(self):
return sum(self.items)
b = Bag()
b.add(10); b.add(20); b.add(30) # add(b, 10) 로 전달됨
print(b.items) # [10, 20, 30]
print(b.total()) # 60
self를 빼먹으면 takes 1 positional argument but 2 were given 오류가 납니다.
상속과 super()
class Shape:
def __init__(self, name):
self.name = name
def area(self):
return 0
class Circle(Shape): # Shape 상속
def __init__(self, r):
super().__init__("원") # 부모 __init__ 호출
self.r = r
def area(self): # 오버라이딩
return 3.14 * self.r * self.r
c = Circle(5)
print(c.name) # 원 (부모가 넣어 준 속성)
print(c.area()) # 78.5 (자식이 덮어쓴 메서드)
자식이 __init__을 새로 정의하면 부모 __init__은 자동 호출되지 않습니다. super().__init__(...)을 빠뜨리면 부모가 세팅할 속성이 비어 AttributeError가 납니다.
재귀 함수 = 자기 자신을 호출하는 함수. 항상 기저 조건(멈추는 지점) + 재귀 호출로 이뤄집니다. 기저 조건이 없으면 RecursionError.
def f(n):
if n == 1: # 기저 조건
return 1
return n * f(n - 1) # 재귀 호출
팩토리얼 추적 (한 번 호출)
def f(n):
if n == 1:
return 1
return n * f(n - 1)
print(f(5)) # 120
# f(5)=5*f(4), f(4)=4*f(3), ... f(1)=1
# 거꾸로: 1 → 2 → 6 → 24 → 120
피보나치 (두 번 호출 = 다중 재귀)
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(5)) # 5
# fib: 0,1,1,2,3,5 ...
중첩 리스트 평탄화
def flatten(x):
if not isinstance(x, list): # 기저: 리스트 아니면 [x]
return [x]
result = []
for item in x:
result += flatten(item) # 재귀 결과 이어 붙이기
return result
print(flatten([1, [2, [3, 4]], 5])) # [1, 2, 3, 4, 5]
print(sum(flatten([1, [2, [3, 4]], 5]))) # 15
다중 재귀 추적 전략 (핵심)
호출을 기저 조건까지 펼친 뒤(왼쪽 먼저), 가장 작은 값부터 거꾸로 계산합니다. 기저 조건이 1이 아니라 n을 반환하는 경우 주의(예: n <= 1: return n이면 n=-1일 때 -1 반환).
def gamja(n):
if n <= 1:
return n
return gamja(n - 1) + gamja(n - 3)
print(gamja(7)) # 2
2차원 리스트에서 자주 틀리는 함정입니다.
| 복사 방법 | 바깥 리스트 | 안쪽 요소 |
|---|---|---|
b = m | 같은 객체 (복사 아님) | 같은 객체 |
b = m[:] | 새 객체 | 같은 객체 (공유) |
b = list(m) | 새 객체 | 같은 객체 (공유) |
b = copy.deepcopy(m) | 새 객체 | 새 객체 |
얕은 복사 — 바깥만 복사, 안쪽은 공유
m = [[1], [2], [3]] b = m[:] # 얕은 복사 print(b == m) # True (값이 같음) print(b is m) # False (다른 바깥 리스트) print(b[0] is m[0]) # True (안쪽은 같은 객체)
append vs += 함정
m = [[1], [2]] b = m[:] b.append([99]) # 바깥에 추가 → m 영향 없음 print(m) # [[1], [2]] m = [[1], [2]] b = m[:] b[0] += [99] # 안쪽 리스트 제자리 수정 → m도 바뀜 print(b) # [[1, 99], [2]] print(m) # [[1, 99], [2]] (함께 바뀜!)
깊은 복사 — copy.deepcopy()
import copy m = [[1], [2], [3]] b = copy.deepcopy(m) b[0] += [99] print(b) # [[1, 99], [2], [3]] print(m) # [[1], [2], [3]] (영향 없음)
다중 할당 / 튜플 언패킹
= 왼쪽 변수 개수와 오른쪽 값 개수가 같아야 합니다(다르면 ValueError).
a, b = 1, 2 x, y, z = 10, 20, 30 point = (10, 20) x, y = point # x=10, y=20 data = [100, 200, 300] a, b, c = data # 리스트도 언패킹
swap — 임시 변수 없이 한 줄 교환
버블 정렬 등에서 자주 출제됩니다.
a, b = 1, 2 a, b = b, a # 오른쪽 (2, 1) 튜플이 왼쪽에 풀림 print(a, b) # 2 1
for 루프 / enumerate / dict.items() 언패킹
for num, ch in [(1, "a"), (2, "b")]: print(num, ch) for i, fruit in enumerate(["사과", "바나나"]): print(i, fruit) for key, value in {"a": 1, "b": 2}.items(): print(key, value)
함수 반환값 언패킹 (divmod 등)
def min_max(nums):
return min(nums), max(nums) # 콤마 반환 → 튜플
small, large = min_max([3, 1, 4, 1, 5])
print(small, large) # 1 5
q, r = divmod(10, 3) # (몫, 나머지)
print(q, r) # 3 1
a, b = input().split() # "10 20" 입력 → a="10", b="20" (문자열)
a, b = int(a), int(b)
print(a + b) # 30
별표(*) 확장 언패킹 — 나머지를 리스트로
head, *tail = [1, 2, 3, 4, 5] print(head, tail) # 1 [2, 3, 4, 5] *rest, last = [1, 2, 3, 4, 5] print(rest, last) # [1, 2, 3, 4] 5 first, *middle, last = [1, 2, 3, 4, 5] print(first, middle, last) # 1 [2, 3, 4] 5
*는 한 줄에 하나만 쓸 수 있습니다(두 개면 SyntaxError). * 변수는 항상 리스트(원소 0개면 []).
명시적 줄 연속: 백슬래시
줄 끝에 백슬래시를 쓰면 다음 줄과 한 줄로 이어집니다. 백슬래시 뒤에 공백 한 칸이라도 있으면 SyntaxError.
total = 1 + 2 + 3 + \ 4 + 5 + 6 print(total) # 21
암시적 줄 연속: 괄호 안
(), [], {} 안에서는 백슬래시 없이 자유롭게 줄을 바꿀 수 있습니다.
total = (1 + 2 + 3 + 4 + 5 + 6) nums = [1, 2, 3, 4, 5, 6]
실무는 암시적 방식을 권장하지만 시험엔 백슬래시도 나오므로 둘 다 알아두세요.
실기는 "실행 결과 쓰기"가 핵심입니다. 아래는 답을 틀리기 쉬운 포인트 모음입니다.
print의 자동 공백: print('a=', 5)의 결과는 a= 5(등호와 5 사이 공백 한 칸). a=5로 쓰면 오답.end 값 한 글자도 확인: end=""는 줄바꿈 제거, end=" "는 공백, end=","는 쉼표. 정답에 공백/줄바꿈이 있는지 반드시 확인.100 vs 100.0: type(100) == type(100.0)은 False. 정수와 실수는 다른 자료형."100"은 str: 따옴표로 감싸면 숫자처럼 보여도 문자열./는 항상 실수: 10 / 3은 3.333..., 정수 몫은 10 // 3.s[1:4]는 인덱스 1·2·3만, range(5)는 0~4까지(5 제외).(100)은 int, (100,)이 tuple.{}는 딕셔너리: 빈 집합은 set().d.get(key)는 기본값 None: 빈도 카운팅은 d.get(ch, 0)처럼 0을 줘야 함(None + 1은 TypeError).map()/range()/제너레이터는 그대로 print하면 객체 주소/범위 정보가 나옴. 값 목록은 list()로 감싸야 함.all([])는 True, any([])는 False (직관과 반대).sorted()는 새 리스트 반환, list.sort()는 원본 수정 후 None 반환.m[:] 후 안쪽 리스트에 += 하면 원본도 함께 바뀜.+ 필요).