LEGB 규칙
-
파이썬에서는 변수의 영역(scope)을 다음과 같이 나누며, 함수에서의 우선순위는 지역변수 > 외부 함수의 지역변수 > 전역변수 > 내장형 이다.
-
Local: 함수 안의 범위
-
Enclosed function locals: 내부 함수 밖에, 외부 함수 안의 범위
-
Global: 모듈 범위로, .py 파일 내부의 모든 범위
-
Built-in: 인터프리터 전역적으로 사용가능한 내장형 함수 및 타입을 의미
-
클로저(Closure)
-
함수 안에 함수를 선언하는 것으로, 바깥 함수를 외부 함수(outer), 안쪽 함수를 내부 함수(inner) 또는 클로저(closure)라고 한다.
-
내부 함수에서는 외부 함수의 지역변수(매개변수 포함)에 접근할 수 있다.
-
단, 내부 함수에서 외부 변수의 쓰기 작업을 수행하려면 nonlocal 키워드를 사용해야 한다.
-
읽기는 자유롭게 가능하다.
-
>>> def outer(num):
... num += 3
... def inner():
... nonlocal num
... num *= 3
... return num
... return inner
...
>>> f = outer(1)
>>> f()
12
>>> def outer(num): # nonlocal 키워드를 쓰지 않을 경우 다음과 같은 예외가 발생한다.
... num += 3
... def inner():
... num *= 3
... return num
... return inner
...
>>> f = outer(1)
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in inner
UnboundLocalError: local variable 'num' referenced before assignment
-
메모리 관점: 외부 함수가 호출되면 외부 함수 자체는 메모리에 남아있지만(함수 주소) 변수들은 모두 제거된다. 그러나 내부 함수는 외부 함수 범위의 변수들을 사용하면 자신의 영역에 별도로 저장하기 때문에 f = outer(1) 이후에도 nums 변수는 메모리(내부 함수 영역)에 남아 있는 것이다.
-
파이썬에서 함수는 일급 객체로 실제로 type(outer) 를 해보면 <class 'function'>이 출력된다. 따라서, 위와 같은 정보를 확인하려면 다음과 같이 객체의 멤버 변수나 함수를 이용하면 된다.
-
>>> type(outer)
<class 'function'>
>>> f.__closure__ # cell 이라는 객체로 튜플 타입으로 저장되어 있다.
(<cell at 0x1061e49d0: int object at 0x10607bbe0>,)
>>> type(f.__closure__[0])
<class 'cell'>
>>> f.__closure__[0].cell_contents # 변수에 저장된 값이 메모리에 남아있다!
12
-
외부 함수에는 여러 개의 내부 함수(closure)를 선언할 수 있고 내부 함수끼리 서로 호출이 가능하다.
-
그러나, 외부 함수의 closure 변수는 모든 내부 함수의 정보를 가지고 있지 않다.
-
다음과 같이 내부 함수가 여러 개일 경우 호출되는 순서에 따라 내부 함수들이 저장되며, 내부 함수 A가 내부 함수 B를 호출하면 내부 함수 A의 __closure__ 에 내부 함수 B가 저장된다.
-
사용되지 않는 내부 함수는 저장되지 않는다.
-
>>> def fn1():
... def inner():
... return 1
... def inner2():
... n = inner()
... return n * 3
... def inner3():
... n = inner2()
... return n + 5
... return inner3
...
>>> x = fn1()
>>> x()
8
>>> x
<function fn1.<locals>.inner3 at 0x106303820>
>>> x.__closure__
(<cell at 0x1062dd400: function object at 0x106303940>,)
>>> x.__closure__[0].cell_contents # inner3 함수 객체 안에 inner2 함수 객체가 저장되어 있다.
<function fn1.<locals>.inner2 at 0x106303940>
>>> x.__closure__[0].cell_contents.__closure__
(<cell at 0x1062dd430: function object at 0x106303af0>,)
>>> x.__closure__[0].cell_contents.__closure__[0].cell_contents # 마찬가지.
<function fn1.<locals>.inner at 0x106303af0>
데코레이터(Decorator)
-
클로저가 가장 많이 사용되는 코딩 패턴으로는 데코레이터(Decorator)가 있다. 이름에서 알 수 있듯이 코드에서 무언가를 꾸며주는 역할을 한다. 실제로 객체 지향 패턴에도 데코레이터 패턴이라는게 있는데 파이썬에서는 클래스로 선언하는 것보다 함수로 선언하는 것이 많이 사용된다.
-
특징은 함수를 인자로 받아서 내부 함수로 한번 감싸서(wrapping) 반환을 한다는 점이다.
-
보통 둘 이상의 함수에 대해 추가적인 작업이 선행되어야 하는 경우 자주 쓰인다.
-
예를 들어, 데이터를 넘기기 전 공통된 전처리가 필요한 경우, 로깅, 리다이렉트, 성능 테스트
-
def decorator_fn(original_fn):
def wrapped_fn(*args, **kwargs):
if len(args) == 2:
args = (args[0] + args[1], args[0] * args[1])
return original_fn(*args, **kwargs)
return wrapped_fn
@decorator_fn
def fn1():
print("HI")
@decorator_fn
def fn2(a, b):
print(a, b)
fn1() # HI
fn2(2, 3) # 5, 6
-
위에서 @을 썼는데 이는 fn = decorator_fn(fn1) 과 동일하게 동작하나 후자는 가독성이 낮아 잘 쓰이지 않는다.
-
클래스에서는 __call__() 이라는 메소드를 이용해서 다음과 같이 선언할 수 있다. 위와 동일하게 동작한다.
class DecoratorClass:
def __init__(self, original_fn):
self.fn = original_fn
def __call__(self, *args, **kwargs):
if len(args) == 2:
args = (args[0] + args[1], args[0] * args[1])
return self.fn(*args, **kwargs)
@DecoratorClass
def fn1():
print("HI")
@DecoratorClass
def fn2(a, b):
print(a, b)
fn1() # "HI"
fn2(2, 3) # 5, 6
'Programming Language > Python' 카테고리의 다른 글
[정리 05] Object-Oriented-Programming (OOP) (0) | 2021.02.06 |
---|---|
[정리 03] 유용한 기능들 (0) | 2021.02.05 |
[정리 02] 표준 데이터 타입(Standard Data-Type) (0) | 2021.02.04 |
[정리 01] Python 기초 (0) | 2021.02.04 |
간단한 알고리즘 (0) | 2019.06.30 |