본문 바로가기

Programing/Python

[Python 입문] 13. 파이썬 클래스 03

목차

  • HousePark and HouseKim 클래스 만들기 (__init__ 메소드, 클래스 상속, 연산자 오버로딩)

6. HousePark and HouseKim 클래스 만들기

  앞서 만들어본 사칙연산 클래스보다 조금 더 복잡한 클래스를 만들어 보자.

 먼저, 아래의 내용 까지만 토대로 만들어 보고자 한다.

# 1. 클래스 이름은 HousePark으로 하고, pey라는 인스턴스를 만든다.
pey = HousePark()

# 2. pey.lastname을 출력하면 '박'이라는 성을 출력한다.
print(pey.lastname)
박

# 3. 이름을 설정하고 pey.fullname이 성을 포함한 이름 전체의 값을 출력한다.
pey.setname("응용")
print(pey.fullname)
박응용

# 4. 여행가고싶은 장소를 입력하면 아래와 같이 출력하는 travel 함수를 만든다.
pey.travel("부산")
박응용, 부산여행을 가다.

 

6.1. 클래스 기능 만들기

 먼저, 아래와 같이 클래스 이름을 설정하고, pey.lastname을 수행하면 '박'을 출력하도록 만들어 보자.

class HousePark:
    lastname = "박"   <---- 클래스 변수

pey = HousePark()
print(pey.lastname)
박

 위의 소스코드의 결과를 확인하면 인스턴스  pey가 lastname '박'으로 설정되는 것을 확인할 수 있다. (클래스 변수는 '클래스명.변수명(HousePark.lastname)으로도 사용할 수 있다.')

 

 위의 소스코드에 이름을 설정하고 print(pey.fullname)을 수행하면 이름을 포함한 결과값이 출력되도록 만들어 보자.

class HousePark:
    lastname = "박"
    def setname(self, name):
        self.fullname = self.lastname + name

pey = HousePark()
pey.setname("응용")
print((pey.fullname))   <---- lastname("박") + name("응용")
박응용

 위 소스코드를 보면 먼저, 이름을 포함한 결과값을 출력하기 위해 setname이라는 메소드를 만들었고, 이 메소드는 다음과 같은 과정을 통해 '박응용'이라는 결과값을 출력한다.

# 성을 포함한 이름을 만드는 문장
self.fullname = self.lastname + name

# 1. setname 메소드의 두 번째 입력값은 "응용"
self.fullname = self.lastname + "응용"

# 2. pey는 setname 함수의 첫 번째 입력으로 들어오는 인스턴스 pey
pey.fullname = pey.lastname + "응용"

# 3. pey.fullname은 클래스 변수로 항상 "박"
pey.fullname = "박" + "응용"

# 4. pey.fullname 출력
print(pey.fullname)
박응용

     1. self.fullname = self.lastname + name 중 name은 앞선 pey.setname("응용")의 적용으로 "응용"으로 바뀐다고 생각하면 된다.

     2. pey = HousePark()로 인해 self는 setname 함수의 첫 번째 입력으로 들어오는 pey라는 인스턴스가 된다.

     3. pey.lastname은 클래스 변수로 항상 "박"이라는 값을 갖는다.

     4. 따라서 pey.fullname을 출력하면 박응용이 결과로 나온다.

 

 이제 입력받은 장소로 박응용이 여행을 간다고 출력해 주는 travel 메소드를 구현하고 사용해 보자.

class HousePark:
    lastname = "박"
    def setname(self, name):
        self.fullname = self.lastname + name
    def travel(self,where):
        print("{0}, {1}여행을 가다." .format(self.fullname, where))

pey = HousePark()
pey.setname("응용")
print(pey.travel("부산"))
박응용, 부산여행을 가다.

 위에서 추가한 travel 메소드는 입력값으로 인스턴스(self)와 장소(where)를 받는다. 그리고 이를 문자열에 삽입하고 출력한다. 여기까지 완료된 클래스를 실행하면 위 소스코드의 마지막과 같이 출력되는 것을 확인할 수 있다. 여기서 travel 함수의 입력 변수인 self는 pey가 되고 where은 '부산'이 되는 것과 같다. 

 하지만 지금까지 만들어본 함수를 아래와 같이 실행하면 오류가 발생한다.

# 실행
pey = HousePark()
print(pey.travel("부산"))

# Error
Traceback (most recent call last):
  File "/Users/HenryKim/Documents/python_study_new/jump_to_python/05/05_01_Class.py", line 76, in <module>
    print(pey.travel("부산"))
  File "/Users/HenryKim/Documents/python_study_new/jump_to_python/05/05_01_Class.py", line 73, in travel
    print("{0}, {1}여행을 가다." .format(self.fullname, where))
AttributeError: 'HousePark' object has no attribute 'fullname'

 이렇게 오류가 발생하는 이유는 travel 함수가 self.fullname이라는 변수를 필요하기 때문이다. 여기서 이름을 설정하는 self.fullname 변수는 setname 함수에 의해 생성되는데 위 실행 방법에서는 setname 함수가 실행되지 않았기 때문에 오류가 발생하는 것이다.

 

6.2. __init__ 메소드로 초깃값 설정하기

 클래스를 설계할 때 위와 같이 오류가 발생할 수 있는 상황을 방지하기 위해는  pey라는 객체를 만드는 순간 setname 메소드가 동작하게 하는 __init__ 메소드를 사용하면 된다.

 위에서 만들었던 HousePark 클래스를 아래와 같이 수정해 보자.

class HousePark:
    lastname = "박"
    def __init__(self, name):
        self.fullname = self.lastname + name
    def travel(self,where):
        print("{0}, {1}여행을 가다." .format(self.fullname, where))

 위의 소스코드를 보면 setname 메소드 이름이 __init__ 으로 바꾸기만 했다. 그리고 아래와 같이 입력을 하고 실행하면 오류가 발생한다.

pey = HousePark()

# Error
TypeError: __init__() missing 1 required positional argument: 'name'

 __init__ 메소드는 2개의 입력값(self, name)이 필요한데 1개의 입력값만 받았기 때문에 오류가 발생한다. 여기서 __init__ 메소드가 받은 입력값 1개는 입력 인수 self를 통해 받은 객체 pey다.

 이런 메소드 __init__에 입력값을 2개 주는 방법은 클래스(Class) 01 포스팅에서 설명한 방법으로 아래와 같이 입력해 보자.

pey = HousePark("응용")
print(pey.travel("부산"))

박응용, 부산여행을 가다.

 이것은 setname 메소드를 사용하는 방법인 pey.setname("응용")과 비슷하다. 이렇게 __init__ 메소드를 이용해서 인스턴스를 만드는 동시에 초깃값을 줄 수 있기 때문에 엄청 편리하다. 이런 __init__ 메소드는 생성자(Constructor)라고도 한다.

 

6.3. 클래스 상속

 상속(Inheritance)는 '물려받다.'라는 뜻으로 클래스에도 이 개념을 적용할 수 있다. 쉽게 말하면 어떤 클래스를 만들 때 다른 클래스의 기능을 물려받을 수 있게 만드는 것이다. 예를 들어 아래와 같이 이전에 만들었던 HousePark 클래스의 기능을 상속받는 HouseKim이라는 클래스를 만들어 보고 실행해 보자.

class HouseKim(HousePark):   <---- class 상속받는 클래스 이름(상속할 클래스 이름)
    lastname = "김"
    
juliet = HouseKim("줄리엣")
print(juliet.travel("독도"))
김줄리엣, 독도여행을 가다.

 위와 같이 HouseKim 클래스는 HousePark 클래스의 모든 기능을 그대로 상속받았기 때문에 따로 __init__ 와 travel 메소드를 구현하지 않더라도 HousePark 클래스와 완전 동일하게 동작한다.

juliet = HouseKim("줄리엣")
print(juliet.travel("독도", 3))   <---- 여행 갈 장소와 시간 모두 출력
김줄리엣, 독도여행 3일 가네.

 만약 HouseKim 클래스가 상속받은 HousePark 클래스의 travel 함수를 HouseKim 클래스에서는 위와 같이 다르게 동작도록 하기 위해서는 어떻게 해야 될까?

class HouseKim(HousePark):
    lastname = "김"
    def travel(self, where, day):
        print("{0}, {1}여행 {2}일 가네." .format(self.fullname, where, day))
        
juliet = HouseKim("줄리엣")
print(juliet.travel("독도", 3))
김줄리엣, 독도여행 3일 가네.

 travel 함수를 다르게 설정하고 싶다면 위와 같이 동일한 이름의 travel 함수를 HouseKim 클래스 안에서 다시 구현하면 된다. 이를 메소드 오버라이딩(Overriding)이라고 한다. 

 

6.4. 연산자 오버로딩

 연산자 오버로딩(Overloading)이란 연산자(더하기, 빼기, 나누기, 곱하기)를 객체끼리 할 수 있게 하는 기법으로 아래와 같이 동작하도록 만들 수 있다.

pey = HousePark("응용")
juliet = HouseKim("줄리엣")
pey + juliet
박응용, 김줄리엣 결혼했네

 위와 같이 동작할 수 있도록 하기 위해서는 이전에 만들었던 클래스들에 몇 가지 사항만 아래와 같이 추가하고 실행해보자.

class HousePark:
    lastname = "박"
    def __init__(self, name):
        self.fullname = self.lastname + name
    def travel(self,where):
        print("{0}, {1}여행을 가다." .format(self.fullname, where))
    def love(self, other):
        print("{0}, {1} 사랑에 빠졌네." .format(self.fullname, other.fullname))
    def __add__(self, other):   <---- 연산자 오버로딩 사용
        print("{0}, {1} 결혼했네." .format(self.fullname, other.fullname))


class HouseKim(HousePark):
    lastname = "김"
    def travel(self, where, day):
        print("{0}, {1}여행 {2}일 가네." .format(self.fullname, where, day))


pey = HousePark("응용")   <---- pey 객체 생성
juliet = HouseKim("줄리엣")   <---- juliet 객체 생성
print(pey.love(juliet))   <---- love 메소드 호출
print(pey + juliet)

박응용, 김줄리엣 사랑에 빠졌네.
박응용, 김줄리엣 결혼했네.

 먼저 pey와 juliet이라는 객체를 생성한다. 그다음 love 메소드를 호출하는데 love 메소드를 보면 입력 인수로 2개의 객체를 받는 것을 확인할 수 있다. 따라서 self에 pey가 들어가고, other에 juliet이 들어간다.

 마지막으로 pey + juliet을 통해 객체끼리 더하려고 하는데 이렇게 더하기 연산자를 객체에 사용하게 되면 HousePark 클래스의 __add__ 함수가 호출된다. __add__ 메소드 역시 self에는 pey가 들어가고, other에는 juliet이 들어가기 때문에 '박응용, 김줄리엣 결혼랬네.'라는 문자열이 출력되는 것이다.

박응용 부산여행을 가다.
김줄리엣 부산여행 3일 가네.
박응용, 김줄리엣 사랑에 빠졌네.
박응용, 김줄리엣 결혼했네.
박응용, 김줄리엣 싸우네.
박응용, 김줄리엣 이혼했네.

 지금까지 배워본 내용을 토대로 위와 같은 결과값이 나오는 클래스를 작성해 보자.

class HousePark:
    lastname = "박"
    def __init__(self, name):
        self.fullname = self.lastname + name
    def travel(self,where):
        print("{0}, {1}여행을 가다." .format(self.fullname, where))
    def love(self, other):
        print("{0}, {1} 사랑에 빠졌네." .format(self.fullname, other.fullname))
    def __add__(self, other):
        print("{0}, {1} 결혼했네." .format(self.fullname, other.fullname))
    def fight(self, other):
        print("{0}, {1} 싸우네." .format(self.fullname, other.fullname))
    def __sub__(self, other):
        print("{0}, {1} 이혼했네." .format(self.fullname, other.fullname))


class HouseKim(HousePark):
    lastname = "김"
    def travel(self, where, day):
        print("{0}, {1}여행 {2}일 가네." .format(self.fullname, where, day))


# 프로그램 실행
pey = HousePark("응용")
juliet = HouseKim("줄리엣")
print(pey.travel("부산"))
print(juliet.travel("부산", 3))
print(pey.love(juliet))
print(pey + juliet)
print(pey.fight(juliet))
print(pey - juliet)

 먼저 기존에 만들었던 클래스에 '박응용, 김줄리엣 싸우네'라는 문장 출력을 위해 fight 메소드를 추가했다. 그리고 pey - juliet을 수행하기 위해 빼기(-) 연산자가 사용될 때 호출되는 __sub__ 이라는 메소드를 만들었다. 이 역시 연산자 오버로딩을 사용한다. 이렇게 최종적인 클래스를 만들었다.

 참고로 연산자 오버로딩에 더하기(__add__), 빼기(__sub__) 이외에 다른 연산자들의 오버로딩 역시 가능하다. 나누기(__truediv__), 곱하기(__mul__)을 사용하면 된다.

 

 

내용 출처: 책 '점프 투 파이썬'의 내용