본 도구에서 사용하는 오픈소스 라이브러리

본 도구의 코드에는 1개의 오픈소스 라이브러리가 포함되어 있습니다.

Python 치트시트 — 빠른 레퍼런스

모던 Python 3 (3.11+) 문법, 내장 타입, 자주 쓰는 표준 라이브러리를 정리한 빠른 레퍼런스. 일상적인 필요의 약 80%를 다룹니다.

Py

Python Python 3.11 (LTS 계열)

CPython · 다중 패러다임 (OOP · 함수형 · 절차형) · 동적 · 강한 타입 · 덕 타이핑

추천 학습 경로

먼저 python3 실행과 REPL 에 익숙해지고 → 변수, 내장 타입, 제어 흐름을 익히고 → 함수, 클래스, 예외를 깊이 다루며 → 표준 라이브러리로 파일/네트워크 I/O 를 작성하고 → 참조와 가변성(얕은/깊은 복사)을 이해한 뒤 → 필요에 따라 asyncio, 프로세스, 정규식을 익히고 → 마지막으로 venv/pip/pyproject 로 프로젝트 운영을 학습하세요. 함정을 피하기 위해 FAQ 섹션은 나중에 다시 보는 것이 좋습니다.

1.Hello World 및 빌드 환경

Python 실행, 대화형 REPL, 가상 환경, 패키지 관리 방법을 다룹니다.

최소 프로그램

print 가 콘솔에 출력합니다. Python은 들여쓰기로 블록을 구성하며 중괄호나 세미콜론이 없습니다.

1
2
3
4
5
6
7
8
# hello.py
print('Hello, world!')
# 실행:python3 hello.py
# 또는 대화형:python3 으로 REPL 에 진입해 직접 입력
print(f'1 + 1 = {1 + 1}')
# stderr 로 출력:
import sys
print('error', file=sys.stderr)

실행 방법

python3 는 스크립트 실행, -c 는 문자열 실행, -m 은 모듈 실행을 담당합니다. 대화형 REPL은 즉시 평가합니다.

1
2
3
4
5
6
7
8
9
10
11
# 스크립트 실행:
# python3 hello.py
# 문자열 실행:
# python3 -c "print('hi')"
# 모듈 실행(__main__ 포함):
# python3 -m http.server 8000
# 버전 확인:
# python3 --version
# REPL 진입:
# python3
# 대화형 프롬프트 >>> 에 코드를 입력하고 Enter 로 실행

가상 환경

venv 는 프로젝트 의존성을 격리합니다. 활성화 후 pip 설치는 현재 환경에만 적용됩니다. 프로젝트마다 가상 환경을 사용하세요.

1
2
3
4
5
6
7
8
9
10
11
12
# 가상 환경 생성:
python3 -m venv .venv
# 활성화(Windows):
# .venv\Scripts\activate
# 활성화(macOS/Linux):
# source .venv/bin/activate
# 비활성화:deactivate
# 활성화 후:
# python -m pip install requests
# 인터프리터 확인:
# which python # Linux/macOS
# 가상 환경 이름이 프롬프트 앞에 표시됨

pip 패키지 관리

pip 으로 의존성을 설치/제거/동결합니다. 버전은 ==, 범위는 >=,<y 로 지정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 설치:
pip install requests
# 버전 지정:
pip install 'requests==2.31.0'
# 버전 범위:
pip install 'requests>=2,<3'
# 업그레이드:pip install -U requests
# 제거:pip uninstall requests
# 의존성 내보내기:
pip freeze > requirements.txt
# 파일에서 설치:
pip install -r requirements.txt
# 목록:pip list
# pip 업그레이드:python3 -m pip install -U pip

pyproject.toml

현대 프로젝트 설정 파일: 의존성, 빌드 시스템, 도구 설정을 모두 pyproject.toml 에 둡니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# pyproject.toml(PEP 621):
# [project]
# name = "my-app"
# version = "0.1.0"
# requires-python = ">=3.11"
# dependencies = [
# "requests>=2.31",
# "httpx>=0.24",
# ]
#
# [build-system]
# requires = ["setuptools>=68"]
# build-backend = "setuptools.build_meta"
#
# [tool.ruff]
# line-length = 88
# 의존성 설치:pip install -e .

Shebang 스크립트

#!/usr/bin/env python3 로 스크립트를 실행 가능하게 합니다. POSIX에서는 chmod +x 후 ./script.py 로 바로 실행할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
# script.py
# 실행 권한 부여:
# chmod +x script.py
# 직접 실행:
# ./script.py
print('可执行脚本')
# 특정 python 경로에 의존하지 않음:
# env 가 python3 를 찾음
# Windows 에서는 .py 를 더블클릭하면 연결된 인터프리터로 실행
# shebang 이 없으면 python3 script.py 로 실행

REPL 상호작용

python3 로 REPL에 진입해 대화형으로 디버깅합니다. _ 가 마지막 결과를 보관하며 exit() 로 종료합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 1 + 2
3
>>> _
3
>>> def f(x): return x * 2
>>> f(21)
42
# REPL 종료:
# exit() 또는 Ctrl+D
# 내장 help:
>>> help(str)
>>> help('str'.upper)
# 객체 속성 확인:
>>> dir('hello')
# 기록:위/아래 화살표 키로 참조

__main__과 모듈

if __name__ == '__main__' 으로 직접 실행과 임포트를 구분합니다. 스크립트 실행/모듈 사용의 표준 관용구입니다.

1
2
3
4
5
6
7
8
9
10
11
12
# greetings.py
def hello(name='world'):
return f'Hello, {name}!'
if __name__ == '__main__':
# 직접 실행 시에만 실행
print(hello())
# 직접 실행:python3 greetings.py
# 임포트해서 사용:
# from greetings import hello
# hello('Nick')
# __name__ 은 직접 실행 시 '__main__',임포트 시 모듈 이름

2.변수와 타입 힌트

동적 타이핑, 타입 힌트, 다중 할당, 스코프, 왈루스 연산자를 다룹니다.

할당과 추론

변수 선언이 필요 없으며 할당이 곧 생성입니다. 타입은 동적으로 추론되고 언제든지 바뀔 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
x = 42 # int 로 추론
y = 3.14 # float
name = 'Rex' # str
ok = True # bool
empty = None # NoneType
# 타입 변경 가능:
x = '现在是字符串'
# 여러 변수 동시에:
a = b = c = 0
# 변수는 이름이며,상자가 아님:
# 객체에 바인딩하고,재할당으로 바인딩을 바꿈

타입 힌트

PEP 484 스타일 타입 힌트로, 콜론 뒤에 타입을 적습니다. 실행 시점에는 영향을 주지 않으며 힌트와 정적 검사에 쓰입니다.

1
2
3
4
5
6
7
8
9
10
11
12
count: int = 0
items: list[str] = []
mapping: dict[str, int] = {}
# PEP 604 유니온 문법(3.10+):
optional: int | None = None
# 함수 애너테이션:
def add(a: int, b: int) -> int:
return a + b
# 애너테이션은 강제되지 않음:
add('a', 'b') # 실행 시 오류 없음
# 검사 도구:mypy / pyright
# 제네릭:list[int] 이며 list[int, ...] 가 아님

다중 할당

a, b = b, a 로 한 줄에 값을 교환합니다. 확장 언패킹은 *rest 를 쓰며 개수가 일치해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a, b, c = 1, 2, 3
# 교환:
a, b = b, a
# 확장 언패킹:
first, *rest = [1, 2, 3, 4]
# first=1, rest=[2,3,4]
*head, last = [1, 2, 3, 4]
# head=[1,2,3], last=4
# 튜플 언패킹:
x, y = (10, 20)
# 사전 키 언패킹:
keys = 'ab'
d = dict(zip(keys, [1, 2]))
# 개수가 맞지 않으면 ValueError

상수 관습

Python 에는 상수 키워드가 없습니다. 모듈 레벨 상수는 ALL_CAPS 로 명명하는 것이 관례입니다.

1
2
3
4
5
6
7
8
9
10
11
PI = 3.14159
MAX_RETRIES = 3
DATABASE_URL = 'postgres://...'
# 어디까지나 관례이며,변경 가능:
# PI = 99 는 가능하지만 하지 말 것
# 역할:
# 한눈에 변하지 않는 값임을 알 수 있음
# 설정을 한곳에서 관리
# Enum 으로 매직 넘버 대체:
# from enum import Enum
# class Color(Enum): RED = 1

스코프 LEGB

이름 탐색 순서는 Local → Enclosing → Global → Built-in 입니다. 함수 안에서 할당하려면 global 또는 nonlocal 선언이 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
counter = 0
def inc():
global counter # 모듈 레벨 읽기/쓰기
counter += 1
def outer():
x = 1
def inner():
nonlocal x # 바깥 함수의 변수 읽기/쓰기
x += 1
inner()
print(x) # 2
# 바깥 변수 읽기만 하면 nonlocal 불필요
# 모듈 맨 앞에 전역을 정의
# 내장 함수(len/print)는 Built-in 계층에 있음

왈루스 연산자

:= 는 표현식 안에서 값을 할당하는 표현식 (Python 3.8+). 같은 계산의 반복을 피할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
# 기존 방식:
line = input('> ')
while line != 'quit':
print(line)
line = input('> ')
# 바다코끼리 연산자:
while (line := input('> ')) != 'quit':
print(line)
# 리스트 컴프리헨션에서 재사용:
# [y for x in data if (y := f(x)) > 0]
# 괄호 주의:할당 표현식은 괄호로 감싸야 함
# 용도:조건 안에서 초기화 후 판정

None과 불리언

None 은 값 없음을 나타냅니다. 진위는 if x 로 검사하고 if x == True 는 피하세요. 빈 컨테이너는 거짓입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
value = None
if value is None: # None 판정은 is
print('无值')
# 진릿값 판정:
if [1, 2]: pass # 비어있지 않은 리스트는 참
if []: pass # 빈 리스트는 거짓
# 거짓값:False/0/''/[]/{}/(,)/None
if not value: # value 가 거짓일 때
pass
# 삼항 연산자:
status = 'ok' if value else 'empty'
# 빈 값 대체:
name = value or '默认值'

del과 가비지 컬렉션

del 은 변수 이름이나 컨테이너 요소를 제거합니다. 이름이 사라지고 객체의 참조 카운트가 1 줄어듭니다.

1
2
3
4
5
6
7
8
9
10
11
x = 42
del x # 이름 삭제
# x 를 다시 참조하면 NameError
items = [1, 2, 3]
del items[0] # 요소 삭제 -> [2, 3]
d = {'a': 1}
del d['a'] # 키 삭제
# 사전 키 삭제는 pop 에 기본값을 넘길 수 있음:
val = d.pop('a', None)
# try 와 조합해 KeyError 를 처리
# del 은 큰 객체를 일찍 해제할 때 사용

3.데이터 타입

내장 타입: 숫자, 불리언, 시퀀스, 매핑, 집합과 고급 타입 힌트.

숫자 타입

int 는 임의 정밀도, float 는 IEEE-754, complex 는 복소수입니다. bool 은 int 의 서브클래스이며 나눗셈은 두 가지 의미가 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
n = 10 # int
g = 3.14 # float
c = 1 + 2j # complex
big = 2 ** 100 # int 임의 정밀도
# 나눗셈:
5 / 2 # 2.5(진짜 나눗셈)
5 // 2 # 2(정수 나눗셈)
5 % 2 # 1(나머지)
# 진법:
0b1010 # 2 진법 10
0o12 # 8 진법 10
0xff # 16 진법 255
# 타입 변환:
int('42'); float(1); bool(0)

불리언과 None

True/False 는 첫 글자가 대문자이며 None 은 싱글톤입니다. and/or 는 단락 평가로 결정한 피연산자를 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t, f = True, False
# 단락 평가:
print(0 and 'x') # 0(단락 평가로 왼쪽 값을 반환)
print('' or '默认') # '默认'
print(None or []) # []
# 삼항 연산자:
age = 17
msg = '成年' if age >= 18 else '未成年'
# 비교 연쇄:
if 0 < x < 10: pass
# None 판정:
if x is None: pass
# bool() 변환:
bool([]) # False
bool([1]) # True

시퀀스 타입

str/list/tuple 은 모두 시퀀스로 인덱싱·슬라이싱·반복이 가능합니다. str 과 tuple 은 불변입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s = 'abc'
# 인덱스:
s[0] # 'a'
s[-1] # 'c'
# 슬라이스:
s[1:] # 'bc'
s[::-1] # 'cba' 반전
# 시퀀스 공통:
len(s), min([3,1]), max([3,1])
sum([1,2,3])
list('abc') # ['a','b','c']
# 존재:
'a' in s # True
# 불변 시퀀스는 변경 불가:
# s[0] = 'x' 오류
# 인덱스 범위 초과 IndexError

매핑 dict

dict 는 키/값 쌍을 저장하며 삽입 순서를 유지합니다. 키는 해시 가능해야 하고, .get 으로 안전하게 값을 얻습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
d = {'a': 1, 'b': 2}
# 조회:
d['a'] # 1,키 없으면 KeyError
d.get('c', 0) # 0,키 없으면 기본값
# 추가/변경:
d['c'] = 3
d.setdefault('x', 0)
# 삭제:
d.pop('a', None)
# 병합:
d.update({'y': 9})
# 뷰:
d.keys(); d.values(); d.items()
# 사전 컴프리헨션:
{n: n**2 for n in range(3)}
# 키는 해시 가능해야 함:숫자/문자열/튜플 가능

집합 타입

set 은 순서 없는 중복 없음 집합, frozenset 은 그 불변 버전입니다. 합집합·교집합·차집합 연산을 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
s = {1, 2, 3}
s.add(4)
s.remove(2) # 키 없으면 KeyError
s.discard(99) # 키 없어도 오류 없음
# 중복 제거:
list(set([1, 1, 2])) # [1, 2]
# 연산:
{1, 2} | {2, 3} # 합집합 {1,2,3}
{1, 2} & {2, 3} # 교집합 {2}
{1, 2} - {2} # 차집합 {1}
# 불변 집합:
frozenset([1, 2])
# 빈 집합은 set(),{} 는 빈 사전

바이트 타입

bytes 는 불변, bytearray 는 가변입니다. encode/decode 로 텍스트와 바이트열을 상호 변환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
b = b'hello' # bytes
ba = bytearray(b'abc')
ba[0] = 120 # 가변
# 인코딩:
text = '你好'.encode('utf-8')
# b'\xe4\xbd\xa0\xe5\xa5\xbd'
text.decode('utf-8') # '你好'
# 인코딩 오류:
text.decode('utf-8', errors='ignore')
# 문자열과 바이트 변환에는 반드시 인코딩 지정
# 바이너리 데이터:
import struct
struct.pack('>i', 42)
# 바이트 오더:> 빅엔디언 < 리틀엔디언

고급 타입 힌트

typing 모듈: Optional, Union, TypeVar, 제네릭 컨테이너. Python 3.10+ 부터는 | 문법도 쓸 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from typing import Optional, Union, TypeVar, Callable
# 옵셔널:
x: Optional[int] = None # int | None 과 동일
# 유니온:
y: Union[int, str] # int | str 과 동일
# 제네릭 변수:
T = TypeVar('T')
def first(items: list[T]) -> T:
return items[0]
# 호출 가능:
def apply(f: Callable[[int], int], n: int) -> int:
return f(n)
# 타입 별칭(3.12+):
type Vector = list[float]
# 검사:mypy / pyright

덕 타이핑

「오리처럼 걷고 울면 오리다」: 타입이 아니라 동작을 봅니다. typing.Protocol 로 구조적 서브타이핑을 정의합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 덕 타이핑:len 을 가진 객체면 사용 가능
def size(obj):
return len(obj)
size([1, 2]) # 2
size('abc') # 3
# 프로토콜(typing.Protocol):
from typing import Protocol
class Sizeable(Protocol):
def __len__(self) -> int: ...
def total_len(objs: list[Sizeable]) -> int:
return sum(len(o) for o in objs)
# 구조적 서브타이핑:모양만 맞으면 됨
# 명시적 상속 불필요

4.참조와 가변성

Python의 객체 참조 의미: 가변/불변, 얕은 복사와 깊은 복사, 공유 참조 함정.

참조 의미

변수는 박스가 아니라 객체에 바인딩됩니다. 여러 이름이 같은 객체를 가리킬 수 있으며 할당은 바인딩을 다시 만드는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
a = [1, 2, 3]
b = a # b 와 a 는 같은 리스트를 가리킴
b.append(4)
print(a) # [1, 2, 3, 4]
# 재바인딩해도 원래 객체는 영향 없음:
a = [9]
print(b) # 여전히 [1,2,3,4]
# 정수 같은 불변 객체:
x = 10
y = x
x = 20
print(y) # 10,값은 바뀌지 않음
# 핵심:「객체 변경」과「바인딩 변경」을 구분

가변과 불변

가변 타입은 제자리에서 바뀝니다: list/dict/set. 불변: int/str/tuple/frozenset.

1
2
3
4
5
6
7
8
9
10
11
12
13
# 가변:제자리에서 변경
lst = [1, 2]
lst.append(3) # 같은 객체가 변화
# 불변:연산으로 새 객체 생성
s = 'ab'
s2 = s.upper() # 새 문자열
# s 는 변하지 않음
# 불변 값은 사전 키가 될 수 있음:
d = {('a', 1): 'x'}
# 리스트는 키가 될 수 없음:
# {[1]: 'x'} 오류
# 가변 요소를 포함한 튜플도 해시 불가
# 불변 객체는 안전하게 공유 가능

얕은 복사

copy.copy 는 얕은 복사로 외부 컨테이너만 새로 만들고 내부 요소는 공유합니다. 리스트 슬라이싱도 얕은 복사입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
# 바깥은 다름:
shallow is original # False
# 안쪽은 공유:
shallow[0] is original[0] # True
# 리스트 슬라이스도 동일:
sub = original[:]
# 안쪽을 변경하면 서로 영향:
shallow[0].append(99)
print(original) # [[1,2,99], [3,4]]
# 얕은 복사는 1 겹 구조에 적합

깊은 복사

copy.deepcopy 는 모든 층을 재귀적으로 복제합니다. 중첩 가변 구조의 변경이 사본에 전파되지 않습니다. 비용에 주의하세요.

1
2
3
4
5
6
7
8
9
10
11
12
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(99)
print(original) # [[1, 2], [3, 4]]
print(deep) # [[1, 2, 99], [3, 4]]
# 깊은 복사는 재귀적으로 복제:
# 모든 중첩 객체가 새로 생성됨
# 비용은 깊이에 비례
# 순환 참조도 처리 가능
# 자기 참조 객체:copy.deepcopy(x)
# 대규모 데이터에선 주의,구조 설계 고려

공유 참조 함정

같은 가변 객체를 공유하면 한 곳의 변경이 모두에 보입니다. 인자 전달도 참조에 의한 전달입니다.

1
2
3
4
5
6
7
8
9
10
11
# 함수 안에서 전달된 리스트를 변경:
def add_item(items):
items.append('x') # 호출 측에 영향!
my_list = []
add_item(my_list)
print(my_list) # ['x']
# 제자리 변경을 피하려면 복사본을 전달:
add_item(my_list.copy())
# 또는 함수 안에서 새 리스트를 반환
# 기본 인자의 함정은 FAQ 참조
# 판단:함수가 읽기 전용을 약속하는지

is와 ==의 차이

== 는 값을, is 는 동일성(같은 객체 여부)을 비교합니다. None 비교에는 is None 을 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = [1, 2]
b = [1, 2]
a == b # True,값이 같음
a is b # False,다른 객체
# 작은 정수 인터닝:
x = 256
y = 256
x is y # True(-5..256 은 캐시)
# 큰 정수:
x, y = 10**6, 10**6
x is y # 불확정,의존하지 말 것
# 올바른 사용법:
if x is None: pass
# 값 비교에는 ==:
if x == 42: pass

기본 매개변수 함정

가변 기본 인자는 정의 시 한 번 만들어져 호출 간에 공유됩니다. None 센티널을 쓰고 함수 내부에서 만드세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:공유되는 기본 리스트
def add(x, acc=[]):
acc.append(x)
return acc
add(1) # [1]
add(2) # [1, 2] !
# GOOD:None + 함수 내부에서 생성
def add(x, acc=None):
if acc is None:
acc = []
acc.append(x)
return acc
# 기본값은 한 번만 평가:
# 정의 시 생성되고,이후 재사용됨

인터닝과 캐싱

작은 정수와 짧은 문자열은 인터닝으로 메모리를 절약합니다. 내용 비교에 is 를 쓰지 마세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 작은 정수 캐시 범위:
# -5 부터 256 은 싱글턴
x, y = 256, 256
x is y # True
# 범위 밖:
a, b = 257, 257
a is b # 불확정(구현 의존)
# 문자열 인터닝:
'hello' is 'hello' # True(컴파일 타임 상수)
# 런타임 생성은 다름:
'he' + 'llo' is 'hello' # 경우에 따라 False
# 결론:
# 내용 비교는 무조건 == 사용
# is 는 None 과 싱글턴에만 사용

5.제어 흐름

조건 분기, 루프, 패턴 매칭, 컴프리헨션.

if / elif / else

들여쓰기로 블록을 나눕니다. elif 로 다중 조건을 다루며 조건식은 임의의 진위 판정식이 될 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
score = 85
if score >= 90:
grade = 'A'
elif score >= 80:
grade = 'B'
elif score >= 70:
grade = 'C'
else:
grade = 'D'
# 한 줄 작성(복잡하면 비권장):
if x > 0: print('正数')
# 여러 조건:
if a and not b: pass
if x in (1, 2, 3): pass

패턴 매칭

match-case 는 구조적 패턴 매칭 (Python 3.10+). 모양으로 매칭하고 구조를 분해합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def handle(command):
match command.split():
case ['quit']:
print('退出')
case ['open', path]:
print(f'打开 {path}')
case ['open', path, *rest]:
print(f'打开 {path} 附加 {rest}')
case _:
print('未知命令')
# 가드:
def f(point):
match point:
case (0, 0): print('原点')
case (x, y) if x == y: print('对角')
case (x, y): print(f'({x}, {y})')
# 기본 분기 _ 로 보완

for 루프

for 는 이터러블을 순회합니다. enumerate 는 인덱스, zip 은 병렬, dict.items() 는 키/값을 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
for i in range(5):
print(i) # 0..4
for idx, item in enumerate(['a', 'b']):
print(idx, item)
for k, v in {'a': 1}.items():
print(k, v)
for a, b in zip([1, 2], ['x', 'y']):
print(a, b) # (1,'x') (2,'y')
# range 스텝:
range(0, 10, 2) # 0 2 4 6 8
# 역순:reversed(lst)
# 역순 + 인덱스:range(len(x)-1, -1, -1)

while 루프

while 은 조건이 참인 동안 반복합니다. break 로 일찍 종료하고 무한 루프를 피하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
n = 0
while n < 5:
print(n)
n += 1
# 무한 루프 + break:
while True:
line = input('> ')
if line == 'quit':
break
print(line)
# while-else:
# break 없이 종료된 경우 else 실행
attempts = 0
while attempts < 3:
if try_connect():
break
attempts += 1
else:
print('连接失败')

break와 continue

break 는 루프 전체를 종료하고 continue 는 현재 반복을 건너뜁니다. break 가 없으면 else 절이 실행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for i in range(10):
if i % 2 == 0:
continue # 짝수 건너뛰기
if i > 7:
break # 중간에 종료
print(i) # 1 3 5 7
# for-else:
for n in range(2, 10):
for d in range(2, n):
if n % d == 0:
break
else:
print(n, '是质数')
# else 는 루프를 끝까지 돌았을 때 실행
# 주의:else 는 for 에 속하며,if 가 아님

컴프리헨션

리스트/딕셔너리/집합 컴프리헨션은 한 줄로 컬렉션을 생성합니다. 조건으로 필터링하고 표현식으로 변환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 리스트 컴프리헨션:
squares = [n ** 2 for n in range(5)]
# [0, 1, 4, 9, 16]
# 조건부:
evens = [n for n in range(10) if n % 2 == 0]
# 사전 컴프리헨션:
{n: n * 2 for n in range(3)}
# {0:0, 1:2, 2:4}
# 집합 컴프리헨션:
{n % 3 for n in range(10)}
# 중첩:
flat = [y for xs in matrix for y in xs]
# 제너레이터 표현식(지연):
total = sum(n * n for n in range(100))

참/거짓 판정

if x 로 진위를 판정합니다. 0, '', [], None 은 거짓입니다. True/False 와의 명시적 비교는 피하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 빈 컨테이너 판정:
items = []
if not items: # GOOD:비면 거짓
print('空列表')
# BAD:이렇게 쓰지 말 것
if len(items) == 0: pass
# None 판정:
if value is not None:
print(value)
# 숫자 판정:
if count: pass # 0 은 거짓
# 문자열:
if name: pass # '' 은 거짓
# 명시적 진릿값:
if bool(x): pass
# 우선순위:가독성 우선,조건을 길게 하지 말 것

반복 관용구

itertools 는 효율적인 반복 조합 도구를 제공합니다. groupby, chain, product 등.

1
2
3
4
5
6
7
8
9
10
11
12
from itertools import chain, groupby, product
# 연결:
list(chain([1, 2], [3, 4])) # [1,2,3,4]
# 곱집합:
list(product('ab', [1, 2]))
# [('a',1),('a',2),('b',1),('b',2)]
# 그룹화(정렬 필요):
rows = sorted(rows, key=lambda r: r['type'])
for key, group in groupby(rows, key=lambda r: r['type']):
print(key, list(group))
# 무한 이터레이션:itertools.count()
# 순열·조합:itertools.permutations / combinations

6.함수

함수 정의, 매개변수 전달, 데코레이터, 제너레이터, 클로저.

함수 정의

def 로 함수를 정의하고 본문은 들여씁니다. return 으로 값을 반환하며 return 이 없으면 None 이 반환됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def greet(name):
return f'Hello, {name}!'
print(greet('Nick'))
# 반환값 없음:
def log(msg):
print(msg) # None 반환
# 여러 반환값(튜플):
def min_max(nums):
return min(nums), max(nums)
lo, hi = min_max([3, 1, 4])
# 타입 애너테이션:
def add(a: int, b: int) -> int:
return a + b
# 독스트링:
def f():
"""描述功能"""
pass

매개변수 종류

위치 인자, 키워드 인자, 기본값. 호출 시 위치와 키워드를 혼용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def show(a, b, c):
print(a, b, c)
# 위치 호출:
show(1, 2, 3)
# 키워드 호출:
show(c=3, a=1, b=2)
# 혼합(위치가 앞):
show(1, c=3, b=2)
# 기본 인자:
def greet(name, greeting='Hi'):
return f'{greeting}, {name}!'
# 기본 인자는 마지막에:
# def f(a=1, b) 오류
# 키워드 전용 인자:
def f(*, strict=False): pass
# 위치 전용 인자(3.8+):
def f(a, /): pass

*args와 **kwargs

*args 는 남는 위치 인자를 튜플로, **kwargs 는 남는 키워드 인자를 딕셔너리로 모읍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def log_all(*args, **kwargs):
print('位置:', args)
print('关键字:', kwargs)
log_all(1, 2, x=3, y=4)
# 전달 호출:
def wrapper(*args, **kwargs):
return original(*args, **kwargs)
# 언패킹 호출:
items = [1, 2, 3]
print(*items) # 1 2 3
config = {'a': 1}
f(**config)
# 순서:위치 -> *args -> 키워드 -> **kwargs
# 남용하지 말 것,인자가 많으면 객체를 권장

익명 함수

lambda 는 한 줄짜리 익명 함수로, 정렬 키나 map/filter 같은 고차 함수에 자주 쓰입니다.

1
2
3
4
5
6
7
8
9
10
11
square = lambda x: x * x
square(5) # 25
# 정렬의 key:
users.sort(key=lambda u: u['age'])
# 고차 함수:
list(map(lambda x: x * 2, [1, 2]))
list(filter(lambda x: x > 1, [1, 2]))
# 즉시 호출:
(lambda x: x + 1)(41) # 42
# 복잡한 로직은 def,lambda 는 간단한 표현식만
# lambda 에 문(if/return)은 작성 불가

클로저

내부 함수가 외부의 변수를 잡아두고, 반환된 함수가 그 스코프를 기억합니다. 팩토리 함수에서 흔히 쓰는 패턴입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def make_multiplier(n):
def multiply(x):
return x * n # n 캡처
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
double(5) # 10
triple(5) # 15
# 클로저에서 바깥 변수 변경:
def counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
# 각 클로저는 자신의 n/count 를 독립적으로 보유

데코레이터

@decorator 는 함수를 감싸 동작을 확장합니다. 코드 수정 없이 타이밍/로깅 등을 추가할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import functools, time
def timing(func):
@functools.wraps(func) # 메타 정보 유지
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f'{func.__name__}: {time.perf_counter()-start:.4f}s')
return result
return wrapper
@timing
def slow():
time.sleep(0.1)
# 인자 있는 데코레이터:
def repeat(times):
def deco(func):
@functools.wraps(func)
def wrapper(*a, **kw):
for _ in range(times): func(*a, **kw)
return wrapper
return deco
# 내장 데코레이터:@staticmethod/@classmethod/@property

제너레이터

yield 는 값을 지연 생성합니다. 제너레이터는 값을 하나씩 흘려보내 대량 시퀀스를 메모리 친화적으로 다룹니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def count_up(n):
i = 0
while i < n:
yield i # 일시 중지 후 반환
i += 1
for x in count_up(3):
print(x) # 0 1 2
# 제너레이터 표현식:
squares = (n * n for n in range(3))
# 무한 시퀀스:
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 지연:전부 메모리에 올리지 않음
# 일회용:다 쓰면 다시 만들어야 함
# 파이프라인:제너레이터를 연결해 스트림 처리

함수형 도구

map/filter/reduce/sorted 가 함수형 처리 도구이며 functools 가 partial 같은 도구를 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import partial, reduce
# map 변환:
doubled = list(map(lambda x: x * 2, [1, 2, 3]))
# filter 필터:
evens = list(filter(lambda x: x % 2 == 0, range(6)))
# reduce 집계:
total = reduce(lambda a, b: a + b, [1, 2, 3])
# 부분 적용:인자 고정
def power(base, exp): return base ** exp
square = partial(power, exp=2)
square(5) # 25
# 제너레이터는 map/filter 에 전달 가능:
list(map(str, range(3))) # ['0','1','2']
# 모던 스타일은 컴프리헨션 권장,가독성 높음

재귀

함수가 자기 자신을 호출합니다. 반드시 기저 사례를 두세요. CPython 의 재귀 깊이 한계는 약 1000 (sys.setrecursionlimit 으로 조정).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
factorial(5) # 120
# 재귀 깊이 제한:
import sys
sys.getrecursionlimit() # 기본 1000
# 깊은 재귀는 반복 또는 스택 사용:
def fib_iter(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
# 트리 순회/분할 정복은 재귀가 자연스러움
# 메모이제이션:@functools.lru_cache

7.문자열

불변 str, f-string 포매팅, 자주 쓰는 메서드, 인코딩.

문자열 기초

작은따옴표/큰따옴표/삼중따옴표로 만들며 이스케이프 시퀀스가 적용됩니다. str 은 불변이며 연산은 새 문자열을 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
s1 = '单引号'
s2 = "双引号"
s3 = '''多行
字符串'''
# raw 문자열(경로/정규식):
r'C:\\path'
# 이스케이프:
'\n' # 줄바꿈
'\t' # 탭
'\\' # 백슬래시
# 문자열은 시퀀스:
s = 'hello'
s[0] # 'h'
s[1:4] # 'ell'
# 불변:
# s[0] = 'H' 오류
# 연결:
first + last
' '.join(['a', 'b'])

자주 쓰는 메서드

upper/lower 는 대소문자, strip 은 공백 제거, split/join 은 분리·결합, startswith 는 접두사 판정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s = ' Hello World '
s.upper() # ' HELLO WORLD '
s.lower()
s.strip() # 앞뒤 공백 제거
s.startswith('He') # True
s.endswith('ld') # True
s.replace('World', 'Python')
# 검색:
s.find('World') # 인덱스 또는 -1
s.index('World') # 없으면 ValueError
s.count('l')
# 분할·결합:
'1,2,3'.split(',') # ['1','2','3']
'-'.join(['a', 'b']) # 'a-b'
# 판정:
'abc'.isalpha(); '123'.isdigit()

f-string 포매팅

f"{var}" 로 표현식을 삽입합니다. 형식으로 정렬·정밀도·천 단위 구분을 제어하며 3.12+ 에서는 따옴표 재사용도 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name = 'Nick'
age = 30
f'{name} 今年 {age} 岁'
# 표현식:
f'{age + 1}'
# 너비 정렬:
f'{name:>10}' # 오른쪽 정렬 10
f'{name:<10}' # 왼쪽 정렬
# 숫자 형식:
f'{3.14159:.2f}' # '3.14'
f'{1000000:,}' # '1,000,000'
f'{0.5:.0%}' # '50%'
# 진법:
f'{255:x}' # 'ff'
# 날짜:
import datetime
f'{datetime.date.today():%Y-%m-%d}'
# 사전 값 조회:f'{d["key"]}'

format과 % 포매팅

str.format 은 위치/이름 플레이스홀더, % 연산자는 오래된 방식. 새 코드에서는 f-string 사용을 권장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# format 메서드:
'{0} {1}'.format('a', 'b')
'{name} 今年 {age}'.format(name='Nick', age=30)
# 정렬:
'{:>10}'.format('hi') # 오른쪽 정렬
'{:.2f}'.format(3.14159)
# 구식 % 포맷:
'%s 今年 %d' % ('Nick', 30)
'%.2f' % 3.14159
'%x' % 255
# 3 가지 스타일 대조:
# f-string:f'{x:.2f}'(3.6+ 권장)
# format:'{:.2f}'.format(x)
# 구식:'%.2f' % x
# 새 코드는 f-string 사용

분할과 결합

split 은 구분자로 분리, rsplit 은 오른쪽부터, partition 은 세 부분으로 분리, join 은 효율적으로 결합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'a,b,c'.split(',') # ['a','b','c']
'a,b,c'.split(',', 1) # ['a', 'b,c']
'a b c'.split() # 공백으로 자동 분할
'a-b-c'.rsplit('-', 1) # ['a-b', 'c']
'key=value'.partition('=') # ('key','=','value')
# 결합(성능):
parts = ['a', 'b', 'c']
', '.join(parts)
# 한 글자씩 분할:
list('abc')
# 여러 구분자:
import re
re.split(r'[;,|]', 'a;b,c|d')
# 루프 안 + 연결은 피할 것(O(n^2))

트림과 패딩

strip/lstrip/rstrip 으로 공백을 제거하고, zfill 은 0 으로 채우고, center 는 가운데 정렬, removeprefix/removesuffix 는 3.9+.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
s = ' hello '
s.strip() # 'hello'
s.lstrip() # 'hello '
s.rstrip() # ' hello'
' x \n'.strip() # 'x'
# 지정 문자:
'xxhelloxx'.strip('x') # 'hello'
# 패딩:
'42'.zfill(5) # '00042'
'hi'.center(7, '-') # '--hi---'
# 접두사 제거(3.9+):
'/usr/bin'.removeprefix('/usr/') # 'bin'
'/usr/bin'.removesuffix('/bin') # '/usr'
# 줄바꿈 제거:
text.rstrip('\n')

Unicode와 인코딩

Python str 은 Unicode 코드 포인트의 시퀀스입니다. encode 로 바이트열, decode 로 다시 문자열, len 은 코드 포인트 수를 셉니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = '你好👋'
len(s) # 3(emoji 는 1 코드포인트)
# 인코딩:
b = s.encode('utf-8')
# 디코딩:
b.decode('utf-8')
# 코드포인트:
ord('中') # 20013
chr(20013) # '中'
# 정규화:
import unicodedata
unicodedata.normalize('NFKC', s)
# 대소문자:
s.casefold() # 더 엄격한 소문자화
# 인코딩 오류 처리:
b.decode('utf-8', errors='replace')
# 코드포인트 순회:for ch in s 는 이미 코드포인트 단위

찾기와 바꾸기

find/index 로 찾기, replace 로 바꾸기, translate 로 테이블 치환, count 로 등장 횟수.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s = 'the quick brown fox'
s.find('quick') # 4
s.find('z') # -1
s.index('quick') # 4(없으면 예외)
s.rfind('o') # 오른쪽에서 탐색
s.count('o') # 4
# 치환:
s.replace('fox', 'dog')
s.replace('o', '0', 1) # 1 번만 치환
# 여러 개 치환(translate):
table = str.maketrans({'a': 'A', 'e': 'E'})
'apple'.translate(table) # 'ApplE'
# 대소문자 무시 치환:
import re
re.sub('(?i)the', 'THE', s)
# 부분 문자열 추출:s[4:9]

텍스트 줄바꿈

textwrap 는 텍스트 줄바꿈: wrap 으로 폭 지정, dedent 으로 공통 들여쓰기 제거, fill 로 단락화.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import textwrap
long_text = '这是一个比较长的文本,需要按照指定宽度断行显示。'
# 너비로 줄바꿈:
lines = textwrap.wrap(long_text, width=20)
# 문단으로 정렬:
textwrap.fill(long_text, width=20)
# 공통 들여쓰기 제거:
block = '''\
line1
line2'''
textwrap.dedent(block)
# 들여쓰기:
textwrap.indent('a\nb', ' ')
# 용도:CLI 도움말 텍스트 정렬
# 콘솔 출력 정렬

8.컬렉션과 컨테이너

list/dict/set/tuple 조작, collections 모듈, 정렬.

리스트 연산

append 는 끝에 추가, insert 는 삽입, remove 는 값으로 삭제, pop 은 끝/지정 위치 꺼내기, index 로 위치, count 로 개수.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nums = [3, 1, 2]
nums.append(4) # [3,1,2,4]
nums.insert(0, 0) # [0,3,1,2,4]
nums.remove(1) # 첫 번째 1 삭제
last = nums.pop() # 끝 pop
first = nums.pop(0) # 지정 위치 pop
nums.index(2) # 인덱스
nums.count(1)
nums.reverse() # 제자리 반전
nums.clear() # 비우기
# 복사:
nums[:] 或 nums.copy()
# 확장:
nums.extend([5, 6])
nums += [7, 8]

딕셔너리 연산

값 가져오기는 .get, 병합은 update / |, 순회는 items, 값 기준 정렬, 중첩 접근.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
d = {'a': 1, 'b': 2}
d['c'] = 3 # 추가/업데이트
d.get('x', 0) # 기본 0
val = d.setdefault('y', 0)
# 병합(3.9+):
merged = d | {'z': 9}
d.update({'z': 9})
# 순회:
for k, v in d.items(): pass
# 키로 정렬:
sorted(d.items())
# 값으로 정렬:
sorted(d.items(), key=lambda kv: kv[1])
# 중첩:
d['user']['name']
# 안전한 접근 체인:
d.get('user', {}).get('name')
# 기본 사전:collections.defaultdict

집합 연산

합집합 |, 교집합 &, 차집합 -, 대칭차 ^. 멤버 판정은 O(1). 중복 제거에 적합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = {1, 2, 3}
b = {2, 3, 4}
a | b # {1,2,3,4} 합집합
a & b # {2,3} 교집합
a - b # {1} 차집합
b - a # {4}
a ^ b # {1,4} 대칭 차집합
# 판정:
2 in a # True O(1)
# 부분 집합:
a <= b; a < b
# 중복 제거하며 순서 유지:
list(dict.fromkeys([3, 1, 3, 2]))
# 중복 요소 찾기:
[x for x in lst if lst.count(x) > 1]
# 불변:frozenset

튜플 사용법

tuple 은 불변 시퀀스로, 다중 반환값·레코드·딕셔너리 키에 쓰입니다. namedtuple 은 필드가 있는 변형입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t = (1, 2, 3)
# 언패킹:
a, b, c = t
# 한 요소는 쉼표 필요:
single = (1,)
# 레코드 용도:
point = (10, 20)
x, y = point
# 명명된 튜플:
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
p.x; p[0] # 2 가지 접근
# 명명된 튜플에는 타입 애너테이션 버전도:
# from typing import NamedTuple
# 불변:t[0] = 9 오류
# 사전 키로 사용 가능(해시 가능)

collections 도구

Counter 는 집계, deque 는 양쪽 끝 큐, defaultdict 는 키 기본값, OrderedDict 는 순서 보존 딕셔너리입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from collections import Counter, deque, defaultdict
# 카운트:
Counter('abracadabra').most_common(2)
# [('a', 5), ('r', 2)]
# 양끝 큐:
q = deque([1, 2])
q.appendleft(0); q.pop(); q.popleft()
# 고정 길이(오래된 요소 폐기):
last5 = deque(maxlen=5)
# 기본 사전:
dd = defaultdict(list)
dd['k'].append(1) # 빈 list 자동 생성
# 순서 있는 사전:
# Python 3.7+ 의 dict 는 원래 순서 유지
# 카운터 연산:
Counter('aab') + Counter('abb')
# Counter('aab') - Counter('b')

정렬

sorted 는 새 리스트를 반환하고 list.sort 는 제자리 정렬합니다. key 로 정렬 기준을 지정하고 reverse=True 로 역순.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nums = [3, 1, 2]
sorted(nums) # [1,2,3] 새 리스트
nums.sort() # 제자리 정렬
# 역순:
sorted(nums, reverse=True)
# key 로 정렬:
words = ['banana', 'apple', 'cherry']
sorted(words, key=len)
# 사전 리스트:
users.sort(key=lambda u: u['age'])
# 다단계 정렬:
sorted(users, key=lambda u: (u['age'], u['name']))
# 안정 정렬:
sorted(users, key=lambda u: u['age'])
# 내림차순 키:
sorted(users, key=lambda u: -u['age'])

슬라이스

seq[start:stop:step] 로 슬라이스합니다. 음수 인덱스는 끝에서 셉니다. 뒤집기·보폭·복사에 활용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = list(range(10))
s[2:5] # [2, 3, 4]
s[:3] # 처음부터
s[7:] # 끝까지
s[-3:] # 끝 3 개
s[::2] # 하나 걸러
s[::-1] # 반전
s[1:8:2] # 스텝
# 할당:
s[1:3] = [9] # 일부 치환
# 삭제:
del s[1:3]
# 복사:
copy = s[:]
# 문자열도 동일:
'hello'[::-1] # 'olleh'
# 슬라이스는 범위 초과도 오류가 아님

힙과 큐

heapq 는 우선순위 정렬, queue 는 스레드 안전한(우선순위) 큐를 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import heapq
nums = [3, 1, 4, 1, 5]
heapq.heapify(nums) # 최소 힙 생성
heapq.heappop(nums) # 최솟값 꺼내기
heapq.heappush(nums, 0)
# 최대/최소 n 개 꺼내기:
heapq.nlargest(2, [3, 1, 4])
heapq.nsmallest(2, [3, 1, 4])
# 우선순위 큐:
import queue
pq = queue.PriorityQueue()
pq.put((1, '低'))
pq.put((0, '高'))
pq.get() # (0, '高')
# 스레드 안전:queue.Queue()
# 작업 큐:queue.Queue(maxsize)

9.메모리와 성능

가비지 컬렉션, 참조 카운팅, 메모리 도구, 성능 최적화.

가비지 컬렉션

참조 카운팅이 기본이며 세대별 GC 가 순환 참조를 회수합니다. 직접 free 할 필요는 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 객체의 참조 카운트가 0 이 되면 회수
x = [1, 2]
y = x
del x # 카운트 감소,아직 생존
del y # 카운트 0,회수
# 순환 참조:
import gc
gc.collect() # 수동 트리거
# 객체 파괴 훅:
class Temp:
def __del__(self):
print('回收')
# 보통 수동 gc 불필요
# 참조 카운트는 순환 참조를 처리할 수 없고,세대별 GC 가 보완

memoryview와 제로 카피

memoryview 는 버퍼를 복사 없이 뷰로 다룹니다. 대용량 바이너리 처리에 효율적입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data = bytearray(b'hello world')
view = memoryview(data)
view[0] # 104
view[6:11] # b'world'
# 슬라이스는 뷰이며 복사가 아님:
sub = view[6:11]
sub[0] = 87 # 변경이 원본에 영향
# 포맷:
mem = memoryview(b'\x00\x01')
mem.cast('H') # 16 비트 부호 없이 읽기
# 용도:
# 1. 큰 파일 분할 처리
# 2. 프로토콜 파싱에서 복사 회피
# 3. struct 로 뷰 결합
# 성능이 중요한 경우 슬라이스 복사보다 빠름

약한 참조

weakref.ref 는 참조 대상의 회수를 막지 않습니다. 큰 객체 캐시와 순환 참조 누수 방지 용도로 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import weakref
class Node:
pass
node = Node()
ref = weakref.ref(node)
print(ref()) # 객체
node = None # 강한 참조 해제
gc.collect()
print(ref()) # None(회수됨)
# WeakKeyDictionary/WeakValueDictionary:
# 키/값을 약한 참조,회수 시 자동 제거
cache = weakref.WeakValueDictionary()
# 순환 참조 예방:
# 어느 한쪽을 약한 참조로 대체
# 용도:캐시、관찰자、싱글턴

__slots__로 메모리 절약

__slots__ 는 속성을 고정해 인스턴스당 __dict__ 를 없앱니다. 수백만 개의 작은 객체에서 메모리와 속도를 크게 개선합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 기본적으로 각 인스턴스에 __dict__ 가 있음:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# __slots__ 사용:
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# p.z = 3 오류:선언되지 않은 속성
# 절약:인스턴스마다 dict 가 1 개 줄어듦
# 수백만 개의 작은 객체에서 효과 큼
# 단점:동적으로 속성 추가 불가,__dict__ 없음

대용량 데이터 처리

큰 데이터는 청크 단위로, 반복은 지연 평가로 처리합니다. 한 번에 모두 읽지 말고 파일은 한 줄씩 스트리밍하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 큰 파일을 줄 단위로 읽기:
with open('big.log') as f:
for line in f: # 지연으로 한 줄씩
process(line)
# 큰 리스트는 제너레이터:
def read_all_lines(path):
with open(path) as f:
for line in f:
yield line
# 분할 처리:
def chunks(iterable, size):
from itertools import islice
it = iter(iterable)
while batch := list(islice(it, size)):
yield batch
for batch in chunks(range(100), 10):
process_batch(batch)
# list(map(...)) 로 전부 읽는 것은 피할 것
# 대규모 수치는 numpy 가 더 효율적

프로파일링

cProfile 는 함수별 시간 분석, timeit 는 마이크로 벤치마크입니다. 병목을 찾은 뒤 최적화하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import timeit, cProfile
# 마이크로 벤치마크:
timeit.timeit("'x' * 100", number=10000)
# 또는 커맨드라인:
# python3 -m timeit "'-'.join(str(n) for n in range(100))"
# 전체 프로파일링:
# python3 -m cProfile -s cumulative script.py
# 코드 내 프로파일링:
cProfile.run('my_func()', sort='cumulative')
# 타이밍 데코레이터:
import time
def timed(f):
def w(*a, **k):
t = time.perf_counter()
r = f(*a, **k)
print(f.__name__, time.perf_counter() - t)
return r
return w
# 원칙:측정 후 최적화

성능 최적화 팁

로컬이 글로벌보다 빠르고 컴프리헨션이 루프보다 빠르며 집합 조회는 O(1). 루프의 무거운 계산은 바깥으로 빼세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 지역 변수 캐시:
import math
def f(n):
m = math.sqrt # 지역 참조
return m(n)
# 집합 멤버 O(1):
allowed = set(['a', 'b'])
if x in allowed: pass
# 컴프리헨션 vs 루프:
# 보통 컴프리헨션이 더 빠르고 간결
squares = [n * n for n in range(1000)]
# 중복 계산 피하기:
# 루프 밖으로 공통 인자 빼기
# 문자열 연결은 join 사용
# 대규모 수치는 numpy
# 복잡한 핫패스는 C 확장 / numba 검토

흔한 메모리 오류

MemoryError (메모리 부족), RecursionError (스택 초과), 순환 참조 문제. 진단 도구도 함께 다룹니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 스택 오버플로:
def infinite():
return infinite()
# RecursionError: maximum recursion depth
# 메모리 부족:
# MemoryError(대량 데이터를 한 번에 읽기)
# 진단:
import tracemalloc
tracemalloc.start()
# 코드 실행...
snapshot = tracemalloc.take_snapshot()
top = snapshot.statistics('lineno')
print(top[:5])
# 객체 참조 그래프:
import objgraph # 서드파티
# objgraph.show_refs([obj])
# 해결:
# 분할 처리、참조 해제、제너레이터 사용

10.클래스와 객체

class 정의, 상속, 던더 메서드, dataclass, enum.

클래스 기초

class 가 타입을 정의합니다. __init__ 이 초기화하고 self 가 인스턴스를 가리킵니다. 인스턴스 메서드는 인스턴스에 바인딩됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f'Hi, {self.name}'
p = Person('Nick', 30)
p.greet()
# 클래스 속성(공유):
class Counter:
total = 0 # 클래스 레벨
def __init__(self):
Counter.total += 1
# 인스턴스 속성(개별):
p.name
# 동적 속성:
p.email = '[email protected]'
# 타입 검사:isinstance(p, Person)

속성 접근

인스턴스 속성이 클래스 속성보다 우선합니다. setattr/getattr/hasattr 로 동적으로 접근하고 property 로 계산 속성을 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User:
default_role = 'user' # 클래스 속성
def __init__(self, name):
self.name = name
# 속성 탐색:인스턴스 -> 클래스 -> 기반 클래스
u = User('a')
u.default_role # 'user'
# 동적 접근:
hasattr(u, 'name') # True
getattr(u, 'name', None)
setattr(u, 'role', 'admin')
# property 속성:
class Circle:
def __init__(self, r):
self._r = r
@property
def area(self):
return 3.14 * self._r ** 2
c = Circle(2)
c.area # 메서드를 속성으로 접근
# property setter 로 검증

세 가지 메서드

인스턴스 메서드는 self, 클래스 메서드는 cls, 스태틱 메서드는 둘 다 갖지 않습니다. @staticmethod/@classmethod 로 장식합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Tool:
count = 0
def __init__(self, name):
self.name = name
Tool.count += 1
def instance_method(self):
return self.name # 인스턴스 접근
@classmethod
def class_method(cls):
return cls.count # 클래스 접근
@staticmethod
def helper(x):
return x * 2 # self/cls 없음
# 호출:
t = Tool('锤子')
t.instance_method()
Tool.class_method()
Tool.helper(4)
# 클래스 메서드는 팩토리/카운터에
# 정적 메서드는 유틸리티 함수에

상속

서브클래스가 부모 메서드를 상속합니다. super() 로 부모를 호출하고 필요시 오버라이드합니다. 다중 상속은 MRO 를 따릅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f'{self.name} 发出声音'
class Dog(Animal):
def speak(self):
return f'{self.name} 汪汪'
def fetch(self):
return f'{self.name} 捡球'
d = Dog('Rex')
d.speak() # 오버라이드
# super() 로 부모 클래스 호출:
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name)
self.color = color
# isinstance 와 issubclass
# 다중 상속은 MRO 순서로 탐색
# 덕 타이핑:상속보다 프로토콜을 많이 사용

던더 메서드

이중 밑줄 메서드는 내장 동작을 커스터마이즈합니다: __repr__ 표시, __eq__ 동등, __len__ 길이, __add__ 더하기.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f'Vector({self.x}, {self.y})'
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __len__(self):
return 2
v = Vector(1, 2)
str(v); v + v; v == Vector(1, 2)
# 자주 쓰는 것:
# __str__ 사용자용
# __getitem__ 인덱스 접근
# __call__ 객체를 호출 가능하게
# __bool__ 진릿값 판정
# __enter__/__exit__ 컨텍스트 매니저

dataclass

@dataclass 는 __init__, __repr__, __eq__ 등을 자동 생성하는 선언적 데이터 클래스 작성법입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from dataclasses import dataclass, field
@dataclass
class Point:
x: int
y: int
label: str = 'origin' # 기본값
tags: list = field(default_factory=list)
p = Point(1, 2)
p2 = Point(1, 2)
p == p2 # True(자동 __eq__)
repr(p) # Point(x=1, y=2, label='origin')
# 동결(불변):
@dataclass(frozen=True)
class Config:
debug: bool = False
# 정렬:
@dataclass(order=True)
class Item:
price: int
# 사전 변환:
from dataclasses import asdict, astuple
asdict(p)
# 상속 필드도 처리됨

Enum 열거형

Enum 은 이름 있는 상수 집합을 정의합니다. str, 자동 번호, 순회를 지원하며 매직 넘버를 대체합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from enum import Enum, auto
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Status(Enum):
ACTIVE = auto() # 자동 번호 부여
INACTIVE = auto()
# 접근:
Color.RED
Color(1) # 값으로 검색
Color.RED.value # 1
Color.RED.name # 'RED'
# 순회:
for c in Color: pass
# 문자열 Enum:
class Mode(str, Enum):
FAST = 'fast'
SLOW = 'slow'
# 비교:
Color.RED is Color.RED # True 싱글턴
# 설명:
class Kind(Enum):
A = ('a', '描述')
def __init__(self, code, desc):
self.code = code
self.desc = desc

컨텍스트 관리자

with 가 자원을 관리합니다. __enter__/__exit__ 을 구현하거나 @contextlib.contextmanager 로 제너레이터로 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc, tb):
import time
print(f'耗时 {time.perf_counter()-self.start:.4f}s')
return False # False 는 예외를 삼키지 않음
with Timer():
pass
# 간단 버전:
from contextlib import contextmanager
@contextmanager
def timer():
import time
t = time.perf_counter()
yield
print(f'耗时 {time.perf_counter()-t:.4f}s')
with timer():
pass
# with open(...) 은 내장 컨텍스트 매니저
# contextlib.suppress / ExitStack

11.예외 처리

try-except, 예외 계층, 사용자 정의 예외, with 자원 관리.

try / except

예외를 잡아 오류를 처리합니다. except 는 타입별로 필터링하고 as e 로 예외 객체를 받습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try:
num = int('abc')
except ValueError:
print('无法转换')
# 예외를 잡아 접근:
except ValueError as e:
print(e)
# 여러 타입:
except (ValueError, TypeError):
pass
# 순서:더 구체적인 타입을 앞에
try:
result = risky()
except (ValueError, KeyError) as e:
print(f'可预期错误: {e}')
except Exception as e:
print(f'未知错误: {e}')
# 잡지 않으면 위로 전파되어 크래시

else와 finally

else 는 예외 없이 성공한 경우에만 실행되고, finally 는 성공/실패와 무관하게 실행되어 정리 작업에 적합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try:
data = parse(input_data)
except ValueError as e:
print(f'解析失败: {e}')
else:
print('解析成功') # 예외가 없을 때만 실행
finally:
cleanup() # 항상 실행
# 조합:
# try 에는 오류 가능 코드
# else 에는 성공 시 로직
# finally 에는 정리(연결 종료/해제)
# finally 는 예외 전파를 막지 않음
# else 나 finally 는 생략 가능

예외 계층

Exception 이 대부분의 예외 기본 클래스이며 ValueError, TypeError, KeyError, IndexError 가 대표적입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try:
d['missing'] # KeyError
except KeyError:
pass
try:
[1][5] # IndexError
except IndexError:
pass
try:
int('x') # ValueError
except ValueError:
pass
try:
1 / 0 # ZeroDivisionError
except ZeroDivisionError:
pass
# 계층:
# BaseException > Exception > 구체적 오류
# KeyboardInterrupt/SystemExit 는 Exception 이 아님
# except Exception 으로 비즈니스 오류를 잡음
# 맨 except: 는 피할 것(종료까지 삼킴)

사용자 정의 예외

Exception 을 상속해 도메인 예외를 정의하고 필드와 메시지를 담습니다. 호출 측은 타입으로 잡습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ValidationError(Exception):
pass
class NotFoundError(Exception):
def __init__(self, entity, key):
self.entity = entity
self.key = key
super().__init__(f'{entity} 不存在: {key}')
# 발생:
def get_user(user_id):
if user_id == 0:
raise NotFoundError('用户', user_id)
return {'id': user_id}
# 잡기:
try:
get_user(0)
except NotFoundError as e:
print(e.entity, e.key)
# 기반 클래스로 묶어 처리:
class AppError(Exception): pass
# 파생 SpecificError(AppError)

raise와 체이닝

raise 로 능동적으로 던지고 raise ... from ... 로 원인을 연결합니다. except 안의 베어 raise 는 현재 예외를 그대로 다시 던집니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def validate(age):
if age < 0:
raise ValueError('年龄不能为负')
# 다시 던지기:
try:
risky()
except SomeError:
raise # 그대로 다시 던짐
# 예외 체인(원인 유지):
try:
parse_file(path)
except FileNotFoundError as e:
raise RuntimeError(f'读取失败: {path}') from e
# 체인 억제:
raise NewError('x') from None
# 체인 표시:Traceback (most recent call last):
# 원인이 앞,현재 것이 뒤
# 디버그 시 __cause__ 유지

assert

assert 는 개발 중 불변 조건 검사입니다. python -O 로 비활성화되므로 사용자 입력 검증엔 절대 사용하지 마세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def process(data):
assert data is not None, 'data 不能为空'
assert isinstance(data, list), '期望列表'
return len(data)
# 어서션 실패 시 AssertionError
# 어서션 비활성 실행:
# python3 -O script.py
# 용도:
# 1. 내부 불변 조건
# 2. 디버그 단계의 인자 검사
# 3. 타입 전제 조건
# assert 에 쓰지 말 것:
# 사용자 입력 검증
# 보안 경계 검사
# 중요한 비즈니스 로직

예외와 로깅

logging 은 스택트레이스와 함께 예외를 기록합니다. except 블록 안에서는 logger.exception 이 표준입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import logging
logging.basicConfig(level=logging.INFO)
try:
x = 1 / 0
except ZeroDivisionError:
logging.exception('除法出错') # 스택 포함
# 또는:
logger.error('出错', exc_info=True)
# 레벨:
logging.debug/info/warning/error
# 설정:
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(message)s',
)
# 구조화:
logger.info('请求', extra={'id': req_id})
# 로그에 print 는 쓰지 말 것(레벨/타임스탬프 없음)

오류 처리 패턴

EAFP (허락보다 용서를) vs LBYL (보면서 가기). 가드 절과 조기 반환으로 중첩을 줄입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# EAFP:시도 후 잡기
def parse_eafp(s):
try:
return int(s)
except ValueError:
return None
# LBYL:확인 후 조작
def parse_lbyl(s):
if not s.isdigit():
return None
return int(s)
# 범용 잡기 vs 정밀 잡기:
# 정밀:except ValueError
# 폴백:except Exception
# 가드 절로 조기 반환:
def handle(user):
if user is None:
return
if not user.active:
return
process(user)
# 중첩을 줄이고,로직을 평탄하게

12.입출력

print/input, 파일 I/O, pathlib, JSON/CSV, 표준 스트림.

출력과 입력

print 는 여러 인자를 받으며 sep/end 로 구분과 끝을 제어합니다. input 은 한 줄을 str 로 읽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print('a', 'b') # a b
print('a', 'b', sep=', ') # a, b
print('end', end='!\n')
# 파일에 출력:
with open('out.txt', 'w') as f:
print('text', file=f)
# 입력:
name = input('你的名字? ') # 항상 str
# 숫자 입력:
age = int(input('年龄? '))
# 한 번에 여러 입력:
a, b = input().split()
# 변수 출력:
print(f'Hi {name}')
# 콘솔 진행률:
print('\r进度 50%', end='')
# 버퍼 플러시:print(..., flush=True)

파일 읽기/쓰기

open 모드: r 읽기, w 쓰기, a 추가, b 바이너리. 읽기는 read/readline/readlines.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 읽기:
f = open('data.txt', encoding='utf-8')
text = f.read() # 전부
line = f.readline() # 한 줄
lines = f.readlines() # 줄 목록
f.close()
# 쓰기:
f = open('out.txt', 'w', encoding='utf-8')
f.write('内容\n')
f.writelines(['a', 'b'])
f.close()
# 모드:
# 'r' 읽기 'w' 덮어쓰기 'a' 추가
# 'rb'/'wb' 바이너리
# 'r+' 읽기/쓰기
# 반드시 with 로 자동 닫기

with와 파일

with 는 예외 발생 시에도 파일을 닫아 줍니다. 파일을 다룰 때의 표준 관용구입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 권장 방식:
with open('data.txt', encoding='utf-8') as f:
text = f.read() # 다 쓰면 자동 닫힘
# 줄 단위:
with open('data.txt', encoding='utf-8') as f:
for line in f:
print(line.rstrip())
# 쓰기:
with open('out.txt', 'w', encoding='utf-8') as f:
f.write('hello')
# 추가:
with open('log.txt', 'a') as f:
f.write('更多')
# 오류 처리:
try:
with open('missing.txt') as f:
pass
except FileNotFoundError:
print('文件不存在')
# 인코딩 지정 필수,크로스플랫폼에서 일관

pathlib 경로

Path 는 플랫폼에 독립적인 경로(/, exists, mkdir, read_text 등) 연산을 제공하며 os.path 의 현대적 대체입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pathlib import Path
p = Path('data') / 'sub' / 'file.txt'
p.exists() # 존재하는지
p.is_file(); p.is_dir()
p.mkdir(parents=True, exist_ok=True)
# 읽기/쓰기:
text = Path('data.txt').read_text(encoding='utf-8')
Path('out.txt').write_text('hi', encoding='utf-8')
# 순회:
for f in Path('.').glob('*.py'):
print(f.name)
# rglob 재귀:
Path('.').rglob('*.json')
# 속성:
p.name; p.suffix; p.stem
p.parent; p.absolute()
# 자주 쓰는 디렉터리:
Path.home()
Path.cwd()
# 작성이 간단하고,크로스플랫폼 통일

JSON 읽기/쓰기

json.loads/dumps 로 직렬화하고 dict/list 를 JSON 과 상호 변환합니다. ensure_ascii=False 로 비 ASCII 문자를 그대로 유지합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import json
data = {'name': 'Nick', 'tags': ['a', 'b']}
# 직렬화:
text = json.dumps(data, ensure_ascii=False)
# 정렬:
json.dumps(data, indent=2, ensure_ascii=False)
# 역직렬화:
obj = json.loads(text)
# 파일 읽기/쓰기:
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with open('data.json', encoding='utf-8') as f:
obj = json.load(f)
# 오류:
try:
json.loads('invalid')
except json.JSONDecodeError as e:
print(e)
# 타입:JSON 객체 <-> dict

CSV 읽기/쓰기

csv 모듈은 쉼표로 구분된 데이터를 처리합니다. writer 로 쓰고 reader / DictReader 로 읽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import csv
rows = [['name', 'age'], ['Nick', 30], ['Anna', 25]]
# 쓰기:
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(rows)
# 읽기:
with open('data.csv', newline='', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row)
# 사전 읽기/쓰기:
with open('data.csv', encoding='utf-8') as f:
for row in csv.DictReader(f):
print(row['name'])
# 사전 쓰기:
with open('o.csv', 'w', newline='') as f:
w = csv.DictWriter(f, fieldnames=['name'])
w.writeheader()
w.writerow({'name': 'x'})
# 따옴표/줄바꿈 처리 주의

표준 스트림

sys.stdin/stdout/stderr 로 파이프 데이터를 다루며 stdin 을 한 줄씩 처리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
# 한 줄 읽기:
line = sys.stdin.readline()
# 전체를 줄 단위로(파이프):
for line in sys.stdin:
sys.stdout.write(line.upper())
# 오류 스트림:
print('error', file=sys.stderr)
# 파이프 사용법:
# cat data.txt | python3 filter.py
# 출력:
# python3 filter.py > out.txt
# 인코딩:
# PYTHONIOENCODING=utf-8 로 스트림 인코딩 제어
# input() 은 sys.stdin.readline() 과 동일
# 진행률은 stderr 로 출력해 stdout 을 오염시키지 않기

바이너리 I/O

바이너리 모드는 바이트열을 읽고 쓰며 struct 로 패킹/언패킹, seek/tell 로 랜덤 접근을 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 바이트 읽기/쓰기:
with open('img.bin', 'rb') as f:
data = f.read()
with open('out.bin', 'wb') as f:
f.write(b'\x00\x01\x02')
# 고정 크기 분할:
with open('file.bin', 'rb') as f:
while chunk := f.read(1024):
process(chunk)
# struct 패킹:
import struct
packed = struct.pack('>I', 1024) # 4 바이트
value = struct.unpack('>I', packed)[0]
# 랜덤 접근:
with open('db.bin', 'rb') as f:
f.seek(100) # 오프셋으로 이동
data = f.read(10)
# 끝에 추가:'ab' 모드

13.일반적인 함정

Python 일상 개발에서 가장 자주 만나는 함정과 올바른 관용구.

가변 기본 인자

가변 기본 인자는 정의 시 한 번 만들어져 호출 간에 누적됩니다. None 센티널을 사용하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:공유되는 기본 리스트
def add(item, basket=[]):
basket.append(item)
return basket
add('a') # ['a']
add('b') # ['a', 'b'] !
# GOOD:None + 함수 내부에서 생성
def add(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
# 원인:기본값은 정의 시 한 번만 평가
# 영향:list/dict/set 모두 영향 받음

is와 == 혼동

is 는 동일성, == 는 값을 비교합니다. 작은 정수/짧은 문자열은 인터닝될 수 있어 is 로 내용을 비교하지 마세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
# BAD:is 로 내용 비교
a = [1, 2]
b = [1, 2]
a is b # False,하지만 값은 같음
# GOOD:== 로 값 비교
a == b # True
# None 판정은 is:
x = None
x is None # True
# 인터닝 주의:
# CPython 에서 257 is 257 이 True 가 될 수 있음
# 다만 구현 세부사항이므로 의존하지 말 것
# 규칙:내용은 ==,싱글턴/None 은 is

루프 클로저 함정

루프 안의 lambda 는 변수를 늦게 바인딩합니다. 지금 값이 필요하면 기본 인자로 캡처하세요.

1
2
3
4
5
6
7
8
9
10
11
# BAD:전부 9 출력
funcs = [lambda: i for i in range(10)]
funcs[0]() # 9
# GOOD:현재 값을 바인딩
funcs = [lambda i=i: i for i in range(10)]
funcs[0]() # 0
# 또는 기본 인자로 캡처:
funcs = [(lambda x: lambda: x)(i) for i in range(10)]
# 원인:클로저는 값이 아닌 변수명을 캡처
# 수정:기본 인자는 정의 시 평가
# 주의:for 루프 변수는 종료 후에도 남음

문자열 결합

루프에서 += 결합은 O(n^2) 입니다. 리스트에 모아 join 하거나 io.StringIO 를 사용하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# BAD:루프 안에서 연결
s = ''
for i in range(10000):
s += str(i) # 매번 새 문자열 생성
# GOOD:모아서 join
parts = []
for i in range(10000):
parts.append(str(i))
s = ''.join(parts)
# 또는 제너레이터:
s = ''.join(str(i) for i in range(10000))
# 조금만 있으면 + 문제없음:
name = first + ' ' + last
# 규칙:많은 연결은 join 사용
# f-string 은 알려진 조각에 적합

반복 중 리스트 수정

반복 중 리스트를 변경하면 요소를 건너뛰거나 중복 처리합니다. 복사본을 순회하거나 새 리스트를 만드세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:요소 삭제로 건너뜀
items = [1, 2, 3, 4]
for x in items:
if x % 2 == 0:
items.remove(x) # 인덱스 어긋남
# GOOD:새 리스트로 재구성
items = [x for x in items if x % 2 == 1]
# 또는 복사본 순회:
for x in items[:]:
if x % 2 == 0:
items.remove(x)
# dict 순회 중 키 변경도 위험
# 원칙:이터레이터 무효 -> 복사본 순회
# 또는 새 컨테이너 생성

내장 이름 가리기

내장 이름을 변수로 쓰면 원래 함수를 가립니다. list, str, input, sum 등을 가리지 마세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# BAD:내장 함수 가림
def total(data):
sum = 0 # 내장 sum 덮어씀
for x in data:
sum += x
return sum
# 이후 sum() 사용 시 오류
# GOOD:다른 이름 사용
total = 0
def total(data):
return sum(data)
# 흔한 함정:
# list = [] 로 list() 가림
# input = ... 로 input() 가림
# type/str/dict 도 동일
# 리스트 컴프리헨션 변수는 누출되지 않음(3+ 독립 스코프)

예외 삼키기

베어 except 는 KeyboardInterrupt 까지 모두 삼킵니다. 최소한 로깅하거나 구체적인 타입으로 좁히세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# BAD:맨 except 로 조용히 삼킴
import os
try:
os.remove('f.txt')
except:
pass # 아무것도 모름
try:
value = int(x)
except Exception:
value = 0 # 타입 오류를 숨김
try:
risky()
except Exception:
print('出错了') # 무슨 오류인지도 모름
# GOOD:정밀 잡기 + 기록
import logging
try:
value = int(x)
except (ValueError, TypeError) as e:
logging.warning('转换失败 %s', e)
value = 0
# KeyboardInterrupt/SystemExit 는 삼키지 말 것

전역 변수 오용

함수 안에서 할당하면 새 로컬이 만들어지고 전역은 바뀌지 않습니다. global 선언이 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# BAD:전역을 바꾼 것 같지만 오류
count = 0
def inc():
count += 1 # UnboundLocalError
# 원인:+= 는 할당이며,지역 count 를 만듦
# GOOD:global 선언
def inc():
global count
count += 1
# 읽기만 하면 선언 불필요:
def show():
print(count)
# 전역 남용 피하기:
# 인자로 전달 + 반환값으로 반환
# 또는 클래스로 상태 캡슐화
# 설정 객체는 모듈 레벨에서 읽기 전용

복사와 정렬

list.sort 는 제자리 정렬, sorted 는 새 리스트를 반환합니다. 공유 참조에 의한 의도치 않은 변경에 주의.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# BAD:sort 는 제자리 변경
original = [3, 1, 2]
sorted_list = original.sort() # None 반환
# 원래 리스트도 정렬됨
# GOOD:sorted 는 새 리스트 반환
original = [3, 1, 2]
sorted_list = sorted(original)
# 또는 copy 후 sort:
c = original[:]; c.sort()
# 참조 공유:
b = original
b.append(9) # original 도 변함
# 독립이 필요하면 복사:
new_list = original[:]
# 함수에서 인자를 바꾸면 호출 측에 영향:
# copy 를 전달

14.동시성과 비동기

threading, asyncio, GIL, 동시성 모델 선택.

스레드 기초

threading.Thread 가 스레드를 만들고 start 로 실행, join 으로 대기, daemon=True 로 백그라운드 스레드를 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading
def work(n):
print(f'线程 {n} 工作')
threads = []
for i in range(3):
t = threading.Thread(target=work, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join() # 완료 대기
# 스레드 객체에 인자:
threading.Thread(target=work, args=(1,), daemon=True)
# daemon 스레드는 메인 스레드와 함께 종료
# 스레드 수는 늘리지 말 것,컨텍스트 스위치 오버헤드 회피
# 단순한 병렬 상황이면 충분

스레드 클래스

Thread 를 상속해 run 을 재정의합니다. 데이터 공유에는 락을 사용하고 작업 스레드를 깔끔히 묶을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading
class Worker(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
self.result = None
def run(self):
self.result = heavy_compute()
w = Worker('job-1')
w.start()
w.join()
print(w.result)
# start 는 오버라이드하지 말고 run 을 오버라이드
# 데이터는 인스턴스 속성으로 반환
# 예외는 서브 스레드에서 조용함:
# run 안에서 try-except 기록
# 반환값이 필요하면 ThreadPoolExecutor 우선

락과 동기화

Lock 이 공유 자원을 보호하며 with lock 으로 획득/해제를 자동화합니다. RLock 은 동일 스레드 재진입을 허용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import threading
lock = threading.Lock()
counter = 0
def increment():
global counter
for _ in range(1000):
with lock: # 자동 획득/해제
counter += 1
threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print(counter) # 10000(락으로 보호)
# RLock:같은 스레드에서 재획득 가능
# 조건 변수:Condition
# 이벤트:Event
# 세마포어:Semaphore 로 병렬 수 제어
# 데드락:락 순서 불일치、락 보유한 채 대기
# Queue 로 데이터를 전달하면 락 불필요,우선

GIL과 병렬 처리

GIL 때문에 CPython 스레드는 CPU 바운드를 진짜 병렬화하지 못합니다. I/O 바운드에서는 여전히 유효합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# GIL:글로벌 인터프리터 락
# 같은 시각에 스레드 1개만 바이트코드 실행
# CPU 집약 작업은 멀티스레드로 가속 안 됨:
# 계산 작업은 스레드면 오히려 느림
# I/O 집약 작업은 스레드 유효:
# 네트워크/디스크 대기 중 GIL 양보
# 진짜 병렬 CPU 집약:
# 1. multiprocessing 으로 멀티프로세스
# 2. C 확장으로 GIL 해제(numpy 등)
# 3. asyncio 로 싱글스레드 비동기
# 판단:
# 병목이 I/O -> 스레드/asyncio
# 병목이 CPU -> 프로세스

다중 프로세스

multiprocessing 가 CPU 바운드를 프로세스 풀로 병렬화하며 ProcessPoolExecutor 가 상위 래퍼를 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from multiprocessing import Pool
def square(n):
return n * n
with Pool(4) as pool:
results = pool.map(square, range(10))
# ProcessPoolExecutor:
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=4) as ex:
results = list(ex.map(square, range(10)))
# 독립 메모리:프로세스 간 공유 안 함
# 통신:Queue/Pipe
# 시작 오버헤드는 스레드보다 큼
# 대량 데이터는 직렬화 오버헤드 주의
# if __name__ == '__main__' 과 조합

asyncio 기초

async def 가 코루틴, await 가 일시 중단, asyncio.run 이 이벤트 루프를 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import asyncio
async def say_hi():
await asyncio.sleep(1)
print('hi')
asyncio.run(say_hi())
# await 은 코루틴을 일시 중지하고 제어를 양보
# 병렬 실행:
async def main():
task = asyncio.create_task(say_hi())
await task
asyncio.run(main())
# 이벤트 루프:
# 같은 스레드 안에서 코루틴 스케줄링
# 블로킹 코드는 루프를 멈춤:
# time.sleep -> await asyncio.sleep
# 블로킹 라이브러리는 비동기판으로:httpx/aiohttp
# 3.11+ TaskGroup 으로 태스크 관리

async / await

async 가 코루틴을 정의하고 await 가 결과를 기다립니다. gather 로 병렬 실행, asyncio.timeout 으로 타임아웃을 설정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import asyncio
async def fetch(url):
await asyncio.sleep(0.1)
return f'data from {url}'
# 직렬:
async def main():
a = await fetch('a')
b = await fetch('b')
# 병렬:
results = await asyncio.gather(
fetch('a'), fetch('b'), fetch('c'))
# 타임아웃:
try:
result = await asyncio.wait_for(fetch('a'), timeout=1)
except asyncio.TimeoutError:
print('超时')
# 태스크 일괄 생성:
tasks = [fetch(u) for u in urls]
await asyncio.gather(*tasks)
# 반환값은 전달한 순서,예외는 전파

비동기 I/O

aiohttp / httpx 로 비동기 HTTP, aiofiles 로 비동기 파일 I/O 를 처리합니다. I/O 바운드에서 처리량이 크게 올라갑니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# httpx 비동기 클라이언트:
# pip install httpx
# import httpx
async def main():
async with httpx.AsyncClient() as client:
r = await client.get('https://example.com')
return r.status_code
# 동기 코드와 혼합:
async def worker():
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, blocking_fn)
# 비동기 파일:
import aiofiles
# async with aiofiles.open('f', 'r') as f:
# text = await f.read()
# 대량 병렬 I/O 에서 처리량 크게 향상
# 데이터베이스:asyncpg/aiomysql

Executor

ThreadPoolExecutor 는 스레드 풀, ProcessPoolExecutor 는 프로세스 풀. 둘 다 submit / map 을 공통 API 로 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from concurrent.futures import ThreadPoolExecutor, as_completed
def work(n):
return n * 2
with ThreadPoolExecutor(max_workers=4) as ex:
futures = [ex.submit(work, i) for i in range(10)]
for f in as_completed(futures):
print(f.result()) # 완료 순서
# map 은 순서대로:
with ThreadPoolExecutor(4) as ex:
results = list(ex.map(work, range(10)))
# 예외:
# f.result() 가 원래 예외를 던짐
# shutdown(wait=True) 로 대기
# 스레드 풀은 I/O 집약에 적합
# 프로세스 풀은 CPU 집약에 적합
# 3.9+ 취소:f.cancel()

15.네트워크와 모듈

모듈 시스템, HTTP 요청, URL 처리, 서버.

모듈과 임포트

import 가 모듈을 적재하고 from x import y 는 특정 이름을 가져옵니다. 패키지는 __init__.py 가 있는 디렉터리입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 임포트:
import os
import os.path
import json as js # 별칭
from datetime import datetime, timedelta
from collections import * # 비권장
# 상대 임포트(패키지 내):
# from . import utils
# from ..other import x
# 임포트 메커니즘:
# sys.path 검색 경로
# 캐시:같은 모듈은 한 번만 로드
# __main__ 엔트리
# 커스텀 모듈:
# utils.py 가 같은 디렉터리에
# import utils
# 지연 임포트:함수 안 import 로 시작 시간 단축

HTTP 요청

requests 라이브러리의 GET/POST, 쿼리 매개변수, 헤더, JSON 본문, 응답 상태 처리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
# GET:
r = requests.get('https://api.example.com/users',
params={'page': 2}, timeout=5)
# 상태:
r.status_code == 200
r.ok
# 데이터:
r.json() # dict
r.text # 텍스트
r.content # 바이트
# POST:
resp = requests.post('https://api.example.com/login',
json={'user': 'nick'},
headers={'Authorization': 'Bearer x'},
timeout=10)
# 오류:
try:
r.raise_for_status()
except requests.HTTPError:
print('请求失败')
# 세션(cookie 유지):
s = requests.Session()

urllib 표준 라이브러리

urllib.request 는 서드파티 없이 쓰는 표준 HTTP 클라이언트이고 urllib.parse 는 URL 파서입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import urllib.request
with urllib.request.urlopen('https://example.com') as resp:
data = resp.read()
print(resp.status)
# 인코딩 인자:
req = urllib.request.Request('https://api.com',
headers={'User-Agent': 'my-app'},
data=b'body')
with urllib.request.urlopen(req) as resp:
print(resp.read().decode('utf-8'))
# URL 파싱:
from urllib.parse import urlparse, urlencode, quote
urlparse('https://a.com/p?q=1#f')
# ParseResult(scheme='https', netloc='a.com', path='/p', query='q=1')
urlencode({'a': 1, 'b': 'x y'}) # 'a=1&b=x+y'
quote('中文') # 퍼센트 인코딩
# 간결한 요청은 requests,표준 라이브러리 urllib 가 폴백

간단한 HTTP 서버

http.server 는 정적 파일과 임시 서버를 제공하며 BaseHTTPRequestHandler 를 상속해 커스터마이즈합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 정적 파일 서비스:
# python3 -m http.server 8000
# 열람 http://localhost:8000
# 바인드 주소:
# python3 -m http.server 8000 --bind 0.0.0.0
# 커스텀 처리:
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = b'{"ok": true}'
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt, *args):
pass # 무음
HTTPServer(('', 8000), Handler).serve_forever()
# 운영은 프레임워크:FastAPI/Flask

소켓 네트워크

socket 은 저수준 TCP/UDP 인터페이스로, 소켓 연결·송수신·프로토콜 분석에 사용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import socket
# TCP 클라이언트:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('example.com', 80))
s.sendall(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
data = s.recv(4096)
print(data[:200])
# TCP 서버:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind(('0.0.0.0', 9000))
server.listen(5)
conn, addr = server.accept()
with conn:
data = conn.recv(1024)
conn.sendall(b'pong')
# UDP:SOCK_DGRAM,sendto/recvfrom
# 타임아웃:s.settimeout(3)
# 복잡한 프로토콜은 프레임워크 우선

URL과 매개변수

urlparse 가 URL 을 파싱, urlencode 가 쿼리 문자열을 만들고, quote 가 퍼센트 인코딩, urljoin 가 상대 경로를 해결합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from urllib.parse import urlparse, urlencode, quote, urljoin
# 파싱:
p = urlparse('https://[email protected]:8080/path?q=1#sec')
p.scheme; p.netloc; p.path
p.query; p.fragment
# 쿼리 문자열 조립:
params = urlencode({'q': 'python 教程', 'page': 2})
# 'q=python+%E6%95%99%E7%A8%8B&page=2'
url = f'https://api.com/search?{params}'
# 연결:
urljoin('https://a.com/docs/', '../about')
# 디코딩:
from urllib.parse import unquote
unquote('%E4%B8%AD') # '中'
# 경로 안전화:
quote('a/b', safe='') # 슬래시 이스케이프
# 파라미터 파싱:
from urllib.parse import parse_qs
parse_qs('a=1&a=2&b=3') # {'a': ['1','2'], 'b': ['3']}

설정과 환경

os.environ 으로 환경 변수에 접근하고 python-dotenv 로 .env 를 로드합니다. 설정은 계층화해 관리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os
# 환경 변수 읽기:
port = int(os.environ.get('PORT', '3000'))
secret = os.environ['SECRET_KEY'] # 없으면 KeyError
# 설정:
os.environ['DEBUG'] = '1'
# 판단:
if os.environ.get('DEBUG') == '1':
print('调试模式')
# dotenv:
# pip install python-dotenv
from dotenv import load_dotenv
load_dotenv() # .env 로드
# .env 파일:
# PORT=3000
# SECRET_KEY=xxx
# 용도:
# 배포 설정과 코드 분리
# 키를 버전 관리에 넣지 않기
# 설정 검증:없으면 즉시 오류
# 레이어:default/dev/prod

API 호출 패턴

재시도, 레이트 리미팅, 오류 처리를 래핑합니다. pydantic 으로 응답을 타입 검증하는 패턴이 흔합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
from time import sleep
def api_get(url, retries=3):
for attempt in range(retries):
try:
r = requests.get(url, timeout=10)
if r.status_code == 429: # 레이트 제한
sleep(2 ** attempt) # 백오프
continue
r.raise_for_status()
return r.json()
except requests.RequestException:
if attempt == retries - 1:
raise
sleep(2 ** attempt)
# 공통 요청 헤더:
HEADERS = {'User-Agent': 'my-app/1.0'}
# 응답 검증(pydantic):
# pip install pydantic
# from pydantic import BaseModel
# class User(BaseModel):
# name: str
# age: int
# u = User.model_validate(r.json())
# 캐시:lru_cache / diskcache

16.날짜와 시간

datetime, 포매팅, 시간 차이, 타임존.

datetime 기초

datetime 생성, date / time 서브클래스, 속성 접근, 비교 연산.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from datetime import datetime, date, time
# 현재 시각:
now = datetime.now() # 로컬
now_utc = datetime.utcnow() # 비권장,timezone 사용
# 생성:
d = datetime(2026, 8, 2, 12, 30, 0)
# 접근:
d.year; d.month; d.day
d.hour; d.minute; d.second
# 요일:
d.weekday() # 0=월요일
d.isoweekday() # 1=월요일
# 날짜/시각만:
date(2026, 8, 2)
time(12, 30)
# 비교:
d > datetime(2026, 1, 1)
# 잘못된 월을 지정하면 ValueError

포맷과 파싱

strftime 로 포매팅하고 strptime 으로 파싱합니다. 지정자: %Y 년, %m 월, %d 일, %H 시.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from datetime import datetime
now = datetime.now()
# 포맷:
now.strftime('%Y-%m-%d') # '2026-08-02'
now.strftime('%Y-%m-%d %H:%M:%S')
now.strftime('%H:%M') # 시분
now.strftime('%A') # 요일의 영어
# 문자열 파싱:
d = datetime.strptime('2026-08-02', '%Y-%m-%d')
d = datetime.fromisoformat('2026-08-02T12:00:00')
# 자주 쓰는 지시자:
# %Y 4자리 연 %y 2자리 연
# %m 월 %d 일 %H 24시간 %I 12시간
# %M 분 %S 초 %f 마이크로초
# 한국어 요일:커스텀 매핑
# 타임존 안전:먼저 tz 를 붙이고 포맷

timedelta

timedelta 로 일/시간 등 시간 차를 표현하고 더하기·빼기·차이 계산에 씁니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from datetime import datetime, timedelta
now = datetime.now()
# 7일 더하기:
now + timedelta(days=7)
# 2시간 더하기:
now + timedelta(hours=2)
# 30분 빼기:
now - timedelta(minutes=30)
# 차이 계산:
d1 = datetime(2026, 8, 2)
d2 = datetime(2026, 1, 1)
delta = d1 - d2
delta.days # 213
delta.total_seconds() # 초
# 상대 파라미터:
# weeks/days/hours/minutes/seconds
# 시간 차는 비교 가능:
if delta > timedelta(days=100):
print('超过百天')
# 월 차는 직접 계산:
# timedelta 에 월 단위 없음

타임존

timezone 은 고정 오프셋, zoneinfo (3.9+) 는 IANA 이름의 타임존을 다룹니다. 저장은 UTC, 표시는 현지 권장.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from datetime import datetime, timezone, timedelta
# 고정 오프셋:
utc8 = timezone(timedelta(hours=8))
now_utc8 = datetime.now(utc8)
# 이름 있는 타임존(3.9+):
from zoneinfo import ZoneInfo
shanghai = ZoneInfo('Asia/Shanghai')
now_sh = datetime.now(shanghai)
# 변환:
utc_time = datetime.now(timezone.utc)
local_time = utc_time.astimezone(shanghai)
# 타임존 인식 vs naive:
# tzinfo 를 가진 것이 인식형
# 비교는 같은 타임존 또는 둘 다 인식 필요
# 실천:
# 데이터베이스에는 UTC 저장
# 표시 시 사용자 타임존으로 변환
# tzdata 패키지가 타임존 라이브러리 제공

타임스탬프

유닉스 초 타임스탬프를 .timestamp() 으로 얻고 fromtimestamp() 으로 복원합니다. 비교와 저장에 적합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
from datetime import datetime, timezone
# unix 타임스탬프(초):
now_ts = time.time()
datetime.now().timestamp()
# datetime 을 타임스탬프로:
dt = datetime(2026, 8, 2, tzinfo=timezone.utc)
dt.timestamp()
# 타임스탬프를 datetime 으로:
datetime.fromtimestamp(now_ts) # 로컬
datetime.fromtimestamp(now_ts, timezone.utc) # UTC
# 밀리초:
int(time.time() * 1000)
# 타임스탬프 비교:
if now_ts > last_ts: pass
# 저장:
# unix 초 정수가 공통 표준
# 표시:타임스탬프에서 로컬 포맷으로 변환
# 정밀도:float 초에 소수 포함

타이밍과 대기

time.sleep 으로 대기, time.perf_counter 로 고정밀 측정, 정기 작업은 schedule 라이브러리 활용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time
# 대기:
time.sleep(1.5) # 초
# 측정:
start = time.perf_counter()
# 코드 실행...
elapsed = time.perf_counter() - start
print(f'{elapsed:.4f}s')
# 정밀도:
time.time() # 벽시계,조정될 수 있음
time.perf_counter() # 고정밀 모노토닉
# 주기 루프:
def run_every(interval, fn):
while True:
fn()
time.sleep(interval)
# 주기 작업 라이브러리:
# pip install schedule
# import schedule
# schedule.every(10).minutes.do(job)
# while True: schedule.run_pending()
# 블로킹 vs 비동기 대기:
# 이벤트 루프 안에서는 asyncio.sleep 사용

날짜 유틸

요일 판정, 월 초/월 말, 상대 날짜, 날짜 범위. calendar 모듈이 도움이 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from datetime import datetime, timedelta
import calendar
d = datetime.now()
# 요일 판단:
d.weekday() < 5 # 평일
# 월초:
d.replace(day=1)
# 월말:
last_day = calendar.monthrange(d.year, d.month)[1]
d.replace(day=last_day)
# 날짜 시퀀스:
start = datetime(2026, 8, 1)
for i in range(7):
day = start + timedelta(days=i)
print(day.strftime('%m-%d %a'))
# 자연어 파싱:
# pip install python-dateutil
# from dateutil.parser import parse
# parse('2026-08-02 10:30')
# 월 차 계산:
# dateutil.relativedelta
# from dateutil.relativedelta import relativedelta
# d + relativedelta(months=1)

시간 구간 판정

특정 시각이 구간 안에 있는지, 구간이 겹치는지, 남은 시간을 판정합니다. 스케줄과 제한 시간 작업에서 자주 등장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from datetime import datetime, timedelta
# 구간 판정:
now = datetime.now()
start = datetime(2026, 1, 1)
end = datetime(2027, 1, 1)
if start <= now < end:
print('在窗口内')
# 구간 겹침:
def overlaps(a1, a2, b1, b2):
return a1 < b2 and b1 < a2
# 남은 시간:
dealine = now + timedelta(hours=2)
remaining = deadline - now
if remaining > timedelta(minutes=30):
print('时间充足')
# 주기 윈도우:
def in_working_hours(dt):
return 9 <= dt.hour < 18
# 경계 처리:
# 반개구간 [start, end) 로 중복 방지
# 날짜를 넘으면 정규화 주의
# 레이트 제한:이전 타임스탬프와 비교

17.프로세스와 시스템

subprocess, sys/os 모듈, 명령행 인자, 시그널.

subprocess

subprocess.run 으로 외부 명령을 실행하고 출력을 캡처하며 반환 코드와 타임아웃을 확인합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import subprocess
# 실행 후 출력 캡처:
result = subprocess.run(
['ls', '-l'],
capture_output=True, text=True)
print(result.returncode) # 0 은 성공
print(result.stdout)
print(result.stderr)
# 실패 검사:
result.check_returncode() # 0 이 아니면 CalledProcessError
# 타임아웃:
subprocess.run(['sleep', '10'], timeout=2)
# 오류:TimeoutExpired
# 안전:
# 인자 리스트를 전달하고,shell 문자열을 조립하지 않기
# shell=True 는 인젝션 위험
# 실시간 출력:subprocess.Popen 으로 줄 단위 읽기

sys 모듈

sys.argv 는 CLI 인자, sys.exit 는 종료 코드, sys.path 는 모듈 검색 경로, 표준 스트림 객체도 함께 다룹니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sys
# 커맨드라인 인자:
sys.argv # ['script.py', 'arg1', 'arg2']
# 종료:
sys.exit(0) # 성공
sys.exit(1) # 실패
# 모듈 검색 경로:
sys.path # 리스트
sys.path.append('/custom') # 추가
# 버전:
sys.version_info # (3, 11, ...)
sys.version
# 스트림:
sys.stdin; sys.stdout; sys.stderr
# 플랫폼:
sys.platform # 'win32' / 'darwin' / 'linux'
# 인코딩:
sys.getdefaultencoding() # 'utf-8'
# 재귀 제한:
sys.getrecursionlimit()
sys.setrecursionlimit(5000)

os 모듈

os 는 환경, 경로, 디렉터리 연산을 제공합니다. os.getcwd 는 현재 디렉터리, os.mkdir 는 생성.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os
# 환경 변수:
os.environ['HOME']
# 디렉터리:
os.getcwd() # 현재 디렉터리
os.chdir('/tmp') # 전환
os.mkdir('dir') # 디렉터리 생성
os.makedirs('a/b/c', exist_ok=True)
# 파일:
os.remove('file')
os.rename('a', 'b')
# 경로(os.path):
os.path.join('a', 'b') # 크로스플랫폼 연결
os.path.exists('f')
os.path.isdir('d')
os.path.dirname('/a/b/c') # '/a/b'
os.path.basename('/a/b/c') # 'c'
os.path.splitext('f.txt') # ('f', '.txt')
# 순회:
for root, dirs, files in os.walk('.'):
print(root, files)
# os.path 대신 pathlib 권장

명령행 인자 파싱

argparse 는 인자 파싱, 도움말 생성, 기본값을 제공합니다. CLI 도구의 표준입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import argparse
parser = argparse.ArgumentParser(description='示例工具')
parser.add_argument('input', help='输入文件')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('-n', '--count', type=int, default=1)
args = parser.parse_args()
print(args.input, args.verbose, args.count)
# 사용법:
# python3 tool.py data.txt -v -n 3
# 도움말:
# python3 tool.py --help
# 옵션:
parser.add_argument('--out', default='out.txt')
# 배타 인자:
# parser.add_mutually_exclusive_group()
# 서브커맨드:
# subparsers = parser.add_subparsers()
# 타입 검증:type=int 로 자동 변환

종료 코드

0 이 성공, 그 외는 실패. CI/스크립트는 종료 코드로 결과를 판정합니다. 처리되지 않은 예외는 기본 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sys
# 정상 종료:
sys.exit(0)
# 실패:
sys.exit(1)
sys.exit('错误信息') # stderr 로 출력하고,1 로 종료
# 미처리 예외는 자동으로 종료 코드 1
# 커스텀 코드:
sys.exit(2) # 사용법 오류 관례
# 종료 코드 판단:
# 커맨드라인:echo $? (POSIX)
# python3 script.py; echo $?
# 호출 측에서 잡기:
import subprocess
r = subprocess.run(['python3', 'x.py'])
if r.returncode != 0:
print('失败')
# CI 에서 0 이 아니면 빌드 실패

파일과 프로세스

대용량 파일 처리, 파일 락, 임시 파일, 원자적 쓰기.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 임시 파일:
import tempfile, os
with tempfile.NamedTemporaryFile(mode='w', delete=True) as f:
f.write('data')
name = f.name
# 임시 디렉터리:
with tempfile.TemporaryDirectory() as d:
print(d) # 다 쓰면 자동 정리
# 원자적 쓰기(임시로 쓰고 리네임):
import os
def atomic_write(path, content):
tmp = path + '.tmp'
with open(tmp, 'w') as f:
f.write(content)
os.replace(tmp, path) # 원자적 치환
# 파일 락:
# pip install filelock
# from filelock import FileLock
# with FileLock('app.lock'):
# 상호 배타 작업
# 큰 파일은 분할 읽기:줄 단위/고정 블록

시그널 처리

signal 은 Ctrl+C 와 SIGTERM 을 처리해 정상 종료를 구현합니다. POSIX 중심이며 Windows 는 제한적입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import signal, sys, time
# 우아한 종료 플래그:
running = True
def handle_stop(signum, frame):
global running
print(f'收到信号 {signum},正在退出...')
running = False
signal.signal(signal.SIGINT, handle_stop) # Ctrl+C
signal.signal(signal.SIGTERM, handle_stop) # kill
# 메인 루프:
while running:
work()
time.sleep(1)
print('清理完成')
# 시그널 무시:
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
# 타임아웃 시그널(POSIX):
# signal.alarm(5) 5초 후 SIGALRM
# Windows 는 일부 시그널만 지원
# 우아한 종료:상태 저장、연결 닫기

데몬과 백그라운드

백그라운드 실행, 로그 로테이션, 데몬화. supervisor 나 systemd 로 운영합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 백그라운드 실행:
# python3 script.py &
# 출력 리다이렉트:
# python3 script.py > out.log 2>&1 &
# nohup 으로 행업 무시:
# nohup python3 script.py &
# 로그 로테이션:
import logging
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
'app.log', maxBytes=1_000_000, backupCount=5)
logging.basicConfig(handlers=[handler])
logging.info('运行中')
# 운영 호스팅:
# systemd unit / supervisor 로 재시작 정책
# 싱글 인스턴스 확인:
# 락 파일 pid 로 판단
# 리소스 제한:ulimit

18.정규식과 텍스트

re 모듈의 매칭, 검색, 치환, 그룹, 컴파일.

match와 search

re.match 는 시작 위치부터 매칭하고 re.search 는 전체에서 찾습니다. Match 또는 None 을 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re
# search:임의의 위치
m = re.search(r'\d+', '订单号 123')
if m:
print(m.group()) # '123'
# match:처음부터
m2 = re.match(r'\d+', '123abc')
# m2.group() == '123'
# 매치 실패는 None 반환:
if re.search(r'xyz', 'abc'): pass
# 위치 정보:
m.start(); m.end(); m.span()
# 경계:
re.search(r'\bcat\b', 'a cat eats')
# 메타 문자는 이스케이프 필요:
r'\.' 匹配点号

findall과 finditer

findall 은 모든 매치를 리스트로, finditer 는 이터레이터로 하나씩 반환합니다(큰 입력에 메모리 친화적).

1
2
3
4
5
6
7
8
9
10
11
12
13
import re
# findall:모든 매치
nums = re.findall(r'\d+', 'a1b22c333')
# ['1', '22', '333']
# 그룹이 있으면 튜플 반환:
re.findall(r'(\d+)-(\d+)', '1-2,3-4')
# [('1','2'), ('3','4')]
# finditer:Match 반복
for m in re.finditer(r'\d+', 'a1b22'):
print(m.group(), m.span())
# 위치 정보가 필요하면 finditer
# 큰 텍스트는 finditer 가 메모리 절약
# 매치 없으면 빈 리스트/빈 이터레이터

sub 치환

re.sub 은 정규식으로 치환하며 치환 문자열에서 \1 등을 참조하거나 함수를 넘겨 동적 치환도 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re
# 단순 치환:
re.sub(r'\s+', ' ', 'a b c') # 'a b c'
# 그룹 참조:
re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1',
'2026-08-02')
# '02/08/2026'
# 함수로 치환:
re.sub(r'\d+', lambda m: str(int(m.group()) * 2),
'1 and 2')
# '2 and 4'
# 횟수 제한:
re.sub(r'a', 'x', 'aaa', count=1) # 'xaa'
# 대소문자 무시:
re.sub(r'hello', 'hi', 'HELLO', flags=re.I)

split

re.split 은 정규식으로 분리하며 여러 구분자, 캡처 보존, 분리 횟수 제한을 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import re
# 쉼표/세미콜론/세로줄로 분할:
re.split(r'[,;|]', 'a,b;c|d')
# ['a', 'b', 'c', 'd']
# 공백으로 분할(여러 개 포함):
re.split(r'\s+', 'a b c')
# 캡처 그룹은 구분자 유지:
re.split(r'(,)', 'a,b,c')
# ['a', ',', 'b', ',', 'c']
# 제한:
re.split(r',', 'a,b,c', maxsplit=1)
# ['a', 'b,c']
# 줄 분할:
re.split(r'\r?\n', text)
# 문단 단위:
re.split(r'\n\s*\n', text)
# str.split 이 여러 구분자를 못 다룰 때 re.split

그룹

() 는 캡처, (?P<name>) 는 이름 있는 그룹, (?:) 는 비캡처, | 는 선택입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import re
# 그룹:
m = re.search(r'(\w+)@(\w+)\.(\w+)', '[email protected]')
m.group(0) # '[email protected]' 전체 매치
m.group(1) # 'a'
m.group(2) # 'b'
m.groups() # ('a','b','com')
# 이름 있는 그룹:
m = re.search(r'(?P<user>\w+)@(?P<domain>\w+)', '[email protected]')
m.group('user') # 'a'
m.groupdict() # {'user': 'a', 'domain': 'b'}
# 비캡처:
re.search(r'(?:ab)+', 'abab')
# 또는:
re.search(r'cat|dog', 'a dog')
# 그룹 참조 치환:\1
# 중첩 그룹 번호는 왼쪽 괄호부터 센다

플래그

re.I 는 대소문자 무시, re.M 은 다중 행, re.S 는 점이 개행과 매칭, re.X 는 상세 모드.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re
# re.I 대소문자 무시:
re.search(r'hello', 'HELLO', re.I)
# re.M 여러 줄 앵커:
re.search(r'^line', 'a\nline b', re.M)
# re.S 점이 줄바꿈에 매치:
re.search(r'a.b', 'a\nb', re.S)
# re.X 상세 모드(주석/공백):
pattern = re.compile(r'''
(\d{4}) # 년
[-/]
(\d{2}) # 월
''', re.X)
# 플래그 조합:
re.search(r'x', 'X', re.I | re.M)
# 컴파일 후 재사용:
pat = re.compile(r'\d+', re.IGNORECASE)
pat.search('a1')
# 프리컴파일로 고속화 + 옵션 고정

자주 쓰는 패턴

이메일/전화번호/URL/IP/한글 등 자주 쓰이는 정규식 스니펫과 업무 포맷 검증.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import re
# 이메일:
EMAIL = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
# 중국 본토 휴대폰 번호:
MOBILE = r'^1[3-9]\d{9}$'
# URL:
URL = r'^https?://[^\s]+$'
# IPv4:
IP = r'^(\d{1,3}\.){3}\d{1,3}$'
# 중국어:
CHINESE = re.compile(r'[\u4e00-\u9fff]+')
# 주민등록번호(18자리):
ID = r'^\d{17}[0-9Xx]$'
# 날짜:
DATE = r'^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$'
# 검증 함수:
def is_email(s): return bool(re.match(EMAIL, s))
# 중국어 추출:
CHINESE.findall('hello 世界 你好')
# 색상:
COLOR = r'^#[0-9a-fA-F]{6}$'

정규식 성능

사전 컴파일, 재귀적 수량자에 의한 재앙적 백트래킹 회피, 수량자 폭 제한. 큰 텍스트는 청크로.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re
# 프리컴파일:
pattern = re.compile(r'\d+') # 모듈 레벨
pattern.findall(text)
# 파멸적 백트래킹 피하기:
# BAD:중첩 수량자
# re.match(r'(a+)+$', 'a' * 30 + '!') 매우 느림
# GOOD:단순화
re.match(r'a+$', 'aaa')
# 폭 제한:
re.match(r'[a-z]{1,20}', 'x' * 100)
# 문자 클래스로 or 대체:
# BAD:[a|b|c]
# GOOD:[abc]
# 큰 텍스트:
# 전체가 아닌 분할 처리
# 단순한 파싱은 str 메서드 우선:
# s.startswith / s.split
# 정규식은 구조 매치까지만

19.빌드와 엔지니어링

의존성 관리, 가상 환경, 테스트, Lint, CI.

requirements.txt

pip freeze 로 requirements.txt 를 생성하고 pip install -r 로 설치. 버전 고정으로 재현성을 확보합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 현재 환경 내보내기:
pip freeze > requirements.txt
# 파일 내용 예시:
# requests==2.31.0
# httpx>=0.24.0
# # 주석으로 용도 설명
# 설치:
pip install -r requirements.txt
# 개발 의존 분리:
# requirements-dev.txt 에 테스트 도구
# 버전 고정 전략:
# == 완전 고정
# >= 업그레이드 허용
# 운영은 정확한 버전으로 고정
# 현대적 대안:
# pyproject.toml 선언 + lock 파일
# uv 로 초고속 설치

venv 실전

모범 사례: 프로젝트마다 1 개의 venv, .venv 는 커밋하지 않기, 손상되면 새로 생성.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 생성:
python3 -m venv .venv
# 활성화(POSIX):
source .venv/bin/activate
# 활성화(Windows):
# .venv\Scripts\activate
# 현재 인터프리터 확인:
which python
# 종료:deactivate
# 삭제 후 재생성(의존 손상 시):
# rm -rf .venv && python3 -m venv .venv
# .gitignore 에 .venv/ 추가
# 의존은 venv 에 설치:
# pip install 은 시스템 환경을 쓰지 않기
# 에디터는 venv 인터프리터 선택

모던 패키지 매니저

uv 는 초고속 의존성 관리, poetry 는 의존성과 패키징을 묶어 제공합니다. 락 파일로 재현성을 보장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# uv(권장,초고속):
# pip install uv
uv venv # 가상 환경 생성
uv add requests # 의존 추가
uv sync # 동기 설치
# pip 대안:
uv pip install -r requirements.txt
# poetry:
# poetry new mypkg
# poetry add requests
# poetry install
# pyproject.toml + poetry.lock
# 일관된 방식:
# 의존 선언 -> 락 파일 -> CI 는 lock 으로 설치
# 락 파일로 모두 같은 버전 보장
# 업그레이드:uv lock --upgrade

pytest 테스트

test_ 로 시작하는 함수에 assert 를 씁니다. fixture 로 설정을 공유하고 parametrize 로 매개변수화합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import pytest
from app import add
def test_add():
assert add(1, 2) == 3
def test_add_negative():
assert add(-1, 1) == 0
# 파라미터화:
@pytest.mark.parametrize('a,b,expected', [
(1, 2, 3),
(0, 0, 0),
])
def test_add_param(a, b, expected):
assert add(a, b) == expected
# fixture:
def setup_db():
db = create_db()
yield db
db.close()
def test_using_fixture(setup_db):
assert setup_db.query()
# 예외 검증:
with pytest.raises(ValueError):
int('x')
# 실행:pytest 또는 python3 -m pytest
# 커버리지:pytest --cov

unittest 표준 라이브러리

표준 라이브러리 테스트 프레임워크. TestCase 를 상속해 assertXxx 메서드와 setUp / tearDown 라이프사이클을 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import unittest
from app import add
class TestAdd(unittest.TestCase):
def setUp(self):
self.data = [1, 2]
def test_add(self):
self.assertEqual(add(1, 2), 3)
def test_types(self):
self.assertIsInstance(add(1, 2), int)
def test_raises(self):
with self.assertRaises(TypeError):
add('a', 1)
if __name__ == '__main__':
unittest.main()
# 실행:
# python3 -m unittest test_app
# 발견 모드:
# python3 -m unittest discover
# 어서션 메서드:
# assertEqual/assertTrue/assertIn
# assertAlmostEqual 부동소수점
# mock:unittest.mock 으로 의존 모킹

Lint와 포매터

ruff 로 빠른 Lint 와 포맷, black 으로 스타일, mypy 로 타입 검사. 규칙은 pyproject.toml 에 둡니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ruff(권장,초고속):
# pip install ruff
ruff check . # 체크
ruff format . # 포맷
ruff check --fix . # 자동 수정
# black:
# pip install black
black --line-length 88 .
# mypy 타입 체크:
# pip install mypy
mypy src/
# pyproject.toml 설정:
# [tool.ruff]
# line-length = 88
# [tool.mypy]
# strict = true
# 에디터 연동:
# 저장 시 포맷
# 커밋 전:pre-commit 훅

패키징과 배포

pyproject.toml 로 패키징을 설정하고 sdist/wheel 을 빌드한 뒤 twine 으로 PyPI 에 배포합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# pyproject.toml:
# [project]
# name = "my-pkg"
# version = "0.1.0"
# description = "..."
# requires-python = ">=3.10"
# dependencies = ["requests"]
#
# [build-system]
# requires = ["setuptools>=68"]
# build-backend = "setuptools.build_meta"
#
# [tool.setuptools.packages.find]
# include = ["mypkg*"]
# 빌드:
# pip install build
# python3 -m build
# dist/*.whl 과 tar.gz 생성
# 배포:
# pip install twine
# twine upload dist/*
# 테스트 버전:test.pypi.org
# 시맨틱 버전:1.2.0
# 배포 전 테스트 설치:pip install .

CI와 배포

CI 단계: lint, 타입 검사, 테스트, 빌드. GitHub Actions 로 구성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# GitHub Actions:
# .github/workflows/ci.yml
# name: CI
# on: [push, pull_request]
# jobs:
# test:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-python@v5
# with: python-version: '3.11'
# - run: pip install -e .[dev]
# - run: ruff check .
# - run: mypy src/
# - run: pytest --cov
# 의존 캐시:actions/cache
# 다중 버전 매트릭스:python-version: [3.10, 3.11, 3.12]
# 실패는 머지를 차단
# 배포:릴리스 시 PyPI 에 업로드
# 환경 변수:GitHub Secrets 에 키 저장

공식 링크

공식 문서와 리소스에 대한 직접 링크.

이 치트시트에 대해

이 페이지는 Python 3.11 의 자체 포함형 빠른 레퍼런스로, 실제 프로젝트에서 일상적인 필요의 약 80%를 차지하는 언어 핵심과 표준 라이브러리를 다룹니다. f-string, 리스트 컴프리헨션, 제너레이터, 컨텍스트 관리자, 타입 힌트, dataclass 같은 모던 관용구와 collections, itertools, pathlib 같은 실용적인 표준 라이브러리 모듈을 중점적으로 살펴봅니다. Python 은 1991 년 Guido van Rossum 이 발표했으며 읽기 쉬운 문법과 "배터리 포함" 철학의 표준 라이브러리로 잘 알려져 있고, 데이터 분석, 머신러닝, 자동화 스크립트, 웹 개발(Django/FastAPI)에서 폭넓게 사용됩니다. 19 개 섹션은 각각 한 가지 주제에 집중합니다: 기본 문법, 변수, 타입과 참조 의미, 제어 흐름, 함수, 문자열, 리스트와 딕셔너리, 메모리와 참조 카운팅, 클래스와 OOP, 예외 처리, 입출력, 흔한 함정, 동시성(스레드/비동기), 네트워크, 시간, 프로세스, 정규식, 빌드 도구(pip/venv). 각 하위 섹션에는 짧은 개념 설명과 복사해 바로 쓸 수 있는 코드 스니펫이 함께 제공됩니다. 모든 코드와 텍스트는 브라우저에서 로컬로 렌더링되며 어떤 데이터도 기기를 떠나지 않습니다. 권위 있는 참고 자료는 Python 공식 문서입니다.

버전 2.1.0