본문 바로가기
프로그래밍 이야기/Python

[Python] 함수 호출 횟수 계산 시 알아둬야하는 Local, Enclosing, Global, and Built-in scopes (LEGB) 규칙

by meticulousdev 2022. 4. 7.
반응형

    함수의 호출 횟수를 계산하는 코드를 짠다고 해보겠습니다. 아래의 예제와 같이 cnt를 선언할 경우 분명 선언 및 초기화를 해주었음에도 불구하고 변수가 선언되지 않았다는 오류가 발생합니다. 저는 개인적으로 이런 식으로 전역 변수를 사용하는 것을 좋아하지 않습니다.

 

cnt: int = 0

def test_func():
    cnt += 1
    # cnt = cnt + 1  # 동일 에러 발생  

if __name__ == "__main__":
    test_func()
Exception has occurred: UnboundLocalError
local variable 'cnt' referenced before assignment

 

자 그럼 왜 이런 오류가 발생할까요? 파이썬 공식문서를 확인해보겠습니다.

 

파이썬의 함수 내에서 참조되는 모든 변수는 암시적으로 전역(global)변수입니다. 만약 함수 내부에서 변수의 할당이 이루어진다면 이는 지역(local) 변수로 간주됩니다. 단, 명시적으로 전역(global)을 선언할 경우 전역(global) 변수로 간주합니다.

원문: In Python, variables that are only referenced inside a function are implicitly global. If a variable is assigned a value anywhere within the function’s body, it’s assumed to be a local unless explicitly declared as global.
- 파이썬 공식문서: Programming FAQ — Python 3.8.12 documentation
 

Programming FAQ — Python 3.8.13 documentation

Self is merely a conventional name for the first argument of a method. A method defined as meth(self, a, b, c) should be called as x.meth(a, b, c) for some instance x of the class in which the definition occurs; the called method will think it is called as

docs.python.org

 

즉, 선언한 변수를 참조(출력)는 가능하지만 할당(cnt += 1, cnt = cnt + 1 등)을 하려는 순간 지역변수로 간주되어 에러가 발생한다는 것 입니다.. 그렇다면 어떻게 해결할 수 있을까요?

 

1) 함수의 호출 횟수를 세는 예제에서 함수 밖에 선언된 cnt 변수(int 인 경우)는 global 변수입니다.

그렇기 때문에 함수 안에서 참조(예를 들어 출력)하는 것은 문제가 되지 않습니다.

이 값을 변경(할당)하려고 할 경우 파이썬(아마도 인터프리터)은 해당 변수가 로컬 변수인지 확인합니다.

하지만 이는 로컬 변수가 아니기 때문에 오류가 발생합니다.

이 값을 할당하여 사용하려면 global이 필요합니다.

 

2) 여기서 cnt 가 리스트의 형태를 가지게 되고, cnt[0]의 값을 변경하려고 한다면 상황이 달라집니다.

이 경우 cnt를 참조(refer)하여 cnt[0]를 보는 것일 뿐 cnt 자체를 변화시키지 않습니다 (id의 확인을 통해서 알 수 있습니다).

그렇기 때문에 함수 카운팅을 문제 없이 진행할 수 있습니다.

 

cnt_list: List = [0]

def test_func():
    print(f"id list   : {id(cnt_list)}")
    print(f"id element: {id(cnt_list[0])}")
    cnt_list[0] += 1
    print(f"id list   : {id(cnt_list)}")
    print(f"id element: {id(cnt_list[0])}")

if __name__ == "__main__":
    test_func()
id list   : 2009543178312
id element: 140722264187248
id list   : 2009543178312
id element: 140722264187280

 

3) 그 외에도 래퍼 함수를 이용해서 함수 호출 횟수에 대한 카운팅이 가능합니다. 

 

# class CountChecker (decorator)
class CountChecker:
    def __init__(self, func):
        self.func = func
        self.cnt = 0

    def __call__(self, n: int):
        self.cnt += 1
        return self.func(n)

    def __del__(self):
        print("Call count for {}     : {} times".format(self.func.__name__, self.cnt))
# end of CountChecker (class)

참고문헌: Python decorator class with function counter variable - Stack Overflow

 

Python decorator class with function counter variable

I have class in Python which I need to use as a decorator class but I want to count something when it is called: class SomeClass: counter = 0 @staticmethod def some_function(a): ...

stackoverflow.com

 

클래스의 선언과 동시에 cnt 값을 초기화 하며, 호출 시 cnt 값이 계속해서 증가시킵니다. 최종적으로 객체가 수명을 다해서 사라지게 되면 counting 결과를 출력합니다.

 

긴 글 읽어주셔서 감사합니다. 
글과 관련된 의견은 언제든지 환영입니다.​
반응형

댓글