<aside> 💡 이 문서에는 언더바(_) 와 이중 언더바(__) 가 많이 나옵니다. 헷갈리지 않게 하기 위해 이중 언더바(__)에만 code block 처리를 했습니다.

</aside>

최근 개인 project 에서 좀 골때리는 걸 만나가지고 며칠간 고생하다가 설마하는 마음에 고친 게 어이없게 풀려 버려서 기록을 좀 남겨야겠다 싶어서 글을 써본다.

문제 상황

대충 상황은 다음과 같다. 상속 관계이고, 공통으로 처래해야 하는 부분과 자식들이 특성에 맞게 처리해야 하는 부분이 있어서 자식 class 에서 method 를 부르면 부모 class 의 로직이 무조건 같이 돌기를 원했다.

그래서 이런 구조를 만들었다.

class Parent:
	def do_action(self):
		print("do parent thing")
		self.__do_action()

	def __do_action(self):
		raise NotImplementedError

class Child1(Parent):
	def __do_action(self):
		print("do child1 thind")

class Child2(Parent):
	def __do_action(self):
		print("do child2 thing")
# 기대한 것
>>> c1 = Child1()
>>> c1.do_action()
do parent thing
do child1 thing
>>> c2 = Child2()
>>> c2.do_action()
do parent thing
do child2 thing

# 실제
>>> c1 = Child1()
>>> c1.do_action()
do parent thing
`NotImplementedError`
>>> c2 = Child2()
>>> c2.do_action()
do parent thing
`NotImplementedError`

do_action() 을 실행하면 Parent 의 할 일(공통) 을 처리하고 각자 Child 들이 구현하고 있는 __do_action() 을 추가로 실행하도록 했다.

Parent__do_action()NotImplementedError 를 던지고 있기 때문에 이를 방어하려면 모든 자식은 필수로 __do_action() 을 구현해야 한다. (pass 만 있더라도)

나름 적절한 구조라고 생각했고 잘못한 부분도 없는 것 같은데 아무리 해도 자꾸 Child 가 구현한 버전이 아닌 Parent 에 들어있는 원본(?) 을 실행하면서 에러를 던졌다.

아무리 해도 안돼가지고 내가 설마 상속과 다형성에 대해 오랜시간동안 착각하고 있는게 아닌가 진짜 기초 책까지 다시 꺼내봤다…. 근데 틀린건 없어…

진짜 이게 mro 문제인건지, 내가 설계 자체를 잘못한건지, 아니면 정말 내가 돌대가리라서 뭔가 크게 잘못 알고 있던건지…. 그래서 며칠간 방치해두고 있었다.

오늘 또 다시 한참 이거저거 뒤지다가 갑지기 번개같이 머리를 스치고 가는 생각. 설마 이거 __ 로 시작해서 그런가….?

뭐 더이상 물러날 데도 없는데 속아나 보자는 생각으로 __do_action()_do_action() 으로 변경했다.

Aㅏ… 모든 테스트가 녹색이다… dunder 가 문제였을 줄이야…

원인 : __(dunder) 와 name mangling (맹글링)

Python 에는 public, protected, private 같은 접근 제한자가 없다. 기본적으로 python 에서는 모두가 서로를 신뢰하는 것을 기반으로 하고 있기 때문에 _ 로 시작하면 protected, __ 로 시작하면 private 와 같이 사용하기로 “약속” 했다. (강제는 아니다. 대충 _ 로 시작하는거 외부에서도 막 부를 수 있다)

그런데 _ 와 달리 __ 에 대해서 python 에서는 나름 접근제한을 위한 작업을 진행하는데, 그게 바로 이번 주제인 name mangling 이다.

__ 로 시작하는 것은 private 으로 하기로 했는데 이걸 외부에서 너무 쉽게 부르면 좀 그러니까 python 이 코드를 변경해서 __ 로 시작하는 함수의 이름을 다음과 같이 변경한다.