programming/Python

Python // 객체와 클래스

깨래 2017. 12. 1. 19:30

객체 = 속성 + 기능

자동차를 예를 들면 

속성은 몸체의 색, 바퀴의 크기, 엔진의 배기량

기능은 전진, 후진, 좌회전, 우회전


속성은 '변수' 기능은 '함수'


객체 = 변수 + 함수


ex) 위의 자동차를 코드로 표현한 것

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#-*-coding:utf-8 -*-
 
color = 0xFF0000        # 몸체의 색
whell_size = 16         # 바퀴의 크기
displacement = 2000     # 엔진 배기량
 
def forward():      # 전진
    pass
 
def backward():     # 후진
    pass
 
def turn_left():    # 좌회전
    pass
    
def turn_right():    # 우회전
    pass

cs


이제 이 것들을 응집시키려면 class 키워드가 필요하다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#-*-coding:utf-8 -*-
 
class Car:
 
    color = 0xFF0000        # 몸체의 색
    whell_size = 16         # 바퀴의 크기
    displacement = 2000     # 엔진 배기량
 
    def forward(self):      # 전진
        pass
 
    def backward(self):     # 후진
        pass
 
    def turn_left(self):    # 좌회전
        pass
 
    def turn_right(self):    # 우회전
        pass
 
C1=Car()
 
C1.forward()
print(C1.color)
print(C1.whell_size)

cs


21행의 객체: C1, 자료형 Car 클래스
객체 대신 인스턴스라고도 한다.

객체지향 프로그램을 해야하는 이유
일체형 PC는 결합도가 높기 때문에 부품교체시 오류가 날 확률이 높다. 조립형 PC는 결합도가 낮기 때문에 PC를 구입한 지 1년 뒤에 더 뛰어난 그래픽 카드가 출시된다면 그래픽 카드만 따로 구입해 PC의 성능을 향상시킬 수 있다.
예를 들어 A()함수를 수정했을 때 B()함수의 동작에 부작용이 생긴다면 이것은 강한 결합도를 보이는 것이도, A()함수를 수정했는데도 B()가 아무런 영향을 받지 안흔다면 이 두 함수는 약한 결합으로 이루어져있다.
하드웨어와는 달리 소프트웨어는 챙겨줘야 할 것이 매우 많아진다. 고객과 품질 부서로부터 버그 리포트가 올라오면 프로그래머는 패치를 만들어야 한다. 이 때 소프트웨어의 결합도가 높으면 코드를 고칠 때마다 부작용이 발생할 가능성이 높아진다. 같은 목적과 기능을 위해 객체로 묶인 코드 요소(변수,함수)들은 객체 내부에서만 강한 응집력을 발휘하고 객체 외부에 주는 영향은 줄이게 된다. 코드가 객체 위주로 이루어질 수 있도록 지향하는 프로그래머의 노력은 코드의 결합도를 낮추는 결과를 낳게 된다. 이것이 객체지향 프로그래밍의 장점이다.

클래스

객체는 클래스의 인스턴스이며 클래스는 자료형이다.
class 클래스 이름:
코드블록

1
2
3
4
5
6
7
8
9
10
11
12
class BusinessCard:
    def set_info(self, name, email, addr):
        self.name = name
        self.email = email
        self.addr = addr
 
    def print_info(self):
        print("--------------------")
        print("Name: ", self.name)
        print("E-mail: ", self.email)
        print("Address: ", self.addr)
        print("--------------------")

cs



파이썬의 class 키워드를 통해 BusinessCard라는 새로운 타입을 만들었으니 인스턴스를 생성해야한다.
클래스 인스턴스를 생성하려면 '클래스 이름()'과 같이 적으면 된다. 
그리고 생성된 인스턴스에 점(.)을 찍어서 인스턴스 변수나 메서드에 접근한다.

1
2
member1 = BusinessCard()
member1.set_info("kim""kim@gmail.com""USA")

cs


위 코드를 살펴보면 먼저 인스턴스를 생성하고 생성된 인스턴스에 데이터를 입력하는 순으로 코드가 구성되어 있다. 붕어빵으로 비유해 보면 붕어빵 틀(클래스)을 이용해 팥소를 넣지 않은 상태로 붕어빵을 구운 후(인스턴스 생성) 나중에 다시 붕어빵 안으로 팥소를 넣는 것과 비슷하다.
어떻게 하면 클래스 인스턴스 생성과 초기값 입력을 한 번에 처리할 수 있을까 ?

파이썬 클래스에는 인스턴스가 생성과 동시에 자동으로 호출되는 메서드인 생성자가 존재한다
__init__(self) 와 같은 이름의 메서드를 생성자라고 하며, 파이썬 클래스에서 __로 시작하는 함수는 모두 특별한 메서드를 의미한다.

1
2
3
4
5
6
7
8
9
10
11
12
class BusinessCard:
    def __init__(self, name, email, addr):
        self.name = name
        self.email = email
        self.addr = addr
 
    def print_info(self):
        print("--------------------")
        print("Name: ", self.name)
        print("E-mail: ", self.email)
        print("Address: ", self.addr)
        print("--------------------")

cs


위처럼 set_info메소드를 __init__으로 바꾸어주기만 하면 된다.

init은 객체가 생성될 때 호출되는 메소드로써, 객체의 초기화를 담당한다. 

새로 정의된 BusinessCard 클래스의 생성자는 인자가 4개임을 확인할 수 있다. 물론 첫 번째 인자인 self는 생성되는 인스턴스를 의미하고 자동으로 값이 전달되므로 인스턴스를 생성할 때 명시적으로 인자를 전달해야 하는 것은 3개이다. 

따라서 인스턴스를 생성할 때 3개의 인자를 전달해주지 않으면 오류가 발생한다.


1
2
member1 = BusinessCard('kim','kim@gmail.com','USA')
member1.print_info()

cs


출력결과
--------------------
('Name: ', 'kim')
('E-mail: ', 'kim@gmail.com')
('Address: ', 'USA')
--------------------

self

클래스 내에 정의된 함수를 메서드라고 부른다그리고 메서드의 첫 번째 인자는 항상 self여야 한다
하지만 메서드의 첫 번째 인자가 항상 self여야 한다는 것은 사실 틀린 말이다.

1
2
3
4
5
6
class Foo:
    def func1():
        print("function 1")
 
    def func2(self):
        print("function 2")

cs


일단 클래스를 정의했으니 해당 클래스에 대한 인스턴스를 생성해보겠다.
그리고 생성된 인스턴스를 통해 인스턴스 메서드를 호출한다.
Foo 클래스의 func2 메서드는 메서드의 인자가 self뿐이므로 실제 메서드를 호출할 때는 인자를 전달할 필요가 없다.

f = Foo()
f.func2()

출력결과
function 2

위 코드에서 메서드 호출의 결과를 보면 화면에 정상적으로 'function 2'가 출력된 것을 볼 수 있다.
참고로 func2 메서드의 첫 번째 인자는 self지만 호출할 때는 아무것도 전달하지 않는 이유는 첫 번째 인자인 self에 대한 값은 파이썬이 자동으로 넘겨주기 때문!

 func1 메서드처럼 메서드를 정의할 때부터 아무 인자도 없는 경우에는 어떻게 될까? 다음과 같이 인스턴스를 통해 func1()을 호출해보면 오류가 발생한다. 
오류 메시지를 살펴보면 "func1()은 인자가 없지만 하나를 받았다"라는 것을 볼 수 있다. 
이는 파이썬 메서드의 첫 번째 인자로 항상 인스턴스가 전달되기 때문에 발생하는 문제이다.

1
2
3
4
5
f.func1()
Traceback (most recent call last):
  File "<pyshell#25>", line 1in <module>
    f.func1()
TypeError: func1() takes 0 positional arguments but 1 was given

cs



클래스 변수와 인스턴스 변수

1
2
3
4
5
6
7
8
9
class Account:
    num_accounts = 0
 
    def __init__(self, name):
        self.name = name
        Account.num_accounts += 1
 
    def __del__(self):
        Account.num_accounts -= 1

cs

Account 클래스에는 생성자와 소멸자가 있다.

생성자 (__init__)가 클래스의 인스턴스가 생성될 때 자동으로 호출되는 함수라면

소멸자 (__del__)는 클래스의 인스턴스가 소멸될 때 자동으로 호출되는 함수이다.


Account 클래스에 num_accounts와 같이 클래스 내부에 선언된 변수를 클래스 변수라고 하며, self.name과 같이 self가 붙어 있는 변수를 인스턴스 변수라고 한다.

클래스 변수는 Account 클래스의 네임스페이스에 위치하며, self.name과 같은 인스턴스 변수는 인스턴스의 네임스페이스에 위치하게 됩니다.

그렇다면 언제 클래스 변수를 사용해야 하고 언제 인스턴스 변수를 사용해야 할까?

은행에 가서 계좌를 개설하면 새로운 계좌가 하나 개설된다. 상황을 파이썬으로 표현하면 다음과 같이 Account 클래스의 인스턴스를 생성하는 것에 해당한다.

kim = Account("kim") lee = Account("lee")

저장이 잘 되어있는지 출력을 해본다

>>> kim.name 'kim' >>> lee.name 'lee'

그렇다면 지금까지 은행에서 개설된 계좌는 총 몇 개일까?

kim 인스턴스나 lee 인스턴스를 통해 num_accounts라는 이름에 접근하면 총 계좌 개설 개수가 2개로 나오는 것을 알 수 있다.

>>> kim.num_accounts 2 >>> lee.num_accounts 2

인스턴스 간에 서로 공유해야 하는 값은 클래스 변수를 통해 바인딩해야 한다. 왜냐하면 파이썬은 인스턴스의 네임스페이스에 없는 이름은 클래스의 네임스페이스에서 찾아보기 때문에 이러한 특성을 이용하면 클래스 변수가 모든 인스턴스에 공유될 수 있기 때문이다.