상세 컨텐츠

본문 제목

Block Scope in Python

개발 Recording/Python

by sm-stack 2023. 7. 11. 14:08

본문

Python: Function-Level Scoping

파이썬은 블록 범위(Block Scope)를 지원하지 않기 때문에, 종종 변수가 overriding되어 예상치 못한 동작으로 이어질 수 있다.

game_level = 3
enemies = ["Skeleton", "Zombie", "Alien"]
def get_new_enemy(game_level):
    if game_level < 5:
    	new_enemy = enemies[0]
    print(new_enemy)

get_new_enemy(game_level) # 결과: Skeleton

위와 같은 파이썬 코드에서, new_enemy가 if 문 안에 선언되었음에도 불구하고 올바른 값을 반환하는 것은 파이썬이 function-level scoping을 사용하기 때문이다. 파이썬은 함수 밖에서 선언된 변수를 전역 범위(global scope), 함수 내에서 선언된 변수를 함수 범위(function scope)로 지정하고, 이 두 범위만 사용하여 변수가 작용하는 영역을 나눈다.

 

이는 다른 언어의 동작과는 사뭇 다를 수 있다. C++에서는 다음과 같이 {} 안에 생성된 변수를 외부에서 사용할 수 없는 블록 범위(Block Scope)라는 용법이 존재한다.

int main()
{
    int a = 0;      // scope of the first 'a' begins
    ++a;            // the name 'a' is in scope and refers to the first 'a'
    {
        int a = 1;  // scope of the second 'a' begins
                    // scope of the first 'a' is interrupted
        a = 42;     // 'a' is in scope and refers to the second 'a'                 
    }               // block ends, scope of the second 'a' ends
                    // scope of the first 'a' resumes
}                   // block ends, scope of the first 'a' ends
int b = a;          // Error: name 'a' is not in scope

이와 같은 용법은 다음과 같이 변수 섀도잉에 사용되기도 한다.

// Rust

let x = 123;
if true {
	let x = 456; // shadows the outer `x`, i.e. creates a new variable with the same name
}
let y = x; // y == 123

// Javascript

function myFunc() {
    let my_var = 'test';
    if (true) {
        let my_var = 'new test';
        console.log(my_var); // new test
    }
    console.log(my_var); // test
}
myFunc();

파이썬에서는 블록 범위가 존재하지 않기 때문에, 일관성을 확보하기 쉽다. 그러나, 다음과 같이 중첩된 블록에서는 의도하지 않은 결과를 낳을 수도 있다.

def greet(self, person):
	name = person.name.title()
	print("Hello", name)
	for child in person.children:
		name = child.name.title()
		print("- child:", name)
	print("Goodbye", name)

위 코드에서 우리는 마지막에 "Goodye, person"이 출력되길 희망하지만, 같은 이름의 name 변수가 반복문 블록 안에도 선언되어 있기 때문에 name 변수가 덮어쓰여지고, 마지막 child의 이름이 출력되게 된다.

 

Principle of Least Astonishment (PLA)

PLA는 프로그램, 사용자 인터페이스 등이 유저가 기대하는 대로 동작해야 한다는 원칙이다. 파이썬은 이 원칙을 생각보다 자주 위반한다. 이 글의 주제와는 조금 상관 없는 이야기긴 하지만, 다음과 같은 예시를 보자.

def thing(seq=[]):
	seq.append(5)
	print(seq)

thing()  # Displays [5]

"""
====================================
"""

def thing(seq=[]):
	seq.append(5)
	print(seq)

thing()  # Displays [5]
thing()  # Displays [5, 5] ?!

파이썬 함수 argument의 기본값은 함수가 선언될 때 한 번 evaluate되기 때문에, 여러 함수 호출에 대해 변경 가능하고 공유되는 특성을 가진다. 이와 관련해서는 별도의 글로 다시 한 번 다뤄보도록 하겠다.

 

Scoping에서도 역시 파이썬은 PLA를 위반하는 동작을 종종 수행하곤 한다. 위에서 살펴본 변수의 잘못된 overriding이 그것이다. 이를 방지하기 위해, 다음과 같은 패키지를 사용할 수 있다.

 

Python Scoping package

pip install scoping을 통해 해당 패키지를 설치하고, 다음과 같이 활용할 수 있다.

from scoping import scoping
a = 2
with scoping():
    assert(2 == a)
    a = 3
    b = 4
    scoping.keep('b')
    assert(3 == a)
assert(2 == a) 
assert(4 == b)

with scoping(): 내에 선언된 변수들은 외부에 영향을 주지 않고, 범위가 끝난 뒤에 사라진다. 만약 scoping 내에서 선언된 변수를 원래와 같이 밖에서도 사용하고 싶다면, scoping.keep('variable name')을 사용하면 된다.

 

 

관련글 더보기