이름 맹글링과 private 네임: _, __ 접두사 완전 정리

Python 이름 맹글링(__name → _ClassName__name), 단일 언더스코어 관례, 던더 메서드(__name__), 임시 변수(_) 등 네이밍 규약을 설명합니다.

· 6 min read · PALDYN Team

지난 글에서 조건부 임포트를 살펴봤습니다. 이번에는 모듈 시스템의 마지막 주제로, Python의 이름 규약을 정리합니다. _name, __name, __name__ — 언더스코어 개수에 따라 의미가 달라지는 이 규약들과, 그중 하나인 **이름 맹글링(name mangling)**이 실제로 어떻게 동작하는지 알아봅니다.

네 가지 이름 패턴

Python에는 접근 제어 키워드(private, protected)가 없습니다. 대신 이름 규약으로 의도를 표현합니다.

이름 규약 요약표

1. name — 공개 (public)

아무 접두사 없는 이름은 공개 인터페이스입니다. 누구나 접근하고 사용해야 합니다.

class Stack:
    def push(self, item): ...  # 공개 메서드
    def pop(self): ...          # 공개 메서드

2. _name — 내부용 관례

언더스코어 하나는 “내부 구현 세부사항, 외부에서 의존하지 마세요”라는 약속입니다. 언어 차원의 강제는 없습니다.

class Parser:
    def _tokenize(self, text): ...  # 내부 메서드
    def _internal_state(self): ...  # 직접 접근 말 것

from module import *_로 시작하는 이름은 기본적으로 제외됩니다(__all__이 없을 때).

3. __name — 이름 맹글링

이중 언더스코어로 시작하고 끝이 언더스코어 1개 이하인 이름은 컴파일 타임에 이름이 변환됩니다.

class MyClass:
    def __init__(self):
        self.__secret = 42      # _MyClass__secret 으로 저장됨
        self.__method_data = [] # _MyClass__method_data 로 저장됨
obj = MyClass()
obj.__secret          # AttributeError!
obj._MyClass__secret  # 42 (맹글링된 이름으로 접근 가능)

이름 맹글링 개요

이름 맹글링의 진짜 목적

__name이 “접근을 막는다”고 이해하면 반은 맞고 반은 틀립니다. 맹글링의 실제 목적은 서브클래스에서의 이름 충돌 방지입니다.

class Base:
    def __init__(self):
        self.__value = 1  # _Base__value

class Child(Base):
    def __init__(self):
        super().__init__()
        self.__value = 99  # _Child__value (다른 이름!)

obj = Child()
print(obj._Base__value)   # 1  (부모 클래스의 것)
print(obj._Child__value)  # 99 (자식 클래스의 것)

self.__value가 두 클래스에서 다른 이름으로 저장되므로 서로 덮어쓰지 않습니다. 프레임워크를 설계할 때 “서브클래스가 절대 건드리지 말아야 할” 내부 상태를 보호하는 용도입니다.

일반 애플리케이션 코드에서는 __name을 남용하지 않는 것이 좋습니다. 대부분의 경우 _name 관례로 충분합니다.

4. name — 던더(dunder) 메서드

앞뒤로 언더스코어 두 개씩이면 Python 언어 프로토콜에서 정의한 매직 메서드입니다. 이름 맹글링이 적용되지 않습니다.

class Vector:
    def __init__(self, x, y):  # 생성자
        self.x, self.y = x, y

    def __add__(self, other):  # + 연산자
        return Vector(self.x + other.x, self.y + other.y)

    def __repr__(self):         # repr()
        return f"Vector({self.x}, {self.y})"

사용자 정의 클래스에서 __dunder__ 이름을 새로 만드는 것은 금지 사항은 아니지만, Python 미래 버전과 충돌할 수 있어 권장하지 않습니다.

5. _ (단독) — 임시/무시 변수

의미 없는 값을 받아야 할 때 쓰는 관례입니다. REPL에서는 마지막 표현식 결과를 가리킵니다.

# 루프 카운터 무시
for _ in range(5):
    print("hello")

# 언패킹 중 불필요한 값 무시
first, *_, last = [1, 2, 3, 4, 5]
x, _ = (10, 20)  # 두 번째 값 무시

# 국제화(i18n) 관례: gettext 별칭
from gettext import gettext as _
message = _("Hello World")

__slots__와 이름 맹글링

__slots__에 맹글링되는 이름을 넣으면 맹글링 전 이름으로 선언합니다.

class MyClass:
    __slots__ = ["__value"]  # _MyClass__value 슬롯 생성

    def __init__(self):
        self.__value = 10    # _MyClass__value 에 저장

dir()로 맹글링 확인

클래스나 객체의 속성 목록을 보면 맹글링된 이름을 직접 확인할 수 있습니다.

class Example:
    def __init__(self):
        self._internal = 1
        self.__mangled = 2

e = Example()
print([a for a in dir(e) if "internal" in a or "mangled" in a])
# ['_Example__mangled', '_internal']

정리

패턴언제 쓰나
name공개 인터페이스 — 항상 기본
_name내부 구현, 외부 의존 금지 관례
__name서브클래스 이름 충돌 방지 필요할 때만
__name__Python 프로토콜 구현 (직접 만들지 말 것)
_임시 변수, 무시 값

Python은 완전한 접근 차단 대신 이름 규약으로 의도를 표현합니다. 팀 코드에서는 _name을 일관되게 쓰고, __name 맹글링은 프레임워크·라이브러리 수준에서 꼭 필요한 경우에만 사용하는 것이 좋습니다.


지난 글: 조건부 임포트


읽어주셔서 감사합니다. 😊