[Python] Python Class
Class
우리가 계산기를 사용하고 있다고 가정해보자.
1 + 2 를 한 결괏값은 3이 되고 그 결과값에 또 + 연산을 하여 5를 입력하게 되면 새로운 결과값 8이 나오게 된다. 이처럼 계산기는 이전에 계산한 결과값을 메모리 어딘가에 저장하고 있어야 한다.
result = 0
def add(num) :
global result
result += num
return result
print(add(3))
간단하게 덧셈 연산을 해주는 계산기가 있다고 치자.
초깃값 0 에서 3을 더해주니 결과는 3이 출력될 것이다.
그런데 만약 한 프로그램에서 이렇게 덧셈을 해주는 계산기가 한 대가 아니라 1000대가 필요하다고 가정해보자.
result1 = 0
result2 = 0
#
#생략
#
result1000 = 0
def add1(num) :
global result1
result1 += num
return result1
def add2(num) :
global result2
result2 += num
return result2
#
#생략
#
def add1000(num) :
global result1000
result1000 += num
return result1000
각 계산기에서는 전에 계산된 결과값을 유지하면서 새로운 덧셈을 실행해야하기 때문에 이렇게 1000대의 계산기를 만들어주어야 할 것이다.
거기다가 계산기는 덧셈의 기능만 있는 것이 아니기 때문에 어떤 계산기에서는 뺄셈을, 어떤 계산기에서는 나눗셈을 해주어야하는 상황도 있을 것이다.
그때마다 이렇게 새롭게 함수를 정의해주는 데에는 한계가 있을 것이다.
꼭 계산기만이 예시는 아니겠지만 우리가 클래스를 사용해야하는 것은 이러한 이유 때문이다.
class Calculator :
def __init__(self) :
self.result = 0
def add(self,num) :
self.result += num
return self.result
cal1 = Calculator()
cal2 = Calculator()
print(cal1.add(3))
print(cal2.add(7))
이런 식으로 덧셈 기능을 기본적으로 가지고 있는 함수를 포함한 클래스를 정의하게 되면 별개의 계산기 cal1, cal2가 각각 새로운 함수를 정의하지 않아도 덧셈 기능을 이용할 수 있으며, 각각의 계산기는 서로 결과값에 영향을 받지 않을 수 있게 된다.
클래스와 객체
쿠키를 만들기 위해서는 쿠키의 모양을 잡아 줄 쿠키 틀이 필요할 것이고 그 쿠키 틀을 사용하여 쿠키를 만들어낼 수 있을 것이다.
여기에서 클래스는 쿠키 틀이 된다.
- 클래스 = 쿠키 틀
- 클래스란 똑같은 무엇인가를 계속해서 만들어 낼 수 있는 설계 도면이고 객체(object)란 클래스로 만든 피조물(쿠키 틀을 사용해 만든 과자)
- 클래스로부터 만들어지는 객체를 인스턴스라고 한다.
- 1개의 클래스는 무수히 많은 객체를 만들어 낼 수 있다.
- 클래스로 만든 여러 개의 객체들은 서로 영향을 주지 않는다.
- 1개의 쿠키틀(클래스)로 만든 쿠키(객체)들의 모양이 같더라도 각 쿠키(객체)들은 모두 다른 쿠키들이다. (1개의 쿠키가 망가지거나 베어먹어도 다른 쿠키에는 영향을 주지 않는다.)
class Cookie() :
pass
a = Cookie()
b = Cookie()
여기에서 Cookie()의 결과값을 돌려받은 a와 b가 바로 객체이다.
- a, b는 객체
- a 객체는 Cookie 클래스의 인스턴스이다.
- 즉 인스턴스란, 특정 객체(a)가 어떤 클래스(Cookie)의 객체인지를 관계 위주로 설명할 때 사용한다.
- ‘a는 인스턴스’라는 말보다 ‘a는 객체’라는 표현이 맞고
- ‘a는 Cookie 클래스의 객체’ 보다는 ‘a는 Cookie 클래스의 인스턴스’라는 표현이 맞다.
그렇다면 사칙연산이 가능한 사칙연산 클래스를 만들어보자
class FourCal :
def setdata(self, first, second) :
self.first = first
self.second = second
a = FourCal()
a.setdata(4,2)
우선 사칙연산을 할 때 사용할 숫자 2개를 받을 setdata 메서드를 만들고 FourCal 클래스의 기능을 수행할 수 있는 객체 a를 만들었다. setdata 메서드는 매개변수로 self, first, second 3개의 입력값을 받는다. 그런데 실제로는 a.setdata(4,2) 와 같이 값을 2개만 전달했다.
그 이유는 a.setdata(4,2)처럼 호출을 하게 되면 setdata 메서드의 첫 번째 매개변수 self에는 setdata 메서드를 호출한 객체 a가 자동으로 전달되기 때문이다.
그렇기에 setdata메서드는 매개변수로 설정한 self, first, second에 각각 a, 4, 2를 전달받게 되는 것이다.
python 메서드의 첫 번째 매개변수 이름은 관례적으로 self를 사용한다. 객체를 호출할때 호출한 객체 자신이 전달되기 때문에 self를 사용한 것이다.
위와 같이 객체를 통해 클래스의 메서드를 호출할 때는 객체.메서드()와 같이 ‘.’의 도트 연산자를 사용한다. 또한 객체가 아닌 클래스를 통해 메서드를 호출하는 것도 가능하다.
a = FourCal()
FourCal.setdata(a,4,2)
위 코드와 같이 클래스.메서드() 형태로 호출할 수도 있는데 이러한 경우에는 객체 a를 첫 번째 매개변수 self에 꼭 전달해 주어야 한다.
a.setdata(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도 마찬가지.
print(a.first)
print(a.second)
각각의 결과값이 4,2인 것을 확인할 수 있다. 객체 a에 객체변수 first와 second가 생성되었다는 의미이다.
a = FourCal()
b = FourCal()
a.setdata(4,2)
print(a.first) # result = 4
b.setdata(3,7)
print(b.first) # result = 3
객체 b의 객체변수 first에 값 3이 입력되었다면 다시 객체 a의 객체변수 first를 호출한다면 3이 출력될까?
결과는 기존의 객체 a에 생성된 객체변수 first(숫자 4)가 그대로 출력된다.
즉, 같은 클래스로 만든 a,b객체가 있을 때, a객체의 first 객체변수 값은 b객체의 first 객체변수 값에 영향을 받지 않는다.
이제는 사칙연산이 가능한 FourCal 클래스를 정의해보자.
class FourCal():
def setdata(self, first, second):
self.first = first
self.second = second
def add(self) :
result = self.first + self.second
return result
def mul(self) :
result = self.fist * self.second
return result
def sub(self) :
result = self.first - self.second
return result
def div(self) :
result = self.first / self.second
return result
a = FourCal()
a.setdata(4,2)
print(a.add())
결과가 출력되는 과정을 살펴보자면,
add메서드의 매개변수는 self이고 반환 값은 result이다. a.setdat(4,2)를 통해 self에는 객체 a가 전달되기 때문에 add메서드에서 result = a.first + a.second가 될 것이고 a.first는 4, a.second는 2가 전달되어 결론적으로 result = 4 + 2가 될 것이다. (결과는 6)
생성자(Constructor)
a = FourCal()
print(a.add())
이렇게 setdata 메서드를 거치지 않고 add메서드를 호출하게 되면 오류가 발생할 것이다. 왜냐하면 setdata메서드를 수행해야 객체 a의 객체변수 first, second가 생성되기 때문이다.
이렇게 객체에 초깃값을 설정해야 할 필요가 있을 때는 생성자를 구현하는 방법을 사용하는 것이 좋다.
생성자(Constructor)란 객체가 생성될 때 자동으로 호출되는 메서드를 의미한다.
# python에서는 메서드 이름을 __init__으로 설정하면 이 메서드는 '생성자'임을 의미한다.
class FourCal :
def __init__(self, first, second) :
self.first = first
self.second = second
def add(self) :
result = self.first + self.second
return result
def mul(self) :
result = self.fist * self.second
return result
def sub(self) :
result = self.first - self.second
return result
def div(self) :
result = self.first / self.second
return result
a = FourCal(4,2)
이렇게 메서드를 설정해주면 생성자로 인식되어 객체가 생성되는 시점에 자동으로 호출된다.
따라서 객체를 생성하는 동시에 first와 second에 해당하는 값을 전달하여 객체를 생성하면 된다.
a = FourCal(4,2)
print(a.add()) #result = 6
print(a.mul()) #result = 8
print(a.sub()) #result = 2
print(a.div()) #result = 2.0
여기까지 생성자를 이용하여 메서드를 만들었고, 만든 메서드들을 모아 하나의 클래스로 정의해보았다.
클래스 상속
클래스 상속이란, 어떤 클래스를 만들 때 다른 클래스의 기능을 물려받을 수 있게 만드는 것이다.
클래스를 상속하기 위해서는
class 상속받을 클래스명(상속해줄 클래스명)
의 형태로 만들면 된다.
# 기존의 FourCal 클래스를 상속받아 사칙연산 기능에서 제곱을 해주는 기능을 추가하여 새로운 클래스 MoreFourCal을 만들기
class MoreFourCal(FourCal):
def pow(self) :
result = self.first ** self.second
return result
a = MoreFourCal(4,2)
print(a.add()) #result = 6
print(a.mul()) #result = 8
print(a.sub()) #result = 2
print(a.div()) #result = 2.0
print(a.pow()) #result = 16
MoreFourCal 클래스는 기존의 사칙연산 기능을 가진 FourCal 클래스를 상속받았으므로 사칙연산 기능은 물론, 추가한 제곱기능까지 가능하다.
메서드 오버라이딩
a = FourCal(4,0)
print(a.div())
4를 0으로 나누려 했기에 ZeroDivisionError가 발생한다. 이러한 경우, 에러가 아닌 0을 돌려주는 메서드를 만들어보자.
class SafeFourCal(FourCal) : #상속
def div(self) :
if self.second == 0:
return 0
else :
return self.first / self.second
a = SafeFourCal(4, 0)
print(a.div())
SafeFourCal 클래스는 FourCal 클래스에 이쓴 div 메서드와 동일한 이름을 가지는 메서드를 다시 작성하였다.
이렇게 부모 클래스(상속해줄 클래스)에 있는 메서드를 동일한 이름으로 다시 만드는 것을 메서드 오버라이딩이라고 한다.
결과는 0인 것을 확인할 수 있다.
클래스 변수
위에서 객체변수는 다른 객체들에 영향을 받지 않고 독립적으로 값을 유지한다는 것을 알아보았다. 그렇다면 클래스 변수는 어떨까?
class Family :
lastname = "김"
a = Family()
b = Family()
print(a.lastname) # result = "김"
print(b.lastname) # result = "김"
Family 클래스에 선언한 lastname이 클래스 변수이다.
클래스 변수는 말 그대로, 클래스 안에 선언한 변수이다.
그렇다면 만약 lastname의 값을 “김”이 아닌 “박”으로 바꾼다면 어떻게 될까?
Family.lastname = "박" #클래스 변수 lastname의 값을 "박"으로 다시 선언
print(a.lastname)
print(b.lastname)
결과는 둘 다 “박”이 출력된다.
즉, 클래스 변수는 클래스로 만든 모든 객체에 공유된다는 특징이 있다.