메뉴 닫기

파이썬 클래스 본문 스코프 완벽 이해 메서드 로컬과의 차이와 예제

파이썬 클래스 본문 스코프 완벽 이해 메서드 로컬과의 차이와 예제

🧭 클래스 본문에서 만든 이름은 별도 로컬로 처리되고 메서드 내부 로컬과 전혀 다르게 동작합니다

코드를 읽다 보면 클래스 블록에서 만든 변수와 메서드 안에서 사용하는 변수가 서로 다른 세계처럼 느껴질 때가 있습니다.
애매하게 넘어가면 헷갈림이 끝없이 이어지죠.
파이썬에서는 클래스 본문이 실행될 때 독립된 네임스페이스가 만들어지고 그 안에서 정의한 이름들이 클래스 속성으로 고스란히 남습니다.
반면, 메서드 내부는 일반 함수와 같은 자신만의 로컬 스코프를 갖고, 클래스 본문 스코프를 자연스럽게 캡처하지 않습니다.
이 차이를 정확히 알아두면 self를 언제 쓰고, 클래스명으로 언제 접근해야 하는지, 그리고 의도치 않은 네임 에러를 어떻게 피할지까지 한 번에 정리할 수 있습니다.

이번 글은 파이썬 언어 기초·문법 관점에서 클래스 본문 스코프를 집중적으로 다룹니다.
핵심은 간단합니다.
클래스 본문에서 만든 이름은 평범한 변수처럼 보이지만 실제로는 클래스 네임스페이스에 저장되는 별도의 로컬이며, 메서드 안에서 쓰는 이름은 함수 로컬이라 서로 직접 연결되지 않습니다.
따라서 메서드에서 클래스 본문 이름을 쓰려면 self.이름 또는 클래스명.이름으로 명시적으로 접근해야 하고, nonlocal은 클래스 본문에 적용되지 않습니다.
이 글에서는 개념, 이름 탐색 규칙, 실수하기 쉬운 포인트, 비교 예제까지 자연스럽게 이어서 설명합니다.



📌 클래스 본문 스코프의 핵심 개념

파이썬에서 클래스 본문은 정의 순간 독립적인 로컬 네임스페이스에서 한 번 실행되는 코드 블록입니다.
이 로컬은 함수의 로컬과 다르게 클래스 속성 딕셔너리를 채우기 위한 임시 공간으로 쓰이고, 실행이 끝나면 그 이름들이 클래스 객체의 속성으로 귀속됩니다.
즉, 클래스 본문 안에서 작성한 평범한 변수는 사실상 “클래스 속성”이 되며, 메서드 내부에서 생성되는 이름과는 완전히 다른 범위를 가집니다.

반대로 메서드 내부는 일반 함수와 동일한 규칙을 따르는 자기만의 로컬 스코프를 갖습니다.
그래서 메서드 안에서 x 같은 이름을 그냥 참조하면, 먼저 함수 로컬에서 찾고, 없으면 전역(모듈)로 올라가며, 클래스 본문 스코프를 자동으로 보지 않습니다.
결과적으로 클래스 본문 로컬메서드 로컬은 서로 별개이며, 메서드에서 클래스 본문 이름을 쓰려면 self.이름 또는 클래스명.이름으로 명시적으로 접근해야 합니다.

또 하나 중요한 점은 nonlocal 키워드가 클래스 본문에 대해 동작하지 않는다는 사실입니다.
클래스 본문은 함수가 아니기 때문에, 메서드 안에서 nonlocal로 클래스 본문 이름을 캡처하려고 하면 오류가 납니다.
이 때문에 상태 공유가 필요하다면 인스턴스 속성(self.속성)이나 클래스 속성(클래스명.속성)으로 설계하는 것이 안전합니다.

CODE BLOCK
class Counter:
    unit = "개"        # 클래스 본문 로컬 → 클래스 속성으로 저장
    start = 0          # 클래스 속성

    def __init__(self):
        self.value = Counter.start  # 클래스명으로 명시 접근
        # 또는 self.__class__.start 도 가능

    def show(self):
        # print(unit)  # NameError: 메서드 로컬에서 'unit'을 자동으로 못 찾음
        print(self.value, self.unit)  # self를 통해 접근해야 함

c = Counter()
c.show()  # 0 개

구분 클래스 본문 로컬 메서드 로컬
역할 클래스 속성 딕셔너리를 채우는 임시 네임스페이스 함수 실행 동안만 유효한 지역 변수 공간
접근 방식 클래스명.이름 또는 self.이름(바인딩 후) 이름 그대로 사용, 필요 시 nonlocal/global
이름 탐색 정의 시점의 로컬에 기록되며 함수의 상위 스코프가 아님 LEGB 규칙 적용(클래스 본문은 E에 해당하지 않음)
nonlocal 적용 불가(클래스는 함수가 아님) 가능(중첩 함수 간에 한정)

💎 핵심 포인트:

클래스 본문에서 만든 이름은 별도의 로컬로 취급되어 클래스 속성이 됩니다.

메서드 내부 로컬은 전혀 다른 범위이므로 해당 이름을 자동으로 가져오지 않으며, 반드시 self 또는 클래스명으로 접근해야 합니다.

  • 🧱클래스 본문 로컬은 클래스 속성을 만들기 위한 임시 공간임을 기억
  • 🧭메서드에서 클래스 이름을 쓸 때는 self.이름 또는 클래스명.이름으로 명시 접근
  • 🚫nonlocal은 클래스 본문에 적용되지 않음

💡 TIP: 클래스 속성은 모든 인스턴스가 공유합니다.
인스턴스마다 다른 값을 원하면 __init__에서 self.속성으로 초기화하세요.

📌 메서드 로컬 스코프와 이름 탐색 규칙

클래스 본문에서 정의된 이름이 클래스 로컬이라면, 메서드 안의 변수들은 전형적인 함수 로컬 규칙을 따릅니다.
파이썬의 이름 탐색 규칙, 즉 LEGB (Local → Enclosing → Global → Built-in) 순서가 그대로 적용됩니다.
이때 클래스 본문은 Enclosing 범위에 포함되지 않기 때문에, 메서드 내부에서는 클래스 본문 이름을 자연스럽게 참조하지 못합니다.

예를 들어, 다음 코드에서 unit은 클래스 본문에 정의되어 있지만, 메서드 내부에서 직접 접근하면 NameError가 발생합니다.
이는 메서드가 클래스 본문을 스코프 체인에 포함하지 않기 때문입니다.

CODE BLOCK
class Product:
    unit = "개"
    price = 1000

    def show_info(self):
        print(unit)  # ❌ NameError 발생

p = Product()
p.show_info()

이 코드를 실행하면 NameError: name ‘unit’ is not defined 오류가 뜹니다.
왜냐하면 메서드 안에서 찾을 수 있는 unit은 자신의 로컬 스코프, 상위 함수의 로컬(없음), 전역, 내장 네임스페이스 순으로만 탐색하기 때문입니다.
클래스 본문은 이 탐색 경로 어디에도 들어가지 않습니다.

💬 클래스 본문은 함수가 아니기 때문에, LEGB 규칙의 ‘E’(Enclosing) 자리에 포함되지 않습니다.
따라서 메서드 내부에서 클래스 본문 이름을 직접 참조할 수 없습니다.

🧩 메서드 내부에서 클래스 속성 접근하기

그렇다면 클래스 속성에 접근하려면 어떻게 해야 할까요?
해답은 명시적인 접근입니다.
메서드 안에서는 self 또는 클래스명을 통해 접근해야 합니다.

CODE BLOCK
class Product:
    unit = "개"
    price = 1000

    def show_info(self):
        print(self.unit, self.price)
        # 또는 print(Product.unit, Product.price)

p = Product()
p.show_info()  # 출력: 개 1000

여기서 self.unit은 인스턴스에서 속성을 찾고, 없으면 클래스에서 탐색합니다.
따라서 인스턴스 속성으로 오버라이드되지 않는 한, 클래스 속성 값을 가져오게 됩니다.
이 동작 덕분에 파이썬은 유연하게 속성 우선순위를 관리할 수 있습니다.

💎 핵심 포인트:

클래스 본문 이름은 메서드 로컬 스코프에 포함되지 않습니다.

메서드 안에서 클래스 속성에 접근하려면 반드시 self 또는 클래스명을 명시해야 합니다.



📌 class 본문 변수와 self 및 클래스명 접근법

클래스 본문에서 만든 변수는 사실상 클래스 속성입니다.
이 속성은 모든 인스턴스가 공유하며, 클래스명으로 접근할 수도 있고, 인스턴스의 self를 통해서도 접근할 수 있습니다.
하지만 메서드 내부에서 단순히 변수명만으로 접근하려 하면 실패합니다.
이유는 메서드의 실행 환경이 함수 로컬이기 때문입니다.

예를 들어 다음과 같이 코드를 작성해볼 수 있습니다.

CODE BLOCK
class ScoreBoard:
    max_score = 100

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def report(self):
        # print(max_score) ❌ NameError
        print(self.name, "/", self.score, "/", ScoreBoard.max_score)  # ✅ 명시적 접근

    def bonus(self):
        # self를 통해 접근 가능
        return self.score + self.max_score * 0.1

user = ScoreBoard("Tom", 80)
user.report()
print(user.bonus())

이 코드에서 max_score는 클래스 본문에서 정의된 클래스 속성입니다.
따라서 ScoreBoard.max_score 또는 self.max_score로 접근해야 하며, 메서드 내부에서 그냥 max_score라고 쓰면 오류가 발생합니다.
클래스명 접근은 클래스 레벨의 명시적 참조를 의미하고, self 접근은 인스턴스에서 우선 검색 후 클래스 속성으로 이어지는 체인을 의미합니다.

🔍 클래스 속성 vs 인스턴스 속성

클래스 속성과 인스턴스 속성의 차이를 명확히 이해하면 스코프 충돌을 피할 수 있습니다.
두 속성의 주요 차이는 아래 표로 정리됩니다.

구분 클래스 속성 인스턴스 속성
정의 위치 클래스 본문 __init__ 또는 인스턴스 메서드 내부
공유 여부 모든 인스턴스가 공유 인스턴스마다 별도 보유
접근 방법 클래스명.속성 또는 self.속성 self.속성만 가능
재정의 시점 인스턴스에서 같은 이름을 만들면 가려짐 클래스 속성과 독립적

💎 핵심 포인트:

클래스 속성은 클래스 전체가 공유하지만, 인스턴스 속성은 각 객체마다 다릅니다.

메서드 내부에서 클래스 본문 변수에 접근하려면 반드시 self 또는 클래스명을 명시하세요.

📌 주의할 함정 nonlocal global 그리고 클로저

파이썬의 스코프를 다룰 때 초보자뿐 아니라 숙련자도 종종 헷갈리는 부분이 바로 nonlocalglobal 키워드의 작동 범위입니다.
이들은 함수 내부에서만 유효한 키워드이므로, 클래스 본문에는 적용되지 않습니다.
즉, 메서드 안에서 클래스 본문에 정의된 이름을 nonlocal로 조작하려고 하면 오류가 발생합니다.

CODE BLOCK
class Demo:
    x = 10
    def change(self):
        nonlocal x  # ❌ SyntaxError: no binding for nonlocal 'x' found
        x += 1

이 예시는 실제로 실행되지 않습니다.
왜냐하면 nonlocal은 함수 안의 중첩 함수에서만 사용 가능하기 때문입니다.
클래스 본문은 함수가 아니기 때문에 상위 함수의 로컬 스코프처럼 취급되지 않습니다.
따라서 nonlocal을 쓸 수 없고, 전역을 다루려면 global을 사용해야 하지만, 이 역시 클래스 본문 변수를 대상으로 하는 것은 아닙니다.

⚠️ 혼동하기 쉬운 클로저와 클래스의 차이

함수 클로저에서는 내부 함수가 바깥 함수의 변수를 nonlocal로 접근할 수 있습니다.
하지만 클래스는 다릅니다.
클래스 본문은 실행 시점에 독립된 네임스페이스를 만들어 그 결과를 클래스 객체로 반환하므로, 메서드 내부는 그 네임스페이스를 포착하지 않습니다.

CODE BLOCK
# 클로저 예시
def outer():
    x = 10
    def inner():
        nonlocal x
        x += 1
        return x
    return inner

fn = outer()
print(fn())  # 11

# 클래스에서는 불가능
class Outer:
    x = 10
    def inner(self):
        # nonlocal x  # ❌ SyntaxError
        return self.x + 1

위 예제처럼 함수 클로저는 상위 함수의 변수 상태를 기억하지만, 클래스는 단지 속성을 정의할 뿐입니다.
클래스 본문 내 이름은 함수 스코프 체인과 무관하게 클래스 객체의 네임스페이스로 들어갑니다.
즉, 클래스 내부의 메서드는 그 변수들을 “캡처”하지 않습니다.

⚠️ 주의: 클래스 본문 이름을 함수처럼 캡처하려는 시도는 항상 실패합니다.
클래스는 클로저가 아니며, 내부 메서드는 클래스 로컬에 대해 nonlocal 접근을 허용하지 않습니다.

💎 핵심 포인트:

nonlocal은 함수 중첩 구조에서만 동작하며, 클래스 본문에는 사용할 수 없습니다.

클래스의 이름들은 메서드 로컬 스코프에 자동으로 연결되지 않기 때문에 항상 self 또는 클래스명으로 접근해야 합니다.



📌 예제로 비교하는 클래스 본문과 메서드 로컬

이제 실제 코드 예제로 클래스 본문 로컬과 메서드 로컬의 차이를 명확히 살펴보겠습니다.
파이썬에서 두 스코프가 어떻게 구분되고, 어떤 상황에서 변수 탐색이 달라지는지 한눈에 확인할 수 있습니다.

CODE BLOCK
# 클래스 본문 변수와 메서드 변수 비교
class Example:
    x = 10  # 클래스 본문 변수 (클래스 속성)

    def __init__(self):
        self.y = 20  # 인스턴스 변수

    def show(self):
        z = 30  # 메서드 로컬 변수
        print("x:", Example.x)   # 클래스 속성 접근
        print("y:", self.y)      # 인스턴스 속성 접근
        print("z:", z)           # 메서드 로컬 접근

obj = Example()
obj.show()

이 코드를 실행하면 다음과 같은 결과가 나옵니다.

x: 10
y: 20
z: 30

여기서 x는 클래스 본문에서 정의된 클래스 속성으로 모든 인스턴스가 공유합니다.
y는 각 인스턴스마다 독립된 값이며, z는 메서드가 실행될 때만 존재하는 지역 변수입니다.
즉, 세 변수는 같은 이름공간에 존재하지 않으며, 파이썬은 각각을 다른 스코프에서 관리합니다.

🧠 이름 탐색 순서로 다시 이해하기

파이썬 인터프리터는 이름을 찾을 때 다음과 같은 순서로 스코프를 탐색합니다.
이 순서를 기억하면 클래스 내부 동작을 훨씬 명확히 이해할 수 있습니다.

  • 1️⃣먼저 로컬 스코프(Local)에서 찾음 (메서드 내부 변수)
  • 2️⃣그다음 전역 스코프(Global) 탐색
  • 3️⃣내장 스코프(Built-in) 확인
  • 🚫클래스 본문은 이 탐색 경로에 포함되지 않음

따라서 메서드 내부에서 클래스 본문 이름을 참조하려면 반드시 self클래스명을 이용해야 합니다.
이 점을 기억하면 스코프 충돌로 인한 오류를 대부분 예방할 수 있습니다.

💡 TIP: 클래스 본문 변수는 모든 인스턴스가 공유하지만, self를 통해 접근하면 개별 인스턴스가 독립적으로 관리할 수도 있습니다.
이 방식은 공통 기본값을 지정할 때 유용합니다.

💎 핵심 포인트:

클래스 본문 로컬, 메서드 로컬, 인스턴스 속성은 모두 독립된 네임스페이스를 갖습니다.

이름 탐색 순서를 이해하면 변수 접근 오류를 손쉽게 피할 수 있습니다.

자주 묻는 질문 (FAQ)

클래스 본문에서 정의한 변수는 왜 함수의 nonlocal 대상이 안 되나요?
클래스 본문은 함수가 아니라 코드 실행을 위한 독립된 네임스페이스이기 때문입니다.
nonlocal은 함수 중첩 구조에서만 사용되며, 클래스 본문은 그 체인에 포함되지 않습니다.
메서드 안에서 클래스 본문 변수를 그냥 이름만 써서 접근할 수 있나요?
불가능합니다.
메서드 내부는 함수 로컬 스코프이므로, 클래스 본문 이름을 자동으로 찾지 않습니다.
반드시 self.변수명 또는 클래스명.변수명으로 명시적으로 접근해야 합니다.
self와 클래스명 접근의 차이는 무엇인가요?
self는 인스턴스의 속성부터 탐색하고, 없으면 클래스 속성으로 넘어갑니다.
클래스명 접근은 항상 클래스 속성만을 직접 참조합니다.
따라서 인스턴스마다 다른 값을 갖고 싶다면 self를 사용하는 것이 일반적입니다.
클래스 속성을 인스턴스에서 변경하면 어떻게 되나요?
인스턴스에 동일한 이름의 속성이 새로 생기며, 클래스 속성을 가립니다.
이후 그 인스턴스에서는 클래스 속성이 아니라 인스턴스 속성이 우선됩니다.
클래스 본문에서 변수 초기화 순서는 어떤가요?
클래스 정의가 실행될 때, 본문 코드가 위에서 아래로 한 번 실행됩니다.
그 결과로 만들어진 이름들이 클래스 네임스페이스에 속성으로 저장됩니다.
메서드 안에서 전역 변수와 클래스 변수가 이름이 같으면 어떤 것이 우선인가요?
함수 로컬 스코프에서 이름이 없으면 전역 변수를 먼저 찾습니다.
클래스 본문 변수는 탐색 경로에 포함되지 않기 때문에 자동으로 사용되지 않습니다.
클래스 안에서 내부 함수를 정의하면 클래스 속성이 되나요?
네, 맞습니다.
클래스 본문에서 정의한 함수는 메서드로 등록됩니다.
다만 일반 함수처럼 정의되어 self를 첫 번째 인자로 두면 인스턴스 메서드로 동작합니다.
nonlocal 대신 클래스 변수 값을 변경하려면 어떻게 해야 하나요?
클래스명.속성명 형태로 직접 재할당하거나, 클래스메서드(@classmethod) 내부에서 cls.속성명으로 접근하면 됩니다.
이렇게 하면 클래스 전체에 공유되는 값을 안전하게 수정할 수 있습니다.

📘 클래스 스코프 이해로 파이썬 객체 설계 완성하기

클래스 본문 스코프를 올바르게 이해하면 파이썬 객체지향 설계의 기초가 한결 단단해집니다.
클래스 본문에서 정의된 이름은 단순한 로컬 변수가 아닌 클래스 속성으로 저장되며, 메서드 내부의 로컬 스코프와 전혀 다른 영역에서 관리됩니다.
따라서 메서드 안에서는 반드시 self 또는 클래스명을 사용해 접근해야 합니다.

또한 nonlocal은 클래스 본문에 사용할 수 없고, global은 전역 변수를 대상으로만 작동합니다.
이 차이를 이해하면 스코프 관련 오류나 의도치 않은 변수 섀도잉 문제를 미리 방지할 수 있습니다.
결국 클래스 본문 스코프는 “정의용”, 메서드 스코프는 “실행용”이라는 개념으로 구분하면 됩니다.

파이썬의 클래스 구조를 단순히 문법으로만 이해하기보다는, 네임스페이스와 스코프의 관계를 시각적으로 떠올려 보세요.
그 순간 객체 설계가 훨씬 명료해지고, self의 역할 또한 자연스럽게 정리됩니다.


🏷️ 관련 태그 : 파이썬클래스, 클래스스코프, 파이썬문법, 객체지향기초, 메서드로컬, 파이썬네임스페이스, self사용법, 프로그래밍기초, 파이썬학습, 코드스코프