2. 클래스 Last updated: 2023-10-17 10:29:58

파이썬은 객체 지향(Object Oriented) 프로그래밍 언어이다. 파이썬에서 대부분은 객체로 구성되고, 속성(property)과 메서드(method)를 갖는다. 클래스는 객체 인스턴스를 생성하기 위한 틀을 제공한다. 클래스는 빵틀이고 객체는 빵틀로 구워낸 빵과 같다. 빵 틀을 이용하면 모양이 같은 여러개의 객체 인스턴스를 만들 수 있다.

2.1 클래스 정의

클래스는 class 라는 키워드를 사용한다. 형식은 다음과 같이 간단한다. class 키워드를 입력하고 그 뒤에 작성할 클래스의 이름을 입력한다. 클래스 정의가 끝났다면 콜론(:)을 입력해 정의를 마무리한다. 클래스 내용은 그 다음 줄부터 작성하되 함수 정의할 때와 마찬가지로 앞에 들여쓰기(보통 공백 4개)를 하고 작성하면 클래스에 포함된 속성 혹은 메서드 임을 파이썬이 알게 된다.

파이썬의 클래스를 위한 특징을 정리하면 다음과 같다.

  • 모든 클래스는 object를 상속받는다.

  • 클래스 정의는 class 키워드로 한다.

  • 클래스의 객체의 인스턴스를 생성하면 자동으로 생성자를 호출한다.


A. 기본 클래스 정의

다음은 클래스의 기본 형식이다.

형식

class my_class:
    def __init__(self):
        # do something

설명

• my_class : 정의할 클래스의 이름
• __init__() : 클래스의 생성자 메서드, 첫 번째 인자는 무조건 자신을 가리키는 self여야 한다.

다음은 기본 생성자를 갖는 클래스의 예제이다.


[예제 5- 17] 기본 생성자를 갖는 클래스 구현하기

# [1] 클래스를 정의한다.
class FruitInventory:
    # [2] 기본 생성자이다.
    def __init__(self):
        self.apple_cnt = 10
        self.banana_cnt = 10

    # [3] 출력
    def print_inventory(self):
        print("현재 과일 재고 : 사과 {}개, 바나나 {}개 이다.".format(self.apple_cnt, self.banana_cnt))

# [4] 클래스 생성 및 메서드 호출
fi_1 = FruitInventory()
fi_1.print_inventory()

[결과]

현재 과일 재고 : 사과 10개, 바나나 10개 이다.

위 예제에 대해 설명한다.

  • [1]에서 클래스를 생성한다.

  • [2]에서 매개변수가 없는 기본 생성자를 정의한다. 생성자 내에서 apple_cnt와 banana_cnt를 10으로 초기값 설정한다. 이렇게 기본값이 정해져 있는 경우 매개변수 없이 기본 생성자만 있어도 구현이 가능하다.

  • [3]에서 현재 과일 재고를 문자열로 출력한다.

  • [4]에서 클래스 객체를 생성한다. 이때 매개변수는 사용하지 않았다. 객체 생성 후 메서드 print_inventory( ) 함수를 호출했다.


B. 생성자를 갖는 클래스 정의

클래스는 생성자를 갖을 수 있다. 생성자는 클래스가 생성될 때 가장 먼저 호출되는 함수이다. 파이썬에서 생성자도 함수이므로 def 키워드로 시작한다. 생성자의 이름은 init(self) 형태를 갖는다. init은 던더(double under)로 시작하고 던더로 끝나는 함수 이름을 갖는다. 파이썬에서 이와 같이 던더가 붙어 있는 메서드나 변수는 시스템이 직접 관리하는 것이라고 이해하면 된다.

형식

class my_class:
    def __init__(self):
        # do something

    def __init()__(self, arg1, arg2, …):
        # do something

설명

• my_class : 정의할 클래스의 이름
• arg1, arg2 : 생성자에 추가로 입력되는 인자

파이썬 클래스의 이름을 입력하는 방법은 CapWords 규칙 권장한다. (PEP-8 참고)

CapWords 명명 규칙은 단어의 첫자를 대문자로 하고 여러 단어를 겹쳐 사용할 경우 언더바(_) 없이 붙여 쓰고, 약어인 경우 모두 대문자로 사용하는 것을 의미한다.


Hint! 클래스 이름 작성 규칙

클래스는 CapWords 명명 규칙을 따른다. 이 규칙은 단어의 첫자를 대문자로 하고 여러 단어를 겹쳐 사용할 경우 언더바(_) 없이 붙여쓰고, 약어인 경우 모두 대문자로 사용하는 것을 의미한다.


권장하는 예)

FruitInventory, HTTPConnectionManage, ApplicationCategory

권장하지 않는 예)

Fruit_Inventory, http_connection_management, applicationcategory


다음에 생성자를 갖는 클래스의 예제를 살펴보자.


[예제 5- 18] 인자가 있는 기본 생성자의 클래스 구현하기

# [1] 클래스를 정의한다.
class FruitInventory:
    # [2] 기본 생성자이다.
    def __init__(self, apple, banana):
        self.apple_cnt = apple
        self.banana_cnt = banana

    # [3] 출력
    def print_inventory(self): 
        print("현재 과일 재고 : 사과 {}개, 바나나 {}개 다.".format(self.apple_cnt, self.banana_cnt))

# [4] 클래스 생성 및 메서드 호출
fi_1 = FruitInventory(10, 12)
fi_1.print_inventory()

[결과]

현재 과일 재고 : 사과 10개, 바나나 12개 이다.

위 예제에 대해 설명한다.

  • [1]에서 클래스를 생성한다.

  • [2]에서 매개변수를 두 개 입력받는 생성자를 정의한다. 입력 받은 매개 변수 apple과 banana는 apple_cnt와 banana_cnt 변수에 값을 할당한다. 객체를 생성할 때 apple과 banana의 입력을 받아서 시작할 수 있다.

  • [3]에서 현재 과일 재고를 문자열로 출력한다.

  • [4]에서 클래스 객체를 생성한다. 이때 매개변수 값을 함께 입력한다. 객체 생성 후 메서드 print_inventory( ) 함수를 호출했다.

물론 생성자의 매개변수도 기본값을 지정할 수 있다. 기본값을 그대로 사용하려면 생성자에 매개변수를 전달하지 않으면 된다.


[예제 5- 19] 클래스 생성자의 인자 기본값 지정하기

# [1] 클래스를 정의한다.
class FruitInventory:
    # [2] 기본 생성자이다.
    def __init__(self, apple=10, banana=10):
        self.apple_cnt = apple
        self.banana_cnt = banana

    # [3] 출력
    def print_inventory(self):
        print("현재 과일 재고 : 사과 {}개, 바나나 {}개 이다.".format(self.apple_cnt, self.banana_cnt))

# [4] 클래스 생성 및 메서드 호출
fi_1 = FruitInventory()
fi_1.print_inventory()

[결과]

현재 과일 재고 : 사과 10개, 바나나 10개 이다.


2.2 메서드 종류

파이썬의 클래스에서 사용 목적에 따라 세 종류의 메서드를 지원한다. 작성 방법과 사용 방법이 조금씩 다르니 특성을 잘 확인하고 용도에 맞게 사용해 보시기 바란다.


A. 인스턴스 메서드 (Instance method)

인스턴스 메서드는 클래스의 가장 많이 사용되는 기본 메서드 형식이다. 인스턴스 메서드는 첫 번째 매개변수로 self를 갖는다. 이는 클래스 객체 자신을 나타낸다. 이 self 객체를 통해 클래스에 정의된 다른 변수와 메서드를 접근할 수 있다. 다음은 인스턴스 메서드를 사용한 예제이다.

[예제 5- 20] 클래스의 인스턴스 메서드 사용하기

# [1] 클래스 선언
class Fruit(object):
    
    # [2] 생성자, 이름 설정
    def __init__(self, name):
        # [3] 인스턴스 변수에 값 설정
        self.name = name

    # name 출력
    def print_name(self):
        # [4] 인스턴스 변수 이용
        print("my name is", self.name)

    # [5] 이름 얻어오기 인스턴스 메서드
    def get_name(self):
        return self.name
    
    # [6] 이름 변경 인스턴스 메서드
    def set_name(self, name):
        self.name = name
    
# [7] 인스턴스 생성
frt1 = Fruit('apple')
frt2 = Fruit('pine apple')

# [8] 인스턴스 메서드 호출
frt1.print_name()
frt2.print_name()

[결과]

my name is apple
my name is pine apple

위 예제에 대해 설명한다.

  • [1]에서 클래스를 선언한다. object는 모든 클래스가 자동으로 상속받는 클래스이므로 따로 명시하지 않고 Fruit( ) 라고 작성해도 결과는 동일하다.

  • [2]에서 생성자 함수를 정의한다. 매개변수로 name을 입력 받는다.

  • [3]에서 입력받은 매개변수 name의 값을 클래스 변수인 self.name에 값을 할당한다.

  • [4]에서 인스턴스 변수 값을 이용해 기능을 수행한다. 클래스 변수는 self 객체를 이용해 접근한다.

  • [5]는 클래스 메서드로 클래스 변수 값을 반환하는 getter 함수이다.

  • [6]은 클래스 메서드로 클래스 변수 값을 변경하는 setter 함수이다.

  • [7]에서 생성자의 인자에 ‘apple’을 입력하고 Fruit 클래스의 인스턴스 frt1을 생성한다. 그리고 두번째로 생성자의 인자에 ‘pine apple’을 입력하고 Fruit 클래스의 인스턴스 frt2를 생성한다.

  • [8]에서 생성한 Fruit 클래스의 인스턴스인 frt1와 frt2의 인스턴스 메서드인 print_name( )을 호출한다.

  • frt1의 출력 결과는 ‘my name is apple’ 이고, frt2의 결과는 ‘my name is pine apple’이다. 이와 같이 각각 별도의 인스턴스가 생성된 후 인스턴스 변수 값을 변경하더라도 서로 영향을 받지 않는다.


B. 클래스 메서드 (Class method)

클래스 메서드를 이해하려면 우선 클래스 변수를 이해해야 한다. 클래스 변수는 같은 유형의 클래스 간에 값을 공유하는 변수이다. 보통 인스턴스 변수는 클래스마다 독립적이기 때문에 각각 변수 값을 변경하더라도 서로 영향을 받지 않는다. 하지만 클래스 변수는 동일한 타입의 클래스 간에 값을 공유하는 특징이 있다. 다음은 Fruit라는 클래스의 예제이다. 클래스 변수로 fruit_type_num이 있고, 이 변수 값은 클래스의 생성자가 호출될 때 마다 1씩 증가한다.

[예제 5- 21] 클래스 메서드 사용하기

class Fruit:
    # [1] 클래스 변수
    fruit_type_num = 0

    def __init__(self, name):
        # [2] 인스턴스 변수
        self.name = name
        # [3] 클래스 변수값 증가
        Fruit.fruit_type_num += 1

# [4] apple과 banana 인스턴스 생성
f1 = Fruit('apple')
f2 = Fruit('banana')

# [5] 각 객체 인스턴스의 값을 출력
print(f1.name, f1.fruit_type_num)
print(f2.name, f2.fruit_type_num)

[결과]

apple 2
banana 2

위 예제에 대해 설명한다.

  • [1]에서 클래스 변수를 선언한다. 이 값은 생성자 함수 안에 있는 [3]에서 1씩 증가한다.

  • [2]에서는 인스턴스 변수 값을 할당한다.

  • [4]에서 apple과 banana의 이름으로 Fruit 객체 인스턴스를 각각 생성 했다.

  • [5]에서 각 클래스 객체의 name과 fruit_type_num을 출력한다.

  • 결과를 확인해보면 인스턴스 객체인 name은 서로 다르게 ‘apple’과 ‘banana’를 출력했다. 하지만 fruit_type_num 변수값은 두 객체가 동일하게 2를 출력했다.

아래 그림을 보면 좀더 쉽게 이해할 수 있다. 인스턴스 객체는 각 인스턴스가 직접 변수를 관리하는데, 클래스 변수는 각 인스턴스가 가지지 않고 공통의 클래스 영역에서 변수를 관리한다.


image

[그림 5-2-1] 클래스 상속 구조


클래스 변수를 사용하려면 {클래스명}.{클래스변수명}과 같이 사용을 했다. 하지만 만일 인스턴스 변수처럼 사용하려면 어떻게 해야 할까? 이 때는 클래스 메서드를 사용하면 된다. 클래스 메서드에서는 첫 번째 매개변수로 cls라는 클래스 객체가 넘어온다. 이 매개변수를 이용해 클래스 변수를 접근하면 된다.

예제를 확인해보자.

다음은 이전의 예제와 동일하지만 클래스 메서드를 이용한 방식이다.

class Fruit:
    # [1] 클래스 변수
    fruit_type_num = 0
    def __init__(self, name):
        # [2] 인스턴스 변수
        self.name = name
        # [3] 클래스 변수값 증가
        self.increase_fruit_type()
    
    # [6] 클래스 메서드 선언
    @classmethod
    def increase_fruit_type(cls):
        # [7] 클래스 변수 사용
        cls.fruit_type_num += 1

# [4] apple과 banana 인스턴스 생성
f1 = Fruit('apple')
f2 = Fruit('banana')

# [5] 각 객체 인스턴스의 값을 출력
print(f1.name, f1.fruit_type_num)
print(f2.name, f2.fruit_type_num)

[결과]

apple 2
banana 2

위 예제에 대해 설명한다.

  • [3]에서 직접 클래스 변수를 사용한 것이 아니라 클래스 메서드를 호출하였다.

  • [6]에서 클래스 메서드를 선언하였다. 클래스 메서드를 선언하려면 하수 정의문 위에 @classmethod라는 키워드를 입력해야 한다. 그리고 첫 번째 인자로 cls를 사용해야 한다. cls는 클래스 객체를 나타내며 클래스 변수에 접근할 수 있다.

  • [7]에서 cls 객체를 이용해 클래스 변수값을 수정했다.

  • 나머지 결과는 이전과 동일하다.


C. 정적 메서드 (Static method)

정적 메서드라고도 하며, 클래스의 변수나 메서드를 사용하지 않는 독립적인 함수이다. self나 cls와 같은 클래스 매개변수도 없다. 정적 메서드는 클래스의 속성을 이용하기 보다는 속한 클래스와 유사한 유틸리티 기능의 함수라고 생각하면 이해가 쉽다.

[예제 5- 23] 정적 메서드 사용하기

class Fruit:
    
    # [1] 정적 메서드 선언
    @staticmethod
    def get_fruit_price(name):
        # [2] 함수 내부 변수
        fruit_price = {'apple':1000, 'banana':800, 'orange':700}
        
        # [3] 이름 존재 여부 검사
        if name in fruit_price:
            # [4] 가격 반환
            return fruit_price[name]
        else:
            return 0

# [5] 스테틱 메서드 호출
print(Fruit.get_fruit_price('apple'))
print(Fruit.get_fruit_price('banana'))
print(Fruit.get_fruit_price('orange'))
print(Fruit.get_fruit_price('mango'))

[결과]

1000
800
700
0

위 예제에 대해 설명한다.

  • [1]에서 클래스에 정적 메서드를 선언한다. 정적 메서드는 함수 중의 위에 @staticmethod 키워드를 적어준다. 함수의 매개변수에 self나 cls같은 필수 항목은 없다. 함수 구현에 필요한 변수를 사용하면 된다.

  • [2]에서 정의한 딕셔너리 변수 fruit_price 는 함수 내에서만 사용하는 객체이다. 정적 메서드는 클래스의 다른 멤서 변수나 멤버 함수를 사용할 수 없다.

  • [3]에서 이름이 딕셔너리 내에 존재하는지 검사한다.

  • [4]에서 딕셔너리에 이름이 있는경우 그에 해당하는 가격을 반환한다.

  • [5]에서 정적 메서드를 호출한다. 정적 메서드는 클래스 객체를 생성할 필요 없이 클래스명을 직접 입력하여 호출한다.


2.3 클래스 상속 (Inheritance)

클래스는 상속이 가능하다. 상속을 하는 이유는 동일한 클래스의 멤버 함수나 변수를 매번 다시 구현하지 않고, 공통적인 것을 부모 클래스에 구현한 후 자식 클래스가 상속받아 보다 쉽게 구현할 수 있게 도와준다. 공통적인 내용을 부모 클래스에 구현해서 사용하면 공통 내용이 변경되도 부모 클래스 한 곳에서만 수정을 하면 자식 클래스에서는 따로 수정을 하지 않아도 함께 반영되므로 관리가 편리한다.

파이썬의 클래스 상속을 사용하기 전에 알아야 할 특성이 몇 가지 있다.

  • 부모 클래스에서 정의한 클래스 메서드와 변수를 자식 클래스가 상속받아 사용이 가능하다.

  • 자식의 생성자 함수 init( )가 호출되더라도 부모의 생성자 함수 init( )은 자동으로 호출되지 않는다. 부모 생성자를 원할 경우 직접 호출해 주어야 한다.

  • 다중 상속이 가능하다.

  • 부모 클래스의 메서드와 동일한 이름의 메서드를 자식 클래스에서 정의하면 이를 재구현(override)이라고 하고, 이후 특별한 지시가 없는 한 자식 클래스의 메서드가 호출된다.


A. 클래스 기본 상속

다음은 기본적인 클래스 상속에 대한 예시이다. 부모 클래스에 생성자와 클래스 멤버 변수를 정의하였고, 자식 클래스에서 이를 상속 받아 정의하였다.

[예제 5- 24] 부모 클래스 상속하기

# [1] 부모 클래스 선언
class Fruit:
    # [2] 부모 클래스의 생성자
    def __init__(self, name='fruit'):
        self.name = name

    # [3] 부모 클래스의 메서드
    def print_name(self):
        print(self.name)

# [4] 자식 클래스 선언
class Apple(Fruit):
    pass

# [5] 자식 클래스 생성
a1 = Apple()
a1.print_name()

# [6] 자식 클래스 생성
a2 = Apple('apple')
a2.print_name()

[결과]

fruit
apple

위 예제에 대해 설명한다.

  • [1]에서 부모 클래스 Fruit을 선언한다. Fruit은 Object를 상속받은 클래스이다.

  • [2]에서 init( )은 부모 클래스의 생성자이다. 기본 매개변수로 name을 입력 받는다. 입력받은 name은 인스턴스 변수인 self.name에 할당한다.

  • [3]에서 부모 클래스의 메서드로 부모의 인스턴스 변수인 self.name값을 출력한다.

  • [4]에서 Fruit을 상속받은 자식 클래스 Apple을 정의한다. Apple 클래스는 부모 클래스 Fruit을 상속받았을 뿐 다른 기능은 없다.

  • [5]에서 자식 클래스의 인스턴스 객체를 생성한다. 이 때 인자값을 주지 않았으므로 self.name값은 부모 클래스 생성자에 정의한대로 기본값인 ‘fruit’가 된다.

  • [6]에서 두 번째 자식 클래스의 인스턴스 객체를 생성한다. 이 때는 name 인자값으로 ‘apple’을 넘겨주어 부모 클래스의 인스턴스 변수 self.name 값이 ‘apple’이 된다.

  • 인스턴스 a1와 a2의 print_name( ) 메서드 실행 결과는 서로 다르다.


B. 부모 클래스 메서드 호출

클래스를 상속받은 경우 부모 클래스의 메서드를 명시적으로 호출할 경우가 있다. 부모의 메서드를 자식 클래스에서 재구현하지 않은 경우라면 상속받은 메서드라도 그냥 자신의 메서드인것 처럼 사용해도 되지만 만일 동일한 이름의 메서드를 재구현 한 경우 그냥 호출하면 자식의 메서드가 호출되고, 부모의 클래스를 호출하려면 특별히 부모 클래스를 지칭하는 super 키워드를 사용해야 한다. 부모 클래스를 명시적으로 호출하는 예제에 대해 살펴보자.

[예제 5- 25] 부모 클래스 호출하기

# [1] 부모 클래스 선언
class Fruit:
    
    # [2] 부모 클래스의 생성자
    def __init__(self, name='fruit'):
        self.name = name

    # [3] 부모 클래스의 메서드
    def print_name(self):
        print("(Parent)", self.name)

# [4] 자식 클래스 선언
class Apple(Fruit):
    
    # [5] 자식 클래스의 생성자
    def __init__(self, name='apple'):
        self.name = name
        
    # [6] 자식 클래스의 메서드
    def print_name(self):
        super().print_name()
        print("(Child)", self.name)

# [7] 자식 클래스 Apple 생성
apl = Apple()
apl.print_name()

[결과]

(Parent) apple
(Child) apple

위 예제에 대해 설명한다.

  • [1]에서 부모 클래스를 선언한다.

  • [2]에서 부모의 생성자 메서드를 정의한다.

  • [3]에서 부모의 print_name( ) 메서드를 정의한다.

  • [4]에서 Fruit 클래스를 상속받은 자식 클래스 Apple을 선언한다.

  • [5]에서 자식의 생성자 메서드를 정의한다. 이 안에서 부모 클래스의 생성자와 동일하게 self.name에 값을 지정한다.

  • [6]에서 자식 클래스의 print_name( ) 메서드를 재정의한다. 구현 내용을 보면 부모 클래스의 print_name( ) 메서드를 호출하기 위해 super( ) 키워드를 사용했다. 그리고 자식의 메서드 내용도 추가했다.

  • [7]에서 Apple 클래스의 인스턴스를 생성했다. 그리고 print_name( ) 메서드를 호출했다. 이렇게 하면 자식 클래스인 Apple의 print_name( ) 메서드가 실행된다. 그 안에서 부모 클래스도 호출하고 자식 클래스 메서드의 내용도 실행된 결과를 확인할 수 있다.


C. 부모 클래스 메서드 재구현 (Overriding)

부모 클래스에서 상속받은 메서드를 재구현 하는 방법에 대해 설명한다. 클래스를 상속받을 때 부모 클래스에 구현된 메서드를 자식 클래스에서 동일한 이름으로 재 구현할 수 있다.

메서드 재구현 하는 방법은 다음과 같다.

  • 부모 클래스에 구현된 메서드를 자식 메서드에 동일한 이름으로 정의한다.

  • 새로 정의한 자식의 메서드의 내용을 다시 작성한다.

[예제 5- 26] 부모 클래스의 재구현 하기

# [1] 부모 클래스 선언
class Fruit:
    fruit_kinds = 0
    # [2] 부모 클래스의 생성자
    def __init__(self, name='fruit'):
        Fruit.fruit_kinds += 1
        self.name = name

    # [3] 부모 클래스의 메서드
    def print_name(self):
        print(Fruit.fruit_kinds, "(Parent)", self.name)

# [4] 자식 클래스 선언
class Apple(Fruit):
    
    # [5] 자식 클래스의 생성자
    def __init__(self, name='apple'):
        # 부모 클래스의 생성자 호출
        super().__init__(name)
                    
# [6] 자식 클래스 선언
class Banana(Fruit):
    
    # [7] 자식 클래스의 생성자
    def __init__(self, name='banana'):
        # 부모 클래스의 생성자 호출
        super().__init__(name)
    
    # [8] 자식 클래스의 메서드
    def print_name(self):
        print(Fruit.fruit_kinds, "(Child)", self.name)
        
# [9] 자식 클래스 선언
class Orange(Fruit):
    
    # [10] 자식 클래스의 생성자
    def __init__(self, name='orange'):
        # 부모 클래스의 생성자 호출
        super().__init__(name)
    
    # [11] 자식 클래스의 메서드
    def print_name(self):
        # 부모 클래스의 메서드 호출
        super().print_name()
        print(Fruit.fruit_kinds, "(Child)", self.name)

# [12] 자식 클래스 Apple 생성
apl = Apple()
apl.print_name()

# [13] 자식 클래스 Banana 생성
ban = Banana()
ban.print_name()

# [14] 자식 클래스 Orange 생성
org = Orange()
org.print_name()

[결과]

1 (Parent) apple
2 (Child) banana
3 (Parent) orange
3 (Child) orange

위 예제에 대해 설명한다.

  • [1]에서 부모 클래스를 정의한다. 부모 클래스 Fruit을 선언한다. Fruit 클래스는 fruit_kinds라는 클래스 변수를 갖는다.

  • [2]에서 클래스 변수 fruit_kinds 값이 1씩 증가된다. 이 값은 객체가 생성될 때마다 증가된다. 또한 인스턴스 변수 self.name에 이름을 설정한다.

  • [3]에서 부모 클래스의 print_name( ) 메서드를 정의한다. 만일 자식 클래스에서 해당 메서드를 재구현하지 않는다면 부모 클래스의 print_name( ) 메서드다 실행된다.

  • [4]에서 Fruit 클래스를 상속받은 자식 클래스 Apple 클래스를 정의한다. Apple 클래스는 부모 클래스의 내용을 그대로 상속받으며 생성자만 재구현 하였다.

  • [5]에서 자식 클래스의 생성자를 정의한다. 생성자에서는 부모 클래스의 생성자 함수를 호출하기 위해 super( ).init( )를 호출하였다. 이렇게 부모 클래스의 생성자를 호출하지 않으면 부모 클래스의 Fruit.fruit_kinds값이 1씩 증가되지 않는다.

  • [6]에서는 새로운 자식 클래스인 Banana를 정의한다. Apple 클래스와 마찬가지로 Fruit 클래스를 상속받는다.

  • [7]에서 자식 클래스의 생성자를 정의한다. 생성자에서는 부모 클래스의 생성자 함수를 호출하기 위해 super( ).init( )를 호출하였다.

  • [8]에서 부모 클래스에 있는 print_name( ) 메서드를 재정의 하였다. 이제 자식 클래스의 인스턴스에서 print_name( ) 메서드를 호출하면 부모가 아닌 자식의 print_name( ) 메서드가 실행된다.

  • [9]에서 새로운 자식 클래스인 Orange를 정의한다. Apple 클래스와 마찬가지로 Fruit 클래스를 상속받았다.

  • [10]에서 자식 클래스의 생성자를 정의한다. 생성자에서는 부모 클래스의 생성자 함수를 호출하기 위해 super( ).init( )를 호출하였다.

  • [11]에서 부모 클래스에 있는 print_name( ) 메서드를 재정의 하였다. 단, 부모 클래스의 print_name( ) 메서드를 호출하고나서, 자식 메서드의 내용을 추가하였다.

  • [12]에서 Apple 클래스의 인스턴스를 생성하고 print_name( ) 메서드를 호출하였다. Apple 클래스는 print_name( ) 메서드를 재구현하지 않았으므로 부모의 print_name( ) 메서드만 호출되었다.

  • [13]에서 Banana 클래스의 인스턴스를 생성하고 print_name( ) 메서드를 호출하였다. Banana 클래스는 print_name( ) 메서드를 재구현하였으므로 print_name( ) 메서드를 호출하면 자식의 print_name( ) 메서드가 실행된다.

  • [14]에서 Orange 클래스의 인스턴스를 생성하고 print_name( ) 메서드를 호출하였다. Orange 클래스는 print_name( ) 메서드를 재구현하였고, 함수 내에서 부모의 print_name( ) 메서드를 호출한다. 따라서 부모의 print_name( ) 내용과 자식의 print_name( ) 내용이 모두 실행되었다.


2.3 다형성 (Polymorphism)

다형성은 동일한 성격을 갖는 다른 형태를 의미한다. 실제 서로 다른 클래스이지만 클래스의 메서드와 클래스 변수 등은 동일한 이름으로 동일하게 동작 시킬 수 있다. 파이썬에서는 객체를 엄격하게 캐스팅하지 않기 때문에 다형성을 구현하는 것이 좀더 자유롭다.


A. 함수 다형성

파이썬에서 제공하는 내장 함수 들 중에서 객체 종류가 다르더라도 동일하게 동작하는 함수들이 여럿 있다. 그 대표적인 함수가 len( ) 이다. 해당 함수에 인자로 사용될 수 있는 객체가 한 가지가 아니라 길이를 나타낼 수 서로 다른 종류의 객체에 대해서도 이 함수를 이용해 길이를 얻어올 수 있다. 이러한 함수 다형성에 해당하는 내장 함수에 대해 알아보자.

형식

len(var)

파라미터

• var : 집합 객체

반환

(int) 객체의 길이

설명

집합 객체에서 길이를 얻어오는 함수이다. var는 길이 값을 얻어오기 위한 대상 객체로 리스트, 튜플, 세트, 딕셔너리, 문자열, range, len( ) 함수를 구현하고 있는 클래스에 사용할 수 있다.

len( ) 함수를 사용한 예시에 대해 살펴보자.


[예제 5- 27] len( ) 함수 정의하기

# [1] 클래스 정의
class Fruit():
    # [2] __len()__ 메서드 정의
    def __len__(self):
        return 10

# [3] 각종 집합 객체 생성
obj_list = ['apple', 'banana', 'ornage']
obj_tuple = ('apple', 'banana', 'ornage')
obj_set = {'apple', 'banana', 'ornage'}
obj_dict = {'apple':5, 'banana':6, 'ornage':7}
obj_str = 'object'
fruit = Fruit()

# [4] 리스트 생성
objs = [obj_list, obj_tuple, obj_set, obj_dict, obj_str, range(10), fruit]

# [5] 리스트에 대한 for loop 반복
for obj in objs:
    # [6] 객체 유형 및 길이 출력
    print(type(obj), len(obj))

[결과]

<class ElistE> 3
<class EtupleE> 3
<class EsetE> 3
<class EdictE> 3
<class EstrE> 6
<class ErangeE> 10
<class E__main__.FruitE> 10

위 예제에 대해 설명한다.

  • [1]에서 Fruit 클래스를 정의한다.

  • [2]에서 len( ) 메서드를 정의한다. len( ) 내장 함수에서 길이를 얻어오도록 하려면 len( ) 메서드를 구현해야 한다. 함수명이 앞뒤에 던더(더블 언더:’__’)가 붙어 있는 함수는 시스템에서 사용하는 사전 정의된 함수 혹은 변수라는 의미이다.

  • [3]에서 각종 반복 가능한 집합 객체를 생성한다. list, tuple, set, dictionary, range, string 등이고 len( ) 함수를 구현한 일반 클래스 객체이다.

  • [4]에서 각 객체 인스턴스를 하나의 리스트로 만든다.

  • [5]에서 리스트에 대해 for 문을 반복한다.

  • [6]에서 각 객체에 대해 유형과 len( ) 함수의 결과를 출력한다.

만일, len( ) 메서드가 없는 클래스를 len( ) 함수의 인자로 넣으면 어떻게 될까? 아래와 같이 len( ) 함수가 없다는 TypeError가 발생한다.


[예제 5- 28] len( ) 메서드가 없는 클래스의 len( ) 호출하기

class Fruit():
    def len(self):
        return 10

obj = Fruit()
len(obj)

[결과]

TypeError: object of type 'Fruit' has no len()


B. 클래스 메서드 다형성

파이썬에서 서로 다른 클래스가 존재하는 경우라도 동일한 이름의 메서드가 존재한다면 서로 같은 유형의 객체가 아니더라도 메서드를 호출하는데 좀더 유연한다. 다음은 서로 다른 클래스들이지만 동일한 메서드를 갖기 때문에 마치 같은 클래스처럼 호출하는 예이다.

[예제 5- 29] 클래스 다형성 사용해보기

# [1] Apple 클래스 정의
class Apple():
    
    def __init__(self, name='apple'):
        self.name = name
        
    # [2] 동일한 메서드
    def get_name(self):
        return self.name
                    
# [3] Banana 클래스 정의
class Banana():
    
    def __init__(self, name='banana'):
        self.name = name
    
    # [4] 동일한 메서드
    def get_name(self):
        return self.name
    
        
# [5] Orange 클래스 정의
class Orange():
    
    def __init__(self, name='Orange'):
        self.name = name
    
    # [6] 동일한 메서드
    def get_name(self):
        return self.name

# [7] 클래스 개체 생성
fruit1 = Apple()
fruit2 = Banana()
fruit3 = Orange()

# [8] 클래스 객체를 반복
for x in (fruit1, fruit2, fruit3):
    # [9] 동일한 메서드 호출
    print(x.get_name())

[결과]

Apple
banana
Orange

위 예제에 대해 설명한다.

  • [1]에서 Apple의 클래스를 [2]에서 Banana의 클래스를 [3]에서 Orange의 클래스를 정의하였다. 각 클래스는 Object 클래스를 상속받고, 동일하게 get_name( )이라는 메서드를 [2], [4], [6]에서 각각 구현하였다. 하지만 세 개의 클래스는 서로 모양은 같으나 완전히 다른 객체 정의이다.

  • [7]에서 Apple, Banana, Orange의 클래스를 각각 인스턴스화 하였다.

  • [8]에서 인스턴스 객체 fruit1, fruit2, fruit3를 항목으로 하는 튜플을 정의하여 for 반복문의 반복자로 사용하였다.

  • [9]에서 각 클래스 인스턴스의 get_name( ) 메서드를 호출하였다. 실제 x 변수는 클래스 Apple, Banana, Orange의 객체로 서로 다르지만 동일한 이름인 get_name( ) 이라는 메서드를 가지고 있기 때문에 x.get_name( ) 처럼 호출하더라도 문제 없이 동작한다.


2.4 private 변수

클래스에서 private 변수는 클래스 내에서는 사용이 가능하지만 클래스 밖에서는 사용할 수 없는 변수이다. 여기서 private은 내부 변수라는 의미로 다양한 프로그래밍 언어에서 사용된다. 하지만 파이썬에서는 public, private, protected와 같은 접근 제한자가 없다. 그렇다면 파이썬에서 private 변수를 생성해 클래스 밖에서 접근하지 못하게 하려면 어떻게 할까? 바로 변수명을 작성할 때 지정하는 것이다. 다시 말해 클래스 변수 이름을 지을 때 변수명 앞에 던더(double under, ‘__’)를 붙인다.

형식

__{var name}

설명

클래스 내에서 private 변수를 선언하려면 변수 이름 앞에 언더바(’_’) 두 개를 사용한다.

‘__변수명’ 형식으로 인스턴스 변수명을 지정하면 파이썬은 이를 private 변수로 인식하여 자신만이 아는 이름으로 변수명을 변경한다. private 변수는 외부에서 변수명을 이용해 직접 접근할 수 없다. 필요한 경우 getter 혹은 setter를 사용하거나 메서드 내에서 호출해 사용하면 된다.


[예제 5- 30] private 변수 접근하기

# [1] 클래스 정의
class Fruit:
    # [2] 생성자
    def __init__(self):
        # [3] private 변수
        self.__name = 'fruit'
        
# [4] 클래스 인스턴스 생성
fruit = Fruit()
# [5] 변수 사용
print("(1)", fruit._Fruit__name)
print("(2)", fruit.__name)

[결과]

(1) fruit
AttributeError: 'Fruit' object has no attribute '__name'

위 예제에 대해 설명한다.

  • [1]에서 클래스를 정의한다.

  • [2]에서 생성자를 정의한다. [3]에서 private 속성을 갖는 인스턴스 변수 __name을 선언했다.

  • [4]에서 클래스를 인스턴스화 한다.

  • [5]에서 클래스의 변수를 사용한다. (1)번으로 private 변수인 __name을 직접 호출하지 않고 _클래스명__변수명 형식으로 fruit.__Fruit_name 변수를 사용하니 정상적으로 출력된다. (참고. private 변수를 이렇게 접근해서 사용하라는 의미가 아니다. private 변수는 이렇게 직접 호출해 사용하지 말고 getter와 setter를 사용하는 것을 추천합니다) (2)는 private 변수명 __name 을 그대로 사용해 보았더니 AttributeError 오류가 발생했다. 해당 변수는 없다는 의미이다.

Hint! private 변수 이름 규칙

파이썬은 스크립트 언어이다. 따라서 컴파일을 하지 않고 바로 실행하기 때문에 private 변수를 접근 못하게 하기보다는 좀더 쉬운 방법으로 파이썬이 이해하는 이름으로 바꾸어 버린다. 예를들어 Fruit 이라는 클래스에 __name 이라는 private 변수가 있다면 __name 으로 접근하면 변수가 없다고 오류를 발생시킨다. 하지만 _Fruit_name 으로 접근하면 해당 변수를 확인할 수 있다. 파이썬은 private 변수 이름을 언더바 + 클래스명 + 더블언더바 + 변수명 으로 바꾸어 private 변수를 숨긴다.

물론 _Fruit_name 이름으로 접근하면 private 변수이지만 외부에서 사용이 가능하다. 이는 약속을 통해 private 처리를 하겠다는 파이썬의 사상이 담겨 있다.



2.5 getter와 setter

클래스에서 인스턴스 변수의 값을 연산자를 이용해 다루기 위해 getter와 setter를 사용할 수 있다. getter는 값을 가져오는 메서드이고 setter는 값을 지정하는 메서드이다. getter는 메서드 정의 위에 다음의 키워드를 입력하면 된다.

형식

class my_class:
    value = "my value"

    @property
    def my_var(self):
        return self.value

설명

getter는 함수명을 마치 변수처럼 사용하는 것이다. 위 예제에서 클래스 my_class의 getter 함수로 사용하기 위해 my_var( ) 함수 위에 @property를 추가했다. 따라서, my_class의 인스턴스를 생성하여 my라는 변수를 생성 했으므로 my_class의 getter 함수인 my_var을 my.my_var과 같이 함수를 변수처럼 호출해 사용 가능하고, my_var( ) 함수에서 반환하는 value 값이 “my value”로 출력된다.

예제

my = my_class()
print(my.my_var)

setter는 메서드 정의 위에 다음의 키워드를 입력하면 된다.

형식

class my_class:
    value = "my value"

    @my_var.setter
    def my_var(self, new_value):
        return self.value = new_value

설명

setter는 함수명을 마치 변수처럼 사용하는 것이다. 위 예제에서 클래스 my_class의 setter 함수로 사용하기 위해 my_var( ) 함수 위에 @my_var.setter를 추가했다. 따라서, my_class의 인스턴스를 생성하여 my라는 변수를 생성 했으므로 my_class의 setter 함수인 my_var을 my.my_var과 같이 함수를 변수처럼 호출해 값 지정 가능하다. 그러면 클래스의 my_var변수 값이 “my new value”로 변경된다.

예제

my = my_class()
my.my_var = “my new value”

getter와 setter의 예제를 살펴보자.


[예제 5- 31] getter와 setter 사용하기

# [1] 클래스 정의
class Fruit:
    # [2] 생성자
    def __init__(self):
        self.__name = 'fruit'
 
    # [3] getter
    @property
    def name(self):           # getter
        return self.__name
 
    # [4] setter
    @name.setter
    def name(self, value):    # setter
        self.__name = value

# [5] 클래스 인스턴스 생성
fruit = Fruit()

# [6] setter 함수 사용
fruit.name = 'apple'

# [7] getter 함수 사용
print(fruit.name)

[결과]

Apple

위 예제에 대해 설명한다.

  • [1]에서 클래스를 정의한다.

  • [2]에서 생성자에 self.__name 이라는 인스턴스 변수를 정의하였다. 변수 이름 앞에 던더(’__’)를 붙이면 파이썬에서는 private 변수로 인지한다.

  • [3]에서 getter 메서드를 정의한다. 함수명 name( ) 윗줄에 @propery라고 getter를 위한 키워드를 적어준다. 그러면 name( )이라는 메서드를 변수명 name처럼 사용해 값을 가져올 수 있다.

  • [4]에서 setter 메서드를 정의 한다. 역시 name( ) 이라는 이름으로 메서드를 정의하고 setter를 위해 메서드 정의 윗줄에 @name.setter라고 적어준다. 메서드명이 name( ) 이므로 name.setter라고 적었다.

  • [5]에서 Fruit 클래스의 인스턴스를 생성한다.

  • [6]에서 setter 함수를 사용한다. setter 함수는 함수명을 변수처럼 사용해 대입 연산자 ‘=’로 값을 지정한다.

  • [7]에서 getter 함수를 사용한다. getter 함수는 메서드명 name( )을 변수 처럼 name을 사용한다.