1. 클래스의 필요성


최근 프로그래밍 언어들의 특징은 객체지향을 등에 업고 나온다는 것이다. C언어처럼 철저히 절차지향적인 언어 이외에는 대부분의 프로그래밍 언어들이 객체지향을 지원한다. 객체지향의 최대 강점은 재사용성이다. 변수, 함수 등을 포함하는 클래스를 한 번 만들어 놓으면 클래스와 같은 구조의 함수를 쉽게 여러개 찍어내어 사용할 수 있다.

가령 여러개의 독립적인 계산기가 필요한 경우를 생각해보자.

result1 = 0
result2 = 0

def cal1(num):
    global result1 # 글로벌 변수 result1을 가져다 쓰겠다는 선언
    result1 += num # result1에 cal1함수의 입력인수 num 더하기
    return result1

def cal2(num):
    global result2
    result2 += num
    return result2

print(cal1(5)) # 5
print(cal1(10)) # 15
print(cal2(7)) # 7
print(cal2(10)) # 17

새로운 수를 더해줄 때 마다 최종결과가 메모리에 저장되고 다음에 입력하는 수를 기존의 결과에 더하는 계산기이다. 위 처럼 계산기가 2개 필요한 경우 글로벌 변수도 2개, 함수도 2개 정의해 주어야한다. 하지만 계산기의 수가 10개, 100개 처럼 많아져야 한다면 매우 수고롭고 더러운 코드가 될 것이다. 이럴 때 사용할 수 있는것이 클래스이다.

클래스에 대해 알아보기 전 맛보기로 개념을 파악하기 위해 다음 코드를 보자.

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

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

cal1 = Cal()
cal2 = Cal()

print(cal1.adder(5))
print(cal1.adder(10))
print(cal2.adder(7))
print(cal2.adder(10))

위 코드는 필요한 구조의 변수와 함수를 하나의 클래스로 만들어놓은 다음 클래스 타입의 함수 cal1과 cal2를 만들어 사용한다. 이처럼 클래스를 사용하면 구조적으로 동일한 여러개의 함수를 클래스를 사용해 여러 개 쉽게 찍어낼 수 있다. 클래스는 하나의 틀이다. 클래스로 찍어낸 객체 하나하나를 특별히 인스턴스(instance)라 부른다.

 

 

2. 클래스의 개념과 구조


1) 기본 개념

class Person:
    name = None
    age = None

per1 = Person()
per1.name = "John"
per1.age = 20

per2 = Person()
per2.name = "Sara"
per2.age = 31

print("person 1 : %s, %d" %(per1.name, per1.age))
print("person 2 : %s, %d" %(per2.name, per2.age))

위에서는 Person이라는 클래스를 만들고, 내부에 이름과 나이 두 가지를 변수로 선언했다. 그리고 per1과 per2라는 인스턴스 2 개를 Person 클래스로 생성하고, 이름과 나이 정보를 입력해주었다. 만약 클래스를 사용하지 않았다면 각 사람마다 이름과 나이를 위한 변수를 각각 선언해 주어야한다. 지금은 그나마 사람이 2명이지, 만약 국가정보원에서 국민의 정보를 데이터베이스화 시켜 관리한다면 클래스 없이는 도저히 불가능할 것이다.

 

 

3. 클래스 함수와 self


위의 코드를 확장해보겠다.

class Person:
    name = None
    age = None
    def loan(self, money):
        print("You borrowed %d won" %money)

per1 = Person()
per1.name = "John"
per1.age = 22
per1.loan(1000) # per1이 클래스의 loan 함수를 호출
Person.loan(per1,1000) # per1.loan(1000)과 같은 의미

먼저, 클래스 내부 함수의 첫 번째 인수는 무조건 self 이다. self는 어떠한 객체가 이 함수를 호출할 때 이 클래스의 인스턴스인지 판단을 하기위해 사용한다. 위 코드의 클래스 내부에있는 loan 함수는 돈을 입력하면 빌려주는 대출 서비스함수라고 하자.

위의 per1.loan(1000) 을 통해서 1000원 대출 신청을 하면 loan 함수의 첫 번째 인수인 self에 자동으로 per1이 대입이 되어 이 클래스의 인스턴스인지 판단을 하여 클래스의 인스턴스면 실행을 해준다. 이렇듯 함수를 호출할 때에 첫 번째 인수인 self는 무시하면 된다. 실제로 Person.loan(per1,1000) 처럼 클래스 자체를 통해서 함수를 호출하면서 self에 per1을 넣어주는 것과 동일하다.

정리를 하자면 다음과 같다.

클래스 내부에 함수를 선언할 때에는 첫 번째 인수로 무조건 self를 넣어주어야 한다. 하지만 클래스의 인스턴스를 만들어 함수를 호출하여 사용할 때에는 첫 번째 입력인수인 self에 자동으로 인스턴스의 이름이 대입이 되어 클래스의 인스턴스인지 확인을 한다. 따라서 self는 없는 것 처럼 사용해도 된다.

한 가지 말해주고 싶은게 있다. 클래스 내부의 함수를 특별히 '메소드(method)'라고 부른다.

조금 더 자세하게 알아보기 위해 메소드를 하나 더 추가했다.

class Person:
    def getcredit(self, score):
        self.credit = score

    def loan(self, money):
        if self.credit <=3:
            print("You borrowed %d won" %money)
        else:
            print("You cannot borrow money")

per1 = Person()
per1.getcredit(7)
per1.loan(1000)

위에서는 인스턴스의 신용정보를 저장하기 위해 getcredit이라는 메소드로 신용점수(score)를 입력받아 저장한다. 위의 코드에서 per1.getcredit(7) 이라는 명령을 실행하면 다음의 과정이 이루어진다. getcredit 메소드의 self에 per1이 대입이 되고 self.credit = score는 per1.credit = score 로 자동으로 바뀐다. 따라서 이후에 per1이라는 인스턴스 명으로 클래스에 접근했을 때에는 per1이라는 사람의 신용등급이 7이라는 기존에 저장된 정보를 알 수 있다. 이처럼 self는 클래스의 서로 다른 인스턴스를 구분하기 위해 쓰이는 것이다. 그리고 특정한 인스턴스로 클래스 메소드에 접근하면 메소드의 첫 번째 인수인 self에는 자동으로 인스턴스 이름이 대입이 된다.

같은 클래스의 메소드라도 인스턴스마다 각각의 변수나 메소드의 내부 값은 다르게 설정할 것이다. 그렇기 때문에 메소드는 서로 다른 인스턴스를 구분할 수 있어야한다. self에 각각의 인스턴스 명이 대입이 되기때문에 인스턴스 마다의 구분이 가능한 것이다.

 

 

 

4. 클래스 함수와 __init__


위에서 설명한 코드에서 어떤 사람이 대출을 하기 위해 메소드를 실행한다고 해보자.

per1 = Person()
per1.loan(1000)

위의 코드를 실행하면 오류가 난다. loan 메소드에서는 신용등급을 확인하여 돈을 빌려줄 지 말지를 결정하는데, 신용등급을 입력해주지 않았기 때문이다. 애초에 인스턴스를 만들 때, 신용도를 입력해야만 인스턴스가 만들어지도록 해놓으면 괜히 잘못된 사용으로 오류가 나는 것을 막을 수 있을 것이다. 이 때 사용하는 것이 __init__이다.

위에서 설명한 코드를 조금 수정해보겠다.

class Person:
    def __init__(self, score):
        self.credit = score

    def loan(self, money):
        if self.credit <=3:
            print("You borrowed %d won" %money)
        else:
            print("You cannot borrow money")

per1 = Person(2)
per1.loan(1000)

기존의 getcredit 메소드의 이름 대신에 __init__ 을 넣었다. 이렇게 __init__으로 설정을 해놓으면 인스턴스를 만들 때 per1 = Person(2) 처럼 클래스 명 옆의 괄호 안에 __init__의 입력 인수를 넣지 않으면 인스턴스가 만들어지지 않는다. 물론 self에는 per1 이라는 인스턴스명이 자동으로 대입이 되니까 문제없다.

아무튼 __init__은 인스턴스를 만들기 위해 필수적으로 초기화가 되어야 하는 변수를 설정하기 위해 사용한다.

위의 코드에서 기존에 대출실행을 하기위한 코드는 다음과 같았다.

per1.Person()
per1.getcredit(2)
per1.loan(1000)

그러나 __init__ 으로 인해 다음과 같이 해야만 인스턴스가 만들어진다.

per1.Person(2)
per1.loan(1000)

 

 

5. 클래스를 활용한 사칙연산 계산기


 

class Cal:
    def __init__(self,num1,num2): # 입력인수 2개 필수로 받아야 인스턴스 생성 가능
        self.first = num1 # 첫 번째 수에 인수 num1 대입
        self.second = num2 # 두 번째 수에 인수 num2 대입

    def sum(self):
        return self.first+self.second # 두 인수의 합

    def sub(self):
        return self.first-self.second # 두 인수의 차

    def mul(self):
        return self.first*self.second # 두 인수의 곱

    def div(self):
        return self.first/self.second # 두 인수의 나누기

a = Cal(10,5) # 인스턴스 a의 인수를 (10,5)로 설정
b = Cal(30,6) # 인스턴스 b의 인수를 (30,6)으로 설정

print(a.sum()) # a의 sum 메소드 리턴값 출력
print(a.sub()) # a의 sub 메소드 리턴값 출력
print(a.mul()) # a의 mul 메소드 리턴값 출력
print(a.div()) # a의 div 메소드 리턴값 출력
print(b.sum()) # b의 sum 메소드 리턴값 출력
print(b.sub()) # b의 sub 메소드 리턴값 출력
print(b.mul()) # b의 mul 메소드 리턴값 출력
print(b.div()) # b의 div 메소드 리턴값 출력

위 코드의 주석을 보면서 천천히 이해해 보기를 바란다.

먼저 __init__으로 숫자 2개를 받아야만 인스턴스 생성이 되도록 했다. 그리고 두 개의 변수에 두 개의 입력인수를 각각 저장했다.

다음으로 4개의 메소드에서 2개의 변수를 불러와 각각의 연산을 수행 후 이를 리턴시킨다.

마지막으로 a,b 각각의 인스턴스마다 4가지의 메소드 리턴값을 출력한다.

이 때 다시 한 번 마음에 새겨야 할 건 클래스 내부의 메소드가 포함하는 입력인수 self와 변수 first, second가 단독으로 쓰이지 않고 self.first와 self.second로 선언되었기 때문에 인스턴스 a와 인스턴스 b가 각각의 서로 다른 변수 값을 가지고 다른 결과를 내어놓을 수 있는 것이다. 따라서 인스턴스 a와 인스턴스 b가 같은 구조의 클래스에 의해 만들어졌지만 서로 독립적인 저장공간과 값을 가지고 독립적으로 동작한다.

'프로그래밍 언어 > Python' 카테고리의 다른 글

(Python) 17 - 모듈  (0) 2020.05.16
(Python) 16 - 클래스 상속과 메소드 오버라이딩  (0) 2020.05.16
(Python) 14 - 파일 입출력  (0) 2020.05.16
(Python) 13 - 사용자 입출력  (0) 2020.05.16
(Python) 12 - 함수  (0) 2020.05.16

+ Recent posts