Devlog

underscore의 의미 본문

언어/Python

underscore의 의미

ehdrb92 2023. 2. 13. 22:16

이 글은 아래 링크의 글을 옮겨 왔습니다.

https://towardsdatascience.com/whats-the-meaning-of-single-and-double-underscores-in-python-3d27d57d6bd1

 

What’s the Meaning of Single and Double Underscores In Python?

I never paid attention to these special characters until I knew what they meant

towardsdatascience.com

 

파이썬에서 함수를 사용하다 보면 "__bar__"와 같이 밑줄이 함수 이름에 포함된 경우들을 본 적이 있다. 파이썬에서 밑줄은 특수한 목적에 의해 함수의 이름 외에도 변수, 메서드 등에 다양한 방법으로 사용된다. 이들의 쓰임새에 대해 알아보자.

Single leading underscores: _foo

선행으로 하나의 밑줄이 오는 경우는 개체가 내부적으로 사용된다는 것을 의미한다. 예시를 보도록 하자.

# module.py

public_variable = 2
_private_variable = 1/2

# interpreter
>>> from module import *

>>> public_variable
2

>>> _private_variable
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-69-28da499e5a9b> in <module>
----> 1 _private_variable

NameError: name '_private_variable' is not defined

module.py에 밑줄이 없는 변수와 있는 변수를 정의해 두었다. 그리고 인터프리터를 통해 해당 모듈에서 변수들을 불러와 값을 확인해 보는 작업을 하면 밑줄이 없는 변수의 경우 정상적으로 해당 값이 나오지만, 밑줄이 있는 경우 불러오지 못하는 모습을 볼 수 있다.

 

그렇다고 이를 아예 불러오지 못하는 것은 아니다. 전역으로 가져오는 것이 아닌 모듈에서 직접적으로 호출할 경우 값을 받을 수 있다.

>>> import module

>>> module.public_variable
2

>>>module._private_variable
0.5

예시에서 봤듯이 사실 해당 변수에 접근할 수 있고, 사실 파이썬에서는 내부 속성이라는 것이 존재하지 않는다. 그래서 내부적으로 감추기 위해 사용한다기보다는 굳이 사용자가 해당 개체에 접근할 필요성이 떨어져 내부적으로만 사용될 경우에 이러한 의도를 드러내기 위해 사용한다고 생각하면 될 것 같다.

 

해당 구조를 잘 사용한 예제 코드를 보고 넘어가도록 하자.

import datetime

class Employee:
     def __init__(self, first_name, last_name, start_date):
         self.first_name = first_name
         self.last_name = last_name
         self.start_date = start_date
         self._seniority = self._get_seniority(start_date)

     def _get_seniority(self):
         today_date = datetime.datetime.today().date()
         seniority = (today_date - start_date).days
         return seniority
     
    def compute_compensation_package(self):
         # use self._seniority here.

        
first_name = "John"
last_name = "Doe"
start_date = datetime.date(2021, 12, 1)
employee = Employee(first_name, last_name, start_date)

>>> employee._seniority
53

Single trailing underscores: foo_

후행으로 하나의 밑줄이 오는 경우는 특별한 것은 없고, 변수명으로 파이썬의 예약어를 쓰고 싶을 때 사용한다. 파이썬 예약어는 class, def, type, object 등등 여러 가지가 있다.

 

하지만 사실 이러한 예약어를 굳이 후행 밑줄까지 사용해 가면서 쓰는 것은 그렇게 좋지는 않다.

Single underscores: _

특별히 다른 이름에 함께 붙이는 것이 아닌 단일 밑줄을 사용하는 경우도 존재한다. 이는 임시로 계속해서 사용되지 않을 변수를 정해줄 때이다.

 

예를 들어 for 반복문에서 range에서 꺼내오는 값을 사용할 필요가 없을 때 사용할 수 있다.

for _ in range(100):
   do_some_work()

그리고 만약 함수가 다섯 개의 원소를 가진 튜플 값을 반환했는데, 그중 몇 개의 값만 다른 변수에 할당하여 사용하고 싶을 때 사용할 수 있다.

# extract_laptop_specs 함수가 5개의 원소를 담은 튜플을 반환한다고 가정
cpu, _, _, memory, _ = extract_laptop_specs(laptop)

PEP 515에 따라 밑줄은 긴 숫자에 대해 가독성을 높이기 위해 사용될 수 있다.

In [2]: 1_000
Out[2]: 1000
  
In [3]: 1_000_000
Out[3]: 1000000

In [4]: 1_000_000_000
Out[4]: 1000000000

Double leading and trailing underscores: __foo__

이름의 선, 후행에 두 개의 밑 줄이 들어간 경우는 파이썬에서 특수한 목적으로 사용되기 위해 예약된 매직 메서드를 정의하기 위해 존재한다. 그리고 이러한 매직 메서드는 덮어쓰기도 가능하다.

  • __init__: 클래스의 생성자로 사용
  • __call__: 객체를 호출 가능하게 만들기 위해 사용
  • __str__: 객체가 print함수로 호출될 때 화면에 표시될 내용을 정의하는 데 사용

파이썬은 내장 모듈의 핵심 메서드들을 사용자가 정의한 메서드와 구별하기 위해 위와 같이 이름의 앞뒤에 밑줄 두 개를 사용하는 명명 규칙을 도입하였다.

 

더 다양한 매직 메서드에 대해 알고 싶다면 아래 링크를 참조하도록 하자.

https://www.section.io/engineering-education/dunder-methods-python/

 

Dunder/Magic Methods in Python

This article will talk about dunder methods like init, len, getitem, add, iadd, etc. Various example functions and classes will be discussed.

www.section.io

Double leading underscores: __bar

이름 앞에 선행으로 두 개의 밑 줄이 오는 경우는 맹글링(mangling)을 위한 용도로 사용된다.

 

파이썬에서 맹글링이란 어떤 특정 속성이 우연히 발견되지 않도록 감추고 싶을 때사용한다. 예시를 통해 보도록 하자.

class Car:
    def __init__(self):
        self.color = "red"
        self._speed = 70
        self.__brand = "bmw"
        
car = Car()

>>> car.__brand

Traceback (most recent call last):
  File "/Users/donggyu/test/main.py", line 9, in <module>
    car.__brand
AttributeError: 'Car' object has no attribute '__brand'

위와 같이 car 인스턴스의 __brand 속성으로 접근하려 할 때 속성이 존재하지 않는다는 에러가 나온다. 하지만 이는 절대적이지는 않다. 사실 별도로 접근하는 방법이 존재한다.

>>> print(dir(car))

['_Car__brand', '__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__', '_speed', 'color']

car 인스턴스에 사용할 수 있는 속성들의 목록을 조회하면 __brand는 없지만 _Car__brand라는 처음 보는 속성이 존재한다. 이것이 맹글링을 통해 만들어준 private 한 속성에 접근하는 속성명이다.

 

그렇다면 이번에는 Car 클래스를 상속하는 클래스에서 맹글링을 한 속성을 선언한 예제를 보자.

class ExtendedCar(Car):
    def __init__(self):
        super(ExtendedCar, self).__init__()
        self.color = "green"
        self._speed = 80
        self.__brand = "audi"
        
extended_car = ExtendedCar()

>>> print(dir(extended_car))

['_Car__brand', '_ExtendedCar__brand', '__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__', '_speed', 'color']

Car 클래스를 상속받은 ExtendedCar 클래스에서 __brand 속성을 선언하면 예상대로 _ExtendedCar__brand속성이 생긴 것을 볼 수 있다.

'언어 > Python' 카테고리의 다른 글

[FastAPI] CORSMiddleware가 동작하지 않는 문제  (0) 2024.04.15
파이썬 Docstring 작성법  (0) 2023.02.13