[Python] @decorator, yield, contextmanager
1. 데코레이터 Decorator
파이썬 관련 코드를 보다 보면 가끔 등장하는 모양이 있다.
1
2
3
@deco
def hello():
print("안녕하세요")
이 건방진 골뱅이(@
)는 어디에 쓰이는 것일까?
이름에 걸맞게 해당 기능은 함수를 장식
하는 기능을 한다. 여기서 장식한다는 뜻은 @
이하에 쓰일 함수를 직접 수정하지 않고 추가적인 기능을 제공한다는 뜻이다.
어떻게 작동하는지 예시를 통해 알아보자.
1.1 예시
1
2
3
4
5
6
7
8
9
10
11
12
def deco(fn):
def deco_function():
print("1************")
fn()
print("2************")
return deco_function
def print_hello():
print("안녕하세요")
hello()
당연하게도 평범한 안녕하세요
가 나온다.
이때 print_hello
함수 위에 @deco
를 붙여보자
1
2
3
4
5
6
7
8
9
10
11
12
13
def deco(fn):
def deco_function():
print("1************")
fn()
print("2************")
return deco_function
@deco
def print_hello():
print("안녕하세요")
hello()
안녕하세요
위 아래로 @deco
에서 정의한 별표가 출력된다. 즉 작동 방식은 다음과 같다.
1
2
3
4
5
6
7
def deco(fn): # fn -> 함수를 매개변수로 받아준다.
def deco_function(): # 추가할 기능을 정의한다.
print("1************") # 추가기능1
fn() # 매개변수로 받은 함수 실행
print("2************") # 추가기능2
return deco_function # 기능을 리턴
1.2 데코레이터 사용 이유
한 눈에 보기 어려운 decorator 를 사용하는 이유는 무엇일까?
코드의 재사용성 향상
위의 예시처럼 위 아래로 별표를 출력해 꾸며주고 싶은 함수가 여러개일 수도 있다. 그 때 매번 여러 함수를 수정하지 않고
@deco
만 함수에 달아준다면 해결 가능하다.코드 가독성 향상
함수 내에서 해당 함수의 이름과 상관없는 기능을 분리할 수 있어 코드 가독성이 향상된다.
2. yield
파이썬 함수를 본다면 return
대신 yield
를 사용한 함수를 볼 수 있다.
1
2
3
4
def range_y(num):
for i in range(num):
print("d")
yield i
함수 내에 yield
가 있다면, 파이썬은 해당 함수를 함수가 아니라 Generator
라고 판단한다.
Generator
는 해당 함수를 바로 동작시키는 것이 아니라 동작시키는 권한을 갖고 있다가 Generator
를 다시 실행시키면 실행된다.
예시를 확인해보자.
2.1 예시
1
2
3
4
5
6
7
8
9
def range_y(num):
for i in range(num):
print("d")
yield i
yf = range_y(9)
for item in yf:
print(f"item = {item}")
재미있는 점은 Generator
그 자체가 실행된 순서를 기억하고 있어서 다음 반복부터는 이전 yield
가 끝났던 지점부터 다시 실행된다는 것이다.
또한 한번 정의한 Generator
가 모두 끝나거나, return
을 만나 종료됐을 경우, 다시 선언하지 않으면 사용하지 못한다.
1
2
3
4
5
6
7
8
9
10
11
12
def print_3s():
yield 3
yield 3.3
yield 3.333 # yield는 함수 내에서 여러개 사용 가능
sss = print_3s()
for item in sss:
print(item)
for item in sss:
print(item)
한번만 실행된 것을 볼 수 있다.
이 Generator
를 다시 실행시키고 싶으면 다시 선언해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def print_3s():
yield 3
yield 3.3
yield 3.333
sss = print_3s()
for item in sss:
print(item)
sss = print_3s()
for item in sss:
print(item)
3. contextmanager
진짜 설명하고 싶었던 것은 바로 이 contextmanager
이다. 이 contextmanager
를 설명하기 위에 decorator
, yield
가 모두 필요했다.
어떤 함수에 contextmanager
가 decorator로 붙는다면 그 함수는 반드시 with
문과 사용된다. 예시를 확인해보자.
3.1 예시
어떤 경로의 txt 파일을 열어서 원하는 문구를 쓰고 다시 닫는 코드가 있다고 해보자.
1
2
with open("directory/memo.txt", "w") as f
f.write("안녕하세요")
해당 경로 안에 이미 memo.txt
가 존재한다면 정상 작동하겠지만 없는 경우도 있을 것이다.
이때 코드를 작성해
- 해당 경로에 해당 이름을 가진 txt를 만들고,
- 원하는 문구를 작성하고
- 열린 파일을 닫는다.
면 되겠지만, 코드가 너무 길어지고 파일을 쓰는 행위와 별로 관련도 없어서 생성 코드만 따로 작성하고 싶다.
이처럼 특정 코드가 작동할때, 그 앞 뒤로 필요한 작업을 미리 수행시켜 주는 코드에 @contextmanager
를 달아주면 된다. 이름 그대로 맥락(context)를 관리(manage) 하는 코드인 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from contextlib import contextmanager
import os
@contextmanager
def write_open(path):
dir_name = os.path.dirname(path)# 해당 경로의 디렉터리
if not os.path.isdir(dir_name): # 디렉터리에 파일이 있는지 확인
os.makedirs(dir_name, exist_ok = True) # 없으면 만듦
fw = open(path, "w")
print(1)
yield fw
fw.close()
print(3)
1
2
3
4
with write_open("directory/memo.txt") as f
print(2)
f.write("안녕하세요")
print(4)
이때 contextmanager
가 달렸다면 반드시 yield
를 사용해야하고, 해당 함수를 사용할 때는 반드시 with
와 함께 사용해야 한다.
실행순서는 다음과 같다.
@contextmanager
가 달린 함수의yield
앞 (예시에서는 경로에 파일이 존재하지 않는다면 해당 경로에 파일을 만들고 연다.)yield
로 보내진 함수가with
문에서 사용된다. (예시에서는 f로 사용된다.)@contextmanager
가 달린 함수의yield
뒤 (예시에서는 열린 파일을 닫는다.)
즉 yield 앞 → 함수 사용 → yield 뒤 의 순서로 실행된다.
print
를 달아놓아서, 해당 코드를 실행시킨다면 1 → 2 → 3 → 4 순으로 출력된다.
내용 출처:
https://ctkim.tistory.com/entry/데코레이터decorator
https://www.youtube.com/watch?v=txDg45IsC9A&ab_channel=임커밋
https://www.youtube.com/watch?v=RiUdirBMBhc&t=214s&ab_channel=임커밋
https://www.youtube.com/watch?v=-zoRYdsfzco&t=312s&ab_channel=임커밋