본문 바로가기
프로그래밍/파이썬 공부하기

Python 공부 - 3 (클래스, 메서드, 인스턴스, 객체, 속성)

by _OlZl 2024. 1. 24.

2024.01.23 - [프로그래밍/파이썬 공부하기] - Python 공부 - 2 (if문, for문)

 

Python 공부 - 2 (if문, for문)

2024.01.21 - [프로그래밍/파이썬 공부하기] - 파이썬 공부하기 - 1 (자료형) 파이썬 공부하기 - 1 (자료형) 2024.01.21 - [프로그래밍/파이썬 공부하기] - 파이썬 공부하기 - 0 (시작) 파이썬 공부하기 - 0 (

olzl07.tistory.com

이전에는 if문과 for문, 특히 리스트 컴프리헨션에 대해 알아보았습니다.

이제부터는 리스트, 딕셔너리, 튜플 등에 대해 알아볼 생각인데 그전에 잠시 클래스, 메서드, 인스턴스 등에 대해 알아보겠습니다. (순서는 제 맘이니까요ㅎ)

파이썬으로 인공지능을 만들다보면 정말 많은 클래스를 생성하고 이를 사용하는데, 이에 대한 정확한 개념이 잡혀있지 않아 이해하는데 어려움이 좀 있더라고요. 그래서 오늘은 이에 대해 좀 알아볼 생각입니다.

 

저희가 이번에 새로 배울 용어들은 클래스(Class), 메서드(Method), 인스턴스(Instance), 객체(Object), 속성(Attribution) 5가지입니다. 그럼 지금부터 하나하나 알아가 보겠습니다.


클래스 (Class)

가장 먼저, 클래스입니다. 클래스는 간단하게 '틀'이라고 생각하시면 됩니다.

붕어빵을 만든다고 예를 들어보겠습니다. 붕어빵을 만들기 위해서는 붕어빵 틀이 필요하겠죠. 이 '붕어빵 틀'이 바로 클래스에 해당하는 겁니다. 붕어빵 틀을 사용하면 붕어빵을 무한대로 만들 수 있죠. 따라서 클래스를 이용하면 클래스로 만든 제작물을 계속해서 만들어낼 수 있습니다. 이러한 점 때문에 클래스를 사용하는 것이기도 합니다.

 

또 다른 예시로 게임에서 몬스터를 만든다고 해보겠습니다. 좀비 몬스터를 만들어서 플레이어를 공격하게 해야 하는데, 좀비 몬스터가 다 똑같은 레벨이면 재미가 없겠죠. 1레벨 좀비부터 100레벨 좀비까지를 만들어볼겁니다. 이때 '좀비'라는 틀이 바로 클래스인 겁니다. 공장에서 좀비들을 찍어낼 때 그 찍어내는 틀이 클래스라고 생각하면 됩니다. 이 '좀비' 클래스를 활용하면 1레벨 좀비든 10레벨 좀비든 50레벨 좀비든 100레벨 좀비든 상관없이 찍어낼 수 있겠죠.

 

이렇게 쉽게 재사용할 수 있다는 점이 클래스의 장점이기도 합니다. 또 클래스로 만든 객체가 모두 독립적이라는 점도 있는데, 이는 뒤에서 자세히 설명하겠습니다.


인스턴스 (Instance) & 객체 (Object)

인스턴스(객체)는 클래스로 만든 피조물이라고 생각하시면 됩니다. 아까 든 예시처럼 '좀비' 클래스를 활용해서 만든 □□레벨 좀비들이 바로 '인스턴스', 즉 '객체'에 해당합니다. 여기서 인스턴스와 객체를 같은 표현으로 썼는데 사실 둘이 거의 똑같습니다. 다만 클래스와의 관계를 표현할 때는 인스턴스, 객체 자체의 실체를 명명할 때는 객체라고 부릅니다.

ex) 1레벨 좀비, 100레벨 좀비는 모두 객체야. (실체 명명)

ex) 1레벨 좀비, 100레벨 좀비는 모두 좀비의 인스턴스야. (클래스와의 관계 표현)

 

객체에는 중요한 특징이 한 가지 있습니다. 바로 객체들은 모두 독립적이라는 것입니다. 플레이어가 1레벨 좀비를 때려도 100레벨 좀비의 체력이 줄어들지는 않는 것처럼, 같은 클래스로 만든 객체라 할지라도 서로에게 영향을 주지 못합니다. 

 

또, 객체는 클래스의 메서드와 속성을 모두 가집니다. (이에 대해선 뒤에서 설명하겠습니다.)


메서드 (Method)

메서드는 클래스에 포함된 함수라고 생각하시면 됩니다. 즉, 클래스가 하는 기능입니다.

예를 들어, 좀비들은 플레이어를 공격해야 할 것이고, 체력이 다 닳으면 죽기도 해야겠죠. 이렇게 '플레이어 공격', '죽기' 같은 좀비 클래스가 갖는 기능들이 바로 메서드입니다.

 

클래스를 이용해 객체를 만들었을 시 그 객체들도 모두 클래스의 메서드를 가집니다. 좀비 클래스를 활용해 1레벨 좀비와 100레벨 좀비를 만들면 둘 다 공격하고 죽듯이 말이죠.


속성 (Attribution)

마지막으로 속성입니다. 속성은 클래스가 갖는 변수라고 생각하면 됩니다.

좀비들의 체력, 공격력, 방어도, 이름 등이 이에 해당되죠.

 

속성에는 두 가지 종류가 있습니다. 클래스 속성과 인스턴스 속성으로 나뉘는데, 클래스 속성은 같은 클래스에서 만들어진 객체라면 모두 같은 속성 값을 갖고, 인스턴스 속성은 객체별로 모두 다른 속성 값을 가집니다.

예를 들어 좀비들의 체력, 방어도는 모두 같은데 레벨에 따라 공격력, 이름은 다르게 지정할 거라면 체력, 방어도는 클래스 속성이고 공격력, 이름은 인스턴스 속성이 됩니다.


클래스의 필요성

이제 이 정도면 개념에 대해서는 잘 알게 된 것 같고, 이제 코드를 보며 클래스에 관해 알아가겠습니다.

 

사실 이렇게만 들으면 클래스를 왜 써야 하는지 잘 이해가 안 될 수도 있습니다. 그래서 간단한 예를 통해 클래스의 필요성에 대해 알아보겠습니다.

예를 들어, 계산기를 만든다고 해보겠습니다.

def add(num):
    result = 0
    result += num  # 결괏값(result)에 입력값(num) 더하기
    return result  # 결괏값 리턴

print(add(3))
print(add(4))
3
7

이런 코드를 통해 간단하게 계산기를 만들 수 있습니다.

 

그런데, 한 프로그램에서 계산기 2대를 써야 한다면 어떨까요?

각 계산기는 각각의 결과를 유지해야 하므로 add 함수를 하나 더 추가해야겠죠.

def add1(num):  # 계산기1
    result1 = 0
    result1 += num
    return result1

def add2(num):  # 계산기2
    result2 = 0
    result2 += num
    return result2

print(add1(3))
print(add1(4))
print(add2(3))
print(add2(7))
3
7
3
10

이렇게, 좀 더 긴 코드를 짜 문제없이 해결했습니다.

 

그런데, 이번엔 계산기 10대가 필요하다면 어떡할까요. 이제 슬슬 함수 추가하기는 귀찮죠? 이럴 때 클래스를 이용하면 손쉽게 이 문제를 해결할 수 있습니다.

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

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

cal1 = Calculator()
cal2 = Calculator()
...
cal10 = Calculator()

print(cal1.add(3))
print(cal1.add(4))
print(cal2.add(3))
print(cal2.add(7))
...
print(cal10.add(3))
print(cal10.add(5))
3
7
3
10
...
3
8

지금 당장은 이 코드에 대해 이해하려고 하지 않으셔도 됩니다. 어차피 뒤를 보면 이해하실테니까요ㅎㅎ. 이렇게 Calaulator 클래스를 제작해 두면 cal1, cal2 ... cal10의 객체들을 생성함으로써 굳이 함수 2개를 만들 필요 없이 간단히 해결할 수 있다는 것만 아심 됩니다. 아까 말했듯이 같은 클래스에서 만들어진 객체라도 서로에게 영향을 주지는 못하기 때문에, 이런 게 가능하게 된 것입니다.

 

이제 이 코드 속에서 아까 설명한 클래스, 객체(인스턴스), 메서드, 속성을 찾아보겠습니다.

클래스는 당연히 Calculator()이겠죠. Calculator 클래스를 활용해 만든 cal1, cal2 ... cal10은 객체(인스턴스)라고 할 수 있겠네요. Calculator 클래스가 갖는 기능인 __init__과 add는 메서드라고 할 수 있고, 클래스 내의 변수인 self.result는 속성이라고 할 수 있습니다.


생성하기

  • 클래스 생성

이제 클래스를 생성하는 법에 대해 알아보겠습니다. 클래스 생성은 기본적으로 다음과 같습니다.

class 클래스명 :
  코드

함수 생성과 상당히 비슷하게 생겼네요.

보통 클래스명은 변수 이름 짓듯이 단어 첫 글자만 대문자로 써줍니다. (ex: ZombieMonster)

 

빈 클래스도 생성할 수 있습니다. 빈 클래스는 '코드' 자리에 pass를 넣으면 됩니다.

 

 

  • 메서드 생성

메서드는 함수와 동일하게 생성합니다.

단, 주의사항이 한 가지 있습니다. 바로 메서드의 첫 번째 매개변수는 반드시 self로 지정해야 한다는 겁니다. 여기서 self는 객체 자기 자신을 의미합니다. 즉 메서드의 첫 번째 매개변수에는 객체 자신이 전달된다는 뜻입니다.

사실 꼭 'self'라는 글자를 쓸 필요는 없지만 관례적으로 self를 사용하기 때문에 self로 쓰는 것을 권장드립니다.

 

메서드 생성 방법은 다음과 같습니다.

class 클래스명 :
  def 메서드명(self, ...) :
    코드

 

 

  • 메서드 호출

메서드 호출은 다음과 같습니다.

객체명.메서드()

 

 

  • 객체 생성

객체 생성은 간단합니다.

객체명 = 클래스명()

 

혹시 이 구문을 보고 떠오르는 게 없으신가요?

맞습니다. 우리가 정말 자주 사용하는 변수 생성도 이렇게 생겼죠.

a = int(3)
num = list(range(10))

 

사실 자료형, 리스트, 딕셔너리 등도 모두 클래스입니다. 그 증거로 이들의 type을 출력해 보면 class라고 나오는 것을 볼 수 있죠.

즉, 우리는 int 클래스에 a라는 객체를 새로 생성해 그것을 다루고 있던 것입니다.

물론 우리는 int() 등은 모두 생략하지만 사실은 이런 것들까지 모두 클래스로 만들어진 것입니다.

print(type(a))
print(type(num))
<class 'int'>
<class 'list'>

 

 

  • 속성 생성

속성은 클래스 속성과 인스턴스 속성으로 나뉘는데, 이 둘이 선언 방식이 약간 다릅니다.

 

1. 인스턴스 속성

인스턴스 속성은 메서드 영역 안에서 선언해 줍니다. 메서드 내부에서 'self.속성명'의 구문을 활용해 선언할 수 있습니다. 또 이렇게 생성된 인스턴스 속성은 각각의 인스턴스(객체)마다 다른 값을 가질 수 있습니다.

인스턴스 속성은 객체를 만든 뒤에 값에 접근하고 수정할 수 있습니다.

 

또, 클래스에서 선언한 속성 이외에도 생성된 객체에 자신만의 속성을 추가할 수 있습니다.

 

2. 클래스 속성

클래스 속성은 클래스 영역 안에서 선언해 줍니다. 메서드 외부 & 클래스 내부에서 일반적으로 변수 선언하듯이 선언할 수 있습니다. 이렇게 생성된 클래스 속성은 모든 인스턴스(객체)에서 같은 값을 가집니다.

클래스 속성은 객체를 만들지 않고도 접근하고 수정할 수 있습니다.

 

<예시 코드>

class MyClass:
  class_attribute = 0  # 클래스 속성

  def input(self, num):
      self.instance_attribute = num  # 인스턴스 속성

print(MyClass.class_attribute)
a = MyClass()
b = MyClass()

# 인스턴스 속성 접근 & 수정
a.input(5)
b.input(10)

print(a.instance_attribute)
print(b.instance_attribute)

# 클래스 속성 접근 & 수정
MyClass.class_attribute = 1
print(MyClass.class_attribute)

# 인스턴스에 새로운 속성 추가
a.extra = '새로 추가 가능!'
print(a.extra)
0
5
10
1
새로 추가 가능!

 

3. 비공개 속성 

위 2종류의 속성은 모두 클래스 밖에서 접근할 수 있었습니다. 그러나, 클래스 밖에서 속성에 접근하지 못하고 메서드를 이용해서만 접근할 수 있는 속성이 있는데, 이를 비공개 속성이라고 합니다.

비공개 속성은 속성 이름 앞에 언더바(_) 2개를 붙입니다.

class MyClass:
  def input(self, value):
      self.__instance_attribute = value

  def get_instance_attribute(self):
      return self.__instance_attribute  # 내부 메서드를 통한 접근

object = MyClass()
object.input(5)

# print(object.__instance_attribute) -> 에러 발생
print(object.get_instance_attribute()) # 메서드를 통해 접근해야함
5

기본적인 클래스 만들어보기

이제 기본적인 클래스를 만들어보겠습니다. 우리는 사칙연산을 할 수 있는 Calculator 클래스를 만들어볼 겁니다.

사칙연산 클래스에는 다음과 같은 기능들이 필요하겠죠.

  • 두 수 입력받기
  • 더하기
  • 빼기
  • 곱하기
  • 나누기

따라서 우리는 이 5가지 기능(메서드)을 갖는 클래스를 만들어야 합니다.

 

이제 각각의 메서드를 구체화하겠습니다.

먼저, 두 수를 입력받는 input() 메서드에는 매개변수로 두 수를 입력해 줄 시 객체에 그것들을 저장합니다. 두 수를 더하는 add() 메서드는 객체에 저장된 두 수를 더하고, 두 수를 빼는 sub() 메서드는 객체에 저장된 두 수를 빼고, mul() 메서드는 두 수를 곱하고, div() 메서드는 두 수를 나눕니다.

 

이제 클래스를 생성해 보겠습니다. 먼저, Calculator 클래스를 만들고, 객체 a까지 만들어보겠습니다.

class Calculator :
  
a = Calculator()

 

이제 여기에 두 수를 입력받는 input 메서드를 추가하겠습니다.

class Calculator :
  def input(self, first, second) :
    self.first = first
    self.second = second

a = Calculator()

이때 input 메서드 내에 우리가 만드는 속성들은 객체가 다 같은 값을 가져야 하는 것이 아닌 다 다른 값을 가져야 하므로, 즉 클래스 속성이 아닌 인스턴스 속성을 가져야하므로 메서드 내부에 'self.속성명'의 형태로 제작해 주었습니다.

 

따라서 객체 b를 새로 만들어 a와 다른 first, second 값을 입력해 줘도 모두 다른 값이 저장되는 것입니다.

class Calculator :
  def input(self, first, second) :
    self.first = first
    self.second = second


a = Calculator()
b = Calculator()

a.input(4, 2)
b.input(3, 7)

print(a.first)
print(b.first)
4
3

 

아까 말했듯이 self에는 객체 자신, 즉 여기선 a가 전달되기 때문에 self.first, self.second는 a.first, a.second와 같은 의미라고 생각하시면 됩니다. self에는 자기 자신이 전달되기 때문에 메서드를 호출할 때에는 self를 입력할 필요가 없습니다.

 

이제 add 메서드를 추가하겠습니다.

class Calculator :
  def input(self, first, second) :
    self.first = first
    self.second = second

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

a = Calculator()
b = Calculator()

a.input(4, 2)
b.input(3, 7)

print(a.add())
print(b.add())
6
10

add 메서드에는 딱히 전달해 줄 값이 없기 때문에 self 매개변수만 전달하였습니다.

여기 있는 result 변수는 add 메서드 내의 지역변수로, add 메서드 실행이 끝나면 바로 사라집니다.

 

마지막으로 sub, mul, div 클래스를 추가하겠습니다.

class Calculator :
  def input(self, first, second) :
    self.first = first
    self.second = second

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

  def sub(self) :
    result = self.first - self.second
    return result

  def mul(self) :
    result = self.first * self.second
    return result

  def div(self) :
    result = self.first / self.second
    return result

a = Calculator()
b = Calculator()

a.input(4, 2)
b.input(3, 7)

print(a.sub())
print(b.mul())
2
21

 

이렇게 사칙연산을 진행해 주는 간단한 클래스를 제작해 보았습니다.


생성자 (__init__)

이제 생성자에 대해 공부해 보겠습니다. 생성자란 객체를 생성할 때 자동으로 호출되는 메서드를 의미합니다. 파이썬에서는 __init__ (init 양옆에 _ 2개씩) 메서드가 대표적입니다. 아까 계산기 10개 만들 때 썼던 함수인데, 아마 그때는 저게 뭔가 싶으셨을 겁니다. 

 

예시 코드를 통해 알아보겠습니다.

class Calculator :
  def __init__(self, first, second) :
    self.first = first
    self.second = second

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

  def sub(self) :
    result = self.first - self.second
    return result

  def mul(self) :
    result = self.first * self.second
    return result

  def div(self) :
    result = self.first / self.second
    return result

아까 만든 사칙연산 계산기 클래스에서 input 메서드의 이름을 __init__으로 변경하기만 하였습니다. 이렇게 메서드 이름을 __init__으로 해놓게 되면 생성자로 인식되어 객체를 만들 때 이 메서드가 자동으로 호출됩니다. 

 

그런데 만약 여기서 객체를 그냥 생성한다면 오류가 발생하게 됩니다.

a = Calculator()
TypeError: Calculator.__init__() missing 2 required positional arguments: 'first' and 'second'

생성자 메서드는 실행했는데 생성자의 매개변수 first와 second에 값이 전달되지 않았기 때문에 에러가 발생하는 것입니다. self 매개변수에는 객체 자신이 자동으로 전달되기 때문에 문제가 없습니다.

 

따라서, 생성자 메서드가 있는 클래스의 객체를 만들 때에는 생성자 매개변수 값까지 같이 전달해줘야 합니다.

class Calculator :
  def __init__(self, first, second) :
    self.first = first
    self.second = second

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

  def sub(self) :
    result = self.first - self.second
    return result

  def mul(self) :
    result = self.first * self.second
    return result

  def div(self) :
    result = self.first / self.second
    return result

a = Calculator(4, 2)

print(a.mul())
print(a.div())
8
2.0

 

이렇게 생성자를 활용하면 객체를 만들때 바로 호출되기 때문에 불필요한 함수들을 줄일 수 있습니다.


클래스 상속 (Class Inheritance)

이번에는 클래스 상속입니다. Kotlin을 배우며 처음 알게 된 개념인데 파이썬에도 있더라고요.

상속은 보통 물려받는다는 의미로 많이 쓰이죠. 클래스 상속의 상속도 이 의미로 받아들이시면 됩니다. 어떤 클래스를 만들 때 다른 클래스의 기능을 물려받게 하는 것입니다. 

 

클래스 상속의 문법은 다음과 같습니다.

class 클래스명(상속할 클래스명) :
  코드

이때, 상속한 클래스 (상속할 클래스명)을 부모 클래스, 새로운 클래스를 자식 클래스라고 합니다.

 

예를 들어, 아까 만든 사칙연산 계산기에 거듭제곱 기능을 추가해 보도록 하겠습니다.

class PowCalculator(Calculator) :
  pass

a = PowCalculator(4, 2)

print(a.mul())
print(a.div())

먼저, PowCalculator라는 클래스를 새로 하나 만들어 Calculator 클래스를 상속해 보았습니다.

클래스 상속을 하게 되면 상속한 클래스의 기능을 사용할 수 있기 때문에 Calculator 클래스의 mul, div 메서드도 사용가능한 것을 확인할 수 있습니다.

 

이제 여기에 거듭제곱 기능을 넣어보겠습니다.

기능을 추가하고 싶으면 그냥 자식 클래스의 코드 자리에 메서드를 추가하면 됩니다.

class PowCalculator(Calculator) :
  def pow(self) :
    result = self.first ** self.second
    return result

이렇게 자식 클래스 PowCalculator에 새로운 메서드 pow를 추가했습니다. 이제 잘 작동하는지도 확인해 보겠습니다.

a = PowCalculator(4, 2)

print(a.pow())
print(a.add())
16
6

 

새로운 메서드인 pow와, 기존 메서드인 add가 모두 잘 작동하는 것을 확인하였습니다.

 

그렇다면 클래스 상속은 왜 할까요? 그냥 원래 클래스를 수정하면 될 텐데 말이죠.

클래스 상속은 보통 기존 클래스를 변경하지 못할 때나 기존 기능을 변경하고자 할 때 사용합니다. 클래스가 라이브러리 형태로 제공되거나, 수정이 허용되지 않는다면 이를 해결하기 위해 클래스 상속을 하는 것입니다.


메서드 오버라이딩 (Method Overiding)

드디어 마지막! 메서드 오버라이딩입니다.

메서드 오버라이딩은 부모 클래스에 있는 메서드를 동일한 이름으로 다시 만드는 것을 의미합니다. 이렇게 메서드를 다시 만들고 자식 클래스에서 해당 메서드를 호출하면 부모 클래스에 있는 메서드가 아닌 새로운 메서드가 호출됩니다. 그냥 메서드 수정이라고만 이해하셔도 될 것 같습니다.

아까 클래스 상속을 하는 이유 중 하나가 '기존 기능을 변경하고자 할 때'라고 했죠. 이 기존 기능을 변경하는 게 바로 메서드 오버라이딩입니다.

 

객체 a에 1, 1을 지정하고 add 메서드를 실행하면 '창문'이 나오게 하고 싶다고 가정해 봅시다. 현재 Calculator 클래스나 PowCalculator 클래스로는 이런 걸 구현할 수가 없겠죠. 따라서 저희는 클래스 상속을 하고, add 메서드를 수정해 볼 겁니다.

class WindowCalculator(Calculator) :
  pass

먼저, 자식 클래스 WindowCalculator를 만들어보았습니다.

이제 add 메서드를 수정해야 하는데 그냥 add 메서드를 하나 생성해서 우리가 실행하고 싶은 코드를 짜면 됩니다.

class WindowCalculator(Calculator) :
  def add(self) :
    if self.first == 1 and self.second == 1 :
      result = '창문'
    else :
      result = self.first + self.second
    return result

이렇게 입력값이 1, 1이라면 창문을 반환하도록 코드를 짜봤습니다. 이제 잘 작동하는지 확인해 보겠습니다.

a = WindowCalculator(4, 2)
b = WindowCalculator(1, 1)

print(a.add())
print(b.add())
6
창문

저희가 의도한 대로 잘 작동하는 것을 확인할 수 있습니다.


오늘은 이렇게 클래스, 객체, 메서드, 속성에 대해 알아보고 클래스에 대해 좀 더 자세히 알아보았습니다. 그냥 단순히 제가 잘 이해하지 못하고 있던 개념들이라 공부해 보았는데 생각보다 내용이 많아서 오늘도 분량이 정말 많아졌네요...(날먹 실패ㅠㅠ)

그래도 이번 글을 통해 이 개념들에 대해서는 확실히 알게 되어 좋은 것 같습니다.