먼저 아래 코드를 보겠습니다.
class Dog:
def __init__(self, name):
self.name = name
def make_sound(self, sound):
print(f"{self.name}! {sound}!")
if __name__ == "__main__":
dog = Dog("Happy")
dog.make_sound("Bow Wow")
Happy! Bow Wow!
오늘 이야기하고 싶은 것은 __init__()
이나 make_sound()
처럼 클래스의 메소드의 첫 번째 인자인 self
는 무엇이고 왜 필요할까입니다. 써야한다고 배웠고 습관적으로 써왔지만 갑자기 그 녀석의(?) 정체가 궁금해집니다.
self
넌 누구냐
class Dog:
def __init__(self, name):
self.name = name
def make_sound(self, sound):
print(f"{self.name}! {sound}!")
print(f"I am {self}")
print(f"I am {type(self)}")
if __name__ == "__main__":
dog = Dog("Happy")
dog.make_sound("Bow Wow")
Happy! Bow Wow!
I am <__main__.Dog object at 0x7f5a912bb040>
I am <class '__main__.Dog'>
self
와 type(self)
를 출력해보니 좀 더 명확합니다. 예상했던대로 self
는 Dog
클래스의 instance이고, dog
의 type은 Dog
클래스입니다. 그래서 당연히 self.name
은 dog
이 가르키는 instance의 name
입니다.
그렇다면 메소드의 첫번째 self
는 어디에서 온거지?
메소드의 첫번째 인자인 self
가 자기 자신을 가르키는 것이라는 것은 알았습니다. 그렇다면 이 self
는 어디에서 누가 할당했을까요? 우리는 dog.make_sound("Bow Wow")
와 같이 호출 했을 뿐인데요.
아래와 같이 실험을 해보겠습니다.
class Dog:
def __init__(self, name):
self.name = name
def make_sound(self, sound):
print(f"{self.name}! {sound}!")
print(f"I am {self}")
print(f"I am {type(self)}")
if __name__ == "__main__":
dog = Dog("Happy")
dog.make_sound()
TypeError: make_sound() missing 1 required positional argument: 'sound'
sound
인자의 값을 지정하지 않았으니 당연한 에러입니다.
그럼 아래 코드는 어떨까요?
class Dog:
def __init__(self, name):
self.name = name
def make_sound(self, sound):
print(f"{self.name}! {sound}!")
print(f"I am {self}")
print(f"I am {type(self)}")
if __name__ == "__main__":
dog = Dog("Happy")
Dog.make_sound()
이번에는 dog.make_sound()
대신 Dog.make_sound()
를 호출했습니다. 즉, instance가 아니고 class를 대상으로 메소드를 호출했습니다.
TypeError: make_sound() missing 2 required positional arguments: 'self' and 'sound'
에러 메시지를 보니 self
와 sound
라는 인자가 필요하다고 합니다. 이전과 다르게 이번에는 추가로 self
도 지정이 필요하다고 합니다.
그렇다면,
class Dog:
def __init__(self, name):
self.name = name
def make_sound(self, sound):
print(f"{self.name}! {sound}!")
print(f"I am {self}")
print(f"I am {type(self)}")
if __name__ == "__main__":
dog = Dog("Happy")
Dog.make_sound(dog, "Bow Wow")
Happy! Bow Wow!
I am <__main__.Dog object at 0x7f133ed9a040>
I am <class '__main__.Dog'>
정상적으로 출력이 됩니다! 다만 dog.make_sound("Bow Wow")
라고 간단히 쓰면 될 것을 Dog.make_sound(dog, "Bow Wow")
라고 쓰기는 했지만요.
이 실험을 통해 메소드의 첫번째 인자인 self
가 어디에서 온 것인지 알 수 있습니다. 우리가 instance를 대상으로 dog.make_sound("Bow Wow")
와 같이 메소드를 호출하면 파이썬이 자동으로 Dog.make_sound(dog, "Bow Wow")
와 같이 첫번째 인자 self
를 채워주는 것입니다.
정리해보자면,
- 메소드의 첫번째 인자
self
는 호출 할 때 대상(.의 왼쪽)인 instance이다 - 그리고 이 instance는 파이썬이 자동으로
self
에 채워준다
왜 이렇게 복잡하게 하지?
그렇다면 왜 굳이 파이썬은 번거롭게 모든 메소드의 첫인자로 self
를 강제했을까요? 예를 들어 아래와 같이 된다면 편하지 않을까요?
class Dog:
def __init__(self, name):
self.name = name
def make_sound(sound):
print(f"{self.name}! {sound}!")
if __name__ == "__main__":
dog = Dog("Happy")
dog.make_sound("Bow Wow")
하지만 아래와 같은 에러가 발생합니다.
Traceback (most recent call last):
File "/home/ceongjeein/dog.py", line 10, in <module>
dog.make_sound("Bow Wow")
TypeError: make_sound() takes 1 positional argument but 2 were given
파이썬이 내부적으로 Dog.make_sound(dog, "Bow Wow")
를 호출했기 때문입니다.
프로그래밍 언어를 설계할 때는 설계자의 철학적인 결정이 많이 반영됩니다. Python의 설계 철학 중 유명한 것은 Zen of Python인데 이 중에는 아래와 같은 문구가 있습니다.
Explicit is better than implicit.
명시적인 것이 암묵적인 것보다 좋다.
만약에 앞의 코드를 허용한다면 make_sound()
의 입장에서는 본적도 없는 self
라는 변수가 등장한 셈입니다. 편리하겠지만 명시적이지 않죠. 파이썬과 다르게 개발자의 편의를 우선해서 많은 암묵적인 기능들을 제공하는 프로그래밍 언어들도 있습니다. 혹자는 이런 것들은 magic
이라고 부르기도 하고요 (프로그래밍 언어에서 mgaic
이라는 것이 꼭 긍정적인 의미만은 아닙니다). 파이썬도 시간이 지나면서 decorator
와 같이 개발자의 편의를 위한 기능들이 추가되기도 했습니다. 이런 기능들을 영어권에서는 syntactic sugar
라고 부릅니다. magic
류의 기능이 많아지면 코드를 작성할 때는 편리할 수 있지만, 코드를 읽거나 디버깅을 할 때는 갑툭튀의 느낌이 있기 때문에 단점으로 작용하기도 합니다.
그렇다면 Dog.make_sound(dog, "Bow Wow")
가 dog.make_sound("Bow Wow")
보다 명시적이지 않을까요? 관점에 따라 다를 수 있지만 dog.make_sound("Bow Wow")
에도 숨겨진 정보가 전혀 없고 오히려 Dog.make_sound(dog, "Bow Wow")
는 필요 이상으로 복잡해보입니다.
마치며
프로그래밍 언어를 배울 때 언어의 기능을 익히는 것도 좋지만 그 배경에 숨겨진 설계 철학을 파악해보는 것도 유익한 일이라고 생각합니다. 설계 철학을 둘러싸고 각 프로그래밍 언어의 열성적인 지지자들 사이의 논쟁을 보는 것도 재미있는 일이고요.
'Python' 카테고리의 다른 글
Python GIL(Global Interpreter Lock)이 무엇일까요? (0) | 2023.01.13 |
---|---|
Python Callable (0) | 2022.07.11 |
Iterator, Generator (0) | 2021.05.08 |
List Comprehension (0) | 2021.05.04 |
Jupyter Lab (0) | 2021.05.01 |