바로 쓰는 파이썬 기초편 Lecture Notes

컴퓨터의 개념 및 실습

This project is maintained by jinsooya

12장: 클래스와 객체

Classes and Objects


박 진 수 교수
Intelligent Data Semantics Lab
Seoul National University


Table of Contents

객체지향 프로그래밍 언어의 핵심 개념

객체지향 프로그래밍 언어의 핵심 개념으로는 다음 네 가지가 있다.

추상화

추상화(abstraction)란?

추상화의 핵심은 객체를 설계하는 사람의 관점에서 객체의 가장 중요하고 본질적인 특성을 규정하는 것이다. 객체의 가장 중요한 ‘속성’과 ‘행위’를 정의하는 것을 추상화라고 한다.

속성과 행위

캡슐화

캡슐화(encapsulation)란?

다음 그림처럼 캡슐화를 통해 객체 내부에서 일어나는 작업 내용(실제 내부에서 처리하는 상태와 행동)을 최소한만 보여줌으로서 외부 세계가 의도치 않게 객체의 속성(데이터)을 변경하거나 손상하는 것을 방지할 수 있다.

encapsulation

캡슐화를 잘하면 객체를 재사용하기 쉽다.

자바는 객체의 대부분 또는 모든 속성을 사적 속성(private attribute)으로 정의해서 객체의 데이터를 보호한다. 하지만 파이썬에서는 사적 속성이 존재하지 않기 때문에 네임 맹글링(name mangling)을 통해 외부에서 객체의 내부 속성에 의도적으로 직접 접근하는 것을 어렵게 한다.

클래스 간의 관계

클래스 간의 관계(relationship)는 다음과 같이 크게 세 가지로 구분한다.

연관 관계는 동등한 관계에 기반을 두고 있으며, 상속 관계와 부분-전체 관계는 클래스의 계층 구조(class hierarchy)에 기반을 두고 있다. 클래스 계층 구조(class hierarchy)는 클래스 사이의 상하 순서 관계를 규정한다.

연관 관계

연관 관계(association)란?

상속 관계

상속 관계(inheritance relationship)란?

하위 클래스는 상위 클래스에서 정의한 속성, 행동, 관계 및 제약 조건 등을 모두 상속 받기 때문에 둘의 관계를 ‘상속 관계’라고 한다. 상속을 통해 하위 클래스(자식 클래스)는 상위 클래스(부모 클래스)의 행동과 속성을 상속받을 수 있기 때문에 공유(share)와 재사용(reuse)이 가능하다. 따라서 상속 관계의 최대 장점은 기존 클래스를 불러와서 재사용하거나 기존 클래스를 토대로 내용을 변경(특수화)할 수 있다는 점이다.

다중 상속(multiple inheritance)이란?

다중 상속의 예로는 다용도 트럭(sport utility vehicle, SUV)이 있다. 다용도 트럭은 승용차(car)와 트럭(truck) 둘의 특징을 상속받은 클래스다.

파이썬은 다중 상속을 지원하는 언어다.

부분-전체 관계

부분-전체 관계(part-whole)란?

부분-전체 관계는 다음 두 종류로 구분할 수 있다.

다형성

다형성(polymorphism)이란?

서로 다른 클래스에 속해 있는 객체들이 같은 이름의 메소드를 사용하더라도 각기 다른 방식으로 행동할 수 있도록 해주는 메카니즘이다. 이를 통해 우리는 서로 다른 클래스에 속한 객체들에게 같은 메시지를 보내지만 각 객체가 자신에게 맞는 행동으로 적절하게 응답하도록 할 수 있게 한다.

클래스와 객체 만들기

클래스와 객체의 개념

클래스(class)란?

객체(object)란?

예시 : 쿠키틀과 쿠키

쿠키를 만들 때 사용하는 쿠키틀(cookie cutter)과 쿠키(cookie)의 관계를 생각해보자. 쿠키는 쿠키의 모양을 잡아주는 ‘틀’이 있기 때문에 우리는 일정한 모양의 쿠키를 특별한 노력 없이도 즐길 수 있다.

쿠키틀(cookie cutter)

같은 쿠키틀에서 만들어지는 쿠키의 형태는 같으므로 쿠키틀은 ‘같은 종류의 집단’을 만들어 주는 역할을 한다. 즉, 같은 종류의 집단에 속한 쿠키들의 특징(속성과 행동)을 정의한다.

쿠키(cookie)

사용자 클래스

사용자 클래스(custom class)란?

사용자 클래스를 작성하는 일반적인 형식은 다음과 같다.

<pre>class 클래스이름: 클래스-명령문</pre>

특징은 다음과 같다.

클래스와 객체 만들기

먼저, 클래스와 객체의 자료형을 확인해보자.

class MyClass: pass
type(MyClass)  # MyClass의 자료형이 클래스다.
type(int)      # int의 자료형이 클래스다.
type(list)     # list의 자료형이 클래스다.
c = MyClass()  
type(c)        # 자료형이 MyClass다.
x = int()
type(x)        # 자료형이 int다.
y = list() 
type(y)        # 자료형이 list다

서울대학교 느티나무 아르바이트생(PartTimer) 클래스를 정의하고 자료형을 확인해보자.

# --- PartTimer 클래스 정의하기
class PartTimer:  
    pass
type(PartTimer)  # PartTimer 클래스 자료형 확인하기

이번에는 방금 정의한 PartTimer 클래스의 객체를 생성하고 자료형을 확인해보자.

# --- PartTimer 클래스의 객체(인스턴스) 생성하기
kim = PartTimer()
lee = PartTimer()
# PartTimer 인스턴스 자료형 확인하기
type(kim), type(lee)

PartTimer 클래스의 특성을 가진(물론 아직은 아무것도 없지만) kimlee 객체를 생성했다. 두 객체 모두 PartTimer 클래스라는 ‘틀’을 이용해 만들어었기 때문에 같은 특징을 가진다. 이처럼 클래스를 한 번 정의해 놓으면 이후에 다시 정의하지 않고도 같은 특성을 가진 객체를 반복적으로 생성할 수 있다.

클래스 속성

앞의 예에서는 클래스의 이름만 정의했지만 클래스의 속성과 메소드를 정의하기 위해서는 함수를 정의할 때와 마찬가지로 들여쓰기(indentation)를 해서 내용을 작성해야 한다.

그럼 먼저 PartTimer 클래스에 필요한 속성이 무엇인지 생각해보자.

시급

클래스 속성을 정의하는 방식은 초깃값을 할당해서 변수를 선언하는 것과 같다.

__TODO__를 채워 다음 코드를 완성해보자.

class PartTimer:
    __TODO__  # 클래스 속성 : 시급이 9500이다.

클래스에 속성을 정의했으니 이번에는 이 클래스의 객체를 다시 생성해보자. 느티나무 카페에 필요한 아르바이트생은 현재 두 명이라고 가정하자.

__TODO__를 채워 다음 코드를 완성해보자.

kim = __TODO__  # PartTimer 클래스의 객체를 생성해서 변수 kim에 할당한다.
lee = __TODO__  # PartTimer 클래스의 객체를 생성해서 변수 lee에 할당한다.

인스턴스에서 속성에 접근하기

객체를 통해 속성에 접근하기 위해서는 도트 연산자(.)를 사용하며 형식은 다음과 같다.

객체이름.속성이름

위에서 생성한 두 객체의 속성 값을 확인해보자.

힌트

__TODO__를 채워 다음 코드를 완성해보자.

kim.__TODO__  # 인스턴스를 통해 클래스 속성에 접근한다.
lee.__TODO__  # 인스턴스를 통해 클래스 속성에 접근한다.

클래스에서 속성에 접근하기

이 결과 둘 다 hour_rate 의 기본값인 ‘9500‘(시급 9,500원)을 가지고 있다. hour_ratePartTimer 클래스의 모든 객체에 적용이 되는 속성이다.

클래스 속성(class attribute)이란?

클래스 속성은 앞서 한 것 처럼 객체를 통해 접근할 수도 있지만 클래스를 통해서도 접근할 수 있으며 형식은 다음과 같다.

클래스이름.속성이름

사실 이렇게 접근하는 것이 의미론적으로는 더 명확하다고 볼 수 있다.

__TODO__를 채워 다음 코드를 완성해보자.

__TODO__.hour_rate  # 클래스를 통해 클래스 속성에 접근한다.

인스턴스에서 속성 값 수정하기

인스턴스의 값은 객체이름.속성이름 = 속성값 으로 수정 또는 추가가 가능하다.

무경험자인 lee 의 시급을 7,000원으로 변경해보자.

__TODO__를 채워 다음 코드를 완성해보자.

# 무경험자인 lee의 시급을 7,000원으로 변경한다.
__TODO__ = __TODO__

이제 변경한 내용이 어떻게 반영되었는지 모든 인스턴스와 클래스의 속성 값을 확인해보자.

# lee의 시급이 변경되었는지 확인한다.
lee.hour_rate          # lee의 시급은 7000이다.
# kim의 시급도 확인한다.
kim.hour_rate         # kim의 시급은 변하지 않았다.
# 클래스 속성인 hour_rate의 값도 확인한다.
PartTimer.hour_rate   # 클래스 속성 값도 여전히 9500이다.

클래스와 인스턴스의 속성

커피 가게에 손님이 많이 찾아와 아르바이트생 한 명을 더 고용했다.

변수 이름이 park 인 객체를 하나 더 생성해보자.

__TODO__를 채워 다음 코드를 완성해보자.

__TODO__ = __TODO__  # park 인스턴스를 생성한다.

park 의 시급이 얼마인지 확인해보자.

__TODO__를 채워 다음 코드를 완성해보자.

__TODO__.hour_rate  # park의 시급은 얼마일까?

최근 열심히 일한 아르바이트생 덕분에 느티나무 카페의 수입이 많이 늘어나서 아르바이트생의 기본 시급을 10,000원으로 올려주기로 결정했다. 따라서 클래스 속성인 시급 hour_rate 의 값을 10,000으로 수정하자.

클래스의 속성 값을 변경하려면 어떻게 하면 될까?

__TODO__를 채워 다음 코드를 완성해보자.

# 클래스 속성을 수정하려면 '클래스이름.속성이름'으로 접근한다.
__TODO__ = 10_000    

클래스 속성 값을 수정했는데, 인스턴스 속성 값도 변경되었는지 확인해보자.

# kim의 시급을 확인한다.
kim.hour_rate                   
# park의 시급을 확인한다.
park.hour_rate
# lee의 시급을 확인한다.
lee.hour_rate

lee 의 경우는 객체이름.속성이름 으로 접근해서 값을 수정했기 때문에 사실상 클래스 속성이라기 보다는 lee에만 한정된 인스턴스 속성으로 바뀌었다. 따라서 더 이상 클래스 속성이 아니기 때문에 클래스 속성의 값을 변경해도 반영되지 않는다.

클래스 속성 추가하기

대부분의 객체지향형 언어들은 클래스에 속성을 추가하려면 클래스를 재정의해야 한다. 하지만 파이썬은 클래스 정의할 때 선언하지 않았던 속성이더라도 나중에 만들어 값을 할당하면 그 클래스의 속성이 된다.

클래스 속성을 하나 더 만들어 추가해보자.

__TODO__를 채워 다음 코드를 완성해보자.

# 새로운 클래스 속성을 만들어 추가한다.
__TODO__.cafe_name = '느티나무 카페'        

나중에 만든 클래스 속성이 이미 만든 인스턴스에 반영이 되었는 지 확인해보자. 그리고 기존의 클래스 속성도 그대로 있는지 확인해보자.

# 새로운 클래스 속성이 추가되었는지 확인하기 위해 kim으로 새로 추가한 속성과 기존 속성을 호출한다.
kim.cafe_name, kim.hour_rate
# 새로운 클래스 속성이 추가되었는지 확인하기 위해 park으로 새로 추가한 속성과 기존 속성을 호출한다.
park.cafe_name, park.hour_rate
# 새로운 클래스 속성이 추가되었는지 확인하기 위해 lee로 새로 추가한 속성과 기존 속성을 호출한다.
lee.cafe_name, lee.hour_rate

인스턴스를 생성한 뒤 나중에 추가한 클래스 속성도 기존 인스턴스에 모두 반영되었음을 알 수 있다.

인스턴스 속성과 초기화 메소드

개별 아르바이트생의 근무 시간은 어떻게 기록해야 할까?

이 데이터는 각 아르바이트생마다 다르기 때문에 객체마다 다르게 기록해야 한다. 따라서 이 경우에는 클래스에 속한 모든 인스턴스에 동일하게 적용하는 클래스 속성을 사용할 수 없다.

그렇다면 객체마다 자신의 값을 가질 수 있도록 하는 방법은 없을까?

인스턴스 속성을 사용하면 된다.

인스턴스 속성(instance attribute)이란?

인스턴스 속성은 클래스가 인스턴스를 생성하는 시점에 만들기 때문에 주로 초기화 메소드 안에 정의한다.

초기화 메소드 __init__()

초기화 메소드의 형식은 다음과 같다.

__init__(self[, 매개변수, ...])

다음과 같은 특징이 있다.

이처럼 클래스 메소드를 정의하는 방법은 아래 한 가지만 제외하고 일반 함수를 정의하는 것과 같다.

다른 이름을 사용해도 되지만 관례에 따라 이 매개변수의 이름을 self 라고 명한다. 자세한 설명은 < 바로 쓰는 파이썬: 기초편 >의 491p를 참조하면 된다.

클래스에 인스턴스 속성 추가하기

아르바이트생의 근무 시간을 기록할 whours 란 인스턴스 속성을 초기화 메소드 안에 정의해보자. 그리고 아르바이트생의 수가 늘어날 것을 대비해 아르바이생마다 닉네임을 만들어 주도록 하자.

인스턴스 속성은 주로 __init__() 메소드 안에 정의한다. 인스턴스 속성은 __init__() 메소드 안에서 self.속성이름 형식으로 정의하면 된다.

__TODO__를 채워 다음 코드를 완성해보자.

class PartTimer:
    cafe_name = '느티나무 카페'   # 클래스 속성 : 아르바이트생이 근무하는 커피 가게 이름
    hour_rate = 9500           # 클래스 속성 : 시급
    def __init__(self, name):  # 초기화 메소드
        __TODO__               # 인스턴스 속성 : 아르바이트생 닉네임
        __TODO__               # 인스턴스 속성 : 근무한 시간

다음 코드를 실행해보자.

lee = PartTimer()

왜 오류가 날까?

오류가 나지 않으려면?

다음 코드처럼 인스턴스 생성시 반드시 닉네임을 지정해야 한다.

lee = PartTimer('네오')         

방금 생성한 lee 의 속성 값들을 학인해보자.

lee.cafe_name   # 클래스 속성
lee.hour_rate   # 클래스 속성
lee.nickname    # 인스턴스 속성
lee.whours      # 인스턴스 속성

객체에 인스턴스 속성 추가하기

조금 전에 생성한 닉네임이 ‘네오’인 객체 lee 에 새로운 속성인 email 을 추가해보자.

__TODO__를 채워 다음 코드를 완성해보자.

# 새로운 속성 email을 PartTimer 인스턴스 lee에 추가한다.
# email 값는 'neo@kfriends.kr' 
__TODO__ = 'neo@kfriends.kr'      

email 속성이 추가 되었는 지 lee 를 통해 확인해보자.

lee.email

email 속성이 클래스에도 추가 되었는 지 확인해보자.

PartTimer.email

클래스에는 추가되지 않았다.

이유는?

클래스 속성과 인스턴스 속성의 차이

클래스 속성

class MyClass:
    counter = 0                # 클래스 속성을 초기화한다.
    def __init__(self, name):  # 인스턴스를 생성할 때 닉네임은 반드시 지정해야 한다.
        self.nickname = name   # 인스턴스 속성을 초기화한다.
        MyClass.counter += 1   # 객체 한 개를 생성할 때마다 클래스 속성 값이 1씩 증가한다. 
# 인스턴스 x를 생성한다. 닉네임은 '네오'다.
x = MyClass('네오')      
# 인스턴스 x의 속성 nickname의 값을 확인한다.
x.nickname              
# 클래스 속성 counter의 값을 확인한다.
# MyClass가 생성한 인스턴스는 현재 1개다.
x.counter    
# 인스턴스 y를 생성한다. 닉네임은 '프로도'다.
y = MyClass('프로도')     
# 인스턴스 y의 속성 nickname의 값을 확인한다.
y.nickname              
# 클래스 속성 counter의 값을 확인한다.
# MyClass가 생성한 인스턴스는 현재 2개다.
y.counter 
# 인스턴스 z를 생성한다. 닉네임은 '라이언'이다.
z = MyClass('라이언')     
# 인스턴스 z의 속성 nickname의 값을 확인한다.
z.nickname
# 클래스 속성 counter의 값을 확인한다.
# MyClass가 생성한 인스턴스는 현재 3개다.
z.counter
# 인스턴스 x로 클래스 속성 counter의 값을 다시 확인한다.
x.counter
# 인스턴스 y로 클래스 속성 counter의 값을 다시 확인한다.
y.counter
# 클래스 이름으로 클래스 속성 counter 값을 확인한다
MyClass.counter

인스턴스 속성

class MyClass:
    counter = 0                # 클래스 속성을 초기화한다.
    def __init__(self, name):  # 인스턴스를 생성할 때 닉네임은 반드시 지정해야 한다.
        self.nickname = name   # 인스턴스 속성을 초기화한다.
        self.counter += 1      # self로 접근 : 인스턴스 속성으로 바뀐다.
# 인스턴스 x를 생성한다. 닉네임은 '네오'다.
x = MyClass('네오')      
# 인스턴스 x의 속성 nickname의 값을 확인한다.
x.nickname              
# 인스턴스 속성 counter의 값을 확인한다.
# 인스턴스 x는 현재 1개다.
x.counter               
# 인스턴스 y를 생성한다. 닉네임은 '프로도'다
y = MyClass('프로도')     
# 인스턴스 y의 속성 nickname의 값을 확인한다.
y.nickname                      
# 인스턴스 속성 counter의 값을 확인한다.
# 인스턴스 y는 현재 1개다.
y.counter
# 인스턴스 z를 생성한다. 닉네임은 '라이언'이다.
z = MyClass('라이언')     
# 인스턴스 z의 속성 nickname의 값을 확인한다.
z.nickname
# 인스턴스 속성 counter의 값을 확인한다.
# 인스턴스 z는 현재 1개다.
z.counter
# 인스턴스 x로 인스턴스 속성 counter 값을 다시 확인한다.
x.counter
# 인스턴스 y로 인스턴스 속성 counter 값을 다시 확인한다.
y.counter
# 클래스 이름으로 클래스 속성 counter 값을 확인한다.
# 클래스 속성을 단 한번도 변경한 적이 없다.
MyClass.counter         

캡슐화와 속내용 감추기

캡슐화(encapsulation)란? - revisited

캡슐화가 잘된 클래스는 데이터 손상을 방지한다. 클래스에서 정의한 객체의 속성은 객체 내부에 존재하며, 다른 객체가 접근하거나 변경하기 쉽지 않도록 설계하기 때문이다. 데이터의 손상을 방지하기 위해서 주로 속성을 사적(private) 속성으로 정의해서 외부로부터 데이터가 변경되거나 손상되는 것을 방지한다. 이를 속내용 감추기 또는 데이터 숨기기(data hiding)이라 한다.

사적 속성(private attribute)이란?

그렇다면 외부 세계에서 객체의 속성(데이터 )에 어떻게 접근할까?

클래스는 객체의 내부 상태, 즉 정보에 접근하려는 외부의 다른 객체들이 사용할 수 있는 인터페이스(interface)를 제공한다. 이는 메소드를 통해 구현한다. 문제는 파이썬에서는 순수한 사적 속성이 존재하지 않는다는 것이다.

그렇다면 파이썬에서는 어떻게 데이터를 외부 세계로부터 보호할 수 있을까?

완벽하지는 않지만 네임 맹글링 등의 방법을 통해 인스턴스 속성에 직접 접근하는 것을 어렵게 할 수 있다.

네임 맹글링

네임 맹글링(name mangling)이란?

따라해보기

class MyClass:
    def __init__(self, attr):
        self.__attr = attr  # 인스턴스 속성 __attr를 초기화한다.
        
c = MyClass('속성')
# 인스턴스 속성 __attr에 접근한다.
c.__attr

이처럼 직접 속성에 접근하려면 오류가 난다. 속성 이름이 없다는 것이다. 그렇다고 속성에 접근하는 것이 불가능한 것은 아니다. 다음과 같은 형식으로 접근이 가능하다.

객체이름._클래스이름__속성이름

c._MyClass__attr

파이썬 기본 철학에 따르면 모든 것을 비공개로 하는 것을 강조하지는 않는다. 하지만 만약 클래스나 인스턴스 속성에 아무나 접근하는 것이 부담스럽다면 완벽하게 객체의 내부 데이터를 보호할 수는 없지만 네임 맹글링을 통해서 이 문제를 어느 정도 해결할 수 있다.

앞서 정의한 인스턴스 속성인 nicknamewhours 를 네임 맹글링해보자.

__TODO__를 채워 다음 코드를 완성해보자.

class PartTimer:
    cafe_name = '느티나무 카페'   # 클래스 속성 : 아르바이트생이 근무하는 커피 가게 이름
    hour_rate = 9500           # 클래스 속성 : 시급
    def __init__(self, name):  # 초기화 메소드
        __TODO__ = name        # 인스턴스 속성 : 아르바이트생 닉네임
        __TODO__ = 0           # 인스턴스 속성 : 근무한 시간

메소드

메소드(method)란?

메소드는 클래스가 외부 세계에 제공하는 서비스며 특정 정보를 전달하거나 어떤 행동을 요청하는 등 서로 다른 객체 사이의 상호 작용을 위한 의사소통 수단으로 사용한다.

클래스가 생성한 인스턴스를 통해 메소드를 호출하기 위해서는 점 연산자(.)를 사용하며 형식은 다음과 같다.

객체이름.메소드이름([...])

직접 클래스를 통해 메소드에 접근할 때는 메소드를 호출하는 인스턴스를 첫 번째 전달인자로 지정해야 하기 때문에 􏰄􏰅􏰆다음과 같은 형식으로 호출한다.

클래스이름.메소드이름(인스턴스[, ...])

먼저 인스턴스 속성에 접근하기 위한 메소드를 정의해보자.

그리고 급여를 계산하는 메소드도 구현한다.

__TODO__를 채워 다음 코드를 완성해보자.

class PartTimer:
    cafe_name = '느티나무 카페'    # 클래스 속성 : 커피 가게 이름
    hour_rate = 9500            # 클래스 속성 : 시급
    def __init__(self, name):
        """초기화 메소드다."""
        self.__nickname = name  # 인스턴스 속성 : 아르바이트생 닉네임
        self.__whours = 0       # 인스턴스 속성 : 근무한 시간
    def nickname(self):
        """닉네임을 불러오는 메소드다."""
        return __TODO__
    def whours(self, hours_worked):
        """근무 시간을 설정(기존 근무 시간 + 현 근무 시간)하는 메소드다."""
        __TODO__
    def total_wage(self):
        """급여를 계산하는 메소드다."""
        return __TODO__

위에서 정의한 PartTimer 클래스가 잘 작동하는지 인스턴스를 몇 개 만들어 테스트 해보자.

kim = PartTimer('프로도')
lee = PartTimer('네오')
# '프로도'가 2시간 근무했다.
kim.whours(2)             
# '네오'가 3시간 근무했다.
lee.whours(3)             
# '네오'가 5시간을 근무했다(총 8시간).
lee.whours(5)             
# '프로도'가 3시간 근무했다(총 5시간).
kim.whours(3)             
# 이번 달 '프로도'의 급여는 9500 x 5시간 = 47,500원이다.
kim.total_wage()             
# 이번 달 '네오'의 월급은 9500 x 8시간 = 76,000원이다.
lee.total_wage()             

클래스로 메소드를 호출해보자.

PartTimer.whours(7)

오류가 난다. 이유가 뭘까?

클래스를 통해 메소드를 호출할 때 이 메소드를 사용하는 인스턴스를 첫 번째 전달인자로 지정해야 한다.

클래스이름.메소드이름(인스턴스[, ...])

# '프로도'가 3시간을 추가 근무했다(총 8시간).
PartTimer.whours(kim, 3)      
# 급여는 '네오'와 같은 9500 x 8시간 = 76,000원이다.
PartTimer.total_wage(kim)        

설명문자열(docstrings)

class PartTimer:
    """느티나무 카페에서 일하는 아르바이트생 클래스다.
    
    Attributes:
        Class Attributes: 
            cafe_name (str): 커피 가게 이름
            hour_rate (int): 시급
        Instance Attributes: 
            __nickname (str): 아르바이트생 닉네임
            __whours (int): 근무한 시간

    Methods:
        __init__(self, name: str) -> None:
        nickname(self) -> str:
        whours(self, hours_worked: int) -> None:
        total_wage(self) -> None:
    """
    # --- 클래스 속성 ---------------------------------------------------------- #
    cafe_name = '느티나무 카페'  # 클래스 속성 : 커피 가게 이름
    hour_rate = 9500          # 클래스 속성 : 시급
    
    # --- 초기화 메소드 --------------------------------------------------------- #
    def __init__(self, name):                      
        """인스턴스를 생성할 때 아르바이트생의 닉네임으로 초기화한다.
        
        Args:
            name (str): 아르바이트생의 닉네임
        """
        self.__nickname = name  # 인스턴스 속성 : 아르바이트생 닉네임
        self.__whours = 0       # 인스턴스 속성 : 근무한 시간
    
    # --- 접근자 메소드 -------------------------------------------------------- #
    def nickname(self):                         
        """아르바이트생의 닉네임을 반환한다.
        
        Returns:
            str: 아르바이트생의 닉네임
        """
        return self.__nickname
    
    def whours(self, hours_worked):             
        """아르바이트생의 근무한 시간을 기록한다.
        
        Args:
            hours_worked (int): 근무한 시간
        """
        self.__whours += hours_worked  # 기존 근무시간에 현 근무 시간을 추가해서 합한다.

    # --- 일반 메소드 ----------------------------------------------------------- #
    def total_wage(self):                          
        """지금까지 근무한 시간을 기준으로 계산한 급여를 반환한다.
        
        Returns:
            int: 현재까지 일한 급여
        """
        return self.__whours * PartTimer.hour_rate   # 총 근무시간 * 시급       
print(PartTimer.__doc__)

help() 함수로 PartTimer 클래스에 대해 알아보면 좀 더 상세한 정보를 알 수 있다. 다음 코드를 실행해보자.

help(PartTimer)

Lab: 아르바이트생 클래스 완성

앞서 다룬 아르바이트생 클래스(PartTimer)를 일부 변경해서 구현한 후 parttimer.py로 저장하자.

클래스 속성은 hour_rate, total_parttimers 가 있다. 시급인 hour_rate 의 기본 시급은 9,500원이다. total_parttimers 는 아르바이트생 전체 수를 담고 있으며 아르바이트생 인스턴스를 하나 생성할 때 마다 1씩 증가한다.

인스턴스 속성은 nickname, workplace, whours 가 있다. nickname 은 개별 아르바이트생의 닉네임을, workplace 는 이 아르바이트셍의 근무 장소(빌딩), 그리고 whours 는 근무 시간을 담고 있다. 인스턴스를 생성할 때 닉네임과 근무지를 전달인자로 사용해야 하는데, 닉네임은 반드시 주어져야 하며, 근무지가 주어지지 않으면 기본값으로 ‘113동’이 할당된다.

매소드는 초기화 메소드인 __init__(), 인스턴스 속성 값을 반환하는 접근자 메소드인 nickname(), workplace(), 근무한 시간을 기록하는 whours() 가 있다. 그리고 시급과 지금까지 근무한 시간으로 현재까지 일한 급여를 계산해서 반환하는 total_wage() 가 있다.

이렇게 구현한 PartTimer 클래스를 import문 또는 from…import문으로 불러와 다음 예처럼 실행해보자.

실행 예

>>> from parttimer import PartTimer
>>> park = PartTimer('라이언')
>>> lee = PartTimer('네오', '137-1동')
>>> park.nickname()
라이언
>>> lee.nickname()
네오
>>> park.workplace()
113
>>> lee.workplace()
137-1
>>> park.whours(4)
>>> lee.whours(3)
>>> park.whours(4)
>>> park.whours(3)
>>> lee.whours(3)
>>> park.total_wage()
104500
>>> lee.total_wage()
57000
>>> PartTimer.total_parttimers
2

클래스 상속

isinstance()issubclass() 함수

isinstance()

isinstance() 함수

isinstance() 함수를 사용하는 형식은 다음과 같다.

isinstance(x, y)

특징은 다음과 같다.

따라해보기

type(-5)
isinstance(-5, int)
isinstance(-5, str)
isinstance(0.0, float)
isinstance('', str)
isinstance([1], list)
class Person:
    pass

p = Person()
isinstance(p, Person)
type(p)
p.__class__

객체이름.__class__.__name__ 을 사용하면 객체를 생성한 클래스의 이름을 확인할 수 있다.

p.__class__.__name__
x = '1'
y = 1
x.__class__.__name__
y.__class__.__name__
class 파이썬:
    pass
k = 파이썬()
isinstance(k, str)
isinstance(k, 파이썬)
k.__class__.__name__
__name__

issubclass()

issubclass() 함수

issubclass() 함수를 사용하는 형식은 다음과 같다.

issubclass(x, y)

특징은 다음과 같다.

따라해보기

class Person: pass
class Student(Person): pass
class CollegeStudent(Student): pass
class Teacher(Person): pass
class Professor(Teacher): pass
# Person -> object
issubclass(Person, CollegeStudent)
# CollegeStudent -> Student -> Person
issubclass(CollegeStudent, Person)
# CollegeStudent -> Student
issubclass(CollegeStudent, Student)
# 클래스 자신은 클래스의 자신의 하위 클래스로 간주한다.
issubclass(CollegeStudent, CollegeStudent)
# Teacher -> Person
issubclass(Teacher, Person)
# 클래스 자신은 클래스의 자신의 하위 클래스로 간주한다.
issubclass(Teacher, Teacher)
# Teacher -> Person 
issubclass(Teacher, Student)
# Teacher -> Person
issubclass(Teacher, Professor)
# Professor -> Teacher -> Person
issubclass(Professor, Student)
# Professor -> Teacher -> Person
issubclass(Professor, Person)
# Student -> Person
issubclass(Student, Professor)

클래스 상속과 하위 클래스 만들기

클래스를 정의할 때 클래스 이름 뒤에 아무것도 지정하지 않으면 기본적으로 최상위 클래스인 object를 상속한다. object가 아닌 다른 클래스의 하위 클래스로 정의하는 형식은 다음과 같다.

class 클래스이름(상위-클래스-이름)
    [클래스 속성 정의 ...] 

    def __init__(self[, 매개변수-1, 매개변수-2, ...]):
        super().__init__([...])   
        [인스턴스 속성 정의 ...]
        명령문...

    [기타 메소드 정의 ...]

파이썬은 다중 상속을 지원한다.

다중 상속(multiple inheritance)이란?

class PartTimer:
    cafe_name = '느티나무 카페'
    hour_rate = 9500
    def __init__(self, name):                      
        self.__nickname = name 
        self.__whours = 0 
    def nickname(self):                         
        return self.__nickname
    def whours(self, hours_worked):             
        self.__whours += hours_worked
    def total_wage(self):                          
        return self.__whours * PartTimer.hour_rate

우선 PartTimer 클래스의 인스턴스 두 개를 생성한다.

kim = PartTimer('프로도')
lee = PartTimer('네오')

아르바이트생들의 일정을 관리하는 수석 아르바이트생인 ChiefPartTimer 클래스를 정의한다. 이 클래스는 PartTimer의 모든 속성과 메소드 외 다음과 같은 추가 속성과 메소드를 가진다.

__TODO__를 채워 다음 코드를 완성해보자.

class ChiefPartTimer(PartTimer):
    def __init__(self, name):        
        super().__TODO__            # super() 함수로 상속받는 모든 속성들을 초기화한다.
        __TODO__.__workers = []     # 인스턴스 속성 : 근무자 리스트

    def add_worker(self, other):
        if __TODO__:                             # PartTimer의 인스턴스면 
            __TODO__.__workers.append(__TODO__)  # __workers에 닉네임을 추가한다.
        else:                                    # PartTimer의 인스턴스가 아니면 
            print(f'{PartTimer.__name__}의 인스턴스가 아닙니다.')  # 오류 메시지를 출력한다.

    def show_workers(self):
        print(__TODO__)   # 자신이 관리하는 아르바이트생 모두의 닉네임을 출력한다.

닉네임이 ‘라이언’인 ChiefPartTimer 클래스의 인스턴스를 생성한다.

__TODO__를 채워 다음 코드를 완성해보자.

# 닉네임이 '라이언'인 ChiefPartTimer 클래스의 객체를 생성해서 변수 park에 할당한다.
park = __TODO__
# park의 닉네임을 확인한다.
park.nickname()
# 아르바이트생 '프로도'를 park이 관리하는 근무자 리스트에 추가한다.
park.add_worker(kim)
# 아르바이트생 '네오'를 park이 관리하는 근무자 리스트에 추가한다.
park.add_worker(lee)
# 문자열 '제이지'를 park이 관리하는 근무자 리스트에 추가한다.
park.add_worker('제이지')
# park이 현재 관리하는 모든 아르바이트생의 닉네임을 출력한다.
park.show_workers()
# '라이언'이 5시간 근무했다(총 5시간).
park.whours(5) 
# 이번 달 '라이언'의 월급은 9500 x 3시간 = 47,500원이다.
park.total_wage()     

마지막으로 인스턴스가 속한 클래스를 확인해보자.

isinstance(park, ChiefPartTimer)
isinstance(park, PartTimer)
isinstance(park, kim.__class__)
park.__class__.__name__
kim.__class__.__name__
isinstance(kim, PartTimer)
isinstance(lee, PartTimer)
isinstance(kim, ChiefPartTimer)
isinstance(lee, ChiefPartTimer)

THE END