이 글의 작성자는 C/C++ 프로그래밍을 하던 사람이다. 이 글은 Python을 복습하며 작성하는 글이니, 부족한 부분이 있으면 얼마든지 피드백을 주시기 바란다.

 

이제 그 동안 다루지 않았던 클래스에 대한 내용을 소개하고 클래스에 대한 이야기를 마무리 하도록 한다.

 

덕 타이핑(duck typing)

If it walks like a duck and it quacks like a duck, then it’s a duck. (오리처럼 걷고 오리처럼 꽥꽥 거린다면, 그건 오리다.)

위의 문구는 덕 타이핑에 대해 검색했을 때, 흔히 볼 수 있는 문구이다. 그 만큼 덕 타이핑에 대해 잘 설명하고 있는 문구이기 때문이다.

파이썬의 변수의 특징 중 하나는, 하나의 변수가 어떤 타입의 객체든 가지고 있을 수 있다는 것이다. 이를 동적 타이핑(dynamic typing)이라고 하며, 정수형 객체를 가지고 있다가 문자열 객체로 바꿔 담을 수 있다.

그것이 안되는 언어들이 있다. 정적 타이핑을 사용하는 언어들(C/C++, Java등)이 그 예시다.

그렇기에 따라오는 문제점이 하나 있다. 지금 이 변수에는 무슨 객체가 담겨있는지 이름만 보고는 알 수가 없다. 다른 정적 타이핑 언어는 변수를 선언할 때, 이 변수에 올 수 있는 타입이 정해지기 때문에 변수의 이름만 보고도 어떤 객체가 담겨있는지 알 수가 있으나, 파이썬은 동적 타이핑을 사용하는 언어이다.

지금 이 변수에 담겨있는 객체가 무엇인지 알 수 없다는 것은 이 변수가 어떤 메서드나 속성을 가지고 있는지 모른다라는 말과 같다. 그래서 파이썬 같은 동적 타이핑 언어들은 변수의 타입을 따지지 않고 프로그래머가 요청하는 속성/메서드가 있으면 그 속성/메서드를 가져오는 방식을 사용한다.

다음과 같은 duck 클래스가 있다고 생각해보자.

class duck:
    def quack(self):
        print("꽥꽥")

d = duck()
d.quack()

뭐 당연하겠지만 "꽥꽥"이 출력될 것이다. 그리고 다음 코드를 보자.

class duck:
    def quack(self):
        print("꽥꽥")

class person:
    def quack(self):
        print("(꽥꽥 소리를 흉내내는 소리)")

d = duck()
d.quack()
p = person()
p.quack()

하나의 person 클래스를 만들고, 오리가 “꽥꽥”하는 메서드와 같은 이름의 메서드를 만들었다. 출력 결과는 사람이 꽥꽥 소리를 흉내내는 텍스트이다.

여기서, duck 클래스와 person 클래스는 전혀 연관이 없는 클래스이다. 부모자식 관계도 아니고, 공통점이라고는 quack 메서드 뿐이다. 두 메서드는 이름이 똑같다라는 점 외에는 연관성이 없다. 오버라이드된 메서드도 아니기 때문이다.

그럼에도 불구하고, 변수에게 quack을 호출해라!라는 명령을 내리면 알아서 해당 변수가 가리키는 메서드 quack을 호출한다. 한마디로 ‘너가 무슨 객체인지는 잘 모르겠지만, 꽥괙 거릴 수 있으면 꽥꽥 거려 봐라’라는 철학이 담겨있는 것이다.

그래서 덕 타이핑의 소개 문구가 저렇게 되어있는 것이다. 뭔진 몰라도 꽥꽥거릴 수 있으면(= quack 메서드가 있다면) 그것은 오리다(= 대충 오리라고 생각하고 quack 메서드를 실행해라) 라는 의미를 담고있는 것이다.

 

특수 메서드

파이썬의 클래스들은 특수 메서드를 가질 수 있다. 그런데 특수 메서드란 이름을 보면 이런 생각이 들 수 있다. “어떤 점이 특별하길래 특수 메서드라는 이름을 가지고 있을까?”

우리는 이미 하나의 특수 메서드를 알고 있다. 바로 이니셜라이저, __init__메서드다. 이 메서드는 객체가 생성되고 초기화를 할 때 호출되는 메서드다. 객체를 만들기만 했을 뿐인데 알아서 호출되는 메서드이므로, __init__메서드는 특별한 메서드라고 할 수 있다.

이런 식으로 특정 동작을 수행할 때 파이썬 자체에서 호출해주는 메서드들을 특수 메서드라고 부른다. 이러한 특수 메서드들을 잘 사용한다면 깔끔한 파이썬 코드를 작성할 수 있을 것이다.

하지만 특수 메서드도 한두개가 아니어서, 여기서 모두 소개하기엔 무리가 있으므로, 여기서는 일부만 소개하도록 한다.

 

  • __eq__ : 두 객체는 같은 값을 가지는가?

이 메서드는 == 연산자를 사용했을 때 호출되는 메서드이다. == 연산자는 두 객체가 같은 값을 가지는가? 를 판별할 때 사용하는 연산자이다. 파이썬에서 기본적으로 제공하는 타입들이라면 값을 어떻게 비교해야 할 지 미리 정해뒀겠지만, (예를 들면 int와 float 사이의 값 비교) 우리가 만든 클래스의 값 비교는 파이썬이 할 수 없을 것이다. 따라서 값을 어떻게 비교해야 할 지는 우리가 정해줘야 한다.

전에 만들었던 student 클래스를 예로 들어보자. 다음 코드의 결과를 보면 왜 우리가 __eq__ 메서드를 따로 정의해야 하는지 이해할 수 있을 것이다.

s = student('mike', 23,'서울',2,'컴퓨터')
q = student('mike', 23,'서울',2,'컴퓨터')
print (s == q)

위의 s 객체와 q 객체가 가지고 있는 모든 값은 같다. 하지만 실제 비교 결과는 어떤가? False가 나오고 있다. 그리고 s == s에 대한 결과를 출력한다면 True가 나오는 것을 확인할 수 있을 것이다. 따라서 __eq__가 정의되지 않으면, 기본적으로는 s is q와 같이 같은 객체를 가리키고 있는지에 대한 여부를 확인한다는 사실을 추측할 수 있다.

그러면 student 클래스의 __eq__를 구현해보자. 두 학생이 같은 학생이라는 사실은 어떻게 판별할 것인가?

사실 가장 좋은 방법은 해당 학생의 학번을 확인하는 것이 가장 좋겠지만, 안타깝게도 이 클래스는 학번 속성이 없으므로, 단순히 이름, 나이, 대학교 이름, 그리고 전공 명이 모두 같은지 확인하는 방법으로 같은 학생인지를 판별해보도록 하자.

만약 이 코드가 실전이라면 그냥 학번을 추가하는 방향으로 잡는게 좋을 것이다. 나이 같은 동명이인이 없으리란 법은 없기 때문이다.

다음과 같이 코드를 추가하면 될 것이다.

class student(person):
...
    def __eq__(self, other):
        return (self.name == other.name and self.age == other.age and self.grade == other.grade and
                self.university == other.university and self.major == other.major)

그러면 이제 같은 속성 값을 가진 객체끼리 == 연산자를 사용하면 True가 리턴되는 것을 확인할 수 있을 것이다.

사실 이 코드는 완전하다고 보기는 힘들다. 비교의 대상으로 student 클래스가 아닌 객체가 들어올 수 있기 때문이다. 이 문제를 지금 당장 해결하고 싶어! 라고 생각한다면 isinstanceof 메서드에 대해 검색해보자.

 

  • __str__ : 이 객체를 표현하는 문자열

이 객체를 이용하여 문자열을 생성하려고 할 때 호출되는 메서드이다. str()을 이용하여 직접 문자열을 생성하거나, print()등으로 출력하는 경우에 볼 수 있다.

student 클래스에서 이 메서드를 구현해보자. 대충 이 학생에 대한 정보를 담는 형식으로 리턴하도록 하자.

class student(person):
...
    def __str__(self):
        return (self.name + ', ' + str(self.age) + '살. ' + self.university + '대학교 ' + str(self.grade) +
                '학년 ' + self.major + '전공')

이 메서드를 구현하기 전에는 이 코드에 대해서 다음과 비슷한 결과가 나왔을 것이다.

s = student('mike', 23,'서울',2,'컴퓨터')
print(s)
<__main__.student object at 0x0171A598>

하지만 __str__ 메서드를 구현하고 나서는 다음 결과가 나올 것이다.

mike, 23살. 서울대학교 2학년 컴퓨터전공

 

이 외에도 사칙연산에 대응하는 __add__, __sub__, __mul__이나 크기를 비교하는 __ge__같은 메서드들도 있지만, student 클래스는 사칙연산과는 딱히 어울리지 않는 형식의 데이터이다. 두 학생을 더하거나 곱했을 때의 결과가 무엇인지 머릿속에 그려지지 않기 때문이다. 따라서 자신이 표현하고자 하는 클래스가 정말로 필요로 하는 형식의 특수 메서드만 제대로 구현을 하자.

만약 이러한 특수 메서드가 무엇이 더 있는지 확인하고 싶다면, 이 링크를 참조하라.

 

Categories:

Updated:

Comments