프로그래밍/Python

언더스코어 인 파이썬 ( 파이썬 객체지향적으로 사용하기 )

코줍 2025. 1. 30. 16:10

1. 이 글을 쓰게된 계기

 
회사가 성장하고는 있지만, 여전히 다양한 실험을 진행하는 스타트업이다 보니, feature 개발에 집중하느라 리팩토링할 여유가 많지 않다. 그래서 관련 feature를 개발하면서 레거시 코드까지 건드려야 하는 상황이 오면, 틈틈이 쫌쫌따리 리팩토링을 진행하곤 한다.
 
얼마 전, 크론잡으로 실행되는 로직에 새로운 기능을 추가하다가 해당 소스 코드 파일을 살펴보게 됐는데,
곳곳에 언더스코어(_)가 마치 디자인 시스템처럼 많이 사용된 흔적을 발견했다. 다른 파일들과는 달리...
 

나름의 이유가 있는 걸까, 아니면 단순한 남용일까?

 
판단을 위해 이 글을 참고하여, 파이썬에서 언더스코어가 어떻게 활용되는지 번역하고 살을 붙여 정리해보았다.
사실 제일 중요한 파트는 5. 언더바를 포함한 변수명 파트다.
 

2. 언더스코어 인 파이썬

1. 인터프리터에서 사용

파이썬같은 인터프리터 언어에선 가장 마지막 표현식의 결과값을 자동적으로 "_"라는 변수에 저장한다. 다만, 대화형 모드 (Python REPL)일 때만 해당된다. ( 잘 쓰이지 않는다는 뜻 )

 

2. 무시하는 값

 
무시하는 값으로도 쓰일 수 있다. 해당값을 unpack하기 싫을 때, "_" 할당해 버리면 된다.

# 2는 할당 안함
a, _, b = (1, 2, 3)    
print(a, b) 


# 여러 개를 무시하는 값으로 unpacking할 수도 있음
a, *_, b = (1, 2, 3, 4, 5, 6, 7)
print(a, b)

 
 
출력결과

 

3. 루프에서 사용

# _를 이용해서 루프를 돈다.
for _ in range(5):
    print(_)


# 리스트 순회하면서 _를 일반변수처럼 사용할 수 있다.
LANG = ['Python', 'Java', 'Js', 'PHP']
for _ in LANG:
    print(_)


_=5
while _ < 10:
    print(_)
    _ += 1

 
출력결과

 

4. 숫자값 구분

숫자가 길다면, 자릿수 구분을 위해 중간중간에 넣어줄 수 있다. 
이진수 값이나 16진수, 8진수 모두 동일하게 적용할 수 있다.

million = 1_000_000
binary = 0b_0010
octa = 0o_64
hexa = 0x_23_ab

print(million)
print(binary)
print(octa)
print(hexa)

 
출력결과

 

5. 언더바를 포함한 변수명 ‼️

5.1 앞에 한 개의 언더바 ⚠️

앞에 언더바로 시작하는 이름은 내부사용용 ( internal use only )이다. 

class Test:
    def __init__(self):
        self.name = 'lucky'
        self.num = 7


if __name__ == '__main__':
    obj = Test()
    print(obj.name)
    print(obj.num)

 
변수명 앞에 "_" 언더스코어 하나 붙였다고, 해당 변수에 접근을 못하는 것은 아니다.
다만, 다른 소스코드에서 해당 module을 import할 때 효과가 발휘된다.
 
그냥 playground2 모듈의 모든객체를 import 하려고 할 때, _private_func() 메소드를 찾지 못한다.
이게 파이썬의 암묵적 네이밍 룰이며, private멤버로 취급하겠다는 의미인 것이다.

원래도 import를 명시적으로 가져오곤 했었는데, 이런 네이밍 규칙을 제대로 알고 나니까 좀 더 똑똑히 사용할 수 있을 것 같다.

 
이럴 때는 from module import _private_function 라고 명시적으로 import해서 가져올 수 있다. 

 

5.2 뒤에 붙이는 언더바 

그냥 변수명 겹치는 거 방지하고 싶어서 붙일 때, 뒤에 언더바를 붙인다.
 

5.3 앞에 두 개 붙이는 언더바 ⚠️

name mangling 이라는 명명법칙에 해당하는 케이스인데, 알아보기 힘든 모양으로 만든다는 의미이다.
왜 알아보기 힘들게 만드냐?
파이썬 인터프리터에게 해당 서브클래스의 attribute 이름을 바꾸어서 이름 충돌이 나지않게끔 하려는 의도이다.

class Sample():
    def __init__(self):
        self.a = 1
        self._b = 2
        self.__c = 3

if __name__ == '__main__':
    obj = Sample()
    print(dir(obj))

 
dir(obj)를 출력하면, 아래와 같이 나온다.

 
dir 함수는 클래스 객체의 모든 attribute를 리턴해주는데, a랑 _b는 잘 출력되지만 __c는 없고 Sample__c라는 값만 존재한다.
이게 바로 name mangling이라는 것인데, 나중에 다른 클래스가 Sample이라는 클래스를 상속할 때 그 클래스에서 이 변수를 override하는 것을 방지해준다.


class Sample():
    def __init__(self):
        self.a = 1
        self._b = 2
        self.__c = 3
        
class SecondSample(Sample):
    def __init__(self):
        super().__init__()
        self.a = 'overriden'
        self._b = 'overriden'
        self.__c = 'overriden'

if __name__ == '__main__':
    obj2 = SecondSample()
    print(obj2.a)
    print(obj2._b)
    print(obj2.__c)

 
출력결과

 
__c를 찾지 못해서 no Attribute 에러가 발생했다.
 
name mangline때문에 obj2.__c가 아닌 obj2.__SecondClass__c로 바뀌었다. obj2._SecondClass__c를 한번 출력해보면,

class Sample():
    def __init__(self):
        self.a = 1
        self._b = 2
        self.__c = 3
        
class SecondSample(Sample):
    def __init__(self):
        super().__init__()
        self.a = 'overriden'
        self._b = 'overriden'
        self.__c = 'overriden'

if __name__ == '__main__':
    obj1 = Sample()
    obj2 = SecondSample()
    print(obj1._Sample__c)

 
출력결과 ( mangled된 변수명으로 접근이 가능해졌다. ) 

 
즉, 멤버함수(메소드)를 이용해서 mangled된 두 개의 언더바로 시작하는 변수를 접근할 수 있다. 
예시 하나 더 살펴보자.

class SimpleClass:
    def __init__(self):
        self.__kozub = "Excellent"

    def get_kozub(self):
        return self.__kozub   

if __name__ == '__main__':
    obj = SimpleClass()
    print(obj.get_kozub()) # Excellent 출력 잘된다.
    print(obj.__kozub)  # 여기서 에러 발생

 
출력결과

 
이렇듯 "__" 언더스코어 두 개가 변수명 혹은 함수명에 붙게 될 경우엔, 접근제한자 private을 사용하는 것과 같은 효과를 낸다.
 
 

5.4 앞뒤로 2개의 언더바

magic method 혹은 dunder 메소드라고 불린다.
보통 연산자 오버로딩을 할 때, 많이 사용하는데 가급적 사용하지 않는 것이 좋다.
 
 

3. 나름의 이유가 있었던 걸까, 아니면 단순한 남용일까?

결론은 이유가 있는 코딩이라고 생각들었다.
언더스코어가 붙은 메소드와 변수들이 해당 모듈 내에서만 사용되었기 때문에 올바르게 사용된 경우라고 판단했기 때문이다.
 
하지만, 다른 소스코드 파일에서는 암묵적으로 private하게 선언된 메소드나 변수가 없어서, 네이밍 컨벤션에 대한 일관성이 없는 코드들이 즐비하는 상황에 약간의 아쉬움을 느낀다. 회사 사정상 우선순위가 낮은 부분이기 때문이니 어쩔 수 없다는 것을 알지만 말이다.
언젠가는 이런 디테일들을 잘 지키며 깔끔한 코드들이 많이 생기는 날이 오기를...