문자열 출력
print("안녕하세요, 파이썬!")
상세 설명
print() 함수는 괄호 안의 값을 화면에 출력한다. 문자열은 작은따옴표나 큰따옴표로 감싼다.
예상 결과
안녕하세요, 파이썬!
활용·주의점
한글은 UTF-8로 저장하면 IDLE에서도 정상적으로 표시된다.
Python 3.13 · IDLE 실행 기준 · 기초 문법부터 자료구조, 파일 처리, 객체지향, 알고리즘, 데이터베이스, 비동기, 테스트까지 단계별 학습
print("안녕하세요, 파이썬!")
print() 함수는 괄호 안의 값을 화면에 출력한다. 문자열은 작은따옴표나 큰따옴표로 감싼다.
안녕하세요, 파이썬!
한글은 UTF-8로 저장하면 IDLE에서도 정상적으로 표시된다.
name = "인수샘"
age = 30
print(name, age)
print()에 여러 값을 쉼표로 넣으면 값 사이에 공백이 자동으로 들어간다.
인수샘 30
sep 인자를 사용하면 구분 문자를 바꿀 수 있다.
score = 95
print(score)
변수는 값을 저장하는 이름이다. score에는 정수 95가 저장된다.
95
변수명은 숫자로 시작할 수 없다.
height = 1.72
weight = 68.5
bmi = weight / (height ** 2)
print(round(bmi, 2))
실수 자료형을 이용해 BMI를 계산하고 round()로 소수 둘째 자리까지 표시한다.
23.15
**는 거듭제곱 연산자이다.
name = input("이름: ")
print("반갑습니다,", name)
input()은 사용자가 입력한 내용을 문자열로 반환한다.
입력값에 따라 달라짐
숫자를 입력받아 계산하려면 int()나 float()로 변환해야 한다.
a = int(input("첫 번째 수: "))
b = int(input("두 번째 수: "))
print("합:", a + b)
input()의 결과를 int()로 변환한 뒤 두 정수를 더한다.
입력값에 따라 달라짐
숫자가 아닌 값을 입력하면 ValueError가 발생할 수 있다.
a = 10
b = 3
print(a + b)
print(a - b)
print(a * b)
print(a / b)
덧셈, 뺄셈, 곱셈, 나눗셈의 기본 연산을 확인한다.
13 7 30 3.3333333333333335
/ 연산 결과는 항상 실수이다.
n = 17
print("몫:", n // 5)
print("나머지:", n % 5)
//는 몫, %는 나머지를 구한다.
몫: 3 나머지: 2
짝수 판별에는 n % 2를 자주 사용한다.
first = "Data"
second = "Science"
print(first + " " + second)
+ 연산자는 문자열을 연결한다.
Data Science
숫자와 문자열을 바로 더할 수는 없다.
word = "파이썬 "
print(word * 3)
* 연산자를 사용하면 문자열을 여러 번 반복할 수 있다.
파이썬 파이썬 파이썬
반복 횟수는 정수여야 한다.
text = "인공지능"
print(len(text))
len()은 문자열에 포함된 문자의 개수를 반환한다.
4
한글도 글자 단위로 계산된다.
text = "Python"
print(text[0])
print(text[-1])
인덱스 0은 첫 글자, -1은 마지막 글자를 의미한다.
P n
존재하지 않는 위치를 사용하면 IndexError가 발생한다.
text = "Information"
print(text[0:4])
print(text[4:])
슬라이싱은 시작 위치부터 끝 위치 직전까지 문자열을 잘라낸다.
Info rmation
끝 위치는 포함되지 않는다.
text = "Python Data"
print(text.upper())
print(text.lower())
upper()는 대문자, lower()는 소문자로 바꾼다.
PYTHON DATA python data
원본 문자열은 바뀌지 않고 새 문자열이 생성된다.
text = " 데이터 분석 "
print(text.strip())
strip()은 문자열 양끝의 공백을 제거한다.
데이터 분석
lstrip()과 rstrip()은 한쪽 공백만 제거한다.
text = "나는 자바를 배운다."
print(text.replace("자바", "파이썬"))
replace()는 지정한 문자열을 다른 문자열로 바꾼다.
나는 파이썬을 배운다.
원본 문자열 자체는 변경되지 않는다.
data = "서울,부산,광주"
cities = data.split(",")
print(cities)
split()은 구분자를 기준으로 문자열을 리스트로 나눈다.
['서울', '부산', '광주']
구분자를 생략하면 공백을 기준으로 나눈다.
name = "민수"
score = 88
print(f"{name}의 점수는 {score}점입니다.")
f-string은 문자열 안에 변수 값을 간단히 삽입하는 방법이다.
민수의 점수는 88점입니다.
중괄호 안에는 계산식도 넣을 수 있다.
n = 7
if n > 0:
print("양수")
if 문은 조건이 참일 때 들여쓰기된 코드를 실행한다.
양수
파이썬은 들여쓰기가 문법의 일부이다.
n = 12
if n % 2 == 0:
print("짝수")
else:
print("홀수")
나머지가 0이면 짝수, 아니면 홀수로 판별한다.
짝수
비교 연산자는 ==이고 대입 연산자는 =이다.
score = 87
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
print(grade)
elif를 이용하면 여러 조건을 순서대로 검사할 수 있다.
B
조건은 위에서 아래로 검사하므로 범위 순서가 중요하다.
age = 17
if 14 <= age <= 19:
print("청소년")
파이썬은 두 비교식을 연결하여 범위를 간단히 표현할 수 있다.
청소년
다른 언어보다 직관적인 비교 문법이다.
temperature = 25
rain = False
if temperature >= 20 and not rain:
print("야외 활동 가능")
and는 두 조건이 모두 참일 때, not은 참과 거짓을 반대로 바꾼다.
야외 활동 가능
or는 둘 중 하나만 참이어도 전체가 참이다.
for i in range(1, 6):
print(i)
range(1, 6)은 1부터 5까지의 정수를 만든다.
1 2 3 4 5
range의 끝값은 포함되지 않는다.
total = 0
for i in range(10):
total += i
print(total)
반복할 때마다 i를 total에 누적한다.
45
total += i는 total = total + i와 같다.
for i in range(1, 10):
print(f"2 × {i} = {2*i}")
반복문과 f-string을 결합해 구구단을 출력한다.
2 × 1 = 2 ... 2 × 9 = 18
곱셈 기호는 출력용 문자이고 실제 연산자는 *이다.
for i in range(5, 0, -1):
print(i)
range의 세 번째 값 -1은 1씩 감소한다는 뜻이다.
5 4 3 2 1
증가폭이나 감소폭은 0이 될 수 없다.
count = 1
while count <= 5:
print(count)
count += 1
while은 조건이 참인 동안 코드를 반복한다.
1 2 3 4 5
조건이 계속 참이면 무한 반복이 발생하므로 변수 갱신이 필요하다.
for i in range(1, 11):
if i == 6:
break
print(i)
break는 반복문을 즉시 종료한다.
1 2 3 4 5
중첩 반복문에서는 가장 가까운 반복문만 종료한다.
for i in range(1, 6):
if i == 3:
continue
print(i)
continue는 현재 반복만 건너뛰고 다음 반복으로 이동한다.
1 2 4 5
반복문 전체를 종료하지는 않는다.
fruits = ["사과", "배", "포도"]
print(fruits)
리스트는 여러 값을 순서대로 저장하는 자료형이다.
['사과', '배', '포도']
리스트는 값을 추가·삭제·수정할 수 있다.
numbers = [10, 20, 30, 40]
print(numbers[1])
print(numbers[-1])
인덱스로 원하는 리스트 항목에 접근한다.
20 40
리스트 인덱스도 0부터 시작한다.
colors = ["빨강", "파랑", "초록"]
colors[1] = "노랑"
print(colors)
리스트는 인덱스를 이용해 특정 값을 바꿀 수 있다.
['빨강', '노랑', '초록']
문자열과 달리 리스트는 변경 가능한 자료형이다.
scores = [80, 90]
scores.append(100)
print(scores)
append()는 리스트 끝에 값을 하나 추가한다.
[80, 90, 100]
여러 값을 한꺼번에 추가하려면 extend()를 사용한다.
animals = ["고양이", "개", "토끼"]
animals.remove("개")
print(animals)
remove()는 지정한 값을 찾아 첫 번째 항목을 삭제한다.
['고양이', '토끼']
없는 값을 삭제하면 ValueError가 발생한다.
numbers = [5, 2, 8, 1]
numbers.sort()
print(numbers)
sort()는 리스트 자체를 오름차순으로 정렬한다.
[1, 2, 5, 8]
sorted()는 정렬된 새 리스트를 반환한다.
numbers = [12, 7, 25, 3]
print(max(numbers))
print(min(numbers))
max()와 min()은 리스트에서 가장 큰 값과 작은 값을 찾는다.
25 3
문자열 리스트에서도 사전식 순서로 비교할 수 있다.
scores = [80, 90, 70, 100]
average = sum(scores) / len(scores)
print(average)
sum()으로 합계를 구하고 항목 수로 나누어 평균을 계산한다.
85.0
빈 리스트는 len()이 0이므로 나눗셈 오류에 주의한다.
point = (3, 5)
print(point[0], point[1])
튜플은 순서가 있지만 생성 후 값을 변경할 수 없는 자료형이다.
3 5
좌표처럼 바뀌지 않아야 하는 값에 적합하다.
numbers = [1, 2, 2, 3, 3, 3]
unique = set(numbers)
print(unique)
set()은 중복을 제거한 집합을 만든다.
{1, 2, 3}집합은 출력 순서가 고정되지 않을 수 있다.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a & b)
& 연산자는 두 집합에 공통으로 포함된 값을 구한다.
{3, 4}|는 합집합, -는 차집합이다.
student = {"이름": "지훈", "점수": 92}
print(student["이름"])
딕셔너리는 키와 값의 쌍으로 데이터를 저장한다.
지훈
키는 중복될 수 없다.
student = {"이름": "지훈", "점수": 92}
student["점수"] = 95
print(student)
키를 지정해 기존 값을 수정할 수 있다.
{'이름': '지훈', '점수': 95}없는 키를 사용하면 새 항목이 추가된다.
scores = {"민수": 90, "영희": 85}
for name, score in scores.items():
print(name, score)
items()는 키와 값을 함께 반복할 수 있게 한다.
민수 90 영희 85
keys()와 values()는 각각 키와 값만 가져온다.
text = "123"
number = int(text)
print(number + 10)
int()는 숫자 형태의 문자열을 정수로 바꾼다.
133
소수 문자열은 float()로 변환해야 한다.
age = 18
message = "나이: " + str(age)
print(message)
str()은 숫자를 문자열로 변환한다.
나이: 18
f-string을 사용하면 형 변환을 직접 쓰지 않아도 된다.
value = 3.141592
print(round(value, 3))
round()의 두 번째 인자는 표시할 소수 자릿수이다.
3.142
부동소수점 표현 특성 때문에 예상과 다른 반올림이 나타날 수 있다.
print(abs(-15))
print(abs(3.5))
abs()는 숫자의 절댓값을 반환한다.
15 3.5
거리 계산 등에서 자주 사용한다.
x = 10
y = "10"
print(type(x))
print(type(y))
type()은 값의 자료형을 확인한다.
<class 'int'> <class 'str'>
겉보기에 같은 10이라도 정수와 문자열은 다르다.
# 원의 넓이를 계산한다.
radius = 5
area = 3.14 * radius ** 2
print(area)
# 뒤의 내용은 실행되지 않고 코드 설명으로 사용된다.
78.5
좋은 주석은 코드가 왜 필요한지 설명한다.
def greet(name):
return f"{name}님, 반갑습니다."
print(greet("서연"))
함수는 반복되는 작업을 하나의 이름으로 묶는다. 매개변수 name을 받아 문자열을 반환한다.
서연님, 반갑습니다.
return은 값을 함수 밖으로 전달한다.
def larger(a, b):
if a > b:
return a
return b
print(larger(10, 7))
조건문과 return을 결합해 두 값 중 큰 값을 반환한다.
10
return이 실행되면 함수는 즉시 종료된다.
def power(base, exponent=2):
return base ** exponent
print(power(5))
print(power(2, 3))
exponent에 기본값 2를 지정하여 인자를 생략할 수 있다.
25 8
기본값이 있는 매개변수는 뒤쪽에 배치한다.
def profile(name, age, city):
print(name, age, city)
profile(city="전주", name="민수", age=17)
키워드 인자를 사용하면 호출 순서와 관계없이 값을 전달할 수 있다.
민수 17 전주
매개변수 이름을 정확히 작성해야 한다.
def total(*numbers):
return sum(numbers)
print(total(1, 2, 3, 4))
*numbers는 전달된 여러 인자를 튜플로 받는다.
10
인자 개수가 정해지지 않은 함수에 적합하다.
message = "전역"
def show():
message = "지역"
print(message)
show()
print(message)
함수 안에서 만든 지역 변수는 함수 밖의 전역 변수와 별개이다.
지역 전역
전역 변수 변경은 최소화하는 것이 좋다.
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
print(factorial(5))
재귀 함수는 자기 자신을 호출한다. 종료 조건이 반드시 필요하다.
120
큰 입력에서는 반복문이 더 안전할 수 있다.
square = lambda x: x ** 2
print(square(6))
lambda는 짧은 함수를 한 줄로 정의한다.
36
복잡한 로직은 일반 def 함수가 더 읽기 쉽다.
squares = [x ** 2 for x in range(1, 6)]
print(squares)
리스트 컴프리헨션은 반복문으로 새 리스트를 간결하게 만든다.
[1, 4, 9, 16, 25]
너무 복잡하게 작성하면 가독성이 떨어진다.
evens = [x for x in range(1, 11) if x % 2 == 0]
print(evens)
if 조건을 추가하여 짝수만 선택한다.
[2, 4, 6, 8, 10]
필터링과 변환을 동시에 할 수 있다.
matrix = [[1, 2], [3, 4], [5, 6]]
print(matrix[1][0])
중첩 리스트는 표나 행렬처럼 2차원 데이터를 표현한다.
3
첫 번째 인덱스는 행, 두 번째 인덱스는 열이다.
fruits = ["사과", "배", "포도"]
for index, fruit in enumerate(fruits, start=1):
print(index, fruit)
enumerate()는 항목과 순번을 함께 제공한다.
1 사과 2 배 3 포도
start 값을 생략하면 0부터 시작한다.
names = ["민수", "영희", "철수"]
scores = [90, 85, 88]
for name, score in zip(names, scores):
print(name, score)
zip()은 여러 반복 가능한 객체의 같은 위치 값을 묶는다.
민수 90 영희 85 철수 88
길이가 다르면 짧은 쪽에 맞춰 종료된다.
words = ["python", "ai", "data"]
print(sorted(words, key=len))
key=len을 지정하면 문자열 길이를 기준으로 정렬한다.
['ai', 'data', 'python']
reverse=True를 추가하면 내림차순이다.
students = [
{"name": "민수", "score": 88},
{"name": "영희", "score": 95}
]
result = sorted(students, key=lambda x: x["score"], reverse=True)
print(result)
람다식을 키 함수로 사용해 점수 기준 내림차순 정렬을 수행한다.
[{'name': '영희', 'score': 95}, {'name': '민수', 'score': 88}]복합 데이터 정렬에 자주 활용된다.
words = ["파이썬", "데이터", "인공지능"]
print(" · ".join(words))
join()은 리스트의 문자열을 지정한 구분자로 연결한다.
파이썬 · 데이터 · 인공지능
리스트 항목은 모두 문자열이어야 한다.
text = "파이썬 데이터 분석"
print("데이터" in text)
print("자바" in text)
in 연산자는 문자열이나 리스트에 값이 포함되어 있는지 확인한다.
True False
대소문자를 구분한다.
text = "banana"
counts = {}
for ch in text:
counts[ch] = counts.get(ch, 0) + 1
print(counts)
get()으로 기존 값을 안전하게 읽어 문자별 등장 횟수를 누적한다.
{'b': 1, 'a': 3, 'n': 2}collections.Counter를 사용하면 더 간단히 구현할 수 있다.
squares = {x: x ** 2 for x in range(1, 6)}
print(squares)
딕셔너리 컴프리헨션은 키와 값을 동시에 계산하여 만든다.
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}키는 중복되면 마지막 값으로 덮어쓴다.
a = {"국어": 90, "영어": 85}
b = {"수학": 95}
merged = a | b
print(merged)
| 연산자는 파이썬 3.9 이상에서 딕셔너리를 병합한다.
{'국어': 90, '영어': 85, '수학': 95}같은 키가 있으면 오른쪽 딕셔너리 값이 사용된다.
a = {1, 2, 3}
b = {3, 4, 5}
print(a ^ b)
^ 연산자는 한쪽 집합에만 존재하는 값을 구한다.
{1, 2, 4, 5}공통 원소는 결과에서 제외된다.
try:
print(10 / 0)
except ZeroDivisionError:
print("0으로 나눌 수 없습니다.")
try에서 오류가 발생하면 해당 except 블록을 실행한다.
0으로 나눌 수 없습니다.
예상 가능한 오류만 구체적으로 처리하는 것이 좋다.
try:
age = int(input("나이: "))
print(age)
except ValueError:
print("정수를 입력하세요.")
정수 변환에 실패할 때 ValueError를 처리한다.
입력값에 따라 달라짐
사용자 입력 프로그램에 예외 처리는 매우 중요하다.
try:
value = int("100")
print(value)
finally:
print("작업 종료")
finally는 오류 발생 여부와 관계없이 항상 실행된다.
100 작업 종료
파일 닫기나 자원 해제에 적합하다.
with open("memo.txt", "w", encoding="utf-8") as file:
file.write("파이썬 파일 저장")
print("저장 완료")
with 문을 사용하면 파일 작업 후 자동으로 닫힌다.
저장 완료
w 모드는 기존 내용을 덮어쓴다.
with open("memo.txt", "r", encoding="utf-8") as file:
content = file.read()
print(content)
read()는 파일 전체 내용을 문자열로 읽는다.
파이썬 파일 저장
파일이 없으면 FileNotFoundError가 발생한다.
with open("memo.txt", "a", encoding="utf-8") as file:
file.write("\n새로운 줄")
a 모드는 기존 내용을 유지하면서 파일 끝에 추가한다.
파일 내용에 새로운 줄 추가
줄바꿈 문자는 \n이다.
with open("memo.txt", "r", encoding="utf-8") as file:
for line in file:
print(line.strip())
파일 객체를 반복하면 한 줄씩 메모리를 절약하며 읽을 수 있다.
파일 내용에 따라 달라짐
strip()으로 줄끝 개행 문자를 제거한다.
import math
print(math.sqrt(81))
print(round(math.pi, 4))
표준 라이브러리 math는 제곱근과 원주율 등의 수학 기능을 제공한다.
9.0 3.1416
외부 설치 없이 사용할 수 있다.
import random
number = random.randint(1, 6)
print(number)
randint(1, 6)은 1부터 6까지의 정수 중 하나를 무작위로 반환한다.
실행할 때마다 달라짐
게임이나 모의실험에 활용할 수 있다.
import random
menu = ["김밥", "라면", "비빔밥"]
print(random.choice(menu))
choice()는 리스트에서 항목 하나를 무작위로 선택한다.
실행할 때마다 달라짐
빈 리스트에서는 오류가 발생한다.
from datetime import datetime
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M"))
datetime.now()로 현재 시각을 얻고 strftime()으로 형식을 지정한다.
실행 시각에 따라 달라짐
형식 코드에서 %Y는 연도, %m은 월이다.
from datetime import date
start = date(2026, 3, 1)
end = date(2026, 3, 15)
print((end - start).days)
date 객체끼리 빼면 timedelta가 생성되고 days로 날짜 차이를 구한다.
14
종료일과 시작일의 차이이므로 시작일 자체는 포함되지 않는다.
class Student:
def __init__(self, name):
self.name = name
student = Student("민수")
print(student.name)
클래스는 데이터와 기능을 하나로 묶는 설계도이다.
민수
__init__은 객체 생성 시 실행되는 초기화 메서드이다.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
r = Rectangle(4, 5)
print(r.area())
인스턴스 메서드는 객체의 속성을 이용해 기능을 수행한다.
20
메서드의 첫 번째 매개변수는 일반적으로 self이다.
class Book:
def __init__(self, title):
self.title = title
def __str__(self):
return f"책: {self.title}"
print(Book("데미안"))
__str__은 객체를 print()로 출력할 때 보여 줄 문자열을 정의한다.
책: 데미안
사용자 친화적인 객체 표현에 유용하다.
class Animal:
def sound(self):
return "소리"
class Dog(Animal):
def sound(self):
return "멍멍"
print(Dog().sound())
상속을 사용하면 기존 클래스의 기능을 이어받고 필요한 부분을 재정의할 수 있다.
멍멍
메서드 재정의를 오버라이딩이라고 한다.
class Counter:
count = 0
def __init__(self):
Counter.count += 1
Counter()
Counter()
print(Counter.count)
클래스 변수는 모든 인스턴스가 공유한다.
2
객체별 값은 인스턴스 변수로 저장한다.
numbers = [4, 7, 2, 9, 5]
target = 9
position = -1
for i, value in enumerate(numbers):
if value == target:
position = i
break
print(position)
앞에서부터 하나씩 비교하여 목표값의 위치를 찾는다.
3
시간 복잡도는 최악의 경우 O(n)이다.
numbers = [5, 3, 8, 1]
for end in range(len(numbers)-1, 0, -1):
for i in range(end):
if numbers[i] > numbers[i+1]:
numbers[i], numbers[i+1] = numbers[i+1], numbers[i]
print(numbers)
인접한 두 값을 비교해 큰 값을 뒤로 보내는 정렬이다.
[1, 3, 5, 8]
학습용으로 좋지만 큰 데이터에는 느리다.
n = 29
is_prime = n >= 2
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
is_prime = False
break
print(is_prime)
제곱근까지만 약수를 검사해 소수 여부를 판별한다.
True
1은 소수가 아니다.
a, b = 48, 18
while b:
a, b = b, a % b
print(a)
유클리드 호제법으로 최대공약수를 빠르게 구한다.
6
math.gcd()를 사용하면 더 간단하다.
a, b = 0, 1
for _ in range(10):
print(a, end=" ")
a, b = b, a + b
두 변수를 동시에 갱신하여 피보나치 수열을 만든다.
0 1 1 2 3 5 8 13 21 34
_는 값을 사용하지 않는 반복 변수에 자주 쓴다.
stack = []
stack.append("A")
stack.append("B")
print(stack.pop())
print(stack)
리스트의 append()와 pop()을 이용해 후입선출 스택을 구현한다.
B ['A']
괄호 검사나 되돌리기 기능에 활용된다.
from collections import deque
queue = deque(["A", "B"])
queue.append("C")
print(queue.popleft())
print(queue)
deque는 앞쪽 삭제가 빠른 큐 자료구조이다.
A deque(['B', 'C'])
일반 리스트의 pop(0)은 데이터가 많을수록 느리다.
from collections import Counter
words = ["사과", "배", "사과", "포도", "사과"]
print(Counter(words))
Counter는 항목별 등장 횟수를 자동으로 계산한다.
Counter({'사과': 3, '배': 1, '포도': 1})most_common()으로 빈도가 높은 순서를 확인할 수 있다.
from collections import defaultdict
groups = defaultdict(list)
for name, class_no in [("민수", 1), ("영희", 2), ("철수", 1)]:
groups[class_no].append(name)
print(dict(groups))
defaultdict는 없는 키에 기본값을 자동으로 만든다.
{1: ['민수', '철수'], 2: ['영희']}그룹별 데이터 수집에 편리하다.
from itertools import product
for item in product(["A", "B"], [1, 2]):
print(item)
product()는 두 집합의 모든 조합을 만든다.
('A', 1)
('A', 2)
('B', 1)
('B', 2)중첩 반복문을 간결하게 표현할 수 있다.
from itertools import combinations
students = ["민수", "영희", "철수"]
print(list(combinations(students, 2)))
combinations()는 순서를 고려하지 않는 조합을 만든다.
[('민수', '영희'), ('민수', '철수'), ('영희', '철수')]순서를 고려하려면 permutations()를 사용한다.
import csv
rows = [["이름", "점수"], ["민수", 90], ["영희", 95]]
with open("scores.csv", "w", newline="", encoding="utf-8-sig") as file:
writer = csv.writer(file)
writer.writerows(rows)
print("CSV 저장 완료")
csv 모듈로 표 형태 데이터를 저장한다. utf-8-sig는 엑셀에서 한글 깨짐을 줄인다.
CSV 저장 완료
Windows에서는 newline=''를 지정하는 것이 좋다.
import csv
with open("scores.csv", "r", encoding="utf-8-sig") as file:
reader = csv.DictReader(file)
for row in reader:
print(row["이름"], row["점수"])
DictReader는 첫 행을 열 이름으로 사용하여 각 행을 딕셔너리로 반환한다.
민수 90 영희 95
숫자도 문자열로 읽히므로 계산 전 형 변환이 필요하다.
import json
data = {"name": "민수", "scores": [90, 88, 95]}
with open("student.json", "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=2)
json.dump()는 파이썬 객체를 JSON 파일로 저장한다.
student.json 생성
ensure_ascii=False로 한글을 그대로 저장한다.
import json
with open("student.json", "r", encoding="utf-8") as file:
data = json.load(file)
print(data["scores"])
json.load()는 JSON 파일을 파이썬 딕셔너리나 리스트로 변환한다.
[90, 88, 95]
파일 구조와 키 이름을 확인해야 한다.
from pathlib import Path
folder = Path("results")
folder.mkdir(exist_ok=True)
print(folder.exists())
pathlib.Path는 운영체제에 독립적인 파일 경로 처리를 제공한다.
True
parents=True를 지정하면 상위 폴더도 함께 만든다.
from pathlib import Path
for path in Path(".").glob("*.py"):
print(path.name)
glob()은 특정 패턴과 일치하는 파일을 찾는다.
현재 폴더의 .py 파일 목록
**/*.py 패턴은 하위 폴더까지 탐색한다.
from pathlib import Path
path = Path("memo.txt")
if path.exists():
print(path.stat().st_size)
stat().st_size는 파일 크기를 바이트 단위로 반환한다.
파일에 따라 달라짐
파일 존재 여부를 먼저 확인하면 안전하다.
import re
text = "온도는 23도, 습도는 58퍼센트입니다."
numbers = re.findall(r"\d+", text)
print(numbers)
정규표현식 \d+는 연속된 숫자를 찾는다.
['23', '58']
결과는 문자열이므로 계산하려면 int()로 변환한다.
import re
email = "student@example.com"
pattern = r"^[\w.-]+@[\w.-]+\.[A-Za-z]{2,}$"
print(bool(re.match(pattern, email)))
정규표현식으로 기본적인 이메일 형식을 검사한다.
True
실제 이메일 표준 전체를 완벽히 검증하는 패턴은 훨씬 복잡하다.
import hashlib
text = "mypassword"
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
print(digest)
SHA-256은 입력을 고정 길이 해시 문자열로 변환한다.
64자리 16진수 문자열
실제 비밀번호 저장에는 salt와 전용 비밀번호 해시 알고리즘이 필요하다.
import secrets
token = secrets.token_urlsafe(16)
print(token)
secrets는 보안에 적합한 난수를 생성한다.
실행할 때마다 달라짐
보안 토큰 생성에는 random보다 secrets가 적합하다.
import html
text = "<script>alert('XSS')</script>"
print(html.escape(text))
html.escape()는 HTML 특수문자를 안전한 문자로 변환한다.
<script>alert('XSS')</script>
웹 출력에서 사용자 입력을 그대로 삽입하면 위험할 수 있다.
import statistics
data = [10, 20, 20, 30, 100]
print(statistics.mean(data))
print(statistics.median(data))
print(statistics.mode(data))
statistics 모듈은 기초 통계량을 계산한다.
36 20 20
이상치가 있으면 평균과 중앙값이 크게 달라질 수 있다.
import statistics
data = [70, 75, 80, 85, 90]
print(round(statistics.pstdev(data), 2))
pstdev()는 모집단 표준편차를 계산한다.
7.07
표본 표준편차는 stdev()를 사용한다.
import statistics
x = [1, 2, 3, 4, 5]
y = [2, 4, 5, 4, 5]
mx, my = statistics.mean(x), statistics.mean(y)
num = sum((a-mx)*(b-my) for a, b in zip(x, y))
den = (sum((a-mx)**2 for a in x) * sum((b-my)**2 for b in y)) ** 0.5
print(round(num / den, 3))
피어슨 상관계수 공식을 직접 구현한다.
0.775
상관관계가 인과관계를 의미하지는 않는다.
import random
counts = {i: 0 for i in range(1, 7)}
for _ in range(1000):
counts[random.randint(1, 6)] += 1
print(counts)
무작위 실험을 반복해 각 눈의 빈도를 관찰한다.
각 값이 약 167회 전후
시행 횟수가 많을수록 이론 확률에 가까워진다.
import random
inside = 0
trials = 100000
for _ in range(trials):
x, y = random.random(), random.random()
if x*x + y*y <= 1:
inside += 1
print(4 * inside / trials)
정사각형 안에 무작위 점을 찍어 원 내부 비율로 원주율을 근사한다.
약 3.14
시행 횟수를 늘리면 대체로 정확도가 높아진다.
import random
answer = random.randint(1, 20)
while True:
guess = int(input("1~20: "))
if guess == answer:
print("정답")
break
print("더 큼" if guess < answer else "더 작음")
무작위 정답을 정하고 반복적으로 입력받아 힌트를 제공한다.
입력에 따라 달라짐
실전에서는 잘못된 입력에 대한 예외 처리도 추가한다.
import random
choices = ["가위", "바위", "보"]
computer = random.choice(choices)
user = "가위"
if user == computer:
result = "무승부"
elif (user, computer) in [("가위","보"),("바위","가위"),("보","바위")]:
result = "승리"
else:
result = "패배"
print(computer, result)
튜플 조합을 이용해 승리 조건을 간결하게 표현한다.
실행할 때마다 달라짐
입력값 검증을 추가하면 완성도가 높아진다.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
return True
return False
account = BankAccount("민수", 10000)
account.deposit(5000)
account.withdraw(3000)
print(account.balance)
입금과 출금 규칙을 메서드로 캡슐화한다.
12000
금액 검증 로직을 객체 내부에 두면 일관성이 높아진다.
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("절대영도보다 낮을 수 없습니다.")
self._celsius = value
print(Temperature(25).celsius)
property를 이용해 속성 접근은 간단하게 유지하면서 값 검증을 수행한다.
25
내부 저장 변수는 관례적으로 밑줄로 시작한다.
class Circle:
def area(self):
return 3.14 * 2 ** 2
class Square:
def area(self):
return 4 ** 2
shapes = [Circle(), Square()]
print([shape.area() for shape in shapes])
서로 다른 클래스가 같은 이름의 메서드를 제공하면 동일한 방식으로 처리할 수 있다.
[12.56, 16]
이를 다형성이라고 한다.
from dataclasses import dataclass
@dataclass
class Student:
name: str
score: int
student = Student("민수", 95)
print(student)
dataclass는 데이터 중심 클래스의 초기화와 문자열 표현을 자동 생성한다.
Student(name='민수', score=95)
파이썬 3.7 이상에서 사용할 수 있다.
class Person:
count = 0
def __init__(self, name):
self.name = name
Person.count += 1
@classmethod
def total(cls):
return cls.count
Person("민수")
Person("영희")
print(Person.total())
classmethod는 클래스 자체를 첫 인자 cls로 받아 클래스 상태를 다룬다.
2
인스턴스 없이도 호출할 수 있다.
class Calculator:
@staticmethod
def add(a, b):
return a + b
print(Calculator.add(3, 4))
staticmethod는 객체 상태와 무관한 보조 기능을 클래스 안에 묶는다.
7
self나 cls를 받지 않는다.
def announce(func):
def wrapper():
print("시작")
func()
print("끝")
return wrapper
@announce
def study():
print("파이썬 공부")
study()
데코레이터는 기존 함수의 코드를 바꾸지 않고 앞뒤 기능을 추가한다.
시작 파이썬 공부 끝
functools.wraps를 사용하면 원래 함수 정보가 유지된다.
def make_multiplier(n):
def multiply(x):
return x * n
return multiply
double = make_multiplier(2)
print(double(7))
클로저는 바깥 함수의 변수를 기억하는 내부 함수이다.
14
설정값을 가진 함수를 만들 때 유용하다.
def countdown(n):
while n > 0:
yield n
n -= 1
for value in countdown(5):
print(value)
yield는 값을 하나씩 생성하여 메모리를 절약한다.
5 4 3 2 1
모든 값을 한꺼번에 저장하지 않는다.
squares = (x ** 2 for x in range(1000000))
print(next(squares))
print(next(squares))
제너레이터 표현식은 필요한 순간에 값을 하나씩 계산한다.
0 1
대용량 데이터 처리에 적합하다.
numbers = [1, 2, 3, 4]
result = list(map(lambda x: x * 10, numbers))
print(result)
map()은 각 항목에 같은 함수를 적용한다.
[10, 20, 30, 40]
간단한 경우 리스트 컴프리헨션이 더 읽기 쉬울 수 있다.
numbers = [1, 2, 3, 4, 5, 6]
result = list(filter(lambda x: x % 2 == 0, numbers))
print(result)
filter()는 조건을 만족하는 항목만 남긴다.
[2, 4, 6]
조건 함수는 True 또는 False를 반환해야 한다.
from functools import reduce
numbers = [1, 2, 3, 4]
product = reduce(lambda a, b: a * b, numbers)
print(product)
reduce()는 여러 값을 차례로 결합하여 하나의 결과를 만든다.
24
합계는 sum()이 더 명확하다.
def binary_search(data, target):
left, right = 0, len(data) - 1
while left <= right:
mid = (left + right) // 2
if data[mid] == target:
return mid
if data[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
print(binary_search([1,3,5,7,9], 7))
정렬된 데이터에서 탐색 범위를 절반씩 줄인다.
3
시간 복잡도는 O(log n)이다.
data = [64, 25, 12, 22, 11]
for i in range(len(data)-1):
min_index = i
for j in range(i+1, len(data)):
if data[j] < data[min_index]:
min_index = j
data[i], data[min_index] = data[min_index], data[i]
print(data)
남은 구간에서 최솟값을 찾아 앞쪽에 배치한다.
[11, 12, 22, 25, 64]
비교 횟수는 많지만 교환 횟수는 적다.
data = [5, 2, 4, 6, 1, 3]
for i in range(1, len(data)):
key = data[i]
j = i - 1
while j >= 0 and data[j] > key:
data[j+1] = data[j]
j -= 1
data[j+1] = key
print(data)
현재 값을 앞쪽의 정렬된 구간에 알맞게 삽입한다.
[1, 2, 3, 4, 5, 6]
거의 정렬된 데이터에서 효율적이다.
def merge_sort(data):
if len(data) <= 1:
return data
mid = len(data) // 2
left = merge_sort(data[:mid])
right = merge_sort(data[mid:])
result = []
while left and right:
result.append(left.pop(0) if left[0] < right[0] else right.pop(0))
return result + left + right
print(merge_sort([5, 1, 4, 2, 8]))
데이터를 나누고 정렬된 부분을 합치는 분할 정복 방식이다.
[1, 2, 4, 5, 8]
실전에서는 pop(0) 대신 인덱스를 쓰면 더 효율적이다.
def quick_sort(data):
if len(data) <= 1:
return data
pivot = data[0]
left = [x for x in data[1:] if x <= pivot]
right = [x for x in data[1:] if x > pivot]
return quick_sort(left) + [pivot] + quick_sort(right)
print(quick_sort([5, 3, 8, 1, 2]))
기준값을 중심으로 작은 값과 큰 값을 나누어 재귀적으로 정렬한다.
[1, 2, 3, 5, 8]
기준값 선택에 따라 성능이 달라진다.
graph = {
"A": ["B", "C"],
"B": ["A", "D"],
"C": ["A"],
"D": ["B"]
}
print(graph["A"])
인접 리스트는 각 정점에 연결된 정점 목록을 저장한다.
['B', 'C']
희소 그래프에 메모리 효율적이다.
from collections import deque
graph = {"A":["B","C"], "B":["D"], "C":["E"], "D":[], "E":[]}
queue = deque(["A"])
visited = {"A"}
while queue:
node = queue.popleft()
print(node, end=" ")
for nxt in graph[node]:
if nxt not in visited:
visited.add(nxt)
queue.append(nxt)
BFS는 큐를 사용해 가까운 정점부터 탐색한다.
A B C D E
최단 거리 탐색의 기초가 된다.
graph = {"A":["B","C"], "B":["D"], "C":["E"], "D":[], "E":[]}
visited = set()
def dfs(node):
visited.add(node)
print(node, end=" ")
for nxt in graph[node]:
if nxt not in visited:
dfs(nxt)
dfs("A")
DFS는 한 경로를 깊게 탐색한 뒤 되돌아온다.
A B D C E
재귀 깊이가 매우 크면 반복문과 스택을 고려한다.
def divide(a, b):
assert b != 0, "b는 0이 아니어야 합니다."
return a / b
print(divide(10, 2))
assert는 개발 중 조건이 반드시 참인지 확인한다.
5.0
운영 환경의 사용자 입력 검증을 assert에만 의존하면 안 된다.
import unittest
def add(a, b):
return a + b
class TestAdd(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)
if __name__ == "__main__":
unittest.main()
unittest는 표준 라이브러리 기반 자동 테스트 도구이다.
테스트 통과 메시지
테스트 파일은 일반적으로 test_로 시작한다.
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logging.info("프로그램 시작")
logging.warning("주의가 필요합니다.")
logging은 print보다 체계적으로 실행 기록을 남긴다.
INFO와 WARNING 메시지
로그 수준에는 DEBUG, INFO, WARNING, ERROR 등이 있다.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--name", default="사용자")
args = parser.parse_args()
print(f"안녕하세요, {args.name}")
argparse는 명령행 인자를 안전하게 처리한다.
실행 인자에 따라 달라짐
IDLE에서는 기본값으로 실행하고 터미널에서 인자를 전달한다.
import os
value = os.getenv("APP_MODE", "development")
print(value)
환경 변수는 비밀키나 실행 설정을 코드와 분리할 때 사용한다.
설정에 따라 달라짐
민감한 값을 소스 코드에 직접 작성하지 않는 습관이 중요하다.
from time import perf_counter
start = perf_counter()
total = sum(range(1_000_000))
elapsed = perf_counter() - start
print(total)
print(round(elapsed, 6))
perf_counter()는 짧은 코드의 실행 시간을 정밀하게 측정한다.
합계와 실행 시간
한 번의 측정보다 여러 번 반복해 비교하는 것이 좋다.
import heapq
heap = []
for value in [5, 1, 3]:
heapq.heappush(heap, value)
while heap:
print(heapq.heappop(heap), end=" ")
heapq는 가장 작은 값을 빠르게 꺼내는 최소 힙을 제공한다.
1 3 5
최대 힙은 음수 값을 이용해 구현할 수 있다.
import bisect
data = [1, 3, 5, 7]
bisect.insort(data, 4)
print(data)
insort()는 정렬된 리스트의 알맞은 위치에 값을 삽입한다.
[1, 3, 4, 5, 7]
삽입 자체는 리스트 이동 때문에 O(n)이다.
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(35))
lru_cache는 이전 계산 결과를 저장해 같은 계산을 반복하지 않는다.
9227465
동적 계획법의 메모이제이션 구현에 유용하다.
import pickle
data = {"name": "민수", "score": 95}
with open("data.pkl", "wb") as file:
pickle.dump(data, file)
with open("data.pkl", "rb") as file:
loaded = pickle.load(file)
print(loaded)
pickle은 파이썬 객체 구조를 그대로 저장하고 복원한다.
{'name': '민수', 'score': 95}신뢰할 수 없는 pickle 파일은 절대 열지 않는다.
def average(values: list[float]) -> float:
if not values:
raise ValueError("빈 리스트입니다.")
return sum(values) / len(values)
print(average([80.0, 90.0, 100.0]))
타입 힌트는 함수가 기대하는 자료형을 문서화하고 정적 분석을 돕는다.
90.0
파이썬 실행 자체가 타입을 강제하지는 않는다.
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
print(first([10, 20, 30]))
TypeVar를 사용하면 여러 자료형에 재사용 가능한 타입 안전 함수를 표현할 수 있다.
10
빈 리스트 처리도 함께 고려해야 한다.
from typing import Protocol
class HasArea(Protocol):
def area(self) -> float: ...
def print_area(shape: HasArea) -> None:
print(shape.area())
class Square:
def area(self) -> float:
return 16.0
print_area(Square())
Protocol은 특정 메서드를 가진 객체라면 상속 관계 없이 사용할 수 있게 한다.
16.0
구조적 타이핑을 표현하는 방법이다.
command = ("move", 10, 20)
match command:
case ("move", x, y):
print(f"{x}, {y}로 이동")
case ("stop",):
print("정지")
case _:
print("알 수 없음")
match-case는 데이터 구조의 형태를 비교하는 구조적 패턴 매칭 문법이다.
10, 20로 이동
파이썬 3.10 이상에서 사용할 수 있다.
numbers = [1, 2, 3, 4, 5]
if (count := len(numbers)) > 3:
print(f"항목 수: {count}")
:= 연산자는 표현식 안에서 값을 변수에 대입한다.
항목 수: 5
과도하게 사용하면 코드가 읽기 어려워질 수 있다.
class Timer:
def __enter__(self):
from time import perf_counter
self.start = perf_counter()
return self
def __exit__(self, exc_type, exc, tb):
from time import perf_counter
print(round(perf_counter() - self.start, 6))
with Timer():
sum(range(100000))
__enter__와 __exit__를 구현하면 with 문에서 사용할 수 있는 컨텍스트 관리자가 된다.
실행 시간 출력
자원 획득과 해제를 한곳에서 관리할 수 있다.
from contextlib import contextmanager
@contextmanager
def managed_resource():
print("자원 열기")
try:
yield "데이터"
finally:
print("자원 닫기")
with managed_resource() as value:
print(value)
contextmanager는 제너레이터 형태로 간단한 컨텍스트 관리자를 만든다.
자원 열기 데이터 자원 닫기
finally에서 반드시 자원을 정리한다.
class InvalidScoreError(Exception):
pass
def validate_score(score):
if not 0 <= score <= 100:
raise InvalidScoreError("점수 범위 오류")
return score
print(validate_score(95))
도메인에 맞는 예외 클래스를 정의하면 오류 의미가 분명해진다.
95
기본 Exception을 상속하는 것이 일반적이다.
try:
int("abc")
except ValueError as error:
raise RuntimeError("데이터 변환 실패") from error
raise ... from ...은 원래 예외와 새 예외의 인과관계를 유지한다.
예외 추적 정보 출력
디버깅 시 원인을 더 쉽게 파악할 수 있다.
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
print(square(7))
partial은 일부 인자를 미리 고정한 새 함수를 만든다.
49
콜백 함수 설정에 유용하다.
from functools import singledispatch
@singledispatch
def describe(value):
return "기타"
@describe.register
def _(value: int):
return "정수"
@describe.register
def _(value: str):
return "문자열"
print(describe(10), describe("a"))
singledispatch는 첫 번째 인자의 자료형에 따라 다른 함수를 선택한다.
정수 문자열
간단한 함수 오버로딩 효과를 낼 수 있다.
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
print(list(CountDown(5)))
__iter__와 __next__를 구현하면 반복 가능한 객체가 된다.
[5, 4, 3, 2, 1]
반복 종료 시 StopIteration을 발생시킨다.
class Config:
debug = True
port = 8000
config = Config()
for name in ["debug", "port"]:
print(name, getattr(config, name))
getattr()은 문자열로 속성 이름을 동적으로 읽는다.
debug True port 8000
hasattr()으로 속성 존재 여부를 먼저 확인할 수 있다.
class Settings:
pass
settings = Settings()
setattr(settings, "theme", "dark")
print(settings.theme)
setattr()은 속성 이름을 문자열로 전달해 값을 설정한다.
dark
설정 파일을 객체로 변환할 때 활용할 수 있다.
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def hello():
print("안녕")
hello()
데코레이터를 한 단계 더 감싸면 설정값을 받을 수 있다.
안녕 안녕 안녕
중첩 함수의 역할을 단계별로 이해해야 한다.
import timeit
list_time = timeit.timeit("[x*x for x in range(1000)]", number=1000)
loop_time = timeit.timeit("result=[]\nfor x in range(1000): result.append(x*x)", number=1000)
print(round(list_time, 4), round(loop_time, 4))
timeit은 코드 조각을 여러 번 실행하여 성능을 비교한다.
환경에 따라 달라짐
운영체제와 하드웨어에 따라 결과가 달라질 수 있다.
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
print(p.x, p.y)
__slots__는 허용할 속성을 제한하고 많은 객체의 메모리 사용을 줄일 수 있다.
3 4
동적 속성 추가가 제한된다.
data = bytearray(b"abcdef")
view = memoryview(data)
view[0] = ord("A")
print(data)
memoryview는 데이터를 복사하지 않고 버퍼를 직접 참조한다.
bytearray(b'Abcdef')
대용량 바이너리 데이터 처리에 유용하다.
from concurrent.futures import ThreadPoolExecutor
def square(x):
return x * x
with ThreadPoolExecutor(max_workers=3) as executor:
print(list(executor.map(square, [1, 2, 3, 4])))
스레드 풀은 여러 I/O 중심 작업을 동시에 처리하는 데 적합하다.
[1, 4, 9, 16]
CPU 중심 작업은 프로세스 풀이 더 적합할 수 있다.
from concurrent.futures import ProcessPoolExecutor
def square(x):
return x * x
if __name__ == "__main__":
with ProcessPoolExecutor() as executor:
print(list(executor.map(square, [1, 2, 3, 4])))
프로세스 풀은 여러 CPU 코어를 활용할 수 있다.
[1, 4, 9, 16]
Windows에서는 반드시 __main__ 보호 구문을 사용한다.
import asyncio
async def greet():
await asyncio.sleep(1)
print("비동기 작업 완료")
asyncio.run(greet())
async def는 코루틴을 정의하고 await는 다른 작업에 실행권을 양보한다.
1초 후 비동기 작업 완료
IDLE 환경에 따라 이미 실행 중인 이벤트 루프가 있으면 주의한다.
import asyncio
async def task(name, delay):
await asyncio.sleep(delay)
return name
async def main():
result = await asyncio.gather(task("A", 1), task("B", 0.5))
print(result)
asyncio.run(main())
gather()는 여러 코루틴을 동시에 실행하고 결과를 모은다.
['A', 'B']
반환 순서는 완료 순서가 아니라 전달 순서이다.
import asyncio
async def slow():
await asyncio.sleep(2)
async def main():
try:
await asyncio.wait_for(slow(), timeout=1)
except asyncio.TimeoutError:
print("시간 초과")
asyncio.run(main())
wait_for()는 지정 시간 안에 끝나지 않는 비동기 작업을 취소한다.
시간 초과
네트워크 작업에는 타임아웃 설정이 중요하다.
import sqlite3
with sqlite3.connect("school.db") as conn:
conn.execute("CREATE TABLE IF NOT EXISTS students (name TEXT, score INTEGER)")
print("테이블 준비 완료")
sqlite3는 별도 서버 없이 파일 기반 데이터베이스를 사용할 수 있게 한다.
테이블 준비 완료
with 문을 사용하면 트랜잭션 관리가 편리하다.
import sqlite3
with sqlite3.connect("school.db") as conn:
conn.execute("INSERT INTO students VALUES (?, ?)", ("민수", 95))
print("삽입 완료")
물음표 자리표시자를 사용하면 SQL 인젝션 위험을 줄일 수 있다.
삽입 완료
문자열 연결로 SQL을 만들지 않는다.
import sqlite3
with sqlite3.connect("school.db") as conn:
rows = conn.execute("SELECT name, score FROM students WHERE score >= ?", (90,))
for row in rows:
print(row)
조건을 사용해 필요한 데이터만 조회한다.
저장 데이터에 따라 달라짐
한 개짜리 튜플은 뒤에 쉼표가 필요하다.
from urllib.request import urlopen
with urlopen("https://example.com", timeout=5) as response:
print(response.status)
print(response.read(60).decode("utf-8", errors="ignore"))
urllib는 표준 라이브러리만으로 HTTP 요청을 수행한다.
상태 코드 200과 일부 HTML
인터넷 연결이 필요하며 예외 처리를 추가해야 한다.
from urllib.parse import urlencode
params = {"q": "파이썬 데이터", "page": 1}
print(urlencode(params))
urlencode()는 한글과 특수문자를 URL 쿼리 문자열로 변환한다.
q=%ED%8C%8C...&page=1
웹 API 요청 주소를 만들 때 사용한다.
class Trie:
def __init__(self):
self.root = {}
def insert(self, word):
node = self.root
for ch in word:
node = node.setdefault(ch, {})
node["$"] = True
def contains(self, word):
node = self.root
for ch in word:
if ch not in node:
return False
node = node[ch]
return "$" in node
trie = Trie()
trie.insert("python")
print(trie.contains("python"))
트라이는 문자열의 접두사를 효율적으로 저장하고 검색한다.
True
자동완성이나 사전 검색에 활용된다.
weights = [2, 3, 4]
values = [3, 4, 5]
capacity = 5
dp = [0] * (capacity + 1)
for w, v in zip(weights, values):
for c in range(capacity, w - 1, -1):
dp[c] = max(dp[c], dp[c-w] + v)
print(dp[capacity])
이전 부분 문제의 결과를 저장해 0-1 배낭 문제를 해결한다.
7
역순 반복은 같은 물건을 여러 번 사용하는 것을 막는다.
data = [10, 20, 10, 30, 20, 50]
dp = [1] * len(data)
for i in range(len(data)):
for j in range(i):
if data[j] < data[i]:
dp[i] = max(dp[i], dp[j] + 1)
print(max(dp))
각 위치에서 끝나는 증가 부분 수열의 최대 길이를 저장한다.
4
이 구현의 시간 복잡도는 O(n²)이다.
import heapq
graph = {
"A": [("B", 2), ("C", 5)],
"B": [("C", 1)],
"C": []
}
dist = {node: float("inf") for node in graph}
dist["A"] = 0
heap = [(0, "A")]
while heap:
cost, node = heapq.heappop(heap)
if cost > dist[node]:
continue
for nxt, weight in graph[node]:
new_cost = cost + weight
if new_cost < dist[nxt]:
dist[nxt] = new_cost
heapq.heappush(heap, (new_cost, nxt))
print(dist)
우선순위 큐를 이용해 음수가 없는 가중치 그래프의 최단 거리를 구한다.
{'A': 0, 'B': 2, 'C': 3}음수 가중치가 있으면 다른 알고리즘이 필요하다.
from collections import deque
graph = {"A":["C"], "B":["C"], "C":["D"], "D":[]}
indegree = {node: 0 for node in graph}
for node in graph:
for nxt in graph[node]:
indegree[nxt] += 1
queue = deque([n for n in graph if indegree[n] == 0])
order = []
while queue:
node = queue.popleft()
order.append(node)
for nxt in graph[node]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
queue.append(nxt)
print(order)
위상 정렬은 선후 관계가 있는 작업을 가능한 순서로 배열한다.
['A', 'B', 'C', 'D'] 또는 A/B 순서 교환
사이클이 있는 그래프에는 적용할 수 없다.
parent = list(range(5))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(a, b):
ra, rb = find(a), find(b)
if ra != rb:
parent[rb] = ra
union(0, 1)
union(1, 2)
print(find(0) == find(2))
Union-Find는 원소들이 같은 집합에 속하는지 빠르게 관리한다.
True
경로 압축으로 탐색 속도를 높인다.
n = 50
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False
for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
for multiple in range(i*i, n+1, i):
is_prime[multiple] = False
print([i for i, value in enumerate(is_prime) if value])
여러 소수를 한꺼번에 구할 때 효율적인 알고리즘이다.
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
i*i부터 지우면 중복 작업을 줄일 수 있다.
def sqrt_newton(value, tolerance=1e-10):
guess = value / 2
while abs(guess * guess - value) > tolerance:
guess = (guess + value / guess) / 2
return guess
print(sqrt_newton(2))
뉴턴 방법은 반복 계산으로 방정식의 근을 빠르게 근사한다.
약 1.41421356237
초기값과 종료 조건 선택이 중요하다.
def integrate(f, a, b, n=1000):
h = (b - a) / n
total = (f(a) + f(b)) / 2
for i in range(1, n):
total += f(a + i * h)
return total * h
print(integrate(lambda x: x**2, 0, 1))
구간을 작은 사다리꼴로 나누어 정적분을 근사한다.
약 0.3333335
n을 늘리면 대체로 정확도가 높아진다.
def euler(f, x0, y0, h, steps):
x, y = x0, y0
for _ in range(steps):
y += h * f(x, y)
x += h
return x, y
x, y = euler(lambda x, y: y, 0, 1, 0.1, 10)
print(x, y)
오일러 방법은 미분방정식의 해를 작은 시간 간격으로 근사한다.
약 1.0 2.5937
간격 h가 작을수록 오차가 줄지만 계산량이 늘어난다.
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
result = [[sum(A[i][k] * B[k][j] for k in range(2)) for j in range(2)] for i in range(2)]
print(result)
행과 열의 대응 원소를 곱해 합산하여 행렬 곱셈을 구현한다.
[[19, 22], [43, 50]]
대규모 행렬은 NumPy가 훨씬 빠르다.
class Discount:
def apply(self, price):
return price
class StudentDiscount:
def apply(self, price):
return price * 0.8
class Order:
def __init__(self, strategy):
self.strategy = strategy
def total(self, price):
return self.strategy.apply(price)
print(Order(StudentDiscount()).total(10000))
전략 패턴은 교체 가능한 알고리즘을 객체로 분리한다.
8000.0
조건문이 많아지는 계산 규칙에 유용하다.
class Subject:
def __init__(self):
self.observers = []
def subscribe(self, observer):
self.observers.append(observer)
def notify(self, message):
for observer in self.observers:
observer(message)
subject = Subject()
subject.subscribe(lambda m: print("알림:", m))
subject.notify("새 데이터")
옵저버 패턴은 상태 변화가 발생했을 때 여러 구독자에게 알린다.
알림: 새 데이터
이벤트 기반 프로그램의 기초 구조이다.
class Dog:
def speak(self): return "멍멍"
class Cat:
def speak(self): return "야옹"
def create_animal(kind):
classes = {"dog": Dog, "cat": Cat}
return classes[kind]()
print(create_animal("cat").speak())
팩토리 함수는 객체 생성 방법을 호출 코드에서 분리한다.
야옹
새 클래스 추가 시 생성 규칙을 한곳에서 관리할 수 있다.
from unittest.mock import Mock
service = Mock()
service.get_score.return_value = 95
print(service.get_score("민수"))
service.get_score.assert_called_once_with("민수")
Mock은 실제 외부 서비스 대신 가짜 객체를 사용해 테스트를 단순화한다.
95
네트워크나 데이터베이스 의존성 테스트에 유용하다.
from tempfile import NamedTemporaryFile
with NamedTemporaryFile(mode="w+", encoding="utf-8", delete=True) as file:
file.write("테스트 데이터")
file.seek(0)
print(file.read())
tempfile은 테스트 후 자동 정리되는 임시 파일을 제공한다.
테스트 데이터
실제 사용자 파일을 손상시키지 않고 파일 기능을 시험할 수 있다.
import cProfile
def work():
return sum(i*i for i in range(100000))
cProfile.run("work()")
cProfile은 함수별 실행 횟수와 시간을 분석한다.
프로파일링 표 출력
느린 구간을 찾은 뒤 최적화해야 한다.
import logging
logging.basicConfig(
filename="app.log",
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
encoding="utf-8"
)
logging.info("프로그램 시작")
로그를 파일에 저장하면 실행 이력을 나중에 분석할 수 있다.
app.log 생성
개인정보나 비밀키를 로그에 남기지 않는다.
import configparser
config = configparser.ConfigParser()
config["APP"] = {"theme": "dark", "font_size": "14"}
with open("settings.ini", "w", encoding="utf-8") as file:
config.write(file)
print("설정 저장")
configparser는 INI 형식 설정 파일을 읽고 쓴다.
설정 저장
숫자와 불리언은 읽을 때 전용 변환 메서드를 사용한다.
from zipfile import ZipFile
with ZipFile("backup.zip", "w") as zip_file:
zip_file.write("memo.txt")
print("압축 완료")
zipfile 모듈은 표준 ZIP 압축 파일을 만들고 해제한다.
backup.zip 생성
압축할 파일이 존재하는지 확인해야 한다.
import hmac
import hashlib
key = b"secret-key"
message = b"important data"
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
HMAC은 비밀키와 해시 함수를 이용해 데이터가 변조되지 않았는지 확인한다.
64자리 서명
키는 코드에 직접 저장하지 않고 안전하게 관리해야 한다.
from html.parser import HTMLParser
class TitleParser(HTMLParser):
def handle_starttag(self, tag, attrs):
if tag == "h1":
print("제목 태그 발견")
parser = TitleParser()
parser.feed("<h1>파이썬</h1>")
HTMLParser는 HTML 문서를 이벤트 방식으로 분석한다.
제목 태그 발견
복잡한 문서 분석에는 전용 외부 라이브러리가 편리할 수 있다.
def area(width: float, height: float) -> float:
"""직사각형의 넓이를 반환한다.
Args:
width: 가로 길이
height: 세로 길이
"""
return width * height
print(area.__doc__)
docstring은 함수의 목적과 인자, 반환값을 코드 안에 기록한다.
함수 설명 출력
help(area)로 문서를 확인할 수 있다.