The Meaning of Underscores in Python

민재원·2022년 3월 31일
0

python

목록 보기
2/2
post-thumbnail
post-custom-banner

intro

나는 항상 어떻게 파이썬을 멋있게 쓸 수 있을지 고민한다. 파이썬은 pythonic이라는 말이 있을정도로 다 함께 약속하고 쓰는 패턴이 있다. 그중 underscores('_') 한국말로 밑줄을 어떻게 쓰는지 갑자기 궁금해졌다.
나는 보통 underscores를 for문에서 나오는 iter 값을 필요 없을때나 private 변수나 메서드를 선언할 때 사용한다. 과연 맞게 쓰고 있는걸까?

1. 인터프리터 안에서 사용한다.

파이참을 사용하면서 나는 간단한 연산이나 연습을 할 때 python console을 자주 사용한다.

>>> 1 + 2
3
>>> _
3
>>> _ + 2
5
>>> _
5

다음과 같이 '_'은 마지막으로 연산된 결과를 저장한다.

2. 무시할 변수들

underscore는 변수를 무시할 때 사용한다. 어떤 메서드의 return값이 최소, 중간, 최대값을 반환하는 1 * 3 dimension tuple 이라고 가정하자. 나는 중간이 싫어서 최소와 최댓값만 활용할거다. 이럴때 중간값을 underscore('_')로 받는다.

def func():
	...
	return min_value, mid_value, max_value
    
min_value, _, max_value = func()

그냥 받고 안쓰면 되는거 아닌가?
전혀 아니다. 이는 Garbage collector와 연관이 되는데 python garbage collector는 object의 reference counting을 세서 메모리 관리를 한다. a = "123" 이라는 변수는 a가 "123"을 참조하는거다. 하지만 다음 줄에서 a = "456"으로 a가 참조하는 값을 바꿔준다면 "123"의 reference counting은 0이 될것이다. 그럼 가비지 컬렉터가 알아서 수거를 해주는 것 이다.

3. 루프 안에서

주로 for문을 range(iter_count)로 돌릴때 iter 변수는 사실 큰 의미가 없다. (만약 index가 필요하다면 enumerate를 사용해 iterator_object를 돌려주는게 더 pythonic하다.) for문을 count번 돌린다는게 중요하니까 iter 변수를 무시해주는 의미로 underscore를 사용한다.

iter_count = 5
for _ in range(iter_count):
    print("hello")

languages = ["Python", "JS", "PHP", "Java"]
for i in range(len(lenguages)):  # bad method
	print(i, lenguages[i])  
   
for i, lenguage in enumerate(lenguages):  # better than up
	print(i, lenguage)

4. 숫자를 읽기 편하도록 분리할때

보통 숫자는 세자리씩 나눠서 표기한다. (100000000000 이거 몇인지 읽어보라 하면 어느세월에 0의 개수 일일이 세고 하나.. 사실 나는 int(1e12)로 쓰긴 하지만..) 하지만 여기서 쓰이는 밑줄은 binary 인지, hexa인지 숫자의 형태를 알리기도 하고 꽤나 다양하게 쓰인다. (이것도 사실 훼이큰게 '_'를 붙여서 구분되게 해준거지 underscore를 사용해 binary hexa로 형변환을 해주는게 아니다.)

million = 1_000_000
binary = 0b_0010
binary = 0b0010
hexa = 0x_23_ab
hexa = 0x23ab

5. naming 에서의 underscore [important !!]

지금까지는 흥미로웠지만 이제부터는 조금 진지해진다.
이부분을 알고 쓰느냐 모르고 쓰느냐는 초급 파이썬 개발자와 중급 파이썬 개발자를 나누는 하나의 기준이 될 수도 있을 정도다.

single pre underscore

자바나 C계열 언어들은 public private라는 개념이 있다. private는 직접접근을 막아서 object.field 이런식으로 접근 할 수 없다. 보통 get set 메서드를 통해 handling한다. 이는 바깥에서 쉽게 변경하지 못하도록 하여 버그를 방지하는 방법인데 파이썬에 그런게 있나? 그러한 걸림돌따위 파이썬은 취급하지 않는다. 하지만 비슷한건 있다.
python에서 naming에 underscore를 붙이는건 내부에서만 사용된다는 의미이다. 그래서 나의 pycharm은 각종 변수와 메서드는 추천해주지만 _로 시작하는 메서드나 변수는 추천해주지 않는다. 당연히 내부에서 쓰이니까
추천해주는거라고는 똑똑한 copilot뿐..

아무튼 private처럼 쓰지만 실제로 보호하진 않는다 왜냐하면 자유도를 떨어트리면 python이 아니니깐.. python은 유연한만큼 개발자에게 책임을 전가한다.
메서드나 클래스에 '_'를 붙이면 다음과 같은 특징도 있다.

# my_functions.py

def _private_func():
    return 7
  
 
# console
>>>from my_functions import *

>>> _private_func()

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

interpreter가 import해올때 '_'는 private라고 알려줬기 때문에 쏙 빼놓고 가져온다.

single post underscore

방금은 underscore를 앞에 붙여주었다. 뒤에 붙이는건 뭘까?
python에는 다양한 내장 함수나 예약어들이 있다. (next, id, class, def 등등)
python에 id라는 내장 함수가 있다는걸 모르는 사람도 많은데 이 id는 주솟값을 return하는 파이썬 내장 함수다.
하지만 db object들을 다루다보면 id를 다룰일이 많은데 그럼 id는 못쓰나?
이럴때 변수명을 id_로 사용한다고 한다. (나는 그냥 user_id 이런식으로 쓰긴 함..)

double pre underscore

앞에 두개의 underscore를 붙이면 이름 맹글링이라는 의미라는데 맹글링이 무슨 말일까?

  • Name Mangling:- interpreter of the Python alters the variable name in a way that it is challenging to clash when the class is inherited.
    파이썬 인터프리터가 클래스가 상속될 때 변수 이름을 충돌 안나도록 장난질을 한거라고 한다.

dir이라는 내장 함수는 그 object가 품고있는 모든걸 뱉어준다.

class Sample():

    def __init__(self):
        self.a = 1
        self._b = 2
        self.__c = 3
>>> obj1 = Sample()
>>> dir(obj1)
['_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']

다양한 매직 메서드와 '_b'까지 출력해주는데 '_c'는 어디갔지?
바로 '_Samplec'라는 거추장스러운 이름으로 변경되었다. 저렇게 보이기만 하는게 아니고 실제로 'obj1._Samplec'로 접근해야 한다 (블로그에서 언더스코어를 자꾸 무시해서 Simple과 c 사이에 언더스코어 두개 있어요..)
이는 상속을 한번 더 받았을때 저 c라는 친구가 오버라이드 되지 않도록 하기 위해 맹글링이란걸 한다.

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

class ChildSample(Sample):
    def __init__(self):
        super().__init__()
        self.a = 4
        self._b = 5
        self.__c = 6

>>> obj1 = ChildSample()
>>> dir(obj1)
['_ChildSample__c', '_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']

위를 찍어보면 'ChildSample__c'로 바뀌었다. 이 말은 즉슨 + classname + __variable 로 변경된다는 말 이다. 정말 기괴하기 짝이없다..

double pre and post underscores

이는 우리에게 친숙한 매직메서드가 바로 이러한 방식이다. (init, iter, etc..)
매직메서드에서 쓰니까 그냥 쓰지 말라고 적혀있다.

outtro

정말 다양한 밑줄 활용법들이 있는데 모든 표현들의 공통점은 underscore is 무시하라 라고 해석하면 쉽다. 다만 맹글링이라는 표현은 처음봐서 신선한데 많이 쓰이는 것 같지는 않다. 하지만 naming에 '_'하나를 앞에 붙여 private로 표현하는건 알아두는게 좋을 것 같다고 생각한다.
( 블로그 쓰면서 계속 한단락에 underscore가 두개 이상이면 무시되어서 공백처리 되어 애를 먹었다.. 정말 싫다 underscore...)

profile
코딩하는 너구리
post-custom-banner

0개의 댓글