상세 컨텐츠

본문 제목

[ Python 기초 문법 ] - 클래스(1)

Language/Python

by bing_su 2023. 8. 25. 10:20

본문

반응형
SMALL

프로그래밍 초보들에게 클래스(class) 개념은 넘기 힘든 장벽과도 같을 수 있다. 이 개념을 처음 접하는 독자들도 분명 존재할 것이다. 이번 게시물에서는 클래스가 왜 필요한지, 그렇다면 클래스는 무엇인지에 대해 기초적인 것부터 차근차근 알아보려고 한다. (조금 어려워도 진짜 중요한 개념인 만큼, 잘 따라와 주셔야 합니다..!)

[ 클래스는 왜 필요한가? ]

현재 다루고 있는 파이썬을 비롯한 다양한 프로그래밍 언어는 말 그대로 언어일 뿐, 근본적인 개념은 모두 비슷하다. 프로그래밍 언어 중에는 지금부터 다룰 클래스(Class) 개념이 존재하는 언어와, 존재하지 않는 언어가 있다. 일단 가장 기본적으로 배우는 프로그래밍 언어에는 클래스 개념이 존재하지 않는다. 즉, C언어로는 클래스 없이 어떤 프로그램이든지 만들 수 있다는 뜻이다.

 

이는 클래스(Class) 개념은 앞서 다뤘던 함수나 자료형과 같이 프로그램을 만들기 위해 꼭 필요한 요소는 아니라는 것이다. 클래스(Class)는 순수하게 편리성과 가독성을 위해 만들어진 기능이라고 할 수 있다.

[ 클래스(Class)란? ]

클래스(Class)는 과자 틀과 비슷하다고 생각하면 된다. 별 모양의 틀(클래스)로 찍으면 별 모양의 쿠키(객체)가 생성된다. 즉, 클래스(Class)는 똑같은 무엇인가를 계속해서 만들어 낼 수 있는 설계 도면(과자 틀)이고, 객체(object)는 클래스에 의해 만들어진 피조물(과자)을 뜻한다. 이를 그림으로 나타내면 아래와 같다.

클래스와 객체의 개념

파이썬 클래스의 가장 간단한 예시를 들면 아래와 같다.

class Cookie:
    pass

위의 클래스는 아무런 기능을 갖지 않는 껍질뿐인 클래스다. 하지만 이렇게 껍질뿐인 클래스도 객체를 생성하는 기능은 가지고 있다. 객체는 클래스로 만들며, 1개의 클래스는 무수히 많은 객체를 만들 수 있다. 위에서 만든 Cookie 클래스의 객체를 만드는 방법은 아래와 같다.

a = Cookie()
b = Cookie()

Cookie()의 결괏값을 반환받은 a와 b가 바로 객체이다. 앞서 살펴본 함수를 사용하여 그 결괏값을 반환받는 모습과 유사하다.

[ 객체(Object)란? ]

클래스에서 만든 객체를 인스턴스(instance)라고도 한다. 객체와 인스턴스는 거의 같은 말이지만, 어감이 살짝 다르다. 

a = Cookie()

위의 코드에서 생성된 a는 객체(object)다. 그리고 객체 a는 Cookie의 인스턴스(instance)가 된다. 다시 말하면, 인스턴스는 특정 객체(a)가 어떤 클래스(Cookie)의 객체인지, 관계를 설명할 때 주로 사용한다. 즉, 'a는 인스턴스'보다는 'a는 객체'라는 표현이 어울리며, 'a는 Cookie의 객체'보다 'a는 Cookie의 인스턴스'라는 표현이 훨씬 어울린다. 정리를 하면, a는 객체이면서 Cookie의 인스턴스가 되는 것이다.

[ 클래스 기초 ]

<< 클래스 변수 >>

class Calculator:
    prompt = "This program performs addition."  # 클래스 변수
result = Calculator()  # result라는 객체로 Calculator 클래스 사용
print(result.prompt)  # 클래스 변수 호출(1)
print(Calculator.prompt)  # 클래스 변수 호출(2)

# 출력 화면
This program performs addition.
This program performs addition.

위에서 작성한 클래스 이름은 Calculator다. 이 클래스를 사용하기 위해 result라는 객체로 클래스 Calculator를 불러온다. 그리고 클래스 내부의 변수(클래스 변수)를 얻기 위해서 객체 result에 도트 연산자(.)로 prompt 변수를 호출하고 있다. 해당 방법 외에도 '클래스명.변수명'으로 클래스 변수를 호출할 수도 있다. 이때 클래스 변수는 해당 클래스를 사용하는 모두에게 공용으로 사용될 수 있다.

<< 클래스 함수 >>

class Calculator:
    prompt = "This program performs addition."  # 클래스 변수
    def add(self, a, b):  # 덧셈 서비스
        add_result = a + b
        print("{0} plus {1} is {2}.".format(a, b, add_result))  # 덧셈 결과를 출력
        
result = Calculator()  # result라는 아이디(객체)로 Calculator 클래스 사용
result.add(1, 1)  # 덧셈 서비스 이용

# 출력 화면
1 plus 1 is 2.

Calculator 클래스에 두 숫자를 더하는 서비스를 추가한 코드다. 클래스 안에 구현된 함수는 다른 말로 메서드(Method)라고 부른다. 참고로 메서드 add는 반환 값을 가지지 않기 때문에 실행만 해도 print문이 동작한다. (이해 안 되면, https://bing-su-b.tistory.com/121 설명 다시 읽으세요.)

<< self란? >>

위의 예시 코드를 계속 보자. Calculator 클래스에서 메서드 add는 첫 번째 parameter로 self를 받고, 두 번째/세 번째 parameter로는 덧셈을 수행할 숫자를 받는다. 즉, 입력을 받는 parameter가 총 3개다. 이때 메서드 add는 첫 번째 parameter인 self를 통해 해당 클래스를 사용하기 위해 생성된 객체인지 아닌지를 판단한다.

argument가 전달되는 과정을 표현.

그림은 메서드에 argument 전달하는 과정을 나타낸 것이다. 예시 코드를 적용하여 설명하면, result.add(1, 1)에서 첫 번째 argument로 result 객체를 넣으면 add 메서드는 Calculator의 인스턴스로 result가 있는지 확인한다. 해당 인스턴스가 존재하기 때문에 add 메서드가 작동된다. 해당 코드는 'Calculator.add(result, 1, 1)'와 같은 결과를 출력한다. self라는 변수를 메서드의 첫 번째 parameter로 받아야 한다는 것은 파이썬만의 특징이다.

※ self 실습

앞에서 실습한 add 서비스를 제공할 때, 사용자에게 이름을 입력받아 함께 출력할 수 있게 해 보자.

class Calculator:
    prompt = "This program performs addition."  # 클래스 변수
    def setname(self, name):  # 이름을 저장하는 함수
        self.name = name  # 인스턴스 변수(아래서 설명 예정)
    def add(self, a, b):  # 덧셈 서비스
        add_result = a + b
         # 덧셈 결과를 출력
        print("{0}, {1} plus {2} is {3}.".format(self.name, a, b, add_result))
        
result = Calculator()  # result라는 객체에 Calculator 클래스 사용
result.setname("Sooob")  # 이름 저장 서비스 이용 
result.add(1, 1)  # 덧셈 서비스 이용

res = Calculator()  # res라는 객체에 Calculator 클래스 사용
res.setname("Judy")  # 이름 저장 서비스 이용
res.add(1, 2)  # 덧셈 서비스 이용

# 출력 화면
Sooob, 1 plus 1 is 2.
Judy, 1 plus 2 is 3.

위의 코드에서 prompt처럼 클래스 내부에 선언된 변수를 클래스 변수라고 하고, self.name과 같이 self가 붙어 있는 변수인스턴스 변수라고 한다.

 

인스턴스 변수는 각각의 인스턴스마다 독립된 값을 가지는 변수라고 생각하면 된다. 즉, 클래스마다 생성된 몇 개의 인스턴스에서 (해당 코드에서는 result, res) 인스턴스 변수에 서로 다른 값을(Sooob, Judy) 대입해도, 각 인스턴스는 서로 다른 고유의 값을 보존하고 있는 것이다. (출력 결과 확인) 클래스 변수는 인스턴스 변수와 달리, 모든 인스턴스에 공유된 값을 가진다. 이를 정리해서 표로 나타내면 아래와 같다.

차이점 클래스 변수 인스턴스 변수
사용 구분 모든 인스턴스에서 공유하는 값 해당 인스턴스만 사용하는 값
정의하는 곳 클래스 정의문 바로 아래에 대입된 변수 함수 정의문 바로 아래에 대입된 self의 속성
변경 모든 인스턴스가 변경된 값을 가짐 해당 인스턴스의 속성만 변경

다시 해당 코드가 실행되는 Logic을 살펴보자.

  1. Calculator 클래스 사용을 위해 result와 res 인스턴스를 얻는다.
  2. result 객체에서 사람의 이름이 "Sooob", res 객체에서 사람의 이름이 "Judy"로 입력된다.
    (result와 res 객체에 각각 "Sooob", "Judy"라는 이름을 연결해 주는 것이 바로 self다.)
  3. 더하기 서비스를 이용해 결괏값을 출력한다.

[ __init__ 메서드 ]

앞에서 사용한 예시 코드를 계속 살펴볼 예정이다. 아래의 코드처럼 result.setname("Sooob"), 사용자 이름 출력의 과정을 빼먹어서 오류가 발생할 수 있다. (생각보다 꽤 있을 수 있음)

result = Calculator()
result.add(1, 1)

이때, 아래와 같이 __init__ 메서드를 사용하면, result.setname("Sooob")와 같은 과정을 생략할 수 있다.

class Calculator:
    prompt = "This program performs addition."  # 클래스 변수
    def __init__(self, name):  # 이름 저장 서비스
        self.name = name
    def add(self, a, b):  # 덧셈 서비스
        add_result = a + b
         # 덧셈 결과를 출력
        print("{0}, {1} plus {2} is {3}.".format(self.name, a, b, add_result))
        
result = Calculator("Sooob")  # result 객체의 사용자 이름은 Sooob
result.add(1, 1)  # 덧셈 서비스 이용

# 출력 화면
Sooob, 1 plus 1 is 2.

기존 클래스에서 메서드를 사용했던 것과 비교하면 함수 이름이 'setname'에서 '__init__'으로 바뀐 것 외에는 나머지는 동일하다. 지금 사용한 __init__ 메서드는 클래스에서 '클래스의 인스턴스를 만들 때 항상 실행된다.'는 특별한 의미를 가진다. 즉, 객체를 부여받을 때마다 실행된다는 뜻이다. 따라서 객체를 부여받을 때 아래와 같이 입력하면 된다. (기존의 코드와 비교해 보자.)

# 기존 코드
result = Calculator()
result.setname("Sooob")

# __init__ 메서드를 이용한 방식
result = Calculator("Sooob")

__init__ 메서드를 이용했기 때문에 위와 같이 클래스를 사용할 때 사용자 이름까지 함께 입력하면 된다.

 

위의 내용에서 클래스, 객체(인스턴스), 메서드, self, __init__ 함수 등의 의미에 대해 접근해 봤다. 이러한 내용을 토대로 클래스의 기본 구조를 정리하면 아래와 같다.

class Class_Name(Inheritance_Class_Name):
    <Class Variance_1>
    <Class Variance_2>
    …
    def Class_Function_1(self, parameter_1, parameter_2, …)
        <Sentence 1>
        <Sentence 2>
        …
    def Class_Function_2(self, parameter_1, parameter_2, …)
        <Sentence 1>
        <Sentence 2>
        …
    def Class_Function_3(self, parameter_1, parameter_2, …)
        <Sentence 1>
        <Sentence 2>
        …
    …

클래스 내부에서 선언할 수 있는 클래스 변수와 메서드의 개수에는 제한이 없다.

class 키워드는 클래스를 만들 때 사용되는 예약어다. 바로 뒤에 클래스의 이름을 입력하면 해당 클래스가 생성된다. 만약 상속할 클래스가 있다면, 클래스 이름 뒤의 괄호() 안에 상속할 클래스의 이름을 적으면 된다. (클래스의 상속에 대해서는 추후에 다룰 예정이다.

[ 실습 - 사칙연산 클래스 만들기 ]

클래스의 기초를 바탕으로 몇 가지 클래스를 만들어 보자. 이 실습을 통해 클래스를 만들기 위한 구상 과정부터 구조, 클래스 상속 등을 자세히 살펴볼 예정이다.

>>> a = FourCal()  # 객체 a 만들기
>>> a.setdata(4, 2)  # 4와 2라는 숫자를 a에 지정
>>> print(a.sum())  # 더하기
6
>>> print(a.sub())  # 빼기
2
>>> print(a.div())  # 나누기
2
>>> print(a.mul())  # 곱하기
8

해당 클래스를 무작정 만들기보다는, 클래스에 의해 만들어진 객체를 중심으로 작동하는 방식을 미리 구상해 보고, 생각했던 것을 하나씩 해결하며 완성을 해야 한다.

클래스 동작 방식 구상

1. 클래스 구조 만들기

제일 먼저 해야 할 일은 a=FourCal()처럼 클래스의 인스턴스를 만들 수 있게 하는 것이다.

class FourCal():
    pass

 

위의 클래스는 어떠한 변수나 메서드를 포함하지 않았지만, 우리가 원하는 객체 a는 만들 수 있는 기능을 가지고 있다. (pass 키워드는 아무것도 수행하지 않는 문법이다. 임시로 코드를 작성할 때 자주 사용한다.)

a = FourCal()  # 객체 a
print(type(a))  # 객체 a의 type을 확인

# 출력 화면
<class '__main__.FourCal'>  # Fourcal 클래스의 인스턴스임을 확인할 수 있음.

2. 객체에 숫자를 지정할 수 있게 만들기

위에서 생성한 객체 a는 아무런 기능을 하지 못한다. 이제 빈 클래스 Fourcal()에 사칙연산(더하기, 빼기, 곱하기, 나누기)의 기능을 하는 객체를 만들어야 한다. 해당 기능을 갖춘 객체를 만들기 위해서는 아래와 같이 객체 a에 사칙연산을 하기 위한 두 개의 숫자를 지정해야 한다.

a.setdata(4, 2)

해당 문장이 수행되기 위해서는 아래와 같이 pass 문장을 제거한 후 setdata 메서드가 추가된 코드를 작성해야 한다.

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)

# 출력 화면
4
2

3. 더하기 기능 만들기

앞서 설정한 두 값을 이용해 더하는 기능을 구현하자. 아래와 같이 class에 sum 메서드를 추가하고 결괏값을 출력하자.

class FourCal():
    def setdata(self, first, second):
        self.first = first
        self.second = second
    def sum(self):
        result = self.first + self.second
        return result
    
a = FourCal()
a.setdata(4, 2)
print(a.sum())

# 출력 화면
6

4. 나머지 사칙 연산 기능 만들기

위에서 다룬 내용을 기반으로 나머지 사칙 연산 기능을 하는 메서드를 클래스에 추가해 보자.

class FourCal():
    def setdata(self, first, second):
        self.first = first
        self.second = second
    def sum(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 = FourCal()
b = FourCal()
a.setdata(4, 2)
b.setdata(3, 6)
print(a.sum())
print(a.sub())
print(b.mul())
print(b.div())

# 출력 화면
6
2
18
0.5

객체 a와 b를 각각 2개씩 사칙연산 클래스에 적용시켰다. 두 개의 객체는 서로 다른 저장 공간을 가지고 있기 때문에 완전히 독립적으로 동작한다. 이러한 의미로 파이썬은 객체 지향 프로그래밍 언어라고 불린다.

반응형
LIST

관련글 더보기

댓글 영역