Skip to content

Latest commit

 

History

History
303 lines (250 loc) · 10.8 KB

211112.md

File metadata and controls

303 lines (250 loc) · 10.8 KB

Day 41

한 권으로 개발자가 원하던 파이썬 심화 A to Z

데코레이터란 함수를 정의해서 전달된 함수를 저장한 후에 실행될 때 내부에 저장된 함수를 실행하는 구조를 말한다.

데코레이터 처리하지 않았을 때

def decorator(func):
    return func

def exefunc(x, y):
    return x + y

exefunc = decorator(exefunc) # 함수를 인자로 받아서 다시 변수에 저장
exefunc(10, 10) # 20

데코레이터 처리했을 때

@decorator
def exefunc(x, y):
    return x * y

exefunc(10, 10)

데코레이터 처리를 위해 내부 함수를 만들어서 외부로 전달하면 이런 환경을 클로저라고 한다.
__closure__ 속성에 실행 함수가 저장된다.

def decorator_1(func):
    def inner():
        return func()
    return inner

@decorator_1
def func_1():
    return "Execute"

func_1.__closure__[0].cell_contents # 데코레이터는 기본으로 클로저를 구성한다.
# 외부 함수의 매개변수에는 실행 함수가 전달되며, 이 실행 함수는 자유 변수로 처리된다.

인자 전달

def decorator_2(func):
    def inner(*args, **kwargs): # 내부 함수로 인자를 받고, 내부에서 실행 함수를 실행한다.
        return func(*args, **kwargs)
    return inner

@decorator_2
def func_2(data_type, n):
    data_ = {'int': int, 'str': str}
    return data_[datatype](n)

func_2('str', '100') # '100'
func_2('int', '100') # 100 

실행 함수 메타 정보 유지

데코레이터 처리하면 내부 함수가 반환되고 기존 실행 함수가 내부에 저장된다.
데코레이팅된 실행 함수가 바로 실행되는 것이 아니라 내부 함수가 실행되어 실행 함수의 메타 정보도 내부 함수에 세팅되지 않아서 필요한 시점에서 수정해야 한다.

실행 함수 메타 정보 확인

def decorator(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@decorator
def func(x, y):
    return x + y

@decorator
def add(x, y):
    return x + y

# 실행 함수의 이름을 확인하면 내부 함수를 반환함을 알 수 있다.
func.__name__, add.__name__ # ('inner', 'inner')

# 세부적인 이름을 확인하면 데코레이터 함수의 내부 함수임을 알 수 있다.
func.__qualname__, add.__qualname__ # ('decorator.<locals>.inner', 'decorator.<locals>.inner')

실햄 함수의 메타 정보를 내부 함수의 메타 정보에 할당할 수 있다.

import functools as fs

def decorator_1(func): # 내부 함수에 데코레이터 처리를 해서 실행 함수의 메타 정보를 내부 함수의 메타 정보로 변경한다.
    @fs.wraps(func)
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@decorator_1
def func_1(x, y):
    return x + y

func_1(5, 5) # 반환되는 내부 함수를 실행한다.

# __name__을 확인하면 inner가 아닌 실행 함수의 이름으로 변경된 것을 확인할 수 있다.
func_1.__name__ # 'func_1', 전달된 실행 함수의 이름으로 변경되었다.
func_1.__qualname__ # 'func_1'

별도의 모듈에 데코리ㅔ이터 함수 작성

모듈 dec.py

import functools as fs

def decorator_2(func):
    @fs.wraps(func)
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

다른 파일

import dec # 작성한 모듈 import

@dec.decorator_2
def add(x, y):
    return x + y

실행 함수의 모듈 속성을 확인하면 현재 모듈인 __main__이지만 함수의 전역 이름공간을 확인하면 데코레이터 함수가 정의된 모듈이라는 것을 알 수 있다.

add.__module__ # '__main__'

# 반환되는 함수가 내부 함수이므로 전역 이름공간은 실제 데코레이터가 작성된 모듈을 가리킨다.
add.__globals__['decorator_2'] # <function dec.decorator_2(func)>

메타 정보를 바꾸지 않으면 실제 예외 등이 발생할 때 현재 실행되는 모듈 등의 정보를 제대로 알 수 없다.
데코레이터 함수를 정의할 때 메타 정보를 직접 갱신해서 처리하면 함수의 이름, 문서화, 모듈, 어노테이션 등을 갱신해줘야 한다.

모듈 dec_1.py

def decorator_3(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

main

import dec_1

@dec_1.decorator_3
def add(x, y):
    return x + y

# 메타 정보를 갱신하지 않으면 함수가 작성된 모듈 이름이 출력된다.
add.__module__ # 'dec20'

메타 정보 직접 갱신

def decorator_3(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)

    # functools.wrap을 사용할 때 갱신되는 것과 같음
    inner.__name__ = func.__name__
    inner.__qualname__ = func.__qualname__
    inner.__doc__ = func.__doc__
    inner.__module__ = func.__module__
    inner.__annotations__ = func.__annotations__
    return inner

데코레이터 매개변수 받기

데코레이터 함수를 정의할 때 데코레이터 함수에서 처리할 매개변수를 추가로 받아서 처리할 수 있다.
데코레이터 단계를 하나 더 구조화해서 처리하고 외부 함수 내에 내부 함수를 정의하고, 그 안에 또 다른 내부 함수가 정의되는 구조이다.

def decorator(para):
    def decorator_(func):
        def inner(*args, **kwargs):
            return func(*args, **kwargs)
        return inner
    print(para)
    return decorator_ # 계층이 하나 더 생겨서 데코레이터를 처리하는 함수도 반환

# 실행 함수에 데코레이팅을 하면 매개변수를 전달하고, 매개변수를 전달받으면 데코레이팅 될때 자동으로 출력된다.
@decorator('Parameter')
def func(x, y):
    return x + y
# 'Parameter' 출력

func(1, 2) # 3

데코레이터 함수에 매개변수를 전달하는 절차

def func(x, y):
    return x + y

func_ = decorator('Para') # 위의 데코레이터 함수에 매개변수를 넣고 실행하면 내부 함수가 반환된다.
# 'Para' 출력

func_ # <function __main__.decorator.<locals>.decorator_(func)>

func_ = func_(func) # 실행 함수를 전달 받아 내부 함수 반환한다.
func_.__name__ # 'inner'
func_(10, 10) # 20, 내부 함수에 인자를 받아 실행 함수를 호출하면 실행 값을 반환한다.

활용

def typecheck(para1, para2): # 매개변수 2개를 받아 실행 함수의 매개변수 타입을 점검하는 데코레이터 함수
    def typeproc(x, y): # 내부 함수를 정의해서 데코레이터에 전달된 매개변수 처리
        if not (type(x) == para1 and type(y) == para2):
            raise TypeError('Check Type Again')
        
    def decorator_(func):
        def inner(*args, **kwargs):
            typeproc(*args, **kwargs) # 실행 함수를 호출하기 전에 매개변수를 점검하는 함수 실행
            return func(*args, **kwargs)
        return inner
    print(para1, para2)
    return decorator_

@typecheck(int, int)
def add(x, y):
    return x + y
# <class 'int'> <class 'int'>

add(10, 10) # 20
add(10, 10.1) # 예외 발생

데코레이터로 클래스 갱신

데코레이터는 함수 하나를 받고 이 함수 대신 클래스를 받아서 내부를 갱신할 수 있다.

초기화 함수를 클래스 밖에 정의하되, 초기화 함수으 처리 규칙인 self를 사용하는 방식은 따라야 한다.

def __init__(self, name, age):
    self._name = name
    self._age = age

디스크립터 클래스 정의

class Descriptor:
    def __set_name__(self, Owner, name): # 정의된 속성 이름을 가져와 자동으로 객체의 속성 이름으로 보관
        self._name = '_' + name

    def __get__(self, other, owner): # 속성 이름으로 접근하면 이름공간을 확인해서 저장된 이름으로 값을 가져온다.
        return other.__dict__[self._name]

    def __set__(self, other, value): # 값을 갱신하면 이름공간에 저장된 이름을 확인한다.
        other.__dict__[self._name] = value # 있으면 갱신하고 없으면 만든다.


def decorator(cls): # 클라이언트 클래스에 초기화 함수를 추가하는 함수 정의
    cls.__init__ = __init__ # 초기화 함수를 __init__에 할당
    return cls # 클래스 반환


@decorator # 클래스를 데코레이터 처리
class Klass: # 데코레이팅을 처리할 클래스에는 클래스 속성 두개 생성한다. 속성에는 디스크립터 객체를 할당한다.
    name = Descriptor()
    age = Descriptor()

파이썬에서는 객체나 클래스가 접근할 때 __set__ 메소드가 정의된 디스크립터 속성을 가장 먼저 참조한다.
클래스에 초기화 함수를 정의하지 않았지만, 데코레이팅 처리하면서 초기화 함수가 들어있다.

k = Klass('Name', 23) # 객체 생성
k._name, k._age # ('Name', 23) 출력, 이름에 밑줄을 하나 붙여 보호 속성으로 생성(직접 속성을 접근해서 조회 가능)

k.name, k.age # ('Name', 23) 출력, 디스크립터 속성으로 정의된 것은 속성의 이름으로 조회한다.
# 디스크립터 클래스의 __get__이 실행되어 객체의 속성을 조회한 것이다.

모듈 클래스

패키지 내에 있는 모듈이나 타 패키지에 있는 모듈을 직접 접근해서 사용하려면 그 모듈이 있는 위치를 알고 현재 작성하는 곳에 import 처리를 해야 한다.

import types
import math

print(type(math)) # <class 'module'>
type(math) == types.ModulType # True

issubclass(type(math), types.ModuleType) # True, math 클래스와 ModuleType 클래스는 같아서 상속 관계 성립
isinstance(math, types.ModuleType) # True, math 모듈은 클래스이고, import하면 객체가 생성된다. 생성관계 성립

같은 모듈은 싱글턴 객체로 사용하기 때문에 한 번만 import하면 된다.
두 번 import해도 내부에 관리하는 객체는 하나라서 같은 레퍼런스를 가진다.

모듈 생성, 실행

모듈을 클래스로 정의

from types import ModuleType

class MyModule(ModuleType): # 모듈을 클래스로 정의하기 위해 ModuleType 정의
    def __repr__(self):
        return 'MyModule'
    
    @staticmethod # 모듈에서 사용하는 함수를 정적 메소드로 정의
    def func(a, b):
        return a, b
    
    class Person:
        name = 'Class inside Module'

함수를 정적 메소드로 정의하는 이유는 이름으로 접근하면 함수 객체를 반환하기 때문이다.

정보 확인

MyModule.__dict__ # 모듈 클래스의 이름공간을 확인하면 정적 메소드, 클래스가 정의되어 있는 것을 볼 수 있다.

MyModule.func(5, 5) # (5, 5)
p = MyModule.Persont() # 모듈 내 클래스로 객체 생성
p.name # 'Class inside Module' 출력, 객체로 클래스 속성 조회