[Python 기초] 파이썬 프로그래밍의 핵심 - 클래스(Class)

업데이트:

개요

jpg

클래스가 왜 필요한가?

계산기를 떠올려보자.
계산기는 3이라는 숫자를 입력후 + 4를 입력하면 7을 돌려준다. 또한 다시 + 2 를 입력하면 9를 돌려준다.
이처럼 계산기는 이전에 계산된 결과값을 메모리에 저장하고 있어야한다.

result = 0
def adder(num):
    global result
    result= result + num
    return result
print(adder(3))
print(adder(4))
[Output]
3
7

파이썬으로 계산기를 간단히 만들어보면 위와 같다.
하지만 이와 같은 계산기가 2개이상 필요하다면?

result1 = 0
result2 = 0

def adder1(num):
    global result1
    result1= result1 + num
    return result1

def adder2(num):
    global result2
    result2= result2 + num
    return result2
print(adder1(3))
print(adder2(2))
print('\n')
print(adder1(3))
print(adder2(2))
[Output]
3
2


6
4

물론 같은 방식으로 adder함수를 추가로 정의해 주면 가능하다. 하지만 같은 기능을 가진 개별적인 계산기가 점점더 많이 필요하게 된다면 반복적인 작업을 통해 함수를 추가해 주어야하는 불편함이 생기게 된다.

이럴때 클래스(class)를 이용할 수 있다.

class Calculator:
    def __init__(self):
        self.result = 0
        
    def adder(self, num):
        self.result += num
        return self.result
    
cal1 = Calculator()
cal2 = Calculator()
print(cal1.adder(3))
print(cal1.adder(4))
print('\n')
print(cal2.adder(2))
print(cal2.adder(2))
[Output]
3
7


2
4

아직 클래스의 사용방식을 쓰진 않았지만 코드를 이해해보면 하나의 클래스를 만들어 놓으면 변수명만 달리해서 다른 계산기를 만들어 낼 수 있게된다.


1. 클래스 만들기 & 예시

class Simple:
    pass
a = Simple()

위와 같이 Simple()이라는 빈 클래스를 만들었다. 그 후 a라는 객체에 클래스를 부여해 주었다.

객체와 인스턴스(instance) :
이 둘은 같은 말이므로 혼동에 주의할 필요가 있다. a = Simple()에서 a는 객체임과 동시에 Simple의 인스턴스라는 표현을 쓴다.

a
[Output] <__main__.Simple at 0x2b25bcbd940>

아직은 빈 클래스이므로 위와 같은 결과가 객체 a에 저장되었다.


2. 클래스에 변수 담기

이제 이 클래스 안에 다양한 내용을 포함시켜보자.

class Service:
    secret = "영구는 배꼽이 두 개다."

Service라는 클래스 안에 sevret변수를 추가했다.

pey = Service()

이 클래스를 이용하려면 어떤 객체에 클래스를 부여해주는 이 작업이 가장먼저 선행되어야 한다.

pey.secret
[Output] '영구는 배꼽이 두 개다.'

부여받은 객체를 이용해 위와 같이 내용물을 꺼내 쓸 수 있는 것이다.

2-1. 클래스에 함수 담기 & self 이해하기

class Service:
    secret = "영구는 배꼽이 두 개다."
    def sum(self, a, b):
        result =  a + b
        print("%s + %s = %s입니다." % (a, b, result))

이번에는 앞서 배웠던 함수 정의를 이용해 추가해보자.
Service라는 class 내에 sum이라는 함수를 만들었다.

그러면 이제,

1.객체에 인스턴스 부여하기

pey = Service()

2.인스턴스로 함수 이용하기

pey.sum(1,1)
[Output] 1 + 1 = 2입니다.

여기서 볼 수 있듯이 클래스(class)에서 함수를 정의할때는 첫 번째 인수는 항상 self를 넣어주어야 한다.
이는 해당 객체(pey)가 클래스인 Service의 인스턴스를 부여받은 객체인지 아닌지를 구별하기 위함이다.

주목할 점은

pey.sum(1,1)

이처럼 함수를 이용할때는 첫번쨰 인자를 생략해도 됨을 알 수 있다.
즉, self인자에 pey가 자동적으로 대입된 것이다.

class Service:
    secret = "영구는 배꼽이 두 개다."
    def setname(self, name):
        self.name = name
    def sum(self, a, b):
        result =  a + b
        print("%s님 %s + %s = %s입니다." % (self.name, a, b, result))

이번에는 setname함수를 정의했다.

1.객체에 인스턴스 부여

pey = Service()

2.인스턴스로 함수 이용하기

pey.setname("YG_analyst")
pey.sum(1,1)
[Output] YG_analyst님 1 + 1 = 2입니다.

위의 결과가 나오기 까지의 과정을 한번 생각해볼 필요가 있다.

    def setname(self, name):
        self.name = name

이와 같이 정의한 함수를 통해 pey.setname("YG_analyst")의 과정은 다음과 같다.

  1. self.name = name 이 수행된다.
  2. pey.name = name (self는 pey라는 객체를 받게 된다)
  3. pey.name = “YG_analyst” (입력받은 “YG_analyst”가 다시 대입된다)
pey.name
[Output] 'YG_analyst'

실제로 제대로 부여가 되었는지 확인해 보려면 위와 같이 하면된다.

    def sum(self, a, b):
        result =  a + b
        print("%s님 %s + %s = %s입니다." % (self.name, a, b, result))

그 후에 실행한 sum함수에서도 마찬가지로 self.name인자에 pey.name인자가 대입되고 이는 결국 “YG_analyst”가 되는 것이다.

2-2. __init__ 이해하기

우선 다음과 같은 코드를 실행해보자.

새로운 객체에 인스턴스 부여

babo = Service()

setname함수를 하지 않고 바로 sum함수 실행

babo.sum(1,1)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-40-383d404c9589> in <module>()
----> 1 babo.sum(1,1)


<ipython-input-30-5bc86baa3eff> in sum(self, a, b)
      5     def sum(self, a, b):
      6         result =  a + b
----> 7         print("%s님 %s + %s = %s입니다." % (self.name, a, b, result))


AttributeError: 'Service' object has no attribute 'name'

이러한 오류는 당연히 sum함수의 결과로 print되어야 할 인자 중 self.name이 정의되지 않았기 때문이다.
이렇게 미리 실행해야 할 중요한 함수가 있을 경우, __init__를 이용할 수 있다.

__init__의 의미 : 인스턴스를 만들때 항상 실행된다.

즉, 객체가 인스턴스를 부여받는 첫 번째 과정에서 항상 실행된다는 것이다.

class Service:
    secret = "영구는 배꼽이 두 개다."
    def __init__(self, name):
        self.name = name
    def sum(self, a, b):
        result =  a + b
        print("%s님 %s + %s = %s입니다." % (self.name, a, b, result))

즉, 같은 클래스 구조에서, 정의했었던 setname__init__로 바꿔주면 된다.

pey = Service("yg_analyst")

그리고 객체에 인스턴스를 부여할 때, 빈 괄호가 아니라 setname함수를 실행할 때와 같이 안에 인자를 입력해주면 된다.

pey.name
[Output] 'yg_analyst'

그러면 자동으로 pey.name에 이름이 적용되었다.

pey.sum(1,1)
[Output] yg_analyst님 1 + 1 = 2입니다.

따라서 sum함수도 오류없이 작동함을 알 수 있다.

이렇게 클래스를 설계한 경우, 객체에 인스턴스 부여 시 빈 괄호만 주게 되면 오류가 발생하므로 주의해야한다.

pey = Service()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-43-54949dd9771b> in <module>()
----> 1 pey = Service()


TypeError: __init__() missing 1 required positional argument: 'name'


3. 클래스의 구조 이해하기

클래스의 기본 구조는 다음과 같다.

class 클래스 이름(상속 클래스명):
    <클래스 변수 1>
    <클래스 변수 2>
    ...
    <클래스 변수 N>
    def 클래스 함수1(self, 인수1, 인수2, ...):
        <수행할 문장 1>
        <수행할 문장 2>
        ...
    def 클래스 함수2(self, 인수1, 인수2, ...):
        <수행할 문장 1>
        <수행할 문장 2>
        ...
    def 클래스 함수2(self, 인수1, 인수2, ...):
        <수행할 문장 1>
        <수행할 문장 2>
        ...
    ...

클래스 내부에 선언할 수 있는 변수와 함수의 개수는 제한되어 있지 않다.

3-1. 예제 - 사칙연산 클래스 만들기

클래스를 만들때는 항상 어떤 구조로 만들지 생각해보고 구현하는 것이 좋다.

사칙연산 클래스를 만들기 위해 다음과 같은 구조를 생각해보자.

a = FourCal() 객체에 인스턴스 부여
a.setdata(4,2) 와 같이 입력해서 4와 2라는 숫자를 지정해주자
a.sum() a.mul() a.div() a.sub() 이렇게 사칙연산 함수로 계산되게 하자

먼저 클래스를 만들고 setdata함수를 만들어보자

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

객체에 인스턴스 부여

a = FourCal()

setdata 함수 이용

a.setdata(4,2)

확인

print(a.first)
print(a.second)
[Output]
4
2

다음으로는 사칙연산 함수들을 정의해보자.

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

먼저 sum함수를 만들어 보자.
이와 같이 앞서 setdata함수를 실행하면 두 숫자는 self.first self.second인자에 저장되어 있는 상황이므로 이 두 인자를 결과값으로 돌려주도록 return을 이용했다.

확인

a = FourCal()
a.setdata(1,3)
a.sum()
[Output] 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 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()
a.setdata(5,2)

print(a.sum())
print(a.mul())
print(a.sub())
print(a.div())
[Output]
7
10
3
2.5

앞서 설명한 것처럼 클래스의 특징으로, 클래스의 인스턴스를 또 다른 객체에 부여해줘서 별도의 메모리를 가진 사칙연산 도구를 만들 수 있다.

b = FourCal()
b.setdata(4,1)

추가로 덧붙여, 방금 배웠던 __init__함수를 이용해 setdata함수를 클래스의 인스턴트 부여 시 항상 실행되게 만들어 보자

class FourCal:
    def __init__(self, first, second):
        self.first = first
        self.second = second
    def sum(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(2,3)
a.sum()
[Output] 5

3-2. 예제 - 신씨네 집 클래스 만들기

이 예제에서도 먼저 클래스의 구조를 구상해보자

yg = HouseShin() 으로 인스턴스를 부여하자

print(yg.lastname) 으로 “신”을 출력하게 하자

yg.setname("용근")으로 이름을 설정하면

print(yg.fullname)으로 “신용근”d을 출력하게 하자

그럼, 이제 클래스를 생성해보자

class HouseShin:
    lastname = "신"
    def setname(self, name):
        self.fullname = self.lastname + name

위와 같이 lastname이란 변수를 먼저 생성,
setname함수를 정의해 name을 입력하면 lastname변수와 입력된 name인자가 합쳐져 fullname을 생성하도록 했다.

확인

yg = HouseShin()
print(yg.lastname)
[Output] 신
yg.setname("용근")
print(yg.fullname)
[Output] 신용근

이번에는 travel이라는 함수를 만들어보자.

class HouseShin:
    lastname = "신"
    def setname(self, name):
        self.fullname = self.lastname + name
    def travel(self, where):
        print("%s, %s여행을 가다." % (self.fullname, where))

이는 setname함수를 통해 생성된 self.fullname인자와 travel함수에 입력된 where인자를 통해 출력을 수행한다.

확인

yg = HouseShin()
yg.setname("용근")
yg.travel("부산")
[Output] 신용근, 부산여행을 가다.

3-3. 초깃값 설정하기

이번에도 마찬가지로 앞에서 배운 __init__을 이용해 setname함수의 기능을 클래스의 인스턴스를 부여받을때 항상 실행되도록 만들어보자.

class HouseShin:
    lastname = "신"
    def __init__(self, name):
        self.fullname = self.lastname + name
    def travel(self, where):
        print("%s, %s여행을 가다." % (self.fullname, where))

이렇게 만들면,

yg = HouseShin("용근")
yg.travel("부산")
[Output] 신용근, 부산여행을 가다.

별도의 setname을 거치지 않을 수 있다.

3-4. 클래스의 상속(Inheritance)

클래스의 상속이란, 말그대로 ‘물려받다’라는 뜻으로, 클래스를 새로 만들때 기존의 다른 클래스로부터 기능을 물려받을 수 있는 것을 의미한다.

class HouseKim(HouseShin):
    lastname = "김"

이와 같이, 다음과 같은 구조로 입력만 해주면 상속을 받게 된다.

class 상속받을 클래스명(상속할 클래스명)

위 코드는 lastname변수만 “김”씨로 변경해주었고, 다른 기능은 모두 물려받았다.

확인

yg = HouseKim("용근")
yg.travel("미국")
[Output] 김용근, 미국여행을 가다.

3-5. 메서드 오버라이딩

어려운 말 같지만 이 기능은 “상속받을 대상인 클래스의 메서드와 이름은 같지만 행동을 다르게 해야할 경우”에 사용한다.

즉, 상속은 받지만 클래스 내의 함수의 기능을 살짝 바꿔주고 싶을때 쓸 수 있다는 것이다.

class HouseKim(HouseShin):
    lastname = "김"
    def travel(self, where, day):
        print("%s, %s여행 %s일 가네." % (self.fullname, where, day))

travel함수의 기존 기능을 살짝 바꾸어 day인자를 추가해, 출력을 달리했다.
이렇게 수정할 사항을 제외한 기능은 모두 같이 상속받으므로 수정할 사항만 수정하면 된다.

확인

yg = HouseKim("용근")
yg.travel("미국","20")
[Output] 김용근, 미국여행 20일 가네.

주의 할 점은 수정할 함수 이름을 똑같이 작성해야 한다.

3-6. 연산자 오버로딩 ` add`

연산자 오버로딩은, 연산자(+, -, *, /, …)를 인스턴스를 부여받은 객체끼리 사용할 수 있게 하는 기법이다.

앞서 만들어 두었던 HouseShin 클래스와 상속 및 수정을 통해 생성된 HouseKim 클래스를 다시 가져와보자.

class HouseShin:
    lastname = "신"
    def __init__(self, name):
        self.fullname = self.lastname + name
    def travel(self, where):
        print("%s, %s여행을 가다." % (self.fullname, where))
    def love(self, other):
        print("%s, %s 사랑에 빠졌네" % (self.fullname, other.fullname))
    def __add__(self, other):
        print("%s, %s 결혼했네" % (self.fullname, other.fullname))
        
class HouseKim(HouseShin):
    lastname = "김"
    def travel(self, where, day):
        print("%s, %s여행 %s일 가네." % (self.fullname, where, day))

여기서 HouseShin 클래스에 몇가지 사항을 추가했다.
love__add__함수를 주목하자.

먼저 love함수는 인자로 other을 넣어주어 출력되는 결과에 other.fullname 인자를 두어 다른 객체의 fullname인자를 받게 했다.
그리고 __add__함수가 바로 연산자의 역할을 하는데, 다른 객체인 other과 연산기호로 표현이 가능하다.
다음 결과를 통해 이해해보자.

두 클래스의 인스턴스를 각각 다른 객체에 부여

yg = HouseShin("용근")
py = HouseKim("파이썬")
yg.love(py)
[Output] 신용근, 김파이썬 사랑에 빠졌네

앞서 설명한 것처럼 yg.love를 통해 인스턴스 yg의 함수 love를 호출하고 안의 other인자에 다른 객체인 py를 넣어주었다.

py객체도 __init__함수를 통해 초기 클래스의 인스턴스 부여 시, fullname을 얻었기 때문에 가능한 것이다.

yg + py
[Output] 신용근, 김파이썬 결혼했네

이와 같이 객체끼리의 +연산자를 통해 __add__함수를 호출해서 명령을 수행한 결과를 출력해준다.

3-7. 예제 - 신씨네 집 클래스 완성하기

의미는 없지만 이해를 돕기위해 지금까지 만들었던 클래스의 구조를 정리할겸 다음과 같은 스토리를 코드로 구현해보자.

신용근은 부산에 놀러 가고

김파이썬도 우연히 3일 동안 부산에 놀러간다.

둘은 사랑에 빠져서 결혼하게 된다.

그러다가 바로 싸우고 이혼을 하게 된다.

따로 설명은 덧붙이지 않겠다.

class HouseShin:
    lastname = "신"
    def __init__(self, name):
        self.fullname = self.lastname + name
    def travel(self, where):
        print("%s, %s여행을 가다." % (self.fullname, where))
    def love(self, other):
        print("%s, %s 사랑에 빠졌네" % (self.fullname, other.fullname))
    def __add__(self, other):
        print("%s, %s 결혼했네" % (self.fullname, other.fullname))
    def fight(self, other):
        print("%s, %s 싸우네" % (self.fullname, other.fullname))
    def __sub__(self, other):
        print("%s, %s 이혼했네" %(self.fullname, other.fullname))
        
class HouseKim(HouseShin):
    lastname = "김"
    def travel(self, where, day):
        print("%s, %s여행 %s일 가네." % (self.fullname, where, day))
yg = HouseShin("용근")
py = HouseKim("파이썬")
yg.travel("부산")
py.travel("부산",3)
yg.love(py)
yg + py
yg.fight(py)
yg - py
[Output]
신용근, 부산여행을 가다.
김파이썬, 부산여행 3일 가네.
신용근, 김파이썬 사랑에 빠졌네
신용근, 김파이썬 결혼했네
신용근, 김파이썬 싸우네
신용근, 김파이썬 이혼했네


Reference

도서 [점프 투 파이썬] 를 공부하며 작성하였습니다.

댓글남기기