포스트

[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 를 사용하는 이유는 무엇일까?

  1. 코드의 재사용성 향상

    위의 예시처럼 위 아래로 별표를 출력해 꾸며주고 싶은 함수가 여러개일 수도 있다. 그 때 매번 여러 함수를 수정하지 않고 @deco 만 함수에 달아준다면 해결 가능하다.

  2. 코드 가독성 향상

    함수 내에서 해당 함수의 이름과 상관없는 기능을 분리할 수 있어 코드 가독성이 향상된다.

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가 존재한다면 정상 작동하겠지만 없는 경우도 있을 것이다.

이때 코드를 작성해

  1. 해당 경로에 해당 이름을 가진 txt를 만들고,
  2. 원하는 문구를 작성하고
  3. 열린 파일을 닫는다.

면 되겠지만, 코드가 너무 길어지고 파일을 쓰는 행위와 별로 관련도 없어서 생성 코드만 따로 작성하고 싶다.

이처럼 특정 코드가 작동할때, 그 앞 뒤로 필요한 작업을 미리 수행시켜 주는 코드에 @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 와 함께 사용해야 한다.

실행순서는 다음과 같다.

  1. @contextmanager 가 달린 함수의 yield 앞 (예시에서는 경로에 파일이 존재하지 않는다면 해당 경로에 파일을 만들고 연다.)
  2. yield로 보내진 함수가 with 문에서 사용된다. (예시에서는 f로 사용된다.)
  3. @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=임커밋



이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.