python - 클래스

Web-Programming School 1월 22일 수업
본 문서는 패스트캠퍼스 ‘Web-Programming School’ 수업 자료를 바탕으로 작성되었습니다.


객체지향프로그래밍

(OOP:Object-Oriented-Programming)

절차형 언어는 프로그램을 여러 기능으로 나누고 이들 모듈을 편성하여 프로그래밍을 할 경우, 각 모듈이 처리하는 데이터에 대해 고려하지 않는 경우가 많아 현실세계의 문제를 프로그램으로서 표현하는 것이 곤란하다.
이를 해결하기 위한, 객체지향프로그래밍은 객체라는 작은 단위로서 모든 처리를 기술하는 프로그래밍 방법이다. 이 방법으로 프로그램을 작성할 경우, 프로그램이 단순화 되고, 생산성을 높이는 시스템을 구축 할 수 있다.
(네이버 지식 사전 참고)

객체가 가진 변수함수 는 각각 속성(attribute)메서드(method) 라고 부른다. 객체는 어떠한 타입, 즉 특정한 클래스 의 형태를 가진 인스턴스 를 나타낸다.

객체와 인스턴스의 차이

a = Cookie()이렇게 만들어진 a는 객체 그리고 a라는 객체는 Cookie의 인스턴스
즉, 인스턴스는 특정 객체(a)가 어떤 클래스(Cookie)의 객체인지를 관계 위주로 설명할때 사용


클래스(class)

클래스는 객체를 만들기 위한 틀

# 초기화 메서드
class Shop:
    def __init__(self,name):
        self.name = name

__init__ 은 클래스를 사용한 객체의 초기화 메서드
객체를 생성할 때 인자를 어떻게 전달받고, 받은 인자를 이용해 어떤 객체를 생성할 지 정의 가능

# 위 클래스를 사용해서 객체를 생성
lotteria = Shop('Lotteria')

코드 동작 순서

  1. Shop 클래스가 정의되었는지 찾는다.
  2. Shop 클래스형 객체를 메모리에 생성한다.
  3. 생성한 객체의 초기화 메서드 init()을 호출한다.
  4. name값을 저장하고, 만들어진 객체를 반환한다.
  5. lotteria변수에 반환된 객체를 할당한다.

객체의 메서드를 정의할 때, 첫 번째 인수는 항상 self
self는 메서드를 호출시 호출한 객체 자신이 전달
ex) lotteria.show_info() 호출 경우, self에 lotteria가 전달 이렇게 self가 자동으로 호출되는 이유는 클래스의 메서드를 사용할 때 어떤 객체가 해당 메서드를 사용하는지 확인하기 위해서이다. 또는 하나의 메서드를 여러 객체가 공유 가능하다.

클래스 속성

하나의 클래스로부터 생성된 객체들이 같은 값을 가지게 하고 싶을 때 사용

class Shop:
    description = 'Python Shop class'
    def __init__(self,name):
        self.name = name

메서드

인스턴스 메서드

인스턴스 메서드는 첫 번째 인수로 self를 가진다.

실습 Shop클래스의 초기화 메서드 인자로 shop_type과 address를 추가하고, 해당 인자들을 사용해 객체의 초기값을 만들어준다
Shop클래스의 인스턴스 메서드 show_info를 아래와 같은 결과를 출력할 수 있도록 수정해본다

class Shop:
    description = 'Python Shop class'
    def __init__(self,name,shop_type,address):
        self.name = name
        self.shop_type = shop_type
        self.address = address
    def shop_info(self):
        print(f'상점 : ({self.name})')
        print(f'종류 : {self.shop_type}')
        print(f'주소 : {self.address}')

lotteria = Shop('Lotteira','패스트푸드','서울시 강남구')
lotteria.shop_info()

#출력
상점 : (Lotteira)
종류 : 패스트푸드
주소 : 서울시 강남구

실습 Shop클래스에 change_type인스턴스 메서드를 추가하고, 상점유형(shop_type)을 변경할 수 있는 기능을 추가한다
새로운 Shop인스턴스를 하나 생성하고, show_info() 인스턴스 메서드를 사용해 본 후 change_type메서드를 사용해 shop_type을 변경시키고 다시 show_info()메서드를 실행해 결과가 잘 반영되었는지 확인한다

class Shop:
    description = 'Python Shop class'
    def __init__(self,name,shop_type,address):
        self.name = name
        self.shop_type = shop_type
        self.address = address
    def shop_info(self):
        print(f'상점 ({self.name})')
        print(f'유형: {self.shop_type}')
        print(f'주소: {self.address}')

    def change_type(self,shop_type):
        self.shop_type = shop_type

lotteria = Shop('롯데리아','패스트푸드','서울시 강남구')
lotteria.shop_info()
lotteria.change_type('음식점')
lotteria.shop_info()

#출력
상점 : (롯데리아)
종류 : 음식점
주소 : 서울시 강남구

속성 접근 지정자 (attribute access modifier)

캡슐화

객체 구현시, 사용자가 반드시 알아야 할 데이터나 메서드를 제외한 부분을 은닉시켜 정해진 방법을 통해서만 객체 조작 할 수 있게 함

# 원래는 change_type()만을 이용하여 수정가능하게 하려고 했음
lotteria.change_type('음식점')

# 그러나 이렇게도 수정이 가능함
lotteria.shop_type('학원')

객체의 데이터나 메서드의 은닉 정도를 결정 시 속성 접근 지정자 를 사용

  • ’ _ ‘가 두개 있으면 private : 자기 클래스 내부의 메서드만 접근 허용
  • ’ _ ‘가 한개 있으면 public : 자기 클래스 내부 또는 상속받은 자식 클래스에서 접근 허용

class Shop:
    description = 'Python Shop class'

    def __init__(self, name, shop_type, address):
        self.name = name
        self.__shop_type = shop_type
        self.address = address

    def show_info(self):
        print(f'상점 ({self.name})')
        print(f'유형: {self.__shop_type}')
        print(f'주소: {self.address}')

    def change_type(self, type):
        if type in ['패스트푸드', 'PC방']:
            self.__shop_type = type

위 코드처럼 __shop_type으로 변경하면 change_type()으로만 type변수를 변경가능

lotteria.shop_type
#출력
'학원'

lotteria.show_info()
#출력
상점 (롯데리아)
  유형: PC방
  주소: 서울시 강남구

그러나 dir(lotteria)명령어 실행 후, __shop_type은 없음.
실제 이름은 _<클래스명>__<속석명> 으로 설정되어 _Shop__shop_type으로 지정

즉 , 다른 언어들과 달리 실제로 사용하지 못하도록 숨기지 않고, 네임 맹글링(name mangling)이라는 기법을 사용. 분법적으로 private데이터에 대한 접근을 막는 법이 없다.
이는 파이썬의 철학 중 ‘개발자에게 최대한 제약을 주지 않는다’와 관련이 있다.

get/set 속성 값과 프로퍼티

외부에서 private 객체 속성을 읽고 쓰기 위채 getter, setter메서드를 사용해야 한다. 파이썬에서너느 해당 기능을 프로퍼티(property)를 사용해 간편히 구현한다.


class Shop:
    description = 'Python Shop class'

    def __init__(self, name, shop_type, address):
        self.__name = name
        self.__shop_type = shop_type
        self.__address = address

    def show_info(self):
        print(f'상점 ({self.__name})')
        print(f'유형: {self.__shop_type}')
        print(f'주소: {self.__address}')

    # 일반적인 getter, setter를 사용하는 기법
    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name

    #파이썬에서의 프로퍼티
    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, new_name):
        if '패스트' not in new_name:
            self.__name = new_name
        else:
            print('"패스트"는 상점이름에 포함될 수 없습니다')

setter프로퍼티를 명시하지 않으면 읽기전용이 되어, 외부에서 조작 불가능

상속(Inheritance)

비슷한 기능을 수행하거나, 약간의 추가적인 기능이 필요한 다른 클래스가 필요한 경우, 기존의 클래스를 상속 받은 새 클래스를 사용

class Restaurant(Shop):
    pass

상속받은 클래스는 부모클래스의 모든 속성과 메서도 사용가능

메서드 오버라이드

상속받은 클래스에서, 부모클래스의 메서드와 다른 동작을 하도록 하기 위해, 부모 클래스를 덮어씌워 사용하는 것을 의미
private 속성자의 경우, 자식 클래스에서 접근이 불가함으로 public 으로 변경


class Shop:
    description = 'Python Shop class'

    def __init__(self, name, address, shop_type='상점'):
        self._name = name
        self._address = address
        self._shop_type = shop_type

    def get_info_string(self):
        ret = f'{self._shop_type} ({self._name})'
        ret += f'\n  주소: {self._address}'
        return ret

    def show_info(self):
        print(self.get_info_string())

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if '패스트' not in new_name:
            self._name = new_name
        else:
            print('"패스트"는 상점이름에 포함될 수 없습니다')

    class Restaurant(Shop):
      def __init__(self, name, address):
      super().__init__(name, address, shop_type='식당')

부모 클래스의 메서드 호출(super)

자식클래스의 메서드에서 부모 클래스에서 사용하는 메서드의 전체를 새로 쓰는것이 아닌, 부모 클래스의 메서드를 호출 후 해당 내용으로 새로운 작업을 해야 할 경우 super()메서드를 사용해서 부모 클래스의 메서드를 직접 호출할 수 있다.

class Restaurant(Shop):
    def __init__(self, name, shop_type, address, rating):
        super().__init__(name, shop_type, address)
        self.rating = rating

정적(static) 메서드

클래스 내부에 정의된 일반 함수이며, 단지 클래스나 인스턴스를 통해 접근할 수 있을 뿐 해당 클래스나 인스턴스에 영향 주는 것은 불가능하다.
@staticmethod데코레이터를 붙여 선언
정적 메소드는 일반 메소드와 달리 첫 인수로 self를 받지 않으며, 필요한 만큼의 인수를 선언해서 사용.

클래스 메서드

클래스 속성에 대해 동작하는 메서드이다. 위의 인스턴스 메서드와 달리 호출 주체가 클래스이며, 첫 번째 인자도 클래스이다.
만약 인스턴스가 첫 번째 인자로 주어지더라도 해당 인자의 클래스로 자동으로 바뀌어 전달된다.
클래스메서드는 @classmethod데코레이터를 붙여 선언하며, 첫 번째 인자의 이름은 관용적으로 cls를 사용한다.
클래스 메서드를 사용하면 부모 클래스를 상속받은 자식클래스에서 해당 부모클래스의 클래스 메서드를 사용할 경우, 자기 자신(자식)의 클래스를 사용할 수 있다.

import random


class Pokemon:
    def __init__(self, name, type):
        self.name = name
        self.type = type

    def __str__(self):
        return f'{self.type}타입 포켓몬 {self.name}'

    @classmethod
    def dummy_pokemon(cls):
        # name에 알 수 없음
        # type에 알 수 없음
        # 이라는 값을 가진 Pokemon인스턴스를 리턴
        return cls('알 수 없음', '알 수 없음')

    @staticmethod
    def electric():
        pokemons = ('피카츄', '라이츄', '붐볼')
        selected_pokemon = random.choice(pokemons)
        return Pokemon(selected_pokemon, '전기')

    @staticmethod
    def water():
        pokemons = ('꼬부기', '아쿠스타', '라프라스')
        selected_pokemon = random.choice(pokemons)
        return Pokemon(selected_pokemon, '물')


class WaterPokemon(Pokemon):
    def water_gun(self):
        print('물대포 발사')

다형성 동적 바인딩

다형성은 관용적으로 일반적으로 하나의 코드가 여러 역할을 한다는 의미이다. 동적 바인딩은 타입을 신경 쓰지 않고 인스턴스를 사용할 수 있게 해주는 방식이다.
동적 바인딩은 속성 검색과정을 통해 이루어진다. obj.attr 을 통해 속성에 접근하면, 인스턴스에서 attr을, 다음엔 클래스에서, 그 다음에는 상속받은 클래스에서 해당 속성을 검색하며 가장 먼저 검색한 속성을 반환한다.
동적으로 속성을 바인딩하는 과정은 obj객체의 타입에 영향받지 않으며, 오로지 해당 객체가 attr에 해당하는 속성을 가졌는지만 검사한다.

def do_attack(pokemon):
    pokemon.attack()
def pikachu_attack():
    print('100만볼트')

pikachu = Pokemon('피카츄', '전기')
pikachu.attack = pikachu_attack
dummy.attack = lambda: print('알수없는 공격')
lotteria.attack = lambda: print('햄버거 공격')

do_attack(pikachu)
do_attack(dummy)
do_attack(lotteria)

pokemon만 받는 것이 아니라, attack을 가지고 있느냐만 확인해서 출력