https://www.datacamp.com/community/tutorials/role-underscore-python

 

Role of Underscore(_) in Python

In this tutorial, you're going to learn about the uses of underscore(_) in python.

www.datacamp.com

이 글은 위 글의 번역 및 정리 글입니다.

 

파이썬관련 찾아볼 일이 있어서 보다가 언더바에 대한 내용들이 있어서 한번 정리합니다.

파이썬 개발자들 중 많은사람들이 파이썬에서 언더바의 역할에 대해 잘 모르는데, 이를 이용해서 코드 생산성을 늘릴 수 있습니다. 이번기회에 한번 알아봅시다.

for _ in range(100)
__init__(self)
_ = 2

만약 당신이 파이썬 개발자라면, 위와 같은 파이썬 코드들을 본 적이 있을 것입니다.

위에서 _는 각각 다른 조건에서 특별한 의미를 갖습니다.

 

일단 6개의 다른 용도가 있는데 차근차근 알아봅시다.

  1. 인터프리터에서의 사용
  2. 무시하는 값
  3. 루프에서 사용
  4. 숫자값의 분리
  5. 명명용
    1. 앞에 하나가 쓰이는 경우
    2. 뒤에 하나가 쓰이는 경우
    3. 앞에 두개가 쓰이는 경우
    4. 앞 뒤로 두개씩 쓰이는 경우

이제 예제들과 함께 알아봅시다.

 

1. 인터프리터에서 사용

파이썬 인터프리터에서 가장 마지막 표현식의 결과값을 자동적으로 "_"라는 변수에 저장합니다. 물론 여기 저장된 값을 다른데다가 저장할 수 있습니다.

일반적인 값처럼 쓸 수 있습니다.

>>> 5 + 4
9
>>> _
9
>>> _ + 6
15
>>> _
15
>>> a = _
>>> a
15

위와 같이 동작하는걸 볼 수 있습니다.

2. 무시하는 값

언더바는 무시하는 값으로도 쓰일 수 있습니다. 해당 값을 unpack하기 싫다면, 그냥 _에다가 할당하면 됩니다.

## 값을 버립니다.
a, _, b = (1, 2, 3) # a = 1, b = 3, _에 2가 할당됩니다.
print(a, b)

## 여러개 값 버리기
## *(변수) 는 unpack할때, 여러개의 값을 하나의 변수에 저장할때 쓰입니다.
## 이는 확장된 Unpacking이라고 불리며,Python 3.x 버전에서만 가능합니다.
a, *_, b = (7, 6, 5, 4, 3, 2, 1)
print(a, b)

테스트를 해보니, _에다가 할당하면 _도 일반 변수처럼 쓰이고, 1번처럼 Last expression을 저장하지 않게 됩니다. del _를 통해 해당 변수를 삭제하면 1번때처럼 Last expression을 저장하게 됩니다.

 

3. 루프에서 사용

for 루프를 돌 때 사용할 수 있습니다. 아래처럼 쓰는 것도 하나의 방법이 됩니다.

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

## 리스트 순회를 _를 이용해서 합니다.
## _ 를 일반 변수처럼 사용할 수 있습니다.
languages = ["Python", "JS", "PHP", "Java"]
for _ in languages:
    print(_)

_ = 5
while _ < 10:
    print(_, end = ' ') # 'end'의 기본값은 '\n'인데 이걸 변경해줍니다.
    _ += 1

실행결과는 예상한 것과 같게 아래처럼 나옵니다.

0
1
2
3
4
Python
JS
PHP
Java
5 6 7 8 9

4. 숫자값의 구분

숫자값이 길다면, 자릿수 구분을 위해 _를 중간중간에 넣어줄 수 있습니다.

이진수값이나 16진수, 8진수 값도 동일하게 적용할 수 있습니다.

## 여러 숫자 표현법
## 아래 값들이 정확한지 확인하기위해 int 함수를 써 볼수도 있습니다.
million = 1_000_000
binary = 0b_0010
octa = 0o_64
hexa = 0x_23_ab

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

실행 결과는 아래와 같습니다.

1000000
2
52
9131

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

 변수, 함수, 클래스 명 등에 언더바가 사용될 수 있습니다.

  • 앞에 하나의 언더바 _variable
  • 뒤에 하나의 언더바 variable_
  • 앞에 둘의 언더바 __variable
  • 앞과 뒤에 두개의 언더바 __variable__

5.1 앞의 하나의 언더바

앞에 하나의 언더바로 시작하는 이름은, 내부 사용용입니다(internal use only). 일단 예시부터 봅시다.

class Test:

    def __init__(self):
        self.name = "datacamp"
        self._num = 7

obj = Test()
print(obj.name)
print(obj._num)

위 코드를 실행하면 아래처럼 됩니다.

datacamp
7

변수명 앞에 _를 하나 붙였다고 해서, 해당 변수를 접근하지 못하게 되진 않습니다. 하지만 모듈을 import해서 쓰는 경우 효과가 발생합니다.

아래와 같은 코드를 한번 확인해봅시다.

## filename:- my_functions.py

def func():
    return "datacamp"

def _private_func():
    return 7

이제 my_functions.py를 가져오기 위해 import 구문을 써보자. 파이썬은 언더바 하나로 시작한 이름들은 import하지 않는다.

>>> from my_functions import *
>>> func()
'datacamp'
>>> _private_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_private_func' is not defined

위의 코드 결과를 보다시피, _로 시작한 _private_func는 찾지를 못한다.

위와 같은 에러를 방지하기 위해, from module import *가 아닌 모듈 자체를 import를 해보자. 

>>> import my_functions
>>> my_functions.func()
'datacamp'
>>> my_functions._private_func()
7

앞에 _ 1개로 시작한 이름은 내부 사용 전용이라는 뜻이다.

5.2 뒤의 하나의 언더바

가끔 파이썬 키워드에 해당하는 이름으로 변수명, 함수명, 클래스명으로 쓰고 싶을때가 있을 수 있다. 이럴때 이 방법이 유용하다. 마지막 부분에 언더바를 하나 추가함으로써 파이썬의 기본 키워드들과 충돌하는거를 방지할 수 있다.

>>> def function(class):
  File "<stdin>", line 1
    def function(class):
                 ^
SyntaxError: invalid syntax
>>> def function(class_):
...     pass
...
>>>

마지막에 하나의 언더바를 추가하는 명명법은 파이썬 키워드와 겹치는 경우를 방지하고 싶을때이다.

5.3 앞의 두개의 언더바

앞에 두개의 언더바로 시작하는 명명법은 name mangling이다. mangle은 짓이기다라는 뜻인데, 알아보기 힘든 모양으로 만든다라는 의미로 볼 수 있겠다.

앞에 두개의 언더바로 시작하는 명명법은 파이썬 인터프리터에게 해당 서브클래스의 attribute 이름을 바꾸어서 이름 충돌이 나지 않게 하라고 말하는 것이다. 서브클래스 이야기가 나온걸로 봐서 상속하는 경우와 관련이 있는 것 같다.

 

일단 예시를 보자.

class Sample():

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

dir한 결과는 아래와 같이 나온다.

['_Sample__c',
 '__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__',
 '_b',
 'a']

dir함수는 클래스 객체의 모든 attribute들을 리턴해준다. 선언한 변수들을 dir 리턴값 중에서 한번 찾아보자.

a와 _b는 잘 보인다. 맨 마지막에 있다. _b는 내부 사용용으로만 쓰인다.

Sample클래스에 분명히 __c라는 맴버변수를 선언했는데, __c는 없고 대신 _Sample__c라는 값이 있다.

이것이 name mangline이다. 나중에 다른 클래스가 Sample이라는 클래스를 상속할때, 그 클래스에서 이 변수를 override하는 걸 방지해준다.

 

다른 클래스를 만들어서 Sample이라는 클래스를 상속해보자.

class SecondClass(Sample):

    def __init__(self):
        super().__init__()
        self.a = "overridden"
        self._b = "overridden"
        self.__c = "overridden"
obj2 = SecondClass()
print(obj2.a)
print(obj2._b)
print(obj2.__c)

상속을 한 뒤, 초기화를 하는 과정에서 Sample의 맴버변수들에 값을 다 대입하고 있다.

overridden
overridden



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

AttributeError                            Traceback (most recent call last)

<ipython-input-2-4bf6884fbd34> in <module>()
      9 print(obj2.a)
     10 print(obj2._b)
---> 11 print(obj2.__c)


AttributeError: 'SecondClass' object has no attribute '__c'

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

print(obj2._SecondClass__c)
overridden

편의상 결과도 같이 썼다.

그러면 _Sample__c는 어떻게 되었는지 한번 보자.

print(obj1._Sample__c)
3

mangle 된 이름으로 잘 있다.

 

맴버함수(메소드)를 이용해서 mangle 된 두개의 언더바로 시작하는 변수를 접근할 수 있다. 예시를 보자.

class SimpleClass:

    def __init__(self):
        self.__datacamp = "Excellent"

    def get_datacamp(self):
        return self.__datacamp

obj = SimpleClass()
print(obj.get_datacamp()) ## "Excellent"를 출력한다. __datacamp에 해당하는 값이다.
print(obj.__datacamp)     ## 여기서 에러가 발생한다. 변수 이름이 바뀐다.

메소드(getter, setter)안에서는 잘 접근을 한다. 하지만 외부에서 멤버 변수로 직접 접근하려고 하면 에러가 난다.

 

언더바 2개로 시작하는 명명법은 변수뿐만 아니라 메소드 이름에도 적용이 가능하다. 예시를 보자.

class SimpleClass:

    def __datacamp(self):
        return "datacamp"

    def call_datacamp(self):
        return self.__datacamp()

obj = SimpleClass()
print(obj.call_datacamp()) ## __datacamp()의 리턴값과 같다.
print(obj.__datacamp())    ## 여기선 에러가 난다.

__로 시작한 함수는 안에서만 호출이 되는 용도라고 보면 될 것 같다.

다른 객체지향 언어에서 private access modifier(접근 지정자)의 역할이라고 보면 될 것 같다.

 

다른 예시를 한번 보자.

_SimpleClass__name = "datacamp"

class SimpleClass:

    def return_name(self):
        return __name

obj = SimpleClass()
print(obj.return_name()) ## "datacamp"를 출력하게 된다.

위와 같은 코드도 정상적으로 동작을 한다.

 

좀 특이한 방식의 개념이다.

5.4 앞뒤로 2개의 언더바

파이썬에서 앞뒤로 2개의 언더바로 둘러쌓인 명명법을 본 적이 있을 것이다. 이녀석들은 매직 메소드(magic method) 혹은 dunder 메소드라고 불린다.

class Sample():

    def __init__(self):
        self.__num__ = 7

obj = Sample()
obj.__num__

이 명명방식을 변수 이름으로 사용할 경우 변수명 충돌이 일어날 수 있기 때문에 가급적 삼가하는게 좋다.

dunder method에서 dunder는 double under(score)를 뜻하며, 보통 연산자 오버로딩을 할 때 많이 사용한다고 한다.

매직 메소드는 __init__, __add__, __len__, __repr__등이 있다.

 

__init__메소드는 생성자라고 보면된다. 호출없이 초기화할때 호출되는 함수이며, 클래스의 인스턴스가 생성될때, 실행되게 된다.

 

__repr__ 메소드는 해당 클래스를 출력하고자 할때 내부적으로 호출되는 메소드라고 보면 된다. 자바로 치면 toString() 함수와 유사하다고 볼 수 있겠다.

References

https://www.geeksforgeeks.org/dunder-magic-methods-python/

+ Recent posts