[Python] Class 클래스

미남잉·2021년 10월 19일
0

5장 정리

1️⃣ 클래스
2️⃣ 예외 처리
3️⃣ 내장 함수
4️⃣ 라이브러리
5️⃣ 연습문제

저는 해당 온라인 교재📕를 보고 학습하고 정리하였습니다.

1. 클래스는 왜 필요한가?

계산기에 숫자 3을 입력하고 + 기호를 입력한 후 4를 입력하면 결괏값으로 7을 보여 줍니다. 다시 한 번 + 기호를 입력한 후 3을 입력하면 기존 결괏값 7에 3을 더해 10을 보여 줍니다. 즉, 계산기는 이전에 계산한 결괏값을 항상 메모리 어딘가에 저장하고 있어야 합니다.

※ 계산기는 이전에 계산한 결괏값을 기억하고 있어야 한다.

이런 내용을 우리가 앞에서 익힌 함수를 이용해 구현해 보겠습니다. 계산기의 "더하기" 기능을 구현한 파이썬 코드는 다음과 같습니다.

result = 0

def add(num):
	global result
    	result += num
    	return result
        
print(add(3))
print(add(4))

실행값

3
7

※ add 함수는 매개변수 num에 받은 값을 이전에 계산한 결괏값에 더한 후 돌려주는 함수입니다.

그런데 만약 2대의 계산기가 필요한 상황이라면 어떻게 해야할까요?

각 계산기는 각각의 결괏값을 유지해야 하기 때문에 위와 같이 add 함수 하나만으로는 결괏값을 따로 유지할 수 없습니다.

따라서 함수를 각각 따로 2개를 만들어야 합니다.

result1 = 0
result2 = 0

def add1():
	global result1
    	result += num
    	return result
        
def add2():
	global result2
    	result += num
    	return result
        
print(add1(3))
print(add1(4))
print(add2(3))
print(add2(7))

실행값

3
7
3
10

계산기 1과 2의 값이 따로 계산되어 나옵니다. 하지만 연산이 큰 경우 이렇게 계속 만들어 줄 수는 없습니다.

이때 클래스를 사용하면 됩니다.

class Cal:
	def __init__(self):
    		self.result = 0

	def add(self, num):
    		self.result += num
            	return self.result
                
cal1 = Cal()
cal2 = Cal()

print(cal1.add(3))
print(cal1.add(4))
print(cal2.add(3))
print(cal2.add(7))

실행값은 위의 경우와 같습니다.

Calculator 클래스로 만든 별개의 계산기 cal1, cal2(파이썬에서는 이것을 객체라고 부른다)가 각각의 역할을 수행합니다.

그리고 계산기(cal1, cal2)의 결괏값 역시 다른 계산기의 결괏값과 상관없이 독립적인 값을 유지합니다.

클래스를 사용하면 계산기 대수가 늘어나더라도 객체를 생성만 하면 되기 때문에 함수를 사용하는 경우와 달리 매우 간단해집니다.

만약 빼기 기능을 더하려면 Calculator 클래스에 다음과 같은 빼기 기능 함수를 추가해 주면 됩니다.



2. 클래스와 객체

  • 과자 틀: 클래스(class)
  • 과자 틀로 만들어진 과자: 객체(object)

여기서 클래스와 객체를 설명하기 위해 과자 틀과 과자 그림이 이용되었습니다.

클래스란 똑같은 무엇인가를 계속해서 만들어 낼 수 있는 설계 도면이고(과자 틀), 객체(object)란 클래스로 만든 과자를 뜻합니다.

클래스로 만든 객체는 객체마다 고유한 성격을 갖습니다. 과자 틀로 만든 과자에 구멍을 뚫든 베어 먹든 다른 과자에는 아무 영향도 끼치지 않습니다. 이처럼 동일한 클래스로 만든 객체들은 서로 전혀 영향을 주지 않습니다.

class Cookie:
	pass

위 클래스는 pass를 사용하여 만들어졌으므로 아무 기능도 갖고 있지 않습니다. 이렇게 껍질 뿐인 클래스도 객체를 생성할 수 있습니다.

a = Cookie()
b = Cookie()

이때 Cookie()의 결괏값을 돌려받은 a와 b가 바로 객체라고 말할 수 있습니다.

a, b는 객체이다.
a는 Cookie의 인스턴스, b는 Cookie의 인스턴스이다.

라고 말 할 수 있습니다.

인스턴스란 특정 객체가 어떤 클래스의 객체인지 관계 위주로 설명할 때 사용됩니다.



3. 사칙연산 클래스 만들기

사칙연산을 가능하게 하는 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.first * self.second
         return result
     def sub(self):
         result = self.first - self.second
         return result
     def div(self):
         result = self.first / self.second
         return result


1) 클래스를 어떻게 만들지 먼저 구상하기

클래스를 만들기 위해서 객체 중심으로 어떻게 동작하게 만들 것인지 먼저 생각한 후 하나씩 해결해가면 좋다고 합니다.

먼저 FourCal 클래스가 다음과 같이 동작한다고 가정합니다.

a = FourCal()

a라는 객체가 만들어졌습니다.

그 다음 숫자 4, 2를 지정해주겠습니다.

a.setdata(4,2)

이제 a.add()를 수행하면 두 수를 합한 결과 (4+2)가 나옵니다.

>>> print(a.add())
6

그 다음 mul, sub, div 등을 수행하면 잘 계산된 값이 나옵니다.



2) 클래스 구조 만들기

위에 나왔던 a = FourCal() 처럼 객체를 불러올 수 있도록 클래스를 만들어보겠습니다.

class Fourcal:
	pass

지금 상태에서 클래스는 아무 변수나 함수도 포함하지 않지만 원하는 객체 a를 만들 수는 있습니다.

>>> a = Fourcal()
>>> type(a)

<class '__main__.FourCal'>

a가 Fourcal의 객체임을 알 수 있습니다!



3) 객체에 숫자 지정할 수 있게 만들기

이제 객체를 불러오기까지 했으니 객체에 숫자를 지정할 수 있게 만들어야 합니다.

class Fourcal:
	def setdata(self, first, second):
    		self.first = first
            	self.second = second

앞에서 적었던 pass를 삭제하고 setdata란 함수를 만들었습니다. 클래스 안에 구현된 구현된 함수는 다른 말로 메서드(Method)라고 부릅니다.

def 함수명(매개변수):
	수행할 문장
    	...

setdata 메서드를 이해하기 위해 다시 살펴보겠습니다.

def setdata(self, first, second):   # ① 메서드의 매개변수
    self.first = first              # ② 메서드의 수행문
    self.second = second            # ② 메서드의 수행문

① setdata 메서드의 매개변수

setdata 메서드는 매개변수로 self, first, second 3개 입력값을 받습니다. 그런데 일반 함수와는 달리 메서드의 첫 번째 매개변수 self는 특별한 의미를 가집니다.

다음과 같이 a 객체를 만들고 a 객체를 통해 setdata 메서드를 호출해 보겠습니다.

a = FourCal()
a.setdata(4,2)

※ 객체를 통해 클래스의 메서드를 호출하려면 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 말고 다른 값으로 해도 상관없습니다.

② setdata 메서드의 수행문

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.fisrt = 4
a.second = 2

a.first = 4 문장이 수행되면 a 객체에 객체변수 first가 생성되고 값 4가 저장됩니다. 마찬가지로 a.second = 2 문장이 수행되면 객체변수 second가 생성되고 값 2가 저장됩니다.

객체에 생성되는 객체만의 변수를 객체변수라고 부릅니다.

>>> a = Fourcal()
>>> a.setdata(4,2)
>>> print(a.first)
4
>>> print(a.second)
2

a 객체에 객체변수 first, second가 생성되었습니다.

이번엔 a, b 객체를 만들어보겠습니다.

a = Fourcal()
b = Fourcal()

a 객체의 객체변수를 first를 다음과 같이 생성합니다.

>>> a.setdata(4,2)
>>> print(a.first)
4

b 객체의 객체변수 first를 다음과 같이 생성합니다.

>>> b.setdata(3,7)
>>> print(b.first)
3

a 객체의 first 값은 b 객체의 first 값에 영향받지 않고 원래 값을 유지하고 있습니다.

클래스로 만든 객체의 객체변수는 다른 객체의 객체변수에 상관없이 독립적인 값을 유지합니다.

id 함수를 사용하면 객체변수가 독립적인 값을 유지한다는 점을 좀 더 명확하게 증명해 보일 수 있다.

>>> a = Fourcal()
>>> b = Fourcal()
>>> a.setdata(4,2)
>>> b.setdata(3,7)
>>> id(a.first)
1839194944
>>> id(b.first)
1839194928

a 객체의 first 주소 값과 b 객체의 first 주소 값이 서로 다르므로 각각 다른 곳에 그 값이 저장된다는 것을 알 수 있습니다. 객체변수는 그 객체의 고유 값을 저장할 수 있는 공간입니다.

객체 변수는 다른 객체들 영향받지 않고 독립적으로 그 값을 유지합니다.

class Fourcal:
	def setdata(self, first, second):
    		self.first = first
            	self.second = second
...

지금까지 살펴 본 내용은 위의 4줄을 설명한 부분입니다!



4) 더하기 기능 만들기

>>> a = Fourcal()
>>> a.setdata(4,2)
>>> print(a.add())
6

이 연산이 가능하도록 다음과 같이 Fourcal 클래스를 만들어 보겠습니다.

class Fourcal:
	def setdata(self, first, second):
    		self.first = first
            	self.second = second
                
	def add(self):
    		result = self.first + self.second
        	return result
...

add 메서드가 새롭게 추가되었습니다.

>>> a = Fourcal()
>>> a.setdata(4,2)

위와 같이 호출한다면 a객체의 first, second 에는 4, 2가 각각 저장될 것이고 이를

>>> print(a.add())
6

으로 나오게 될 것입니다.

이 과정을 다시 살펴 보겠습니다.

def add(self):
    result = self.first + self.second
    return result

add 메서드의 매개변수는 self이고 반환 값은 result입니다. result를 계산하는 부분은

result = self.first + self.second

이고, a.add()와 같이 a 객체에 add 메서드가 수행되면 add 메서드의 self에는 객체 a가 자동으로 입력되므로 위의 내용은

result = a.first + a.second

와 똑같이 해석됩니다.

위 내용은 a.add() 메서드 호출 전에 a.setdata(4, 2) 가 먼저 호출되어 a.first = 4, a.second = 2 라고 이미 설정되었기 때문에 다시 다음과 같이 해석합니다.

result = 4 + 2

그래서

>>> print(a.add())
6

이 되는 겁니다.



5) 곱하기, 빼기, 나누기 기능 만들기

이제 사칙연산 모두 넣어보겠습니다.

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.first * 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()
b = Fourcal()
a.setdata(4,2)
b.setdata(3,8)

이고

>>> a.add()
6
>>> a.mul
8
>>> a.sub()
2
>>> a.div()
2
>>> b.add()
11
>>> b.mul()
24
>>> b.sub()
-5
>>> b.div()
0.375

여기까지 목표로 한 사칙연산 기능을 가진 클래스입니다.



4. 생성자 (Constructor)

클래스 파트를 한 세번은 다시 본 것 같은데, 이 생성자 파트를 드디어 이해했습니다. 클래스가 어렵다면 조코딩 영상을 보고 광명 찾으세요.😎🌞🌞🌞🔥

먼저 위에서 만들었던 Fourcal 클래스를 사용해보겠습니다.

>>> a = Fourcal
>>> a.add

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in add
AttributeError: 'FourCal' object has no attribute 'first'

오류가 난 이유는 Fourcal class의 인스턴스 a에 setdata 메서드를 수행하지 않고 add 메서드를 수행해서입니다. setdata 메서드를 수행해야 객체 a의 객체변수 first, second가 생성되기 때문입니다.

올바른 호출은

>>> a = Fourcal
>>> a.setdata(4,2)
>>> a.add

가 되겠습니다. 하지만!

이렇게 객체에 초깃값을 설정해야 할 필요가 있을 때는, setdata 메서드를 불러와 설정해야 하는데, 매번 이렇게 구현하는 것보다 생성자를 구현하는 것이 더 안전한 방법입니다.

생성자(Constructor)란 객체가 생성될 때 자동으로 호출되는 메서드를 의미합니다.

파이썬에서는 메서드 이름으로 __init__을 사용하면 이 메서드는 생성자가 됩니다.

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 mul(self):
         result = self.first * self.second
         return result
     def sub(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__ 이기 때문에 생성자로 인식되어 객체가 생성되는 시점에 자동으로 호출된다는 차이점이 있습니다.

이제 다시 클래스를 호출해보겠습니다.

>>> a = Foural()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() missing 2 required positional arguments: 'first' and 'second'

오류명에는 메서드 안에 2개 매개변수가 빠졌다고 알려주고 있습니다. first와 second겠죠.

올바른 사용법은

>>> a = FourCal(4, 2)

입니다. 이렇게 first, second 값을 전달하여 객체를 생성해야 합니다

매개변수
self생성되는 객체
first4
second2

※ __init__ 메서드도 다른 메서드와 마찬가지로 첫 번째 매개변수 self에 생성되는 객체가 자동으로 전달됩니다.

따라서 __init__ 메서드가 호출되면 setdata 메서드를 호출했을 때와 마찬가지로 first와 second라는 객체변수가 생성됩니다.

다음과 같이 객체변수의 값을 확인해보겠습니다.

>>> a = FourCal(4,2)
>>> print(a.first)
4
>>> print(a.second)
2
>>> a.add()
6
>>> a.div()
2.0

이상 없이 잘 동작합니다.



5. 클래스의 상속

상속(Inheritance)이란 "물려받다"라는 뜻으로, "재산을 상속받다"라고 할 때의 상속과 같은 의미입니다.

클래스에도 이 개념을 적용할 수 있습니다. 어떤 클래스를 만들 때 다른 클래스의 기능을 물려받을 수 있게 만들 수 있습니다.

이번에는 상속 개념을 사용하여 우리가 만든 FourCal 클래스에 ab (a의 b제곱)을 구할 수 있는 기능을 추가해보겠습니다.

앞에서 FourCal 클래스는 이미 만들어 놓았으므로 FourCal 클래스를 상속하는 MoreFourCal 클래스는 다음과 같이 간단하게 만들 수 있습니다.

class MoreFourCal(Fourcal):
	pass

클래스를 상속하기 위해서는 다음처럼 클래스 이름 뒤 괄호 안에 상속할 클래스 이름을 넣어주면 됩니다.

class 클래스 이름(상속할 클래스 이름)

MoreFourCal 클래스는 FourCal 클래스를 상속했으므로 FourCal 클래스의 모든 기능을 사용할 수 있습니다.

>>> a = MoreFourCal(4, 2)
>>> a.add()
6
>>> a.mul()
8
>>> a.sub()
2
>>> a.div()
2

상속해야하는 이유는 무엇일까요?

기존에 만들어진 클래스를 변경하지 않기 위함입니다. 기존 기능을 변경하거나 추가할 수 있습니다.

이제 제곱 기능을 넣는 자식 클래스를 만들어보겠습니다.

class MoreFourCal(Fourcal):
     def pow(self):
         result = self.first ** self.second
         return result

pass 문장은 삭제하고 위와 같이 두 수의 거듭제곱을 구할 수 있는 pow 메서드를 추가했습니다.

>>> a = MoreFourCal(4,2)
>>> a.pow()
16

MoreFourCal 클래스로 만든 a 객체에 값 4와 2를 설정한 후 pow 메서드를 호출하면 4의 2제곱 (42)인 16을 돌려주는 것을 확인할 수 있습니다.

상속은 MoreFourCal 클래스처럼 기존 클래스(FourCal)는 그대로 놔둔 채 클래스의 기능을 확장시킬 때 주로 사용합니다.



6. 메서드 오버라이딩

>>> a = FourCal(4, 0)
>>> a.div()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    result = self.first / self.second
ZeroDivisionError: division by zero

FourCal 클래스의 객체 a에 4와 0 값을 설정하고 div 메서드를 호출하면 4를 0으로 나누려고 하기 때문에 위와 같은 ZeroDivisionError 오류가 발생합니다.

하지만 0으로 나눌 때 오류가 아닌 0을 돌려주도록 만들고 싶다면 어떻게 해야 할까요?

다음과 같이 FourCal 클래스를 상속하는 SafeFourCal 클래스를 만들어 보겠습니다.

class SafeFourCal(FourCal):
     def div(self):
         if self.second == 0:  # 나누는 값이 0인 경우 0을 리턴하도록 수정
             return 0
         else:
             return self.first / self.second

SafeFourCal 클래스는 FourCal 클래스에 있는 div 메서드를 동일한 이름으로 다시 작성하였습니다. 이렇게 부모 클래스(상속한 클래스)에 있는 메서드를 동일한 이름으로 다시 만드는 것을 메서드 오버라이딩(Overriding, 덮어쓰기)이라고 합니다. 이렇게 메서드를 오버라이딩하면 부모클래스의 메서드 대신 오버라이딩한 메서드가 호출됩니다.

SafeFourCal 클래스에 오버라이딩한 div 메서드는 나누는 값이 0인 경우에는 0을 돌려주도록 수정했습니다. 이제 다시 위에서 수행한 예제를 FourCal 클래스 대신 SafeFourCal 클래스를 사용하여 수행해 보겠습니다.

>>> a = SafeFourCal(4, 0)
>>> a.div()
0

FourCal 클래스와는 달리 ZeroDivisionError가 발생하지 않고 의도한 대로 0을 돌려줍니다.



7. 클래스 변수

객체변수는 다른 객체들에 영향받지 않고 독립적으로 그 값을 유지합니다.

이번에는 객체변수와는 성격이 다른 클래스 변수에 대해 알아보겠습니다.

다음 클래스를 작성하겠습니다.

class Family:
	lastname = '김'

Family 클래스에 선언한 lastname이 바로 클래스 변수입니다. 클래스 변수는 클래스 안에 함수를 선언하는 것과 마찬가지로 클래스 안에 변수를 선언하여 생성합니다.

이제 Family 클래스를 다음과 같이 사용하겠습니다.

>>> print(Family.lastname)
김

클래스 변수를 사용할 때는

클래스이름.클래스 변수

해당 예와 같이 사용해야 합니다.

또는 다음과 같이 Family 클래스로 만든 객체를 통해서 클래스 변수를 사용 가능합니다.

>>> a = Family()
>>> b = family()
>>> print(a.lastname)
김
>>> print(b.lastname)
김

만약에 클래스 변수를 변경하기 위해

>>> Family.lastname = "박"

위처럼 설정했다면

>>> print(a.lastname)
박
>>> print(b.lastname)
박

클래스 변수 값을 변경했더니 클래스로 만든 객체의 lastname 값이 모두 변경되었습니다. 즉, 클래스 변수는 클래스로 만든 모든 객체에 공유된다는 특징이 있습니다.

id 함수를 사용하면 클래스 변수가 공유된다는 사실을 증명할 수 있습니다.

>>> id(Family.lastname)
4480159136
>>> id(a.lastname)
4480159136
>>> id(b.lastname)
4480159136

id 값이 모두 같으므로 Family.lastname, a.lastname, b.lastname은 모두 같은 메모리를 가리킵니다.

클래스 변수를 가장 늦게 설명하는 이유는 클래스에서 클래스 변수보다는 객체변수가 훨씬 중요해서입니다! 실무 프로그래밍을 할 때도 클래스 변수보다는 객체변수를 사용하는 비율이 훨씬 높다고 합니다.



지금까지 파이썬에서 가장 어렵게 느껴지는 class의 개념을 정리했습니다!👏

profile
Tistory로 이사갔어요

0개의 댓글