ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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!!


    댓글