파이썬 클래스를 사용하다 보면 메서드 이름 앞뒤에 __가 붙어 있는 코드를 자주 볼 수 있습니다.
대표적으로 다음과 같은 메서드가 있습니다.
__init__()
__str__()
__repr__()
__len__()
__eq__()
__add__()
이런 메서드를 Special Method(특수 메서드)라고 합니다.
흔히 이름이 양쪽의 밑줄 두 개로 둘러싸여 있어서 Dunder Method라고 부르기도 합니다.
이번 글에서는 특수 메서드가 무엇인지 알아보고, 직접 클래스를 만들 때 자주 사용하는 특수 메서드들을 살펴보겠습니다.
1. Special Method란?
특수 메서드는 파이썬의 특정 문법이나 내장 함수가 실행될 때 자동으로 호출되는 메서드입니다.
예를 들어 리스트에서 다음과 같이 사용할 수 있습니다.
numbers = [1, 2, 3]
print(len(numbers))
len()은 리스트의 길이를 반환합니다.
그런데 우리가 직접 만든 객체도 len()을 사용할 수 있도록 만들 수 있습니다.
class Team:
def __init__(self, members):
self.members = members
def __len__(self):
return len(self.members)
team = Team(["Kim", "Lee", "Park"])
print(len(team))
실행 결과:
3
여기서 우리가 직접 team.__len__()을 호출한 것이 아닙니다.
len(team)
이라고 작성했을 때 파이썬이 객체의 __len__()을 사용합니다.
즉,
len(team)
은 개념적으로 다음과 연결됩니다.
team.__len__()
특수 메서드의 가장 중요한 특징이 바로 이것입니다.
특정 문법이나 내장 함수가 객체를 어떻게 동작시킬지 정의할 수 있다.
2. __init__()도 특수 메서드다
앞에서 클래스를 만들면서 이미 특수 메서드를 사용했습니다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
객체를 생성하면 다음 코드가 실행됩니다.
person = Person("Kim", 30)
이때 __init__()이 객체의 초기 상태를 설정하는 데 사용됩니다.
즉, __init__() 역시 특수 메서드입니다.
하지만 특수 메서드는 __init__()만 있는 것이 아닙니다.
파이썬은 객체가 문자열, 숫자, 반복문, 비교 연산 등과 자연스럽게 동작할 수 있도록 다양한 특수 메서드를 제공합니다.
3. __str__() – 객체를 문자열로 표현하기
클래스로 객체를 만들고 print()를 사용하면 기본적으로 객체의 정보가 사람이 보기 좋은 형태로 나오지 않습니다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Kim", 30)
print(person)
결과는 대략 다음과 같은 형태입니다.
<__main__.Person object at 0x000001...>
이런 형태는 개발할 때 크게 도움이 되지 않습니다.
이때 __str__()을 사용할 수 있습니다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} ({self.age})"
person = Person("Kim", 30)
print(person)
결과:
Kim (30)
print()가 객체를 문자열로 표현해야 할 때 __str__()이 사용됩니다.
다음과 같이 직접 str()을 사용해도 마찬가지입니다.
print(str(person))
즉,
print(person)
또는
str(person)
을 사용했을 때 __str__()을 정의해 두면 원하는 문자열을 반환하도록 만들 수 있습니다.
주의할 점
__str__()은 반드시 문자열을 반환해야 합니다.
def __str__(self):
return f"{self.name} ({self.age})"
다음처럼 숫자를 직접 반환하면 안 됩니다.
def __str__(self):
return self.age
__str__()의 반환값은 반드시 str 타입이어야 합니다.
4. __repr__() – 객체의 개발자용 표현
__str__()과 비슷한 특수 메서드로 __repr__()이 있습니다.
둘 다 객체를 문자열 형태로 표현하지만 사용하는 목적이 조금 다릅니다.
일반적으로 __str__()은 사용자가 보기 좋은 표현을 만들 때 사용하고, __repr__()은 개발자가 객체의 상태를 확인하기 좋은 표현을 만들 때 사용합니다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} ({self.age})"
def __repr__(self):
return f"Person(name={self.name!r}, age={self.age!r})"
사용하면 다음과 같습니다.
person = Person("Kim", 30)
print(str(person))
print(repr(person))
결과:
Kim (30)
Person(name='Kim', age=30)
repr()은 객체의 상태를 확인하거나 디버깅할 때 특히 유용합니다.
예를 들어 여러 객체가 리스트에 들어 있는 경우 __repr__()을 정의해 두면 객체의 내용을 확인하기 편해집니다.
people = [
Person("Kim", 30),
Person("Lee", 25)
]
print(people)
클래스의 용도에 따라 __str__()과 __repr__()을 적절하게 작성하면 디버깅이 훨씬 편해집니다.
5. __len__() – len() 사용하기
앞에서 간단하게 살펴본 것처럼 __len__()을 정의하면 우리가 만든 객체에 len()을 사용할 수 있습니다.
class Team:
def __init__(self, members):
self.members = members
def __len__(self):
return len(self.members)
team = Team(["Kim", "Lee", "Park"])
print(len(team))
결과:
3
여기서 중요한 것은 Team 객체 자체에는 기본적으로 길이라는 개념이 없다는 것입니다.
하지만 우리가 __len__()을 정의하면서
“이 객체의 길이는
members의 개수로 사용하자.”
라고 규칙을 만들어 준 것입니다.
이런 방식으로 사용자 정의 객체를 파이썬의 기본 자료형처럼 사용할 수 있습니다.
6. __eq__() – == 비교 정의하기
객체를 비교할 때 사용하는 == 연산도 특수 메서드로 정의할 수 있습니다.
예를 들어 사람의 이름과 나이가 같으면 같은 사람으로 판단하도록 만들어 보겠습니다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
if not isinstance(other, Person):
return NotImplemented
return self.name == other.name and self.age == other.age
이제 두 객체를 비교할 수 있습니다.
person1 = Person("Kim", 30)
person2 = Person("Kim", 30)
person3 = Person("Lee", 25)
print(person1 == person2)
print(person1 == person3)
결과:
True
False
== 연산을 사용했을 때 파이썬은 __eq__()을 이용해 두 객체가 같은지 판단합니다.
여기서 중요한 점이 하나 있습니다.
person1 = Person("Kim", 30)
person2 = Person("Kim", 30)
두 객체의 내용은 같지만 서로 다른 객체입니다.
즉, 다음 두 개념을 구분해야 합니다.
==: 값이나 내용이 같은지 비교is: 같은 객체인지 비교
print(person1 == person2)
print(person1 is person2)
결과:
True
False
__eq__()은 이러한 == 비교 방식을 직접 정의할 때 사용합니다.
7. __add__() – + 연산 정의하기
특수 메서드는 비교뿐만 아니라 연산자에도 사용할 수 있습니다.
대표적인 예가 __add__()입니다.
+ 연산을 사용할 때 호출되는 특수 메서드입니다.
간단한 좌표 클래스를 만들어 보겠습니다.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if not isinstance(other, Point):
return NotImplemented
return Point(
self.x + other.x,
self.y + other.y
)
이제 두 좌표를 더할 수 있습니다.
point1 = Point(10, 20)
point2 = Point(5, 3)
point3 = point1 + point2
print(point3.x)
print(point3.y)
결과:
15
23
우리가 직접 다음과 같이 작성하지 않았습니다.
point1.__add__(point2)
대신 다음과 같이 작성했습니다.
point1 + point2
파이썬의 + 연산이 우리가 만든 __add__()를 사용하도록 연결해 준 것입니다.
8. __getitem__() – [] 사용하기
특수 메서드를 사용하면 객체에 인덱싱도 적용할 수 있습니다.
예를 들어 음악 목록을 관리하는 클래스를 만들어 보겠습니다.
class Playlist:
def __init__(self, songs):
self.songs = songs
def __getitem__(self, index):
return self.songs[index]
이제 객체에 대괄호를 사용할 수 있습니다.
playlist = Playlist([
"Song A",
"Song B",
"Song C"
])
print(playlist[0])
print(playlist[1])
결과:
Song A
Song B
다음 코드가 실행될 때
playlist[0]
__getitem__()이 사용됩니다.
즉, 우리가 만든 객체도 리스트와 비슷한 방식으로 사용할 수 있습니다.
9. __iter__() – for 반복문 사용하기
객체를 for문으로 반복할 수 있도록 만들 수도 있습니다.
class Team:
def __init__(self, members):
self.members = members
def __iter__(self):
return iter(self.members)
이제 다음과 같이 사용할 수 있습니다.
team = Team(["Kim", "Lee", "Park"])
for member in team:
print(member)
결과:
Kim
Lee
Park
__iter__()을 정의했기 때문에 Team 객체 자체를 반복할 수 있게 되었습니다.
이처럼 특수 메서드는 단순히 객체의 정보를 출력하는 기능을 넘어 객체가 파이썬의 다양한 문법과 자연스럽게 동작하도록 만들어 줍니다.
10. 자주 사용하는 Special Method 정리
특수 메서드는 매우 많기 때문에 처음부터 전부 외울 필요는 없습니다.
실제로 클래스를 작성하면서 필요한 기능에 맞춰 사용하는 것이 좋습니다.
| 특수 메서드 | 관련 기능 | 예시 |
|---|---|---|
__init__() | 객체 초기화 | Person(...) |
__str__() | 문자열 표현 | str(obj), print(obj) |
__repr__() | 개발자용 표현 | repr(obj) |
__len__() | 길이 | len(obj) |
__eq__() | 같음 비교 | obj1 == obj2 |
__add__() | 덧셈 | obj1 + obj2 |
__getitem__() | 인덱싱 | obj[0] |
__iter__() | 반복 | for item in obj |
__call__() | 함수처럼 호출 | obj() |
특히 마지막의 __call__()은 조금 특별합니다.
11. __call__()을 사용하면 객체를 함수처럼 사용할 수 있다
일반적으로 객체는 다음과 같이 사용합니다.
person = Person("Kim", 30)
print(person.name)
그런데 __call__()을 정의하면 객체 뒤에 ()를 붙여 함수를 호출하듯 사용할 수 있습니다.
class Calculator:
def __call__(self, x, y):
return x + y
calculator = Calculator()
print(calculator(10, 20))
결과:
30
여기서
calculator(10, 20)
은 객체를 함수처럼 사용한 것입니다.
이 기능을 이용하면 특정 상태를 가지고 있으면서 함수처럼 동작하는 객체를 만들 수 있습니다.
이러한 객체를 Callable Object라고 합니다.
__call__()은 활용 범위가 상당히 넓기 때문에 다음 글에서 별도로 자세히 살펴보겠습니다.
12. Special Method를 직접 호출해야 할까?
특수 메서드는 일반적으로 직접 호출하기보다 파이썬의 문법이나 내장 함수를 사용하는 것이 자연스럽습니다.
예를 들어 다음 코드가 있다면
len(team)
굳이 다음과 같이 작성할 필요는 없습니다.
team.__len__()
마찬가지로
point1 + point2
를 사용하는 것이
point1.__add__(point2)
보다 일반적인 방법입니다.
특수 메서드는 파이썬의 문법과 객체를 연결하는 역할을 한다고 이해하면 좋습니다.
| 작성하는 코드 | 연결되는 특수 메서드 |
|---|---|
str(obj) | __str__() |
repr(obj) | __repr__() |
len(obj) | __len__() |
obj1 == obj2 | __eq__() |
obj1 + obj2 | __add__() |
obj[index] | __getitem__() |
for item in obj | __iter__() |
obj() | __call__() |
13. NotImplemented는 왜 사용할까?
앞에서 __eq__()이나 __add__()을 작성하면서 다음 코드를 사용했습니다.
return NotImplemented
이것은 단순히 False를 반환하는 것과는 다릅니다.
예를 들어 다음과 같은 코드가 있다고 해보겠습니다.
def __eq__(self, other):
if not isinstance(other, Person):
return NotImplemented
return self.name == other.name
현재 객체가 처리할 수 없는 타입의 비교가 들어왔다는 것을 파이썬에게 알려주는 역할을 합니다.
특히 연산자 관련 특수 메서드를 작성할 때는 지원하지 않는 타입에 대해 무조건 False를 반환하기보다 NotImplemented를 반환하는 것이 적절한 경우가 많습니다.
처음에는 모든 특수 메서드의 동작 방식을 외울 필요는 없습니다.
다만 다음 정도는 기억해 두면 좋습니다.
내가 정의한 객체가 처리할 수 없는 연산이라면
NotImplemented를 고려한다.
14. Special Method를 사용하는 이유
그렇다면 굳이 이런 복잡한 메서드를 사용하는 이유는 무엇일까요?
가장 큰 이유는 사용자 정의 객체를 파이썬의 기본 객체처럼 자연스럽게 사용할 수 있기 때문입니다.
예를 들어 특수 메서드가 없다면 다음과 같이 직접 메서드를 호출해야 할 수도 있습니다.
team.get_length()
하지만 __len__()을 정의하면 다음과 같이 사용할 수 있습니다.
len(team)
좌표를 더하는 경우에도
point1.add(point2)
대신
point1 + point2
처럼 사용할 수 있습니다.
즉, 특수 메서드는 클래스의 사용 방법을 파이썬의 기본 문법에 맞춰주는 역할을 합니다.
15. Special Method를 무조건 많이 만들 필요는 없다
특수 메서드를 많이 정의한다고 좋은 클래스가 되는 것은 아닙니다.
클래스에 실제로 필요한 기능만 구현하는 것이 중요합니다.
예를 들어 객체를 문자열로 보여줄 필요가 있다면
__str__()
을 사용할 수 있고,
객체의 길이를 len()으로 확인해야 한다면
__len__()
을 사용할 수 있습니다.
객체를 반복해야 한다면
__iter__()
를 고려하면 됩니다.
그리고 객체를 함수처럼 사용해야 하는 상황이라면
__call__()
을 사용할 수 있습니다.
즉,
특수 메서드는 외워서 사용하는 기능이 아니라 객체가 어떤 방식으로 동작해야 하는지에 따라 선택하는 기능입니다.
마무리
이번 글에서는 파이썬 클래스의 Special Method(특수 메서드)에 대해 알아봤습니다.
핵심은 특수 메서드가 특정 문법이나 내장 함수와 클래스 객체를 연결해 준다는 것입니다.
대표적으로 다음과 같은 특수 메서드가 있습니다.
__str__() → print(), str()
__repr__() → repr()
__len__() → len()
__eq__() → ==
__add__() → +
__getitem__() → []
__iter__() → for
__call__() → ()
특히 __str__(), __repr__(), __len__(), __eq__() 정도는 클래스를 직접 작성할 때 자주 만나게 됩니다.
그리고 이번 글에서 살펴본 __call__()은 조금 더 재미있는 기능입니다.
객체를 함수처럼 호출할 수 있게 만들어 주기 때문입니다.
다음 글에서는 이 __call__()을 중심으로 Callable Object(호출 가능한 객체)가 무엇인지 알아보겠습니다.