Python

Python 메소드의 첫번째 인자인 `self`는 무엇인가?

둔진 2022. 3. 24. 21:43

먼저 아래 코드를 보겠습니다.

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'>

selftype(self)를 출력해보니 좀 더 명확합니다. 예상했던대로 selfDog 클래스의 instance이고, dog의 type은 Dog 클래스입니다. 그래서 당연히 self.namedog이 가르키는 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'

에러 메시지를 보니 selfsound라는 인자가 필요하다고 합니다. 이전과 다르게 이번에는 추가로 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")는 필요 이상으로 복잡해보입니다.

마치며

프로그래밍 언어를 배울 때 언어의 기능을 익히는 것도 좋지만 그 배경에 숨겨진 설계 철학을 파악해보는 것도 유익한 일이라고 생각합니다. 설계 철학을 둘러싸고 각 프로그래밍 언어의 열성적인 지지자들 사이의 논쟁을 보는 것도 재미있는 일이고요.

From https://twitter.com/jfenal/status/717319558546251777

'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