본문으로 건너뛰기
← 블로그로 돌아가기
CS 기초>튜토리얼
Python치트시트

파이썬 치트시트: 자주 쓰는 함수와 메서드숫자·문자열·리스트부터 딕셔너리·집합·반복 도우미까지

28분 읽기

Day 2, Day 3, Day 4를 공부하면서 외우려고 노트에 따로 뽑아 둔 표가 본문보다 길어졌습니다. 그래서 날짜 대신 주제별로 묶어 이 페이지 하나로 옮겼습니다. 예제의 결과 주석은 전부 실행해서 확인한 값입니다.

변수 이름과 키워드

이름을 지을 때나 이름 때문에 에러가 났을 때 펼치는 부분입니다.

이름 짓는 규칙

규칙예
문자, 숫자, 밑줄(_)만 사용my_name, age2 (O) / my-name, age@2 (X)
숫자로 시작 불가name1 (O) / 1name (X)
예약어(키워드) 사용 불가my_class (O) / class, if, for (X)
대소문자 구분Name과 name은 다른 변수

이름은 짧은 것보다 뜻이 보이는 쪽이 낫습니다. a = 25, x = "서울"보다 user_age = 25, user_city = "서울"이 읽기 쉽습니다.

키워드

키워드는 파이썬이 자기 용도로 미리 예약해 둔 단어라 변수나 함수 이름(식별자)으로 쓸 수 없습니다. 파이썬 버전이 바뀌면 목록도 달라질 수 있습니다.

False     None      True      and
as        assert    async     await
break     class     continue  def
del       elif      else      except
finally   for       from      global
if        import    in        is
lambda    nonlocal  not       or
pass      raise     return    try
while     with      yield

전체 목록은 keyword 모듈로 확인합니다. Python 3.14 기준으로 35개입니다.

import keyword

print(len(keyword.kwlist))  # 35
print(keyword.kwlist[:5])
# ['False', 'None', 'True', 'and', 'as']

코딩할 때 주의할 점

  • 들여쓰기: C의 중괄호 대신 들여쓰기로 코드 블록을 나눕니다. 잘못 들여쓰면 바로 에러이고, 스페이스 4칸을 권장합니다.
  • 네이밍: 변수·함수 이름은 PEP 8의 snake_case를 따릅니다.
  • 주석: #으로 씁니다. 나중에 코드를 고칠 때 나를 살려 주는 건 결국 주석입니다.
  • 모듈과 패키지: 코드를 모듈(파일)과 패키지(폴더)로 나눠 구성할 수 있습니다.
  • 예외 처리: 안 하면 프로그램이 비정상 종료될 수 있습니다.

숫자와 연산자

나눗셈 결과의 타입이나 진법 변환이 헷갈릴 때 봅니다.

기본 숫자형

타입설명예시
int정수(소수점 없음). 양수, 음수, 010, -5, 0
float실수(소수점 있음)3.14, -2.5, 1.0e3
bool참 / 거짓True, False
count = 100
price = 19.99
is_active = True
print(type(count), type(price))
# <class 'int'> <class 'float'>
print(type(is_active))  # <class 'bool'>

print(1.0e3)  # 1000.0
print(0o23)  # 19
print(0xB3)  # 179

8진수는 0o, 16진수는 0x로 시작합니다. 0o23은 8*2 + 3이라 19이고, 0xB3은 16*11 + 3이라 179입니다. 자료형이 궁금하면 type()으로 확인합니다.

산술 연산자

연산자의미
+덧셈
-뺄셈
*곱셈
/나눗셈(결과는 항상 float)
//몫(아래로 내림)
%나머지
**거듭제곱
print(10 + 3)  # 13
print(10 - 3)  # 7
print(10 * 3)  # 30
print(10 / 3)  # 3.3333333333333335
print(10 // 3)  # 3
print(10 % 3)  # 1
print(10 ** 2)  # 100
print(10 / 2)  # 5.0
print(-7 // 2)  # -4

//는 C처럼 0 쪽으로 버리지 않고 아래로 내립니다. 그래서 -7 // 2가 -3이 아니라 -4입니다.

복합 연산자

연산과 대입을 한 번에 합니다. a += b는 a = a + b와 같고 -=, *=, /=, //=, %=, **=도 같은 방식입니다. 구문은 짧아지지만 가독성은 조금 떨어집니다.

a = 5
b = 2
a += b
print(a)  # 7
a //= b
print(a)  # 3

숫자를 다루는 내장함수

import 없이 바로 쓰는 숫자용 함수입니다.

함수설명
divmod(a, b)몫과 나머지를 튜플로
pow(a, b, m)a ** b % m을 큰 수에서도 빠르게
ord(c) / chr(n)문자와 코드 번호 변환
bin / oct / hex2·8·16진수 문자열로
int(x, base)base진수 문자열을 정수로
isinstance(x, T)x가 T 타입인지
round(x, n)소수 n자리로 반올림
abs(x)절댓값
print(divmod(17, 5))  # (3, 2)
print(pow(2, 10, 1000))  # 24
print(ord("a"), chr(98))  # 97 b
print(bin(5))  # 0b101
print(int("101", 2))  # 5
print(isinstance(3, int))  # True

print(divmod(-7, 2))  # (-4, 1)
print(ord("가"), chr(44033))  # 44032 각
print(oct(19), hex(179))  # 0o23 0xb3
print(int("b3", 16))  # 179
print(isinstance(True, int))  # True

print(round(3.14159, 2))  # 3.14
print(round(2.5))  # 2
print(abs(-5))  # 5

divmod()도 //처럼 아래로 내려서 -7을 2로 나눈 몫이 -4입니다. ord()는 아스키 코드가 아니라 유니코드 번호라 한글도 됩니다.

oct()와 hex()는 0o23, 0xB3를 거꾸로 문자열로 만들어 줍니다(16진수는 소문자로 나옵니다). int()에 base를 주면 다시 정수로 돌아옵니다. bool은 int의 하위 타입이라 True도 int로 통과합니다.

round()는 딱 .5일 때 짝수 쪽으로 붙어서 round(2.5)가 3이 아니라 2입니다.

math와 datetime 모듈

내장함수로 부족한 계산이나 현재 시각이 필요할 때 import해서 씁니다.

math 함수 표

import math 뒤에 math.gcd()처럼 씁니다. 표에서는 앞의 math.을 생략했습니다.

함수설명
gcd / lcm최대공약수 / 최소공배수. 인자 여러 개도 가능(lcm과 여러 인자는 3.9부터)
isqrt(n)정수 제곱근(내림), 큰 수도 정확
sqrt(x)제곱근, 결과는 float
ceil / floor올림 / 내림
trunc(x)0 쪽으로 버림(int()와 같음)
factorial(n)팩토리얼
comb / perm조합 nCk / 순열 nPk
prod(it)곱(sum의 곱셈판)
log(x, base)로그, base를 생략하면 자연로그
log2 / log10밑이 2 / 10인 로그
exp(x)e의 x제곱
pi / e / inf원주율 / 자연상수 / 무한대
isclose(a, b)float 두 개가 거의 같은지
import math

print(math.gcd(12, 18), math.lcm(4, 6))
# 6 12
print(math.isqrt(17), math.sqrt(16))
# 4 4.0
print(math.ceil(2.1), math.floor(-2.1))
# 3 -3
print(math.trunc(-2.7))  # -2
print(math.factorial(5))  # 120
print(math.comb(5, 2), math.perm(5, 2))
# 10 20
print(math.prod([2, 3, 4]))  # 24
print(math.log(8, 2))  # 3.0
print(math.log2(1024))  # 10.0
print(math.exp(1))  # 2.718281828459045
print(math.pi)  # 3.141592653589793

올림 나눗셈, 제곱근, float 비교

import math

a, b = 10, 3
print(math.ceil(a / b), -(-a // b))  # 4 4

big = 10**18 + 1
print(math.ceil(big / 2))
# 500000000000000000
print(-(-big // 2))
# 500000000000000001

n = 10**16 - 1
print(int(math.sqrt(n)))  # 100000000
print(math.isqrt(n))  # 99999999

print(0.1 + 0.2 == 0.3)  # False
print(math.isclose(0.1 + 0.2, 0.3))
# True
print(math.log(1000, 10))
# 2.9999999999999996
print(math.log10(1000))  # 3.0

올림 나눗셈은 math.ceil(a / b)보다 -(-a // b)가 안전합니다. /가 결과를 float으로 바꾸는 순간 큰 수는 끝자리가 뭉개집니다. //는 아래로 내리니 부호를 두 번 뒤집으면 올림이 되고, import도 필요 없습니다.

제곱근도 같은 문제입니다. 10**16 - 1은 float으로 바뀌면서 1e16으로 반올림되고, 그래서 int(math.sqrt(n))이 하나 큰 값을 돌려줍니다. 정수 제곱근은 처음부터 math.isqrt()로 구합니다. 결과가 정수라서 range(2, math.isqrt(n) + 1)처럼 range()에 바로 넣을 수 있습니다.

float끼리는 == 대신 math.isclose()로 비교합니다. 밑이 10이나 2인 로그도 log(x, 10)보다 log10(), log2()가 정확합니다.

datetime

now()는 실행한 시각을 돌려주니 결과가 매번 다릅니다. from datetime import datetime으로 가져오면 datetime.now()처럼 짧게 쓸 수 있습니다.

import datetime

now = datetime.datetime.now()
# 실행한 시각이 찍힙니다(매번 다름)
print(now)
print(now.year, now.month, now.day)

출력: print와 f-string

출력 모양을 원하는 대로 맞춰야 할 때 찾습니다.

print의 sep과 end

sep는 값 사이에 들어갈 문자(기본은 공백), end는 맨 끝에 붙일 문자(기본은 줄바꿈)입니다.

a, b, c = 92, 96, 98
print(a, b, c)  # 92 96 98
print(a, b, c, sep=', ')  # 92, 96, 98
print(a, b, c, sep='\n')  # 값마다 줄바꿈
print(a, end=' ')  # 줄바꿈 대신 공백
print(b)  # 앞 줄에 이어서 출력
92 96 98
92, 96, 98
92
96
98
92 96

sep나 end에 빈 문자열을 주면 사이를 띄우지 않고 그대로 붙입니다.

print('c =', 10.5)  # c = 10.5
print('Hello', 'world', sep='')
# Helloworld
print('Hello', end='')
print('world')
# Helloworld

\n은 문자열 안에 있어야 줄바꿈입니다. print(a \n)처럼 따옴표 밖에 쓰면 SyntaxError가 납니다.

f-string 서식

서식의미
:.2f소수점 2자리
:,천 단위 콤마
:>10오른쪽 정렬(10칸)
:<10왼쪽 정렬(10칸)
:^10가운데 정렬(10칸)
:-^10가운데 정렬, 빈칸은 -로 채우기

정렬은 공백이 눈에 안 보이니 대괄호로 감싸서 확인합니다.

print(f"{3.14159:.2f}")  # 3.14
print(f"{1000000:,}")  # 1,000,000
print(f"[{'Hi':>10}]")  # [        Hi]
print(f"[{'Hi':<10}]")  # [Hi        ]
print(f"[{'Hi':^10}]")  # [    Hi    ]
print(f"[{'Hi':-^10}]")  # [----Hi----]

rate = 3.14159
print(f"비율: {rate:.2f}%")  # 비율: 3.14%
print(f"센서: {'TEMP':>10}|")
# 센서:       TEMP|
print('{:>10}'.format('홍길동'))
#        홍길동

format()도 같은 문법을 씁니다. '{:>10}'.format('홍길동')처럼 중괄호 안에 옵션을 넣으면 됩니다.

문자열

입력을 다듬거나 쪼개고 바꿀 때 가장 자주 펼치는 부분입니다.

따옴표와 이스케이프

여러 줄이 필요하면 삼중따옴표(''' 또는 """)를 쓰고, docstring도 이 형태입니다. 따옴표 안에 따옴표를 넣으려면 다른 종류로 감싸거나 백슬래시로 이스케이프합니다.

quote1 = "He said 'Hello'"
quote2 = 'She said "Hi"'
quote3 = "He said \"Hello\""
print(quote1)  # He said 'Hello'
print(quote2)  # She said "Hi"
print(quote3)  # He said "Hello"

text = '''여러 줄
문자열을
작성할 수 있습니다'''
문자의미
\n줄바꿈
\t탭
\\백슬래시 자체
\', \"따옴표
print("Hello World!\nHello Python!")
print("홍길동\t27세\t프로그래머")
print("백슬래시 출력: \\")
Hello World!
Hello Python!
홍길동	27세	프로그래머
백슬래시 출력: \

인덱싱과 슬라이싱

인덱스는 0부터 세고, 음수는 뒤에서부터 셉니다. 공백도 한 칸을 차지합니다.

text = "Hello Python World"
        |     |      |
        0     6      13
text = "Python"
print(text[2])  # t
print(text[-2])  # o

text = "Hello Python World"
print(text[0:5])  # Hello
print(text[:5])  # Hello
print(text[6:12])  # Python
print(text[13:])  # World

[m:n]은 n번째 글자를 포함하지 않습니다. 시작을 비우면 처음부터, 끝을 비우면 끝까지입니다. 리스트 절의 슬라이싱 규칙이 문자열에도 그대로 통합니다.

더하기, 곱하기, 인덱싱 범위

+는 연결, *는 반복입니다. 리포트 머리줄 같은 구분선을 만들 때 *가 편합니다.

line = "-" * 30
print(line)
print("센서 상태 리포트")
print(line)

first, second = "Hello", "World"
print(first + " " + second)
# Hello World
print("Hi" * 3)  # HiHiHi

슬라이싱은 범위를 벗어나도 있는 만큼만 잘라 주지만, 인덱싱은 바로 에러입니다.

# 의도적으로 에러를 내는 코드
# IndexError: string index out of range
text = "Python"
print(text[10])

대소문자 메서드

메서드설명
upper()모두 대문자로
lower()모두 소문자로
capitalize()첫 글자만 대문자, 나머지는 소문자
title()단어마다 첫 글자 대문자
swapcase()대소문자 반전
print("hello".upper())  # HELLO
print("HELLO".lower())  # hello
print("hELLO wORLD".capitalize())
# Hello world
print("hello world".title())
# Hello World
print("Hello".swapcase())  # hELLO

공백 제거

strip()(양쪽), lstrip()(왼쪽), rstrip()(오른쪽)입니다. 괄호 안에 문자를 넘기면 공백 대신 그 문자들을 양쪽에서 걷어 냅니다.

text = "   Hello Python   "
print(f"[{text.strip()}]")
# [Hello Python]
print(f"[{text.lstrip()}]")
# [Hello Python   ]
print(f"[{text.rstrip()}]")
# [   Hello Python]

print('#world#$$#'.strip('$#'))  # world

찾기와 바꾸기

find()는 위치를 정수로 돌려주고, 못 찾으면 -1입니다. rfind()는 마지막 위치를 찾고, replace()는 일치하는 걸 전부 바꿉니다.

text = "Python is easy. Python is powerful."
print(text.find("Python"))  # 0
print(text.rfind("Python"))  # 16
print(text.count("Python"))  # 2
print(text.find("Java"))  # -1
print(text.replace("Python", "Java"))
# Java is easy. Java is powerful.

n = int('123,456'.replace(',', ''))
print(n)  # 123456

마지막 두 줄처럼 콤마를 지우고 숫자로 바꾸는 패턴은 자주 씁니다.

분리와 결합

split()은 문자열을 리스트로, join()은 리스트를 문자열로 만듭니다.

date = "2024-01-15"
parts = date.split("-")
print(parts)  # ['2024', '01', '15']
y, m, d = parts
print(f"연도: {y}, 월: {m}, 일: {d}")
# 연도: 2024, 월: 01, 일: 15

words = ["Hello", "Python", "World"]
print(" ".join(words))
# Hello Python World
구분split() (권장)split(" ")
기준모든 공백(스페이스, 탭, 줄바꿈)스페이스 한 칸만
연속 공백하나로 묶고 빈 문자열 제거빈 문자열 ''이 끼어 들어감
s = "a  b"
print(s.split())  # ['a', 'b']
print(s.split(" "))  # ['a', '', 'b']

자리 맞추기

rjust(n), ljust(n), center(n)은 n칸을 확보한 뒤 오른쪽, 왼쪽, 가운데로 정렬합니다.

str2 = "Dream"
print('[' + str2.rjust(10) + ']')
# [     Dream]
print('[' + str2.ljust(10) + ']')
# [Dream     ]
print('[' + str2.center(10) + ']')
# [  Dream   ]

그 밖의 메서드

돌려주는 값의 타입이 제각각이라 같이 적었습니다.

메서드설명과 반환 타입
startswith(x)x로 시작하는지(bool)
endswith(x)x로 끝나는지(bool)
isalpha()글자로만 이뤄졌는지(bool)
isalnum()글자와 숫자로만 이뤄졌는지(bool)
zfill(n)앞을 0으로 채워 n칸(str)
partition(sep)첫 구분자 기준으로 앞·구분자·뒤 세 조각(tuple)
splitlines()줄바꿈 기준으로 나누기(list)
print("abc".startswith("ab"))  # True
print("abc".endswith("bc"))  # True
print("한글abc".isalpha())  # True
print("abc123".isalnum())  # True
print("7".zfill(3))  # 007
print("-7".zfill(3))  # -07
print("key=a=b".partition("="))
# ('key', '=', 'a=b')
print("a\nb".splitlines())  # ['a', 'b']

isalpha()는 한글도 글자로 칩니다. zfill()은 부호를 알아서 맨 앞에 둬서 "-7".zfill(3)이 '-07'이 됩니다. 숫자인지 검사하는 isdigit() 형제들은 아래 입력 받기 절에 모았습니다.

이어 붙이기와 속도

문자열은 불변이라 어떤 메서드도 원본을 바꾸지 않습니다. 반복문에서 s += x로 계속 이어 붙이면 원칙적으로 매번 새 문자열이 만들어집니다. CPython이 일부 경우를 최적화해 주긴 하지만, PEP 8은 거기에 기대지 말고 조각을 리스트에 모았다가 "".join()으로 한 번에 합치라고 권합니다.

리스트

값을 순서대로 모아 두고 넣고 빼고 정렬할 때 씁니다.

실생활 예

실생활리스트
장바구니 목록["사과", "우유", "빵"]
학생 점수[85, 90, 78, 92]
할 일 목록["청소", "빨래", "요리"]

생성

fruits = ["사과", "바나나", "오렌지"]
numbers = [1, 2, 3, 4, 5]
empty_list = []

print(type(fruits))  # <class 'list'>
print(len(empty_list))  # 0

숫자가 차례로 든 리스트는 list(range(...))로 바로 만듭니다. 예제는 반복 도우미의 range 절에 있습니다.

인덱싱과 슬라이싱

fruits = ["사과", "바나나", "오렌지",
          "포도", "딸기"]
인덱스:   0, 1, 2, 3, 4
음수:    -5, -4, -3, -2, -1
fruits = ["사과", "바나나", "오렌지",
          "포도", "딸기"]
print(fruits[0])  # 사과
print(fruits[-1])  # 딸기
print(fruits[-5])  # 사과
print(fruits[1:3])  # ['바나나', '오렌지']
print(fruits[:3])
# ['사과', '바나나', '오렌지']
print(fruits[2:])
# ['오렌지', '포도', '딸기']
print(fruits[::2])
# ['사과', '오렌지', '딸기']
print(fruits[1::2])  # ['바나나', '포도']
print(fruits[::-1])
# ['딸기', '포도', '오렌지', '바나나', '사과']

[m:n]은 n번째를 포함하지 않고, [:]는 전체 복사, [::-1]은 역순입니다.

a = [0, 1, 2, 3, 4, 5]
print(a[-4:-1])  # [2, 3, 4]

first_three = slice(0, 3)
print(a[first_three])  # [0, 1, 2]
print("hello"[first_three])  # hel

slice()는 범위 자체를 변수에 담아 둡니다. 같은 범위를 리스트와 문자열에 그대로 쓸 수 있습니다.

슬라이싱 패턴

상황코드
앞 n개 / 뒤 n개a[:n] / a[-n:] (n이 0이면 a[-n:]은 전체가 나옴)
마지막 하나 빼고a[:-1]
왼쪽으로 k칸 회전a[k:] + a[:k] (k가 길이보다 크면 k %= len(a) 먼저)
팰린드롬 판별s == s[::-1]
얕은 복사a[:]

슬라이스 대입과 del

a = [0, 1, 2, 3, 4, 5]
a[1:3] = [9, 9, 9]  # 길이가 달라도 됨
print(a)  # [0, 9, 9, 9, 3, 4, 5]
del a[::2]
print(a)  # [9, 9, 4]

b = a
a[:] = []  # 같은 객체를 비움
print(a, b)  # [] []

간격을 준 슬라이스(a[::2])에 대입할 때는 길이가 같아야 하고, 다르면 ValueError입니다. a[:] = []는 clear()처럼 같은 리스트를 비워서 b도 같이 빕니다. 문자열은 불변이라 슬라이스 대입과 del 모두 안 됩니다.

연산

리스트끼리 더하면 결합, 숫자를 곱하면 반복입니다. 빼기와 나누기는 안 됩니다.

data1 = [1, 2, 3]
data2 = ["A", "B"]
print(data1 + data2)
# [1, 2, 3, 'A', 'B']
print(data1 * 3)
# [1, 2, 3, 1, 2, 3, 1, 2, 3]

메서드와 시간 복잡도

n은 리스트 길이, k는 붙이거나 잘라 낸 개수입니다.

원본을 바꾸는 것:

메서드설명시간
append(x)끝에 추가O(1)
insert(i, x)i 위치에 삽입O(n)
remove(x)처음 나오는 x 삭제, 없으면 ValueErrorO(n)
pop()마지막 요소를 꺼내서 반환O(1)
pop(i)i 위치 요소를 꺼내서 반환(앞쪽일수록 느림)O(n)
sort()정렬. 내림차순은 reverse=TrueO(n log n)
reverse()역순으로 뒤집기O(n)
extend(리스트)풀어서 뒤에 이어 붙이기O(k)
clear()전부 삭제O(n)
del a[i]인덱스나 슬라이스로 삭제O(n)

원본을 그대로 두는 것:

메서드설명시간
index(x)처음 나오는 x의 위치, 없으면 ValueErrorO(n)
count(x)x의 개수O(n)
copy()얕은 복사(a[:]와 같음)O(n)
a[i]인덱싱O(1)
x in a포함 여부O(n)
a[i:j]슬라이싱, 새 리스트를 만듦O(k)

append()의 O(1)은 평균값입니다. 공간이 모자랄 때 가끔 전체를 옮기기 때문입니다.

fruits = ["사과", "바나나"]
fruits.append("오렌지")
fruits.insert(0, "포도")
print(fruits)
# ['포도', '사과', '바나나', '오렌지']

last = fruits.pop()
print(last)  # 오렌지
print(fruits)  # ['포도', '사과', '바나나']

fruits.remove("사과")
print(fruits)  # ['포도', '바나나']
print(fruits.index("바나나"))  # 1

fruits.clear()
print(fruits)  # []

내장함수와 key

len(), sum(), min(), max()는 import 없이 바로 쓰는 내장함수입니다. 포함 여부를 묻는 in은 함수가 아니라 연산자입니다.

numbers = [10, 20, 30, 40, 50]
print(sum(numbers))  # 150
print(min(numbers))  # 10
print(max(numbers))  # 50
print(sum(numbers) / len(numbers))
# 30.0

fruits = ["사과", "바나나", "오렌지"]
print("바나나" in fruits)  # True
print("포도" in fruits)  # False

sorted(), max(), min()은 key로 비교 기준을 바꿀 수 있습니다. key=len이면 길이로 비교합니다. 빈 리스트에 max()를 쓰면 ValueError가 나는데, default를 주면 그 값을 대신 돌려줍니다.

words = ["banana", "kiwi", "apple"]
print(max(words))  # kiwi
print(max(words, key=len))  # banana
print(min(words, key=len))  # kiwi
print(sorted(words, key=len))
# ['kiwi', 'apple', 'banana']
print(sorted(words, key=len,
             reverse=True))
# ['banana', 'apple', 'kiwi']
print(max([], default=0))  # 0
print(sorted("cba"))  # ['a', 'b', 'c']

words.sort(key=len)
print(words)
# ['kiwi', 'apple', 'banana']

key 없이 문자열을 비교하면 사전순이라 max(words)가 kiwi입니다. list.sort()도 key와 reverse를 똑같이 받지만 리스트에만 있는 메서드입니다. sorted()는 문자열처럼 순회할 수 있는 건 뭐든 받아서 새 리스트로 돌려줍니다.

한 줄 패턴

from collections import deque

a = [1, 2, 3, 4]
a[0], a[-1] = a[-1], a[0]
print(a)  # [4, 2, 3, 1]

first, *rest = a
print(first, rest)  # 4 [2, 3, 1]

print(not [])  # True

stack = [1, 2, 3]
stack.append(4)
print(stack.pop())  # 4

queue = deque([1, 2, 3])
queue.append(4)
print(queue.popleft())  # 1
print(queue)  # deque([2, 3, 4])

리스트 요소끼리도 한 줄로 맞바꿀 수 있고, *rest는 남은 요소를 항상 리스트로 받습니다. 원리는 아래 튜플 절의 언패킹과 같습니다. 빈 리스트는 거짓으로 취급돼서 if not a:로 비었는지 검사합니다.

스택은 리스트의 append()와 pop()이면 충분합니다(둘 다 O(1)). 큐처럼 앞에서 꺼내야 하면 pop(0)이 O(n)이라 데이터가 많을 때 느려집니다. 그럴 땐 collections.deque의 popleft()를 씁니다(O(1)).

튜플

바뀌면 안 되는 값을 한 묶음으로 다룰 때 꺼냅니다.

성질

  • 순서가 있어서 인덱싱·슬라이싱이 됩니다
  • 같은 값이 중복으로 들어가도 됩니다
  • 만든 뒤에는 수정·추가·삭제가 안 됩니다 (t[0] = 10은 TypeError)
  • 괄호를 생략해도 튜플입니다. t = 1, 2, 3도 튜플이고, 리스트는 [ ]를 생략할 수 없습니다
  • 요소 하나짜리는 콤마가 필요합니다. (90)은 정수, (90,)는 튜플

만들기와 인덱싱

empty = ()  # tuple()도 같음
nested = (90, 70, ('A', 'B'))
print(nested[2][1])  # B

t = ("사과", "바나나", "오렌지")
print(t[0])  # 사과
print(t[-1])  # 오렌지
print(t[1:])
# ('바나나', '오렌지')

연산

print((90, 85) + (9, 8))
# (90, 85, 9, 8)
print((1, 2) * 3)
# (1, 2, 1, 2, 1, 2)

+는 이어 붙이기, *는 반복입니다. 둘 다 새 튜플을 만들기 때문에 불변 규칙과 부딪히지 않습니다.

꼭 고쳐야 할 때

t = (1, 2, 3)
temp = list(t)
temp[0] = 10
t = tuple(temp)
print(t)  # (10, 2, 3)

언패킹

a, b, c = 10, 20, 30
print(a + b + c)  # 60

a, b = b, a  # 스왑
x, y = divmod(45, 20)
print(x, y)  # 2 5

리스트와 무엇이 다른지는 아래 자료형 비교 절의 표에 모았습니다.

딕셔너리

이름이나 번호로 값을 찾을 때, 개수를 셀 때 씁니다.

규칙

  • 반드시 키: 값 쌍으로 씁니다. 키만 있으면 집합이 됩니다
  • 키는 중복될 수 없습니다. 같은 키를 두 번 쓰면 마지막 값으로 덮어씁니다
  • 값은 중복돼도 됩니다
  • 키는 해시할 수 있는 값만 됩니다 (문자열, 숫자, 가변 값이 없는 튜플). 리스트는 TypeError
  • 넣은 순서는 파이썬 3.7부터 유지됩니다
ok = {"a": 1, "b": 1}
print(ok)  # {'a': 1, 'b': 1}
# 값 중복은 문제없음

dup = {"a": 1, "a": 2}
print(dup)  # {'a': 2}

만들기

phone = {"홍길동": "010-1234-5678"}
words = {'love': '사랑', 'dream': '꿈'}
empty = {}  # 빈 집합은 set()

print(dict(이름='홍길동', 나이=20))
# {'이름': '홍길동', '나이': 20}

pairs = [['홍길동', 100], ['김철수', 90]]
print(dict(pairs))
# {'홍길동': 100, '김철수': 90}

pairs = (('홍길동', 100), ('김철수', 90))
print(dict(pairs))
# {'홍길동': 100, '김철수': 90}

print(dict(zip("abc", [1, 2, 3])))
# {'a': 1, 'b': 2, 'c': 3}

dict()는 키워드 인자, [키, 값] 쌍이 든 리스트, 튜플, zip() 결과를 모두 받습니다.

조회 메서드

m = {'이름': '홍길동',
     '취미': ['독서', '여행'],
     '성별': 'M', '등급': 'Gold'}

print(m['이름'])  # 홍길동
print(m.get('지역'))  # None
print(m.get('지역', '없음'))  # 없음
print('이름' in m)  # True
print('홍길동' in m)  # False (키만 본다)
print(len(m))  # 4 (키 개수)

print(list(m.keys()))
# ['이름', '취미', '성별', '등급']
print(list(m.values()))
# ['홍길동', ['독서', '여행'], 'M', 'Gold']
print(list(m.items())[0])
# ('이름', '홍길동')
  • keys(): 키 목록
  • values(): 값 목록
  • items(): (키, 값) 튜플 목록
  • get(키, 기본값): 없는 키면 None이나 기본값. 대괄호는 KeyError
  • 키 in d: 키가 있는지 (값은 안 봄)

추가·삭제 메서드

위에서부터 순서대로 실행한 결과입니다.

m = {'이름': '홍길동',
     '취미': ['독서', '여행'],
     '성별': 'M', '등급': 'Gold'}

m['나이'] = 20  # 없는 키: 추가
m['나이'] = 21  # 있는 키: 수정
del m['나이']  # 삭제

print(m.setdefault('지역', '서울'))
# 서울 (없던 키라 추가됨)
print(m.setdefault('성별', 'F'))
# M (있는 키는 기존 값)

print(m.pop('성별'))  # M
print(m.pop('없는키', None))  # None

m.update({'나이': 21, '지역': '부산'})
print(m['지역'])  # 부산
print(m.popitem())  # ('나이', 21)
  • setdefault(키, 기본값): 키가 없으면 기본값을 넣고 돌려줌, 있으면 기존 값
  • pop(키, 기본값): 꺼내면서 삭제. 없는 키는 KeyError, 기본값을 주면 그 값
  • update(딕셔너리): 한꺼번에 추가·수정
  • popitem(): 마지막에 넣은 (키, 값)을 꺼내면서 삭제
  • del d[키]: 삭제
  • clear(): 전부 삭제. 괄호를 빼면 아무 일도 안 일어남

전부 지울 때는 d.clear()나 d = {}를 씁니다. 다만 d = {}는 이름에 새 빈 딕셔너리를 붙일 뿐이라, 같은 딕셔너리를 가리키던 다른 변수에는 내용이 남습니다.

d = {'a': 1}
alias = d
d = {}
print(alias)  # {'a': 1}

d = alias
d.clear()
print(alias)  # {}

복사와 fromkeys

m = {'취미': ['독서', '여행']}
c = m.copy()  # 얕은 복사
c['취미'].append('운동')
print(m['취미'])
# ['독서', '여행', '운동']

m.clear()
print(m)  # {}

print(dict.fromkeys(['국어', '수학'], 0))
# {'국어': 0, '수학': 0}

d = dict.fromkeys(['a', 'b'], [])
d['a'].append(1)
print(d)  # {'a': [1], 'b': [1]}

copy()는 얕은 복사라 안에 든 리스트는 원본과 같이 씁니다. dict.fromkeys()의 값에 []를 주면 모든 키가 리스트 하나를 같이 씁니다.

중첩과 반환값

d = {'v1': 32, 'l1': [1, 2, 3],
     'd1': {'a': 1, 'b': 2}}

print(d['v1'])  # 32
print(d['d1']['a'])  # 1
print(d.get('v2', 0))  # 0
print(d.keys())
# dict_keys(['v1', 'l1', 'd1'])
print(d.values())
# dict_values([32, [1, 2, 3],
#   {'a': 1, 'b': 2}])
print(d.items())
# dict_items([('v1', 32),
#   ('l1', [1, 2, 3]),
#   ('d1', {'a': 1, 'b': 2})])

출력 주석의 줄바꿈은 화면 폭 때문에 넣었습니다. 실제로는 한 줄로 찍힙니다.

정렬과 Counter

from collections import Counter

count = {'h': 1, 'e': 1, 'l': 2, 'o': 1}
print(sorted(count.items(),
             key=lambda x: x[1]))
# [('h', 1), ('e', 1), ('o', 1), ('l', 2)]
print(sorted(count.items(),
             key=lambda x: x[1],
             reverse=True))
# [('l', 2), ('h', 1), ('e', 1), ('o', 1)]

c = Counter("hello")
print(c)
# Counter({'l': 2, 'h': 1, 'e': 1, 'o': 1})
print(c.most_common(1))  # [('l', 2)]
print(c['z'])  # 0 (없는 키도 0)

sorted()는 값이 같으면 원래 넣은 순서를 지킵니다.

키마다 리스트 모으기

from collections import defaultdict

pairs = [('a', 1), ('b', 2), ('a', 3)]

g = {}
for k, v in pairs:
    g.setdefault(k, []).append(v)
print(g)  # {'a': [1, 3], 'b': [2]}

dd = defaultdict(list)
for k, v in pairs:
    dd[k].append(v)
print(dict(dd))  # {'a': [1, 3], 'b': [2]}

둘 다 키가 있는지 먼저 확인하지 않아도 됩니다. for로 키와 값을 꺼내는 순회는 반복 도우미 절에 모았습니다.

집합

중복을 지우거나 있는지만 빠르게 확인할 때 씁니다.

성질

  • 중괄호 안에 값만 나열합니다 (키: 값 쌍이면 딕셔너리)
  • 같은 값은 하나만 남고 자동으로 지워집니다
  • 순서가 없어서 인덱싱·슬라이싱이 안 됩니다 (s[0]은 TypeError)
  • 요소 규칙은 딕셔너리 키와 같습니다 (숫자, 문자열, 튜플은 되고 리스트는 안 됨)
  • 빈 집합은 set()입니다. {}는 빈 딕셔너리

만들기와 변환

nums = {1, 2, 3, 4, 5}
etc = {"길동이", 100, (1, 2, 3)}  # 섞어도 됨
empty = set()

print(set(range(0, 5)))  # {0, 1, 2, 3, 4}

names = set(['홍길동', '김철수', '홍길동'])
print(len(names))  # 2

food = {'햄버거', '피자', '소고기', '피자'}
print(len(food))  # 3
print('소고기' in food)  # True

print(tuple(set((1, 1, 2))))  # (1, 2)

연산

  • 합집합: a | b, a.union(b). 둘 중 어디든 있는 것 전부 (중복은 한 번)
  • 교집합: a & b, a.intersection(b). 둘 다 있는 것만
  • 차집합: a - b, a.difference(b). a에만 있는 것
  • 대칭 차집합: a ^ b, a.symmetric_difference(b). 한쪽에만 있는 것 (교집합 빼고 전부)
  • 부분집합: a <= b, a.issubset(b). a가 전부 b 안에 있으면 True
  • 상위집합: a >= b, a.issuperset(b). b가 전부 a 안에 있으면 True
  • 서로소: 기호 없음, a.isdisjoint(b). 겹치는 게 하나도 없으면 True
food1 = {'피자', '치킨', '닭발',
         '위스키', '초밥'}
food2 = {'초밥', '커리', '파스타',
         '김치', '위스키'}

print(len(food1 | food2))  # 8
print(sorted(food1 & food2))
# ['위스키', '초밥']
print(sorted(food1 - food2))
# ['닭발', '치킨', '피자']
print(sorted(food1 ^ food2))
# ['김치', '닭발', '치킨',
#  '커리', '파스타', '피자']

same = food1.intersection(food2)
print((food1 & food2) == same)  # True
print({'피자', '초밥'} <= food1)  # True
print(food1.issuperset({'치킨'}))  # True
print(food1.isdisjoint(food2))  # False

메서드

  • add(값): 하나 추가 (리스트라면 append 자리)
  • update([값, 값]): 한꺼번에 추가
  • pop(): 아무거나 하나 꺼내면서 삭제 (어떤 값이 나올지 보장 안 됨)
  • remove(값): 삭제, 없으면 KeyError
  • discard(값): 삭제, 없으면 그냥 무시
  • clear(): 전부 삭제
s = {1, 2, 3}
s.add(4)
s.update([5, 6])
s.discard(100)  # 없어도 조용히 넘어감
print(s)  # {1, 2, 3, 4, 5, 6}
s.remove(100)  # 의도한 에러: KeyError

중복 제거와 공통 요소

nums = [1, 2, 2, 3, 4, 5, 5]
print(sorted(set(nums)))  # [1, 2, 3, 4, 5]

a = [1, 2, 4, 6, 8, 9]
b = [2, 3, 4, 5, 7, 9]
print(sorted(set(a) & set(b)))  # [2, 4, 9]
print(sorted(set(a) | set(b)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

nums = [3, 1, 3, 2, 1]
print(list(dict.fromkeys(nums)))
# [3, 1, 2] (원래 순서 유지)

list(set(nums))는 순서를 보장하지 않습니다. 원래 순서를 지키려면 dict.fromkeys()를 씁니다.

자료형 비교와 변환

리스트, 튜플, 딕셔너리, 집합 가운데 무엇을 쓸지 고를 때 봅니다.

괄호 모양이 자료형을 정한다

괄호자료형다룬 날
대괄호 [ ]리스트 (List)Day 2
소괄호 ( )튜플 (Tuple)Day 3
중괄호 { }집합 (Set)Day 3
중괄호 { : }딕셔너리 (Dictionary)Day 3
print(type([1, 2, 3, 4]))
# <class 'list'>
print(type((1, 2, 3, 4)))
# <class 'tuple'>
print(type({1, 2, 3, 4}))
# <class 'set'>
print(type({"a": 1, "b": 2}))
# <class 'dict'>

리스트와 튜플

구분리스트튜플
괄호[ ]( )
인덱싱OO
수정O (가변)X (불변)
중복OO
빈 것[], list()(), tuple()
x inO(n)O(n)
키·원소로XO (안에 가변 값이 없을 때)
속도·메모리상대적으로 무거움더 가볍고 약간 빠름
쓰는 곳계속 바뀌는 데이터 (점수 목록, 장바구니)바뀌면 안 되는 데이터 (좌표, 설정값), 딕셔너리 키

딕셔너리와 집합

구분딕셔너리집합
괄호{키: 값}{값}
인덱싱X (키로 접근)X
수정OO (추가·삭제)
중복키 X, 값 OX
빈 것{}, dict()set()
x in키 기준 평균 O(1)평균 O(1)
키·원소로XX (frozenset은 O)

"키·원소로"는 딕셔너리 키나 집합 원소로 쓸 수 있느냐입니다.

어떤 걸 쓸까

이럴 때이걸로
순서가 중요하고 값을 계속 바꿔야 함리스트
좌표 (x, y)처럼 한 묶음이고 바뀌면 안 됨튜플
방문 여부, 중복 제거처럼 있는지만 빠르게 확인집합
이름이나 번호로 값을 찾거나 개수를 셈딕셔너리

집합과 딕셔너리의 시간 복잡도

  • 포함 검사: x in s, k in d(키만) 둘 다 평균 O(1)
  • 조회: 집합은 인덱싱 불가, d[k]는 평균 O(1)
  • 추가: s.add(x), d[k] = v 둘 다 평균 O(1)
  • 삭제: s.discard(x), del d[k] 둘 다 평균 O(1)
  • 정렬·슬라이싱: 둘 다 없음 (sorted()가 리스트로 만들어 줌)

해시값이 겹치면 느려질 수 있어서 "평균"이 붙습니다. 리스트의 in은 O(n)입니다.

리스트와 딕셔너리 문법

  • 값 꺼내기: lst[0] (위치) / d['키'] (키)
  • 에러 없이 꺼내기: 리스트는 없음 (IndexError) / d.get('키', 기본값)
  • 전부 나열: lst 그대로 / d.keys(), d.values(), d.items()
  • 값만 나눠 담기: a1, a2, a3 = lst / i1, i2, i3 = d.values()
  • 추가: lst.append(값) (맨 뒤) / d['새키'] = 값
  • 삭제: lst.pop(), del lst[0] / del d['키'], d.pop('키')
  • 있는지 확인: 값 in lst / '키' in d
  • 길이: len(lst) / len(d) (키 개수)

서로 변환하기

print(list((1, 2)))  # [1, 2]
print(tuple([1, 2]))  # (1, 2)
print(sorted(set([2, 1, 2])))  # [1, 2]

visited = set()
visited.add((1, 2))  # 좌표 튜플은 된다
print((1, 2) in visited)  # True
print({frozenset({1, 2}): "ok"})
# {frozenset({1, 2}): 'ok'}
visited.add([1, 2])  # 의도한 에러: TypeError

zip()으로 두 리스트를 딕셔너리로 바꾸는 예는 위 딕셔너리 만들기에 있습니다. frozenset은 못 바꾸는 집합이라 튜플처럼 딕셔너리 키나 집합 원소가 될 수 있습니다.

조건문과 반복문

C 습관대로 if와 while을 쓰다가 막힐 때 봅니다.

if 기본 규칙

  • 위에서부터 검사하다가 처음 True가 나온 블록 하나만 실행합니다.
  • C의 else if는 elif입니다. elif와 else는 생략할 수 있습니다.
  • 블록이 비어 있으면 에러입니다. 쓸 코드가 없으면 pass로 자리만 채웁니다.
  • 실행할 문장이 한 줄이면 if score_list: print(score_list)처럼 붙여 써도 됩니다.

거짓으로 취급되는 값

  • False, None
  • 숫자 0: 0, 0.0
  • 빈 값: "", [], (), {}, set()
  • 나머지는 전부 참입니다.
score_list = [75, 80, 70, 95, 90]

if score_list:  # 요소가 있으면 True
    print(score_list)
else:  # 빈 리스트 []면 False
    print("요소 없음")
# [75, 80, 70, 95, 90]

if에서 자주 나는 오류

  • 조건 끝 콜론 빠짐: SyntaxError
  • 블록을 들여쓰지 않음: IndentationError (expected an indented block)
  • 같은 블록 안에서 칸이 안 맞음: IndentationError (unexpected indent)

break와 continue

키워드하는 일
break반복문을 즉시 탈출
continue아래 코드를 건너뛰고 다음 반복으로

while 기본형과 인덱스로 돌기

i = 1
while i <= 5:
    print(i)  # 1~5 한 줄씩
    i += 1  # 빼먹으면 무한루프

fruits = ['apple', 'banana', 'cherry']
i = 0
while i < len(fruits):
    print(fruits[i])
    i += 1
# apple
# banana
# cherry
  • len()은 리스트면 요소 개수, 문자열이면 글자 수를 돌려줍니다.
  • C의 do-while은 없습니다. while True: 안에서 if 조건: break로 씁니다.
  • while True:와 break 조합은 일부러 쓰는 패턴입니다. 입력을 받을 때 자주 씁니다.
  • 무한루프는 터미널에서 Ctrl+C, 주피터에서 정지 버튼으로 끕니다. 맥 터미널의 Ctrl+Z는 일시정지일 뿐이라 프로그램이 뒤에 남습니다.

C와 파이썬 반복문 비교

  • 블록 구분: C는 { }, 파이썬은 콜론 :과 들여쓰기입니다.
  • 조건 괄호: C는 필수(while (i <= 10)), 파이썬은 생략(while i <= 10:)합니다.
  • for의 정체: C는 초기식; 조건식; 증감식으로 숫자를 세는 반복(사실상 while을 한 줄로)입니다. 파이썬은 for 변수 in 반복대상으로 값을 하나씩 꺼내는 반복(foreach에 가까움)입니다.
  • 범위: C는 i = 1; i <= 10, 파이썬은 range(1, 11)(끝 숫자 미포함)입니다.
  • 증감 연산자: C는 i++, i--가 있고 파이썬은 없어서 i += 1로 씁니다.
  • 반복 중 i 수정: C는 반영돼서 반복 흐름이 바뀝니다. 파이썬 for는 다음 반복에서 range의 다음 값으로 덮어써서 무시됩니다.
  • 반복 끝난 뒤 i: C에서 for (int i ...)로 선언하면 밖에서 못 씁니다. 파이썬은 남아 있습니다(range(1, 11)을 돌았으면 10).
  • do-while: C는 있고, 파이썬은 없어서 while True: 안에서 if 조건: break로 씁니다.
  • 무한루프: C는 while (1), for (;;), 파이썬은 while True:입니다.
  • 논리 연산자: C는 &&, ||, !, 파이썬은 and, or, not입니다.
  • break / continue: 둘 다 똑같이 있습니다.
  • 반복문 + else: C는 없습니다. 파이썬은 for ... else:가 있어서 break 없이 끝까지 돌면 else를 실행합니다.

반복 도우미

for 문을 짧게 줄이거나 여러 값을 같이 돌려야 할 때 찾는 도구들입니다.

range

range나오는 값
range(5)0 ~ 4
range(1, 11)1 ~ 10 (끝 숫자 미포함)
range(0, 10, 2)0 2 4 6 8 (2칸씩)
range(10, 0, -1)10 9 … 1 (거꾸로)

list()로 감싸면 숫자 리스트가 바로 나옵니다.

print(list(range(10)))
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(range(0, 10, 2)))
# [0, 2, 4, 6, 8]
print(list(range(9, -1, -1)))
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

함수처럼 보이지만 클래스

range, enumerate, zip, map, filter, reversed는 함수처럼 부르지만 클래스입니다. print(zip([1, 2], ['a', 'b']))는 <zip object at 0x...> 같은 객체 정보만 보여 주니 안을 보려면 list()로 감쌉니다.

  • range: 숫자를 차례로 만듭니다.
  • enumerate: (순번, 요소) 튜플을 꺼냅니다.
  • zip: 같은 자리끼리 짝짓습니다.
  • map: 요소마다 함수를 적용합니다.
  • filter: 함수가 True를 준 요소만 남깁니다.
  • reversed: 뒤에서부터 꺼냅니다(원본은 그대로).
print(list(range(5, 0, -1)))
# [5, 4, 3, 2, 1]
print(list(enumerate(['a', 'b'], 1)))
# [(1, 'a'), (2, 'b')]
print(list(zip([1, 2], ['a', 'b'])))
# [(1, 'a'), (2, 'b')]
print(list(map(int, ['1', '2'])))
# [1, 2]
print(list(filter(str.isnumeric,
                  ['12', 'a', '7'])))
# ['12', '7']
print(list(reversed([1, 2, 3])))
# [3, 2, 1]

map과 filter에는 함수를 괄호 없이 이름만 넘깁니다. str.isnumeric은 isnumeric() 메서드를 함수처럼 꺼낸 것입니다.

enumerate

  • list(enumerate(['a', 'b']))는 [(0, 'a'), (1, 'b')]입니다. 그래서 for i, m in enumerate(...)는 튜플 언패킹입니다.
  • enumerate(member, 1)처럼 두 번째 인자를 주면 순번이 1부터 시작합니다.

zip 더 보기

print(list(zip([1, 2, 3], ['a', 'b'])))
# [(1, 'a'), (2, 'b')]  짧은 쪽에 맞춤

matrix = [[1, 2, 3],
          [4, 5, 6]]
print(list(zip(*matrix)))
# [(1, 4), (2, 5), (3, 6)]  행과 열 뒤집기

zip(*matrix)의 별은 호출할 때 푸는 별입니다. 행 두 개를 풀어서 zip([1, 2, 3], [4, 5, 6])으로 넘기니 같은 열끼리 묶입니다.

이터레이터는 한 번 쓰면 끝

nums = map(int, ['1', '2', '3'])
print(sum(nums))  # 6
print(sum(nums))  # 0 (이미 다 꺼냄)

r = range(4)
print(sum(r), sum(r))  # 6 6

it = iter([10, 20])
print(next(it), next(it), next(it, '끝'))
# 10 20 끝
  • range는 몇 번이고 다시 돌 수 있습니다. enumerate, zip, map, filter, reversed는 한 번 꺼내면 소진됩니다.
  • iter(리스트)로 이터레이터를 직접 만들고, next(it, 기본값)은 다 꺼낸 뒤 기본값을 돌려줍니다.
  • 두 번 돌 거면 처음부터 list()로 바꿔 둡니다.

for + append로 걸러 담기

빈 리스트를 만들고, for로 돌면서 조건에 맞는 값만 append()합니다. 컴프리헨션이 손에 붙기 전에는 이렇게 풀어 씁니다.

score = [75, 46, 93, -82, -77,
         130, 100, 75, -10]

score01 = []
for x in score:
    if x >= 0:
        score01.append(x)  # 음수 제거

score02 = []
for x in score01:
    if x <= 100:
        score02.append(x)  # 100 초과 제거

print(score02)  # [75, 46, 93, 100, 75]
print(sum(score02),
      sum(score02) / len(score02))
# 389 77.8
my_nums = [10, 22, 30, 41, 56,
           60, 71, 80, 90]
my_nums.append(100)

result = []
for n in my_nums:
    if n % 10 == 0:
        result.append(n // 10)
print(result)  # [1, 3, 6, 8, 9, 10]

리스트 컴프리헨션 패턴

위 for 문은 컴프리헨션 한 줄로 줄일 수 있습니다. 형태는 [표현식 for 변수 in 반복대상 if 조건]입니다. for를 먼저 읽고, if로 거르고, 맨 앞 표현식으로 바꿔 담습니다.

# 조건 붙이기: 짝수만 제곱
even_squares = [n ** 2
                for n in range(1, 11)
                if n % 2 == 0]
print(even_squares)
# [4, 16, 36, 64, 100]

# 10의 배수만 골라 10으로 나누기
my_nums = [10, 22, 30, 41, 56,
           60, 71, 80, 90, 100]
print([n // 10 for n in my_nums
       if n % 10 == 0])
# [1, 3, 6, 8, 9, 10]

for를 두 번 쓰면 2차원 리스트를 펼 수 있습니다. 왼쪽 for가 바깥 반복, 오른쪽 for가 안쪽 반복입니다.

grid = [[1, 2, 3],
        [4, 5, 6]]

flat = [x for row in grid for x in row]
print(flat)  # [1, 2, 3, 4, 5, 6]

# 풀어 쓰면 적힌 순서 그대로 겹친다
flat = []
for row in grid:
    for x in row:
        flat.append(x)
print(flat)  # [1, 2, 3, 4, 5, 6]

any() · all()

scores = [61, 100, 76]

print(any(s <= 60 for s in scores))
# False (60점 이하가 하나도 없음)
print(all(s > 60 for s in scores))
# True (전부 60점 초과)

print(all([]), any([]))  # True False
  • 대괄호 없이 함수 괄호 안에 쓰면 제너레이터 식입니다. 리스트를 통째로 만들지 않습니다.
  • 답이 정해지는 순간 멈춥니다. any는 첫 True에서, all은 첫 False에서 끝납니다.
  • 빈 값이면 all([])은 True, any([])는 False입니다. 거짓인 요소가 하나도 없으니 "전부 참"이 맞고, 참인 요소도 없으니 "하나라도 참"은 틀린 겁니다.

딕셔너리와 집합 순회

딕셔너리를 for로 돌리면 키만 나옵니다. 키와 값을 같이 받으려면 items()를 씁니다.

score = {'홍길동': 85, '김철수': 95,
         '이지수': 90}

for key in score:  # 키만 나옴
    print(key, score[key])

for key, value in score.items():
    print(key, value)

# 딕셔너리 컴프리헨션: 90점 초과만
print({k: v for k, v in score.items()
       if v > 90})
# {'김철수': 95}

두 for 문 모두 홍길동 85, 김철수 95, 이지수 90을 차례로 출력합니다. 키와 값이 같이 필요하면 items() 쪽이 읽기 편합니다.

member_set = {'홍길동', '최나영',
              '이지수', '유재석'}

for i, m in enumerate(member_set):
    print(i, m)
# 순번은 0~3, 이름 순서는 실행마다 다를 수 있음

함수 인자

인자 개수가 정해지지 않은 함수를 만들거나 읽을 때 봅니다.

*args와 **kwargs 비교

항목*args**kwargs
받는 것이름 없는 값 '라떼'이름=값 size='톨'
짝 맞추는 기준순서이름
묶이는 자료형튜플딕셔너리
꺼내는 법args[0], for x in argskwargs['size'], .items()
안에서 수정불가가능 (함수 밖엔 영향 없음)
  • args, kwargs라는 이름은 관례입니다. 중요한 건 별이라 *nums, *items, **info처럼 뜻이 보이는 이름을 써도 됩니다.
  • 튜플은 바꿀 수 없어서 받은 입력을 그대로 지키고, 리스트보다 조금 가볍기도 합니다. 고쳐 쓰려면 nums = list(args).
  • 키워드 인자는 이름으로 꺼내니 순서를 신경 쓸 필요가 없습니다.

kwargs에서 값 꺼내기

  • 넘길 때는 size='톨'처럼 따옴표 없이 쓰지만, 딕셔너리 안에서는 문자열 키 'size'가 됩니다.
  • 그래서 kwargs['size']로 꺼냅니다. 안 넘어왔을 수도 있으면 kwargs.get('size', '톨')처럼 기본값을 줍니다.

같이 쓸 때의 규칙

  • 정의할 때: 일반 매개변수, *args, **kwargs 순서입니다. **kwargs는 무조건 맨 끝입니다.
  • 호출할 때: 위치 인자를 먼저, 키워드 인자를 뒤에 씁니다.
  • 일반 매개변수가 앞에서 먼저 가져가고, 남은 위치 인자는 args, 남은 키워드 인자는 kwargs로 갑니다.

가변 인자를 쓰는 내장 함수

print(1, 2, 3)  # 1 2 3
print(1, 2, 3, sep='-')  # 1-2-3
print(max(3, 7, 2))  # 7

t = "{} 나누기 10의 나머지는 {}"
print(t.format(17, 7))
# 17 나누기 10의 나머지는 7
print("{name}님 안녕".format(name='홍길동'))
# 홍길동님 안녕
  • print()가 인자를 몇 개든 받는 건 별 하나 매개변수 덕분이고, sep, end는 키워드 인자입니다.
  • str.format()의 정의는 format(*args, **kwargs)입니다. 위치 인자는 args로, name='홍길동'은 kwargs로 들어갑니다.
  • C의 printf("%d %d", a, b) 같은 가변 인자 함수(...) 역할을 파이썬에서는 *args가 합니다.

별의 세 가지 쓰임

  • 정의할 때 def f(*args, **kwargs): 남는 인자를 묶습니다(튜플, 딕셔너리).
  • 호출할 때 f(*lst), f(**d): 리스트는 위치 인자로, 딕셔너리는 키워드 인자로 풉니다.
  • 대입할 때 first, *rest = [1, 2, 3, 4]: rest는 항상 리스트 [2, 3, 4]입니다.

함수에서 자주 나는 오류

  • option('라떼') (**kwargs만 받는 함수에 위치 인자): TypeError: option() takes 0 positional arguments but 1 was given
  • order(size='톨') (*args만 받는 함수에 키워드 인자): TypeError: order() got an unexpected keyword argument 'size'
  • order(size='톨', '라떼'): SyntaxError. 키워드 인자 뒤에 위치 인자를 못 씁니다.
  • def f(**kwargs, *args):: SyntaxError. **kwargs는 맨 끝이어야 합니다.
  • option(size=1, size=2): SyntaxError. 같은 키워드를 두 번 넘길 수 없습니다.
  • args.append(4): AttributeError. 튜플이라 append가 없습니다.

lambda 더 보기

nums = [1, 2, 3, 4]
print(list(filter(lambda x: x % 2 == 0,
                  nums)))
# [2, 4]
  • lambda x: x[1]은 def f(x): return x[1]을 한 줄로 쓴 이름 없는 함수입니다.
  • 정렬 기준이 둘 이상이면 key가 튜플을 돌려주게 합니다. 숫자에 마이너스를 붙이면 그 기준만 거꾸로 됩니다.
  • key=lambda x: (x[1], x[0])에 reverse=True를 주면 이름 기준까지 같이 뒤집힙니다.

입력 받기와 숫자 검사

키보드나 문제 입력을 숫자로 바꿔야 할 때 봅니다.

input()은 문자열을 돌려준다

input()은 항상 문자열을 돌려줍니다. 숫자로 쓰려면 int(input())처럼 바꿔야 하고, 여러 개면 map(int, ...)로 한 번에 바꿉니다.

print(int("10") + 1)  # 11
print(list(map(int, ["1", "2"])))
# [1, 2]

isnumeric() 결과 모음

print("123".isnumeric())  # True
print("12a".isnumeric())  # False
print("".isnumeric())  # False
print("-5".isnumeric())  # False
print("3.14".isnumeric())  # False
print(" 12".isnumeric())  # False
  • 문자가 섞이거나, 빈 문자열이거나, 마이너스·소수점·공백이 있으면 False입니다.
  • 키보드 숫자 기준으로는 0 이상의 정수만 통과합니다. 음수와 소수는 try/except로 받습니다.

isdecimal · isdigit · isnumeric

열 이름은 각각 isdecimal(), isdigit(), isnumeric()의 결과입니다.

입력decimaldigitnumeric
"123"OOO
"²" 제곱XOO
"½" 분수XXO
"三" 한자XXO
"-5"XXX
"3.14"XXX
  • 통과 범위는 isdecimal ⊂ isdigit ⊂ isnumeric 순서로 넓어집니다.
  • int()로 바꿀 생각이면 isdecimal()이 가장 안전합니다.
print("123".isdigit())  # True
print("²".isnumeric())  # True
print(int("²"))  # 의도한 에러: ValueError

입력이 많을 때: sys.stdin.readline

import sys
# 이 아래 input()은 전부 readline
input = sys.stdin.readline

n = int(input())  # 첫 줄: 3
# 둘째 줄: 4 1 7
arr = list(map(int, input().split()))
print(n, sum(arr), max(arr), min(arr))
# 3 12 7 1
  • readline()은 줄 끝의 "\n"까지 돌려줍니다. int()와 split()은 알아서 무시하지만, 문자열로 쓸 때는 input().rstrip()처럼 떼어 냅니다.
  • readline으로 받은 '12\n'은 줄바꿈 때문에 .isnumeric()이 False입니다. 검사하려면 .rstrip()부터 합니다.
  • 바꿔치기한 뒤에는 input('숫자를 입력하세요')처럼 안내 문구를 넣을 수 없습니다. 괄호 자리는 읽을 최대 글자 수(정수)라서 문자열을 넣으면 TypeError입니다.
  • map(int, ...)도 숫자가 아닌 값을 만나면 ValueError입니다. 사람이 직접 치는 입력은 input()으로 받고 isnumeric() 검사를 거칩니다.

외우기용 목록

시간이 없을 때는 이 목록만 다시 훑습니다. Day 2 범위(변수에서 리스트까지)에서 손에 자주 잡히는 것만 추렸습니다.

문자열

  • split() / join(): 쪼개기·합치기
  • strip(): 양쪽 공백 제거
  • replace(a, b): 바꾸기
  • find(x): 위치(없으면 -1)
  • count(x): 개수
  • upper() / lower(): 대소문자
  • f"{x:.2f}": 포맷팅
  • s[::-1]: 뒤집기

리스트

  • append(x): 끝에 추가
  • pop(): 마지막 꺼내기
  • insert(i, x): 중간 삽입
  • remove(x): 값으로 삭제
  • sort() / reverse(): 정렬·뒤집기
  • index(x) / count(x): 위치·개수
  • x in a: 포함 여부
  • a[1:3]: 슬라이싱

내장함수

  • print / input: 출력·입력
  • len: 길이
  • type: 자료형 확인
  • int / float / str: 형 변환
  • sum / max / min: 집계
  • sorted: 정렬한 새 리스트
  • range: 숫자 나열
  • map(int, ...): 일괄 변환

복붙용 단골 조합

반복문(for)은 Day 2 범위가 아니지만 append 패턴이 워낙 자주 나와서 같이 넣었습니다.

# 입력 두 줄:
# 10 20
# 1 2 3
a, b = map(int, input().split())
nums = list(map(int, input().split()))
print(a + b, nums)  # 30 [1, 2, 3]

# 문자열과 리스트 오가기
words = "Life is short".split()
sentence = " ".join(words)
print(sentence)  # Life is short

# 숫자 리스트를 공백으로 이어서 출력
# (join은 문자열만 받아서 map(str, ...))
print(" ".join(map(str, [1, 2, 3])))
# 1 2 3

# 뒤집기 (문자열, 리스트 공통)
print("hello"[::-1])  # olleh
print([1, 2, 3][::-1])  # [3, 2, 1]

# 평균
nums = [10, 20, 30]
avg = sum(nums) / len(nums)
print(avg)  # 20.0

# 빈 리스트에 하나씩 쌓기 (append 패턴)
result = []
for i in range(5):
    result.append(i * i)
print(result)  # [0, 1, 4, 9, 16]

n = len(nums)
print(f"평균: {avg:.1f}점, 총 {n:,}명")
# 평균: 20.0점, 총 3명

입력은 map(int, input().split()), 문자열은 split·join·strip·replace, 리스트는 append·pop·sort, 집계는 len·sum·max·min. 이 조합만 손에 익어도 Day 2 범위 문제는 거의 다 풀립니다.

// RELATED

관련 포스트