[Python] Decorator

kimkrh·2022년 2월 27일
0

Python

목록 보기
12/14

1. 함수

def foo():
	return 1
    
foo() 
# 1
  • Python에 있어서 함수는 def 키워드로 함수명과 파라미터의 리스트(임의)를 이용해 정의한다.

2. 스코프

def foo(arg):
	x = 10
    print(locals()) # locals() : 변수 이름 및 현재 값 등을 알 수 있다.

foo(20) 
# {'arg': 20, 'x': 10}

y=30
print(globals()) # globals() : 글로벌 변수 이름 및 현재 값 등을 알 수 있다.
# {.......'y': 30} # python이 자동적으로 만든 글로벌 변수도 표시된다.
  • Python에서는 함수를 만들면 새로운 스코프가 만들어진다.
  • 각각의 함수가 각각의 이름 공간을 가지고 있다는 의미이다.

3. 변수의 해결 규칙

  1. 작성할 때는 항상 새로운 변수가 그 이름공간 안에 만들어진다.
  2. 참고는 먼저 이름공간 내부터 검색하고 없으면 외부로 검색 영역을 넓혀간다.
text = "I am global!"
def foo():
    print(text)
    
foo() 
# I am global!
text = "I am global!"
def foo():
    text = "I am local!"
    print(locals())
    
foo()
print(text)
# {'text': 'I am local'}
# I am global!
  • 함수의 안쪽에서는 글로벌 변수에 참고할 수 있는 것을 대입할 수 없다.

4. 변수의 라이프사이클

def foo():
    x=1

foo()
print(x)
# Traceback (most recent call last):
#  File "c:\Users\kimkr\Desktop\replit\study\decorator.py", line 27, in <module>
#    print(x)
# NameError: name 'x' is not defined
  • 스코프만으로는 에러가 발생한 이유를 설명할 수 없다.
  • 네임스페이스는 함수 foo가 호출될 때마다 생성되며 처리가 끝나면 사라져버린다.

5. 함수의 인수와 파라미터

def foo(x):
    print(locals())

foo(1)
# {'x': 1} # 파라미터 x가 로컬 변수명으로써 사용되고 있다.
def foo(x, y=0):
    return x-y

print(foo(3, 1)) # 2
print(foo(3)) # 3
print(foo()) # TypeError: foo() missing 1 required positional argument: 'x'
print(foo(y=1, x=3)) # 2

6. 함수의 중첩

  • Python에서는 함수 내에 다시 함수를 정의, 즉 충첩할 수 있다.
def outer():
    x = 1
    def inner():
        print(x)
    inner()

outer() # 1
  • print(x)에서 로컬 변수 x를 찾아보지만 없으므로 네임스페이스를 찾아서 outer내에 정의되어 있는 x를 참고한다.
  • inner()를 호출하고 있지만, 여기서 중요한 것은 inner이라는 것도 하나의 변수명에 지나지 않고, 해결 규칙 내용을 바탕으로 outer내에 네임스페이스의 정의를 찾아서 호출한다.

7. 함수는 Python에 있어서 퍼스트 클래스 오브젝트이다.

  • Python에서 함수는 객체에 불과하다.
print(issubclass(int, object))
# Python내의 모든 객체는 같은 공통 클래스를 상속하여 만들어진다.

def foo():
    pass
print(foo.__class__)

print(issubclass(foo.__class__, object))
# True
# <class 'function'>
# True
  • 즉, 함수를 일반적으로 다른 변수와 동일하게 취급한다.
    -> 다른 함수의 인수로 전달하거나 함수의 리턴 값으로써 사용할 수 있다.
def add(x, y):
    return x+y
def sub(x, y):
    return x-y
def apply(func, x, y):
    return func(x, y)

print(apply(add, 2, 1))
print(apply(sub, 2, 1))
# 3
# 1
def outer():
    def inner():
        print("Inside inner")
    return inner
    
foo = outer()

print(foo)
foo()
# <function outer.<locals>.inner at 0x0000022475A9B1F0>
# Inside inner
  • return inner에서 리턴 값으로써 실행 결과를 보여주는 것이 아닌 함수 그 자체를 지정하고 있다. (inner() x)
  • 이것은 보통 대입가능하므로 foo에 함수를 넣어 실행하는 것이 가능하다.

8. 클로저

def outer():
    x = 1
    def inner():
        print(x)
    return inner
    
foo = outer()

foo() # 1
print(foo.__closure__)
# (<cell at 0x000002A1671FA850: int object at 0x000002A166D06930>,)
  • inner는 outer에 의해 반환되는 함수로, foo에 저장되어 foo()에 의해 실행된다.
  • 변수 x는 outer함수가 실행되는 동안에만 존재한다. 여기서 outer함수의 처리가 종료된 후에 inner함수가 foo에 대입하고 있으므로 foo()는 실행할 수 없지 않을까?
    -> Python은 Function closure의 기능을 가지고 있기 때문에 가능하다.
  • 클로저는 글로벌 스코프 이외에 정의된 함수(inner)가 정의했을 때의 자신을 포함한 스코프 정보를 기억하고 있는 것이다.
  • 정의했을 때 -> inner함수는 outer함수가 호출될 때마다 새롭게 정의된다.
def outer(x):
    def inner():
        print(x)
    return inner

foo1 = outer(1)
foo2 = outer(2)

foo1()
foo2()
# 1
# 2
  • foo1, foo2에 직접 값을 인수로써 넣지 않아도 각각의 내부의 inner함수가 어떤 값을 출력해야하나를 기억하고 있다.
    -> 이것을 이용해서 고정 인수를 얻도록 커스터마이즈한 함수를 생성하는 것도 가능하다.

9. 데코레이터

  • 데코레이터란 함수를 인수로 받아 명령을 추가한 뒤에 다시 함수의 형태로 반환하는 함수이다.
def outer(some_func):
    def inner():
        print("before some_func")
        ret = some_func()
        print(ret + 1)
    return inner

def foo():
    return 1

decorated = outer(foo)

decorated()
# before some_func
# 2
  • 파라미터로 some_func를 받는 outer함수를 정의했다.
  • outer안에 inner라는 내부 함수가 정의되어 있다.
  • inner는 문자열을 print한 후에 반환하는 값을 받았다.
  • some_func는 outer를 호출하는 것으로 다른 값을 얻을 수 있지만 여기서는 무엇을 받던 간에 그 결과에 1을 더한 값을 반환한다.
  • outer함수는 inner함수의 것을 반환한다.
class Coordinate(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return "Coord: " + str(self.__dict__)

def add(a, b):
    return Coordinate(a.x + b.x, a.y + b.y)
def sub(a, b):
    return Coordinate(a.x - b.x, a.y - b.y)

one = Coordinate(100, 200)
two = Coordinate(300, 200)

print(add(one, two))
# Coord: {'x': 400, 'y': 400}
one = Coordinate(100, 200)
two = Coordinate(300, 200)
three = Coordinate(-100, -100)

print(sub(one, two))
print(add(one, three))
# Coord: {'x': -200, 'y': 0}
# Coord: {'x': 0, 'y': 100}
  • 취급할 좌표계가 0이하일때 체크 처리가 필요하다면 어떻게 할 수 있을까?
def wrapper(func):
    def checker(a, b):
        if a.x < 0 or a.y < 0:
            a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
        if b.x < 0 or b.y < 0:
            b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
        ret = func(a, b)
        if ret.x < 0 or ret.y < 0:
            ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
        return ret
    return checker

add = wrapper(add)
sub = wrapper(sub)

one = Coordinate(100, 200)
two = Coordinate(300, 200)
three = Coordinate(-100, -100)

print(sub(one, two))
print(add(one, three))
# Coord: {'x': 0, 'y': 0}
# Coord: {'x': 100, 'y': 200}

10. @심볼의 적용

add = wrapper(add) # 아래와 같이 작성할 수 있다.

@wrapper
def add(a, b):
    return Coordinate(a.x + b.x, a.y + b.y)

11. *args, *kwargs

  • 위의 데코레이터 wrapper은 유용하지만 파라미터가 2개뿐인 함수에만 적용할 수 있다.
  • 함수를 정의할 때는 파라미터에 *를 붙이면 임의의 수의 필수 파라미터를 수용하도록 할 수 있다.
  • 정의할 때뿐만 아니라 호출할 때에도 인수에 *를 붙이면 기존 리스트나 튜플 형식의 인수를 언패키징해서 고정 인수에 적용해준다.
def one(*args):
    print(args)
one()
# ()


def two(x, y, *args):
    print(x, y, args)
two('a', 'b', 'c')
# a b ('c',)


def add(x, y):
    return x + y
lst = [1,2]
print(add(lst[0], lst[1]))
print(add(*lst))
# 3
# 3
  • **에서는 사전형이 된다.
  • **kwargs은 명시적으로 지정하지 않은 파라미터는 kwargs이라는 이름의 사전으로 저장된다는 의미이다.
  • *args와 동일하게 함수를 호출할 때의 언패키징에도 대응한다.
dct = {'x':1, 'y':2}
def bar(x, y):
    return x + y
print(bar(**dct))
# 3

12. 제네릭한 데코레이터

  • 위의 기능을 이용하여 함수의 인수를 로그에 출력해주는 데코레이터를 작성해보자
def logger(func): # 어떠한 함수에 대해서도 데코레이터 logger을 적용할 수 있다.
    def inner(*args, **kwargs): # inner함수는 임의의 개수, 형식의 파라미터를 취득할 수 있다.
        print('Arguments were: %s, %s' % (args, kwargs))
        return func(*args, **kwargs) # 언패키징하여 인수로 전달할 수 있다.
    return inner

@logger
def foo1(x, y=1):
    return x*y

@logger
def foo2():
    return 2

print(foo1(5, 4))
print(foo1(1))
print(foo2())
# Arguments were: (5, 4), {}
# 20
# Arguments were: (1,), {}
# 1
# Arguments were: (), {}
# 2

0개의 댓글