🔑 클래스(class)란 똑같은 무엇인가를 계속해서 만들어 낼 수 있는 설계 도면이고, 객체(object)란 클래스로 만든 피조물을 뜻한다.
클래스로 만든 객체에는 중요한 특징이 있다. 바로 객체마다 고유한 성격을 가진다는 것이다. 과자 틀로 만든 과자에 구멍을 뚫거나 조금 베어 먹더라도 다른 과자에는 아무 영향이 없는 것과 마찬가지로 동일한 클래스로 만든 객체들은 서로 전혀 영향을 주지 않는다.
🤍 객체는 클래스로 만들며 1개의 클래스는 무수히 많은 객체를 만들어 낼 수 있다.
예시)
class Cookie :
pass
a = Cookie()
b = Cookie()
클래스로 만든 객체를 인스턴스라고도 한다. 그렇다면 객체와 인스턴스의 차이는 무엇일까? 이렇게 생각해 보자. a = Cookie() 이렇게 만든 a는 객체이다. 그리고 a 객체는 Cookie의 인스턴스이다. 즉 인스턴스라는 말은 특정 객체(a)가 어떤 클래스(Cookie)의 객체인지를 관계 위주로 설명할 때 사용한다. "a는 인스턴스"보다는 "a는 객체"라는 표현이 어울리며 "a는 Cookie의 객체"보다는 "a는 Cookie의 인스턴스"라는 표현이 훨씬 잘 어울린다.
🔑 클래스 기본구조는 다음과 같다.
class 클래스이름 :
메서드 또는 수행할 내용
객체에 숫자를 지정할 수 있게 만들기 위해서는 클래스 안에 구현된 함수 즉 메서드가 필요하다.
예시)
class FourCal:
def setdata(self, first, second) : # 메서드의 매개변수
self.first = first # 메서드의 수행문
self.second = second # 메서드의 수행문
a = FourCal()
a.setdata(4, 2)
그런데 뭔가 좀 이상하지 않은가? setdata 메서드에는 self, first, second 총 3개의 매개변수가 필요한데 실제로는 a.setdata(4, 2)처럼 2개 값만 전달했다. 왜 그럴까? 그 이유는 a.setdata(4, 2)처럼 호출하면 setdata 메서드의 첫 번째 매개변수 self에는 setdata메서드를 호출한 객체 a가 자동으로 전달되기 때문이다. 다음 그림을 보면 객체를 호출할 때 입력한 값이 메서드에 어떻게 전달되는지 쉽게 이해할 수 있을 것이다.
파이썬 메서드의 첫 번째 매개변수 이름은 관례적으로 self를 사용한다. 객체를 호출할 때 호출한 객체 자신이 전달되기 때문에 self를 사용한 것이다. 물론 self말고 다른 이름을 사용해도 상관없다.
다음 예시 메서드의 수행문에 대해 알아보자.
def setdata(self, first, second) : # 메서드의 매개변수
self.first = first # 메서드의 수행문
self.second = second # 메서드의 수행문
a.setdata(4, 2)처럼 호출하면 setdata 메서드의 매개변수 first, second에는 각각 값 4와 2가 전달되어 setdata 메서드의 수행문은 다음과 같이 해석된다.
self.first = 4
self.second = 2
self는 전달된 객체 a이므로 다시 다음과 같이 해석된다.
a.first = 4
a.second = 2
a.first = 4 문장이 수행되면 a 객체에 객체변수 first가 생성되고 값 4가 저장된다. 마찬가지로 a.second = 2 문장이 수행되면 a 객체에 객체변수 second가 생성되고 값 2가 저장된다.
다음과 같이 확인해 보자.
class FourCal:
def setdata(self, first, second) : # 메서드의 매개변수
self.first = first # 메서드의 수행문
self.second = second # 메서드의 수행문
a = FourCal()
a.setdata(4, 2)
print(a.first)
print(a.second)
a 객체에 객체변수 first와 second가 생성되었음을 확인할 수 있다.
🔑 생성자(Constructor)란 객체가 생성될 때 자동으로 호출되는 메서드를 의미한다.
파이썬 메서드 이름으로 __init__를 사용하면 이 메서드는 생성자가 된다. 다음과 같이 FourCal 클래스에 생성자를 추가해 보자.
※ __init__ 메서드의 init 앞뒤로 붙은 __는 언더스코어(__) 두 개를 붙여 쓴 것이다.
class FourCal:
def __init__(self, first, second):
self.first = first
self.second = second
def setdata(self, first, second):
self.first = first
self.second = second
def add(self):
result = self.first + self.second
return result
def div(self):
result = self.first / self.second
return result
새롭게 추가된 생성자 init 메서드만 따로 떼어 내서 살펴보자.
def __init__(self, first, second):
self.first = first
self.second = second
__init__ 메서드는 setdata 메서드와 이름만 다르고 모든 게 동일하다. 단 메서드 이름을 __init__으로 했기 때문에 생성자로 인식되어 객체가 생성되는 시점에 자동으로 호출되는 차이가 있다.
상속(Inheritance)이란 "물려받다"라는 뜻으로, "재산을 상속받다"라고 할 때의 상속과 같은 의미이다. 클래스에도 이 개념을 적용할 수 있다. 어떤 클래스를 만들 때 다른 클래스의 기능을 물려받을 수 있게 만드는 것이다.
이번에는 상속 개념을 사용하여 우리가 위에서 만든 FourCal 클래스에 ab (a의 b제곱)을 구할 수 있는 기능을 추가해 보자.
앞에서 FourCal 클래스는 이미 만들어 놓았으므로 FourCal 클래스를 상속하는 MoreFourCal 클래스는 다음과 같이 간단하게 만들 수 있다.
class MoreFourCal(FourCal):
pass
클래스를 상속하기 위해서는 다음처럼 클래스 이름 뒤 괄호 안에 상속할 클래스 이름을 넣어주면 된다.
📣 class 클래스 이름(상속할 클래스 이름)
MoreFourCal 클래스는 FourCal 클래스를 상속했으므로 FourCal 클래스의 모든 기능을 사용할 수 있어야 한다.
class FourCal:
def __init__(self, first, second):
self.first = first
self.second = second
def setdata(self, first, second):
self.first = first
self.second = second
def add(self):
result = self.first + self.second
return result
def div(self):
result = self.first / self.second
return result
class MoreFourCal(FourCal):
pass
a = MoreFourCal(4, 2)
print(a.add())
보통 상속은 기존 클래스를 변경하지 않고 기능을 추가하거나 기존 기능을 변경하려고 할 때 사용한다.
"클래스에 기능을 추가하고 싶으면 기존 클래스를 수정하면 되는데 왜 굳이 상속을 받아서 처리해야 하지?" 라는 의문이 들 수도 있다. 하지만 기존 클래스가 라이브러리 형태로 제공되거나 수정이 허용되지 않는 상황이라면 상속을 사용해야 한다.
이제 원래 목적인 a의 b제곱(ab)을 계산하는 MoreFourCal 클래스를 만들어 보자.
class MoreFourCal(FourCal):
def pow(self):
result = self.first ** self.second
return result
a = MoreFourCal(4, 2)
print(a.pow())
MoreFourCal 클래스로 만든 a 객체에 값 4와 2를 설정한 후 pow 메서드를 호출하면 4의 2제곱 (42)인 16을 돌려주는 것을 확인할 수 있다.
🔑 부모 클래스(상속한 클래스)에 있는 메서드를 동일한 이름으로 다시 만드는 것을 메서드 오버라이딩(Overriding)이라고 한다. 메서드를 오버라이딩하면 부모클래스의 메서드 대신 오버라이딩한 메서드가 호출된다.
예시로 위에서 만들어둔 FourCal 클래스의 div 메서드를 오버라이딩 해보자.
class SafeFourCal(FourCal) :
def div(self):
if self.second == 0 :
return 0
else :
return self.first / self.second
🔑 객체변수는 다른 객체들에 영향받지 않고 독립적으로 그 값을 유지한다는 점을 이미 알아보았다. 이번에는 객체변수와는 성격이 다른 클래스 변수에 대해 알아보자.
다음 클래스를 작성해 보자.
class Family:
lastname = "김"
Family 클래스에 선언한 lastname이 바로 클래스 변수이다. 클래스 변수는 클래스 안에 함수를 선언하는 것과 마찬가지로 클래스 안에 변수를 선언하여 생성한다.
이제 Family 클래스를 다음과 같이 사용해 보자.
class Family:
lastname = "김"
print(Family.lastname)
클래스 변수는 위 예와 같이 클래스이름.클래스 변수로 사용할 수 있다.
또는 다음과 같이 Family 클래스로 만든 객체를 통해서도 클래스 변수를 사용할 수 있다.
class Family:
lastname = "김"
a = Family()
b = Family()
print(a.lastname)
print(b.lastname)
만약 Family 클래스의 lastname을 다음과 같이 "박"이라는 문자열로 바꾸면 어떻게 될까?
class Family:
lastname = "김"
a = Family()
b = Family()
Family.lastname = "박"
print(a.lastname)
print(b.lastname)
클래스 변수 값을 변경했더니 클래스로 만든 객체의 lastname 값도 모두 변경된다는 것을 확인할 수 있다. 즉 클래스 변수는 클래스로 만든 모든 객체에 공유된다는 특징이 있다.
클래스 변수를 가장 늦게 설명하는 이유는 클래스에서 클래스 변수보다는 객체변수가 훨씬 중요하기 때문이다. 실무 프로그래밍을 할 때도 클래스 변수보다는 객체변수를 사용하는 비율이 훨씬 높다.