All about Underscores in Python

2019-08-03
programming

All About Underscores in Python

  • Python을 공부하다 보면 클래스 안에서 underscore와 double underscores로 시작하거나 끝나는 변수 명을 많이 보게 됩니다. 변수/함수명이 test라면, _test, test_, __test, __test__ 와 같은 변수명들을 흔히 발견할 수 있습니다. 이 blog에서는 이 4가지 single underscore과 double underscores의 의미에 대해 알아보고, 어떻게 Python 프로그램에 영향이 가는지 알아보겠습니다.

1. 한개의 underscore로 시작하는 함수/변수명 : _test

한개의 underscore가 prefix (접두사)로 들어가는 변수/함수명은 컨벤션임을 의미 합니다. 즉, Python 사용자들끼리는 알고있는 의미이지만, 프로그래밍 작동에 있어서는 영향을 끼치지 않는다는 뜻입니다. Underscore prefix는 다른 Python 사용자들에게 underscore로 시작하는 함수/변수명은 내부용 (internal use)이라는 것을 알려줍니다. 이 컨벤션은 PEP 8, Python 코드 스타일 가이드, 에도 정의 되어 있습니다.

하지만, 이 컨벤션은 Python 인터프리터가 작동하는데 있어서 영향을 주지 않습니다. Python은 변수/함수 명이 “private” 이냐 “public”이냐에 있어서 큰 구별을 두지 않습니다. 그렇기에 한개의 underscore를 함수/변수명 앞에 붙이는 것은 “이 함수/변수명은 이 클래스의 퍼블릭 인터페이스에서 사용하지 않는 것을 권장하니 그냥 두세요” 라고 말하는 것과 같습니다.

아래에 Test라는 class를 만들어 single underscore가 들어간 함수명에 접근해 보도록 하겠습니다.

1
2
3
4
class Test:
def __init__(self):
self.test1 = 'python'
self._test2 = 'programming'
1
2
3
t = Test()
print(t.test1)
print(t._test2)
python
programming

test2 변수 앞에 underscore를 붙이는 것이 클래스에 있는 test2변수에 접근하는 것을 막지 않았음을 볼 수 있습니다. 위에 언급한 것 처럼 그저 컨벤션 이기 때문입니다. 하지만 이 underscore prefix는 module을 import할 때 영향을 줍니다. 아래와 같이 test_module.py를 만들어서 import all (import *)를 해 보겠습니다.

1
2
3
with open('test_module.py', 'w') as f:
f.write('def public_func(): return "python"\n')
f.write('def _internal_func(): return "programming"')
1
2
3
from test_module import *
print(public_func())
print(_internal_func())
python



---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-12-911f6273b411> in <module>
      1 from test_module import *
      2 print(public_func())
----> 3 print(_internal_func())


NameError: name '_internal_func' is not defined

위에서 _internal_func()를 프린트 하려고 할 때 name error가 나는 것을 확인할 수 있습니다. Python은 import 를 사용할 때 한개의 underscore로 시작하는 변수/함수들을 import하지 않습니다. 하지만 PEP 8에서도 import 은 될 수 있으면 피해야 한다고 나와있습니다. 그 이유는, 어떤 이름들이 네임스페이스 안에 있는지 명확하지 않기 때문입니다. 일반 import는 single underscore 컨벤션에 영향을 받지 않습니다. 아래에 일반 import를 사용하여 확인해 봅니다.

1
2
3
import test_module
print(test_module.public_func())
print(test_module._internal_func())
python
programming

2. 한개의 underscore로 끝나는 함수/변수명 : test_

프로그래밍을 하다 보면 그 변수에 가장 맞는 이름이 Python 언어에게 이미 빼앗긴(?) 경우가 있을 수 있습니다. 에를들어, class를 나타내고 싶은 함수인데 class라는 이름은 이미 python에서 class를 만드는데 사용되는지라 사용될 수 없습니다. 그럴 때 class_처럼 마지막에 underscore를 붙여줌으로서 충돌을 방지해 줍니다. 이 내용 또한 PEP 8에 적혀있는 컨벤션 입니다.

3. 두개의 underscores로 시작하는 함수/변수명 : __test

두개의 underscores로 시작하는 함수/변수명은 Python 인터프리터가 서브클래스들 (subclasses)간의 이름 충돌이 일어나지 않게 하기 위해 클래스의 속성(함수/변수) 이름을 다시 쓰게 합니다. 이것은 name mangling 이라고 부릅니다. 인터프리터가 변수/함수의 이름을 바꿈으로서 클래스가 더 확장이 되었을 때 충돌이 일어나는 것을 방지합니다. 예제로 알아 보겠습니다.

1
2
3
4
5
class Test:
def __init__(self):
self.test1 = 'python'
self._test2 = 'programming'
self.__test3 = 'is fun'

아래는 객체 t가 가지고 있는 속성들 입니다. test1_test2는 보이는데 __test3이 보이지 않습니다. 그 대신, _Test__test3이라는 속성이 존재 합니다. 이 것이 name mangling입니다. Python 인터프리터가 변수/함수 이름을 바꿈으로서 그 변수/함수가 서브 클래스에서 overidden되는 것을 방지 합니다.

1
2
t = Test()
dir(t)
['_Test__test3',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 '_test2',
 'test1']

Test를 상속 받은 클래스를 만들어서 Test클래스의 constructor에 있는 속성들을 override 하려는 시도를 해 봅니다.

1
2
3
4
5
6
class TestClassExtended(Test):
def __init__(self):
super().__init__()
self.test1 = 'overriding'
self._test2 = 'overriding'
self.__test3 = 'overriding'

아래와 같이 TestClassExtended로 만든 객체 t2를 가지고 속성에 print 함수를 통해 접근해 봅니다. 하지만 t2.__test3을 프린트 하려고 할 때 attribute error가 나는 것을 확인할 수 있습니다. 위에서 언급한 name mangling 때문 입니다.

1
2
3
4
t2 = TestClassExtended()
print(t2.test1)
print(t2._test2)
print(t2.__test3)
overriding
overriding



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-21-b9b94d755c5a> in <module>
      2 print(t2.test1)
      3 print(t2._test2)
----> 4 print(t2.__test3)


AttributeError: 'TestClassExtended' object has no attribute '__test3'

t2의 속성들도 아래와 같이 확인해 봅니다. t2의 속성에서도 __test3이 없으며, 대신 _TestClassExtended__test3가 존재한다는 것을 확인할 수 있습니다. __test3_TestClassExtended__test3으로 바뀌면서 실수로 이름이 바뀌어 버리는 것을 막아 줍니다.

1
dir(t2)
['_TestClassExtended__test3',
 '_Test__test3',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 '_test2',
 'test1']

TestClassExtended로 만든 t2 객체의 _TestClassExtended__test3와 Test로 만든 t객체의 _Test__test3가 둘 다 존재한다는 것을 확인 합니다.

1
2
print(t2._TestClassExtended__test3)
print(t._Test__test3)
overriding
is fun

위와 같이 직접 변수/함수명으로 불러오는 것은 되지 않지만, 다른 함수를 사용하여 name mangling된 변수/함수를 가져오는 것은 가능 합니다. 아래의 TestNameMangling 클래스를 만들어 확인해 봅니다.

1
2
3
4
5
6
7
8
9
10
11
12
class TestNameMangling:
def __init__(self):
self.__test_mangling = 'python'

def get_mangling(self):
return self.__test_mangling

def __manglingmethod(self):
return "programming"

def call_manglingmethod(self):
return self.__manglingmethod()

아래와 같이, 다른 함수 get_mangling을 사용하여 생성자 안에 있는 __test_mangling의 값을 가져올 수 있지만, 직접 __test_mangling변수는 접근이 불가하다는 것을 확인 할 수 있습니다.

1
2
print(TestNameMangling().get_mangling())
print(TestNameMangling().__test_mangling)
python



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-28-afce42ce199f> in <module>
      1 print(TestNameMangling().get_mangling())
----> 2 print(TestNameMangling().__test_mangling)


AttributeError: 'TestNameMangling' object has no attribute '__test_mangling'

함수 또한 같은 방법으로 접근이 가능합니다.

1
2
print(TestNameMangling().call_manglingmethod())
print(TestNameMangling().__manglingmethod())
programming



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-30-e221b1fb59b4> in <module>
      1 print(TestNameMangling().call_manglingmethod())
----> 2 print(TestNameMangling().__manglingmethod())


AttributeError: 'TestNameMangling' object has no attribute '__manglingmethod'

4. 두개의 underscores로 시작하고 끝나는 함수/변수명 : __test__

name mangling은 이름이 두개의 underscores로 시작하고 끝나면 적용되지 않습니다.

1
2
3
4
5
class TestNameMangling:
def __init__(self):
self.__test__ = 'python'

TestNameMangling().__test__
'python'

하지만 두개의 underscores로 시작하고 끝나는 함수/변수명들은 특별한 경우에 사용 되기 위해 “예약”되어 있습니다. 자주 보이는 생성자 함수인 __init__이나 객체를 callable하게 만들 때 사용하는 __call__ 함수가 그 예 입니다. 이 dunder (Double UNDERscore) method (함수)는 magic method라고도 불립니다. 하지만 위의 예시처럼 변수의 이름을 지을 때, 두개의 underscore로 시작하고 끝나는 이름으로 짓는 것은 피해야 하는데 이유는 Python 언어가 바뀌거나 업그레이드 될 때 충돌을 방지해야 하기 때문입니다.

Summary

  • 한개의 underscore로 시작하는 함수/변수명 _test : 함수/변수가 내부용 (internal use)이라는 것을 Python 사용자들에게 알려주는 컨벤션이며 Python 인터프리터가 작동하는데에 영향을 주지 않습니다 (import * 를 제외하고)
  • 한개의 underscore로 끝나는 함수/변수명 test_ : Python 언어가 사용하는 키워드 (class, def, list등)들과의 충돌을 막기 위해 마지막에 underscore를 붙여 주는 컨벤션 입니다.
  • 두개의 underscores로 시작하는 함수/변수명 __test : 클래스를 만들 때, name mangling 이 되며, Python interpreter가 코드를 읽음에 있어서 영향을 줍니다.
  • 두개의 underscores로 시작하고 끝나는 함수/변수명 __test__ : Python 언어에서 정의 되는 special methods (magic methods)입니다. 직접 클래스에서 속성을 만들 때에는 이 이름 짓는 방식을 피하는 것이 좋습니다 (혹시 있을 충돌 방지를 위해!)