본문 바로가기

Etc/Python

#09 클래스

 클래스



 c 같은 경우에는 클래스가 없는데 왜 클래스가 필요한가에 대해 의문을 품을 수 있다. 예시를 보면 이해가 되는데 클래스는 함수로 각각 선언해야하는 것을 쉽게 도와주는 느낌이다.


 2개의 계산기로 계산하는 것을 함수를 이용해 짠 예시

result1 = 0

result2 = 0 def adder1(num): global result1 result1 += num return result1 def adder2(num): global result2 result2 += num return result2 print(adder1(3)) print(adder1(4)) print(adder2(3)) print(adder2(7))


 2개의 계산기로 계산하는 것을 클래스를 이용해 짠 예시

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(cal2.adder(3)) 

print(cal2.adder(7))


 모르는 단어들이 들어가 있어서 정확하게는 해석이 안되지만 확실히 간편하게 cal1 = Calculator()라는 문장을 이용해 함수처럼 만들어내고 있구나 하고 알아챌 수 있다.

 결론은 클래스를 이용하면 위처럼 cal1, cal2라는 별개의 계산기(객체)의 개수를 늘릴때 매우 간단해진다.


 객체



  객체는 클래스에 의해 만들어진 피조물정도로 여기는게 좋은 듯하다.


  다음은 파이선 클래스의 가장 간단한 예인데,

>>> class Programmer: >>> pass

 말그대로 객체를 생성하는 껍질뿐인 클래스의 기본이라 잘 눈여겨보는편이 좋다.


 이제 이 클래스를 이용해 클래스의 객체를 만드는 법은 위에서 언급했던 것처럼 다음과 같다.

>>> first = Programmer()

>>> second = Programmer()


 # 객체와 인스턴스의 차이

 클래스에 의해서 만들어진 객체를 인스턴스라고도 한다. 그렇다면 객체와 인스턴스의 차이는? first = Programmer() 이렇게 만들어진 first은 객체이다. 그리고 first이라는 객체는 Programmer의 인스턴스이다. 즉, 인스턴스라는 말은 특정 객체(first)가 어떤 클래스(Programmer)의 객체인지를 관계 위주로 설명할 때 사용된다. 즉, "first는 인스턴스" 보다는 "first는 객체"라는 표현이 어울리며, "first는 Programmer의 객체" 보다는 "first는 Programmer의 인스턴스"라는 표현이 훨씬 더 어울린다고한다.


 클래스를 간단히 사용해보기



 클래스 변수


>>> class Service:

... secret = "Secret Information. 2630"

 클래스 이름은 Service, 이 클래스를 인터넷 서비스 제공 업체라 가정하고 secret. 비밀정보를 가지고 있다. 그래서 일단 가입을 시도한다.

>>> pey = Service()

 자 pey라는 아이디로 가입을 했다. 그럼 이제 비밀 정보를 얻어낸다.

>>> pey.secret

"Secret Information. 2630"

 아이디에다가 서비스 업체가 제공하는 secret이라는 변수를 '.'(도트연산자)로 호출하면 정보를 얻어낼 수 있다.


# 클래스 변수는 객체간 서로 공유되는 변수로 보통 클래스에 의해 생성되는 객체들이 공통적으로 사용할 목적으로 쓰인다. 

그래서 Service클래스에 의해 만들어진 아이디(객체)는 모두 secret이라는 클래스변수를 사용할 수 있게 되는 것이다.


  또 클래스 변수는 객체를 통한 접근(예:pey.secret)방법 외에 클래스를 통한 접근도 가능하다.

 [ 클래스를 통해 직접 클래스변수에 접근하는 방법 : -

>>> Service.secret

"Secret Information. 2630"

>>> Service.secret = "Secret Information. 1111"

>>> print(pey.secret)

"Secret Information. 1111"

 위처럼 공유되는 변수인 클래스 변수를 클래스를 통해 접근하고, 수정해서 객체를 통해 접근해 값을 확인할 수 있다.


 클래스 함수


  Service라는 업체가 이번엔 더하기 시스템을 제공해주려고 한다고 생각해본다. 그렇게 더하기 시스템을 추가해 클래스를 업그레이드했다.

>>> class Service:

... secret = "Secret Information. 2630" # 유용한 정보 ... def sum(self, a, b): # 더하기 서비스 ... result = a + b ... print("%s + %s = %s입니다." % (a, b, result))

 

 이렇게 추가된 더하기 시스템을 이용하는 방법은 다음과 같다.


 일단 회원가입이 안되어있다면 가입을 한다.

>>> pey = Service()


 그런 다음 더하기 서비스를 이용한다. secret변수를 호출 할때와 마찬가지로 도트(.) 연산자를 이용해서 sum함수를 호출한다.

>>> pey.sum(1,1)

1 + 1 = 2입니다.

 더하기 결과값이 정상출력되었다.


 하지만 의문이 조금 생긴다. 일단 sum함수를 선언할때 self라는 파라미터를 넣어놨는데 호출할때는 사용하지 않았다.. self는 무엇일까?


 self 살펴보기


 일단 결론부터 말하자면 self라는 것은 이 서비스를 이용하는 '회원 이름'을 데리고오는 변수라고 보면 될 것같다. (정확한 표현으로는 호출 시 이용했떤 객체이다.) 

 여기서 오해하기 쉬운데 self라는 이름에 큰 의미가 있어서 이러한 기능을 수행하는 것이 아니라 원래 클래스 함수의 첫번째 파라미터는 이러한 변수로 지정되어있는 것이다. (그렇기에 self말고 다른 이름으로 선언하여도 상관없다.)

... def sum(self, a, b):

... result = a + b ... print("%s + %s = %s입니다." % (a, b, result))
>>> pey.sum(1, 1)


 그리고 특징으로는 매개변수로 입력을 받지 않고 알아서 데리고 온다는 점이다. 그렇기 때문에 다음과 같이 입력하면 오류가 뜬다.

>>> pey.sum(pey, 1, 1) # 틀린표현


Traceback (most recent call last):

  File "<pyshell#33>", line 1, in <module>

    pey.sum(pey,1,2)

TypeError: sum() takes exactly 3 arguments (4 given)

 (※ pey.sum(1, 1)은 Service.sum(pey, 1, 1)처럼 사용해도 동일한 결과를 얻는다.)


 객체 변수


 이러한 서비스에서 이제 더하기 시스템을 이용할때 회원의 이름을 함께 "Prestige. %s + %s = %s" 처럼 이름을 출력해주려고한다. 이를 추가해 변경하면 다음과 같다.

>>> class Service:

... secret = "Secret Information. 2630"

... 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))

 이렇게 변경한뒤 이용자들에게 이 서비스의 이용법을 알려주었고 그렇다면 다음과 같이 이용하게 될것이다.


 일단 pey 라는 아이디를 가지고 가입을 한다.

>>> pey = Service()

 그런뒤 자신의 이름을 서비스 업체에 등록한다.

>>> pey.setname("Prestige")

 이제 더하기 서비스를 이용한다.

>>> pey.sum(1, 1)

Prestige님 1 + 1 = 2입니다.


# 자 이제 위와 같은 시스템이 돌아가는 과정을 살펴보자


 1. pey라는 아이디를 가진 사람이 "Prestige" 라는 이름을 setname 함수에 입력으로 준다.

>>> pey.setname("Prestige")


 2. 이 값("Prestige")은 name이라는 변수에 담기게 되고 그 다음의 문장이 실행된다.

... def setname(self, name):

... self.name = name


 3. self는 setname 함수의 첫 입력값이기때문에 pey라은 아이디가 자동으로 전달되므로 다음과 같이 바뀐다! (self > pey)

pey.name = name


 4. 또 name엔 "Prestige" 담겨있기때문에 결론적으로는 다음과 같은 실행이 이뤄지게 되는 것이다.

 pey.name = "Prestige"


 이로써 pey라는 아이디의 name값은 "Prestige" 라고 저장된것이다. 이제 이것을 호출시키는 부분을 보자.

... def sum(self, a, b):

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

 self.name으로 pey.name 값을 데리고 오는 걸 확인할 수 있다.


 _init_ 이란 무엇인가?


 자 이제 이러한 서비스엔 오류가 발생하는 부분이 있다. 오류의 내용은 다음과 같다.

>>> babo = Service()

>>> babo.sum(1, 1) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in sum AttributeError: 'Service' object has no attribute 'name'


 위와 같이 입력하면 오류가 발생한다. 얼핏보면 왜 그런지 알아채지 못할 수도 있지만 setname 업데이트이후 setname과정을 빠뜨리면 오류가 발생하는 것이다.

 이러한 오류를 띄우지 않게 하기위해 우리는 서비스에 가입할때 이름을 입력받아야만 아이디를 부여해주는 방식으로 바꿔주려한다.


 그래서 _init_ 이라는 함수를 이용하기로 했고 이용하는 방식은 다음과 같다.

>>> class Service: ... secret = "Secret Information. 2630"

... 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_으로 바뀐점이다. 다소 익숙하지않아 어색하지만  _init_함수는 클래스에서 특별한 의미를 갖는데 이는 다음과 같다.


 "객체를 만들 때 항상 실행된다."


 즉, 아이디를 부여받을때 항상 실행된다는 의미다. 따라서 이젠 서비스에 가입하려면 다음과 같이 입력해야한다.


>>> pey = Service("Prestige")


 이전엔 pey = Service() 였지만 이젠 이름까지 입력해야만 한다.


 이렇게 수정하고나면 아래와 같이 과정이 단순하게 변한다.

 기존

>>> pey = Service() >>> pey.setname("Prestige") >>> pey.sum(1, 1)

 수정후

>>> pey = Service("Prestige")
>>> pey.sum(1, 1)


 자 지금까지 객체, self. _init_ 에 대해 간단하게 훑어보았다. 스스로 테스트도 해보고 응용도 해보며 세부적으로 어떤 제약과 경우들이 있는지 확인해보는 것이 좋을 듯하다.


 클래스 자세히 알기


 간단하게 클래스를 배웠으니 이번엔 좀 더 자세히 내용들을 짚고 넘어갈 것이다. 이제부터가 본격적이다.

 일단 클래스란 객체를 찍어내는 공장이라 생각하면 편하다. 어떻게 사용할지는 구조를 보면 된다.

 클래스의 구조


 기본적으로 구조는 다음과 같다.

class 클래스이름[(상속 클래스명)]:

<클래스 변수 1> <클래스 변수 2> ... def 클래스함수1(self[, 인수1, 인수2,,,]): <수행할 문장 1> <수행할 문장 2> ... def 클래스함수2(self[, 인수1, 인수2,,,]): <수행할 문장1> <수행할 문장2> ... ...


 class는 클래스를 만들때 사용하는 예약어이고 그 뒤엔 클래스명을 입력한다. 클래스명 뒤에는 상속할 클래스가 있다면 괄호()안에 상속할 클래스명을 입력한다. 또 클래스 내부에는 클래스 변수, 클래스 함수들이 존재한다.


 사칙연산 클래스 만들기


 간단하다. 각각의 사칙연산 클래스들을 만들어보자.

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


 보통 코딩을 할때는 만들고 나서 사용할때의 과정 및 결과를 생각해놓고 짜는 편이 수월하다. 지금 만들 사칙연산 클래스들의 사용과정을 다음과 같다.


 1. 사칙연산을 가능하게하는 FourCal이라는 클래스를 a = FourCal()처럼 입력해서 a라는 객체를 만든다.

>>> a = FourCal()


 2. 그런 다음 a.setdata(4,2)처럼 입력해 a에 숫자 둘을 지정해주고

>>> a.setdata(4, 2)


 3. a.sum()으로 합을 출력하고

>>> print(a.sum()) 6


 4. a.mul()로 곱을 출력하고

>>> print(a.mul())

8


 5. a.sub()로 차를 출력하고

>>> print(a.sub())

2


 6. a.div()로 나눈 결과를 출력한다.

>>> print(a.div())

2


 이런 클래스를 만들면 된다. 아마 위의 '간단히 사용하기'를 잘 봤다면 대강 그림이 그려질 것이다.


클래스 구조 만들기


 자, 일단 a = FourCal()처럼 객체를 만들 수 있도록 해야하는데 아무 기능없이 만들기만 하는 건 다음과 같이 하면 된다.

>>> class FourCal:

... pass


 우선 대화형 인터프리터에서 pass란 문장만을 포함한 FourCal 클래스를 만든다. pass는 아무것도 수행하지 않는 문법이다. (임시로 코드작성시 주로 사용한다)


 >>> a = FourCal()

>>> type(a) <class '__main__.FourCal'>

 실제로 객체가 만들어지는지 보기위해 만들고 타입을 보면 정상적으로 객체 a FourCal 클래스의 인스턴스가 됬음을 알 수 있다. 

(type함수는 내장함수중 하나로 객체타입을 출력한다) 


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


 이제 아무 기능은 없는 이 객체 a 2개의 숫자 지정할 수 있도록 만들것이다. 마치 아래와 같이

>>> a.setdata(4, 2)


 위와 같은 문장을 실행하기 위해선 아래와 같은 소스 코드가 필요하다.

>>> class FourCal:

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


 '간단히 사용하기'를 해보았기에 코드를 보면 setdata라는 함수를 선언하는데 이 함수는 파라미터로 self(호출한 객체명), first, second라는 변수를 생성하고 또 그렇게 수 두개를 입력받는 것을 알 수 있다. 그리곤 self를 이용해 객체 변수로 self.first, self.second 에 각각 입력받은 두 수를 저장하는 것을 볼 수 있다. 가장 헷갈린다고들 하는 포인트는 역시 "self라고 정한 첫번째 파라미터값은 우리가 입력할 필요가 없다"는 부분이니 잘 짚고 넘어가야한다.


더하기 기능 만들기


 이제 지정된 두 수를 연산해주는 기능을 클래스에 추가하면 된다. 일단 이 또한 동작과정을 아래와 같이 그리고

>>> a = FourCal()

>>> a.setdata(4, 2) >>> print(a.sum()) 6


 이를 가능하도록 FourCal 클래스를 수정한다.

>>> class FourCal:

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

 sum이라는 메서드를 추가했는데 잘 살펴보면 self는 첫 번째 파라미터(호출 하는 객체명)로 들어가는 것이고 result = self.first + self.second로 덧셈 계산을 한 뒤 result에 넣는 모습을 볼 수 있다. 그런 다음 return으로 result값을 출력하는데 프린트문은 없기때문에 아래와같이 사용할 수 있다.

>>> print(a.sum())

6

 result = 4 + 2 가 return되어 print(6)이 되었다고 보면 된다.


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


 이제 더하기를 만들었으니 똑같은 방식으로 나머지 연산도 만든뒤에 클래스에 추가하면된다.

>>> 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() >>> b = FourCal() >>> a.setdata(4, 2) >>> b.setdata(3, 7) >>> a.sum() 6 >>> a.mul() 8 >>> a.sub() 2 >>> a.div() 2 >>> b.sum() 10 >>> b.mul() 21 >>> b.sub() -4 >>> b.div() 

0


 "박씨네 집" 클래스 만들기



 이번엔 또 다른 클래스를 가지고 만들어보면서 더 세부적인내용을 다뤄보자

 클래스 구상하기


 일단 이번 클래스는 HousePark() 이다.

아래와 같은 과정으로 사용되는 클래스이다.


 일단 여태 해왔던것처럼 pey = HousePark()로 객체를 만든 뒤에

>>> print(pey.lastname) 박

 성을 출력시킬수 있고


>>> pey.setname("응용") >>> print(pey.fullname) 박응용

 이름을 설정했다면 성을 포함한 값을 가지도록 할 수 있고


>>> pey.travel("부산")

박응용, 부산여행을 가다.

 위처럼 장소를 여행 장소를 입력하면 저런 문자열을 출력하는 단순한 프로그램이다.


 클래스 기능 만들기


 우선 계산기때처럼 클래스를 만들고, "박"이라는 값을 담을 변수를 선언해 저장한다.

>>> class HousePark:

... lastname = "박"

 이렇게 선언한다면 어떤 객체더라도 객체이름.lastname은 모두 "박"이 될것이다.


 이제 이름을 입력받고 lastname값과 합쳐서 fullname을 만들어주는 작업을 하고

>>> class HousePark:

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

 위의 코드처럼 이번엔 where로 입력받은뒤 여행 문장을 짜준다. 자세히 보면 self.fullname으로 설정한 이름을 데리고 오는걸 확인할 수 있다.


 간단하게 기능을 구현했는데 정해진 절차에 따르지 않으면 오류가 발생한다는 점이 있다. setname없이 travel을 하면 fullname이 없어서 오류가 생기는 것처럼 말이다. 그래서 이 오류를 해결하기위해서 초깃값을 설정해주는 방법을 이용하려한다.


 _init_ 메서드로 초깃값을 설정한다. 


 앞에서도 했지만 _init_은 "객체를 만들 때 항상 실행된다." 라는 의미를 가지는데 


>>> class HousePark:

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

 단지 이번에도 setname을 _init_으로 바꾼것이다.


 이렇게 하면 객체를 만들때 이름을 입력해주어야 하고 입력해주면 깔끔하게 해결되고 편해진다.

>>> pey = HousePark("응용")


 이제 이를 이용하려면 다음처럼 하면 된다.

>>> pey = HousePark("응용") >>> pey.travel("태국"

박응용, 태국여행을 가다.


 클래스의 상속



 클래스는 다른 클래스의 기능을 물려받아 만들 수 있다. 방법은 다음과 같다.

>>> class HouseKim(HousePark): 

... lastname = "김"

 위의 소스를 보면 HouseKim을 만들면서 HousePark를 안에 넣어 기능을 데리고 오고, lastname값은 "김"으로 바꾸는 것을 볼 수 있다.


 이제 잘되는지 사용해보면 된다.

>>> juliet = HouseKim("줄리엣") >>> juliet.travel("방콕"

김줄리엣, 방콕여행을 가다.

 분명 travel은 선언안했음에도 불구하고 HousePark에서 물려받아 만들어진 것을 볼 수 있다.


 메서드 오버라이딩


 이제 여기서 HouseKim()의 travel의 기능을 조금 수정하려한다.

>>> class HouseKim(HousePark):

... lastname = "김" ... def travel(self, where, day): ... print("%s, %s여행 %d일동안 가다." % (self.fullname, where, day))


 이렇게 메서드 이름을 동일하게 다시 구현하는 것을 메서드 오버라이딩(Overriding)이라고 한다.


 연산자 오버로딩



 연산자 오버로딩이란 연산자를 객체끼리 사용할 수 있게 하는 기법으로 다음과 같이 동작시킬 수 있는 것이다.

>>> pey = HousePark("응용")

>>> juliet = HouseKim("줄리엣") >>> pey + juliet 박응용, 김줄리엣 결혼했네


 결과만 보면 어떻게 돌아가는건지 모르겠는데 재밌는 몇 기능을 추가해 다음처럼 작성해보자.

# house.py

class HousePark:

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(HousePark): lastname = "김" def travel(self, where, day): print("%s, %s여행 %d일 가네." % (self.fullname, where, day)) pey = HousePark("응용") juliet = HouseKim("줄리엣") pey.love(juliet) pey + juliet


 위처럼 작성하면 맨아래 네 줄로 다음과 같은 결과를 출력한다.

박응용, 김줄리엣 사랑에 빠졌네

박응용, 김줄리엣 결혼했네


 pey.love(juliet)와 +가 실질적인 출력구문을 만들어내는데 love는 이전에 해왔던것과 같지만 +는 조금 독특하니 눈여겨 보면 좋을 것같다. + 연산자를 객체에 사용하게되면 __add__가 자동으로 호출된다는 것이다.


 이러한 것들은 수치 연산자 메소드라고 불리는데 _두 개씩이 양옆에 붙어있는 형태이다. __add__, __sub__, __mul__, __div__, 등등 여럿 있고 확장 산술 연산자 메소드, 기타 형 변환 메소드 등 다양한 내용이 있으니 만약 이후 사용할 일이 있다면 자료를 참고하면 좋을 것같다.


 "박씨네 집" 클래스 완성하기



 점프 투 파이썬에서 연산자 오버로딩을 이해하기 쉽게 하도록 비극적인 이야기를 클래스로 만든 것이 있으니 소스코드를 읽어보며 이해해보자.


 실행결과

박응용 부산여행을 가다. 김줄리엣 부산여행 3일 가네. 박응용, 김줄리엣 사랑에 빠졌네 박응용, 김줄리엣 결혼했네 박응용, 김줄리엣 싸우네 박응용, 김줄리엣 이혼했네


 소스코드

class HousePark: 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 fight(self, other): print("%s, %s 싸우네" % (self.fullname, other.fullname)) def __add__(self, other): print("%s, %s 결혼했네" % (self.fullname, other.fullname)) def __sub__(self, other): print("%s, %s 이혼했네" % (self.fullname, other.fullname)) class HouseKim(HousePark): lastname = "김" def travel(self, where, day): print("%s, %s여행 %d일 가네." % (self.fullname, where, day)) pey = HousePark("응용") juliet = HouseKim("줄리엣") pey.travel("부산") juliet.travel("부산", 3) pey.love(juliet) pey + juliet pey.fight(juliet) 

pey - juliet




이렇게 필수적인 클래스를 공부해보았다. 몇 단어를 간단하게 요약해서 정리하자면 다음과 같다.


클래스 변수 :

  클래스안의 변수 (객체간 서로 공유되는 변수) ex)pey.secret

 

메서드 :

 클래스 함수

 

self :

  호출하는 객체명

 

객체 변수 :

  메서드 안에서 선언된 변수. 객체.객체변수 = 값 같은 방식으로 사용 (객체별로 고유한 값이 저장되는 변수)

 

_init_ :

 객체가 만들어질때 항상 호출되는 함수

 

클래스 상속 :

 다른 클래스의 기능을 물려받는 것

 

메서드 오버라이딩 :

 상속받을때 어느 메서드의 이름을 동일하게 다시 구현하는 것

 

연산자 오버로딩 :

  연산자를 객체끼리 사용할 수 있도록 하는 기법(ex.__add, __sub__)

 


'Etc > Python' 카테고리의 다른 글

#11 내장함수, 외장함수  (0) 2017.01.12
#10 모듈, 패키지, 예외처리  (0) 2017.01.03
#08 함수, 파일 입출력  (0) 2016.11.30
#07 제어문 (if, while, for)  (0) 2016.11.28
#06 자료형의 참과 거짓, 변수  (0) 2016.11.27