-
[Python] 데코레이터 (함수를 꾸미는 객체)언어/파이썬 & 장고 2016. 10. 7. 16:09
데코레이터는 __call__()메소드를 구현하는 클래스
__call__() 메소드는 객체를 함수 호출 방식으로 사용하게 만드는 마법 메소드입니다.
# -*- coding: utf-8 -*- class Call: def __call__(self): print("call") obj = Call() obj() # 인스턴스 뒤에 괄호 (와 )를 붙여 호출하면 내부적으로 __call__ 메소드가 호출됩니다. # 결과 # call
데코레이터는 자신이 수식할 함수나 메소드를 내부에 받아놓아야 합니다. 그러기 위해 데코레이터는 __init__()메소드의 매개변수를 통해 함수나 메소드를 넘겨받아 데이터 속성에 저장해둡니다.
# -*- coding: utf-8 -*- class Deco: def __init__(self, f): # __init__() 메소드의 매개변수를 통해 함수를 받아들이고 데이터 속성에 저장해둡니다. print("start") self.func = f def __call__(self): print ("begin : {0}".format(self.func.__name__)) self.func() # __call__() 메소드가 호출되면 생성자에서 저장해둔 함수(데이터 속성)를 호출합니다. print("end : {0}".format(self.func.__name__)
데코레이터가 함수의 장식이지만 데코레이터가 하는 일의 본질은 함수를 '대리호출'해주는 것 뿐입니다. 앞에서 @staticmethod와 @classmethod 데콜이터를 사용했던 것과 같습니다.
생성자
# -*- coding: utf-8 -*- class Deco: def __init__(self, f): print("start") self.func = f # Deco의 func 데이터 속성이 print_hello를 받아둡니다. def __call__(self): print("begin : {0}".format(self.func.__name__)) self.func() print("end : {0}".format(self.func.__name__)) def print_hello(): print("hello") # Deco의 인스턴스가 만들어지며 __init__() 메소드가 호출됩니다. print_hello 식별자는 앞에서 정의한 함수가 아닌 # Deco의 객체입니다. print_hello = Deco(print_hello) print_hello() # __call__() 메소드 덕에 Deco 객체를 호출하듯 사용할 수 있습니다. # start # begin : print_hello # hello # end : print_hello
print_hello = Deco(print_hello)를 통해 print_hello라는 식별자는 더이상 앞서 정의한 함수가 아니라 Deco의 객체를 가리키게 됩니다. 그렇다고 print_hello가 원래 가리키던 함수코드가 사라지는 것이 아닌 Deco안에 저장이되어 있습니다. print_hello가 __call__() 메소드를 구현하는 Deco의 인스턴스이기 때문에 함수처럼 호출이 가능합니다.
@기호
def print_hello(): print("hello") print_hello = Deco(print_hello)
위의 코드를 아랫처럼 바꿀 수 있습니다.
@Deco def print_hello(): print("hello")
for문으로 순회를 할 수 있는 객체 만들기
리스트처럼 순회 가능(iterable)한 객체를 만드는 방법을 설명하겠습니다.
파이썬 for문을 실행할 때 가장 먼저 하는 일은 순회하려는 객체의 __iter__() 메소드를 호출하는 것입니다. __iter__() 메소드는 iterator라고 하는 특별한 객체를 for문에게 반환합니다. 이터레이터는 __next__() 메소드를 호출하여 다음 요소를 얻어냅니다. for문과 같이 자주 사용하는 range()함수가 반환하는 것이 사실은 __iter__() 메소드를 구현하는 순회가능한 객체입니다.
iterator = range(3).__iter__() iterator.__next__()
3번 이상 호출할 시 에러를 발생시킵니다.
# -*- coding: utf-8 -*- class MyRange: def __init__(self, start, end): self.current = start self.end = end def __iter__(self): return self def __next__(self): if self.current < self.end: current = self.current self.current += 1 return current else: raise StopIteration() for i in MyRange(0, 5): print(i) # 결과 # 0 # 1 # 2 # 3 # 4
제너레이터
제너레이터(generator)는 이터레이터처럼 동작하는 함수지만 훨씬 간편하게 구현할 수 있습니다. 클래스를 정의하지 않아도 되고 __iter__() 메소드나 __next__()메소드를 구현할 필요가 없습니다. 그저 함수안에서 yield문을 사용해 값을 반환하면 됩니다.
yield문은 return처럼 함수를 실행하다가 값을 반환하지만 함수를 종료시키지 않고 중단시켜 놓기만 합니다.
# -*- coding: utf-8 -*- def generator(): yield 0 yield 1 yield 2 yield 3 iterator = generator() print(iterator.__next__()) print(iterator.__next__()) print(iterator.__next__()) print(iterator.__next__()) # 결과 # 0 # 1 # 2 # 3
추상 기반 클래스
추상 기반클래스가 요구하는 메소드를 자식 클래스가 구현하지 않는다면, 자식클래스의 인스턴스를 생성하고자 할 때 파이썬은 TypeError 예외를 일으킵니다. 파이썬은 모든 것이 객체입니다. 클래스 또한 객체입니다. 객체의 자료형인 클래스가 존재해야 합니다. 메타 클래스는 '클래스에 대한 정보를 갖고 있는 클래스'를 말합니다. 클래스를 정의할 때 metaclass에 별도의 메타 클래스를 지정하지 않으면 type 클래스가 기본적으로 사용됩니다.
# -*- coding: utf-8 -*- from abc import ABCMeta from abc import abstractmethod class AbstratDuct(metaclass=ABCMeta): @abstractmethod def Quack(self): pass
위 예제 코드에서 AbstratDuct 클래스를 정의할 때 사용한 ABCMeta 메타 클래스는 클래스가 특정 메소드를 구현하는지를 테스트하는 기능을 갖고 있습니다.
AbstratDuct의 파새 클래스는 @abstractmethod 데코레이터로 정의된 Quack() 메소드를 반드시 구현해야 합니다. 안하게 되면 파생 클래스의 인스턴스를 생성하려고 할 때 TypeError 예외를 일으킵니다.
# -*- coding: utf-8 -*- from abc import ABCMeta from abc import abstractmethod class AbstratDuct(metaclass=ABCMeta): @abstractmethod def Quack(self): pass class Duck(AbstratDuct): pass duck = Duck() # TypeError: Can't instantiate abstract class Duck with abstract methods Quack
다음은 제대로 구현한 예입니다.
# -*- coding: utf-8 -*- from abc import ABCMeta from abc import abstractmethod class AbstratDuct(metaclass=ABCMeta): @abstractmethod def Quack(self): pass class Duck(AbstratDuct): def Quack(self): print("duck!!") duck = Duck() duck.Quack() # 결과 # duck!!