[2021년 10월 업데이트]

안녕하세요. 스쿨오브웹의 이상희입니다.

이전 강좌에서 배운 인스턴스 메소드는 ‘self’인 인스턴스를 인자로 받고 인스턴스 변수와 같이 하나의 인스턴스에만 한정된 데이터를 생성, 변경, 참조 한다면, 클래스 메소드는 ‘cls’인 클래스를 인자로 받고 모든 인스턴스가 공유하는 클래스 변수와 같은 데이터를 생성, 변경 또는 참조하기 위한 메소드라고 생각하시면 됩니다. 예제를 보면서 클래스 메소드의 사용법에 대해서 설명을 드리겠습니다.

oop_4.py

class Employee:
    raise_amount = 1.1  # 1 연봉 인상율 클래스 변수

    def __init__(self, first, last, pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first.lower() + '.' + last.lower() + '@schoolofweb.net'

    def apply_raise(self):
        self.pay = int(self.pay * self.raise_amount)

    def full_name(self):
        return '{} {}'.format(self.first, self.last)

    def get_pay(self):
        return '현재 "{}"의 연봉은 "{}"입니다.'.format(self.full_name(), self.pay)


emp_1 = Employee('Sanghee', 'Lee', 50000)
emp_2 = Employee('Minjung', 'Kim', 60000)

# 연봉 인상 전
print(emp_1.get_pay())
print(emp_2.get_pay())

# 연봉 인상
emp_1.apply_raise()
emp_2.apply_raise()

# 연봉 인상 후
print(emp_1.get_pay())
print(emp_2.get_pay())
$ python oop_4.py
현재 "Sanghee Lee"의 연봉은 "50000"입니다.
현재 "Minjung Kim"의 연봉은 "60000"입니다.
현재 "Sanghee Lee"의 연봉은 "55000"입니다.
현재 "Minjung Kim"의 연봉은 "66000"입니다.

위의 코드를 보시면 클래스 변수인 ‘raise_amount’를 정의하여 ‘apply_raise’인스턴스 메소드를 실행하여 각 직원의 연봉을 인상할때 모든 직원 인스턴스에게 같은 인상율이 적용되도록 하였습니다. 만약에 다음 해에 연봉 인상율을 변경해야 한다면 클래스 메소드를 사용하여 변경하는 것이 좋습니다. 물론 “Employee.raise_amount = 1.2″와 같이 직접 클래스 변수를 변경하는 방법도 있지만 데이터 검사나 다른 부가 기능등의 추가가 필요할때 클래스 메소드를 사용하면 아주 편리합니다. 아래의 코드를 보시죠.

oop_4.py

class Employee:
    raise_amount = 1.1  # 연봉 인상율 클래스 변수

    def __init__(self, first, last, pay):
        self.first = first
        self.last = last
        self.pay = pay

    def apply_raise(self):
        self.pay = int(self.pay * self.raise_amount)

    def full_name(self):
        return '{} {}'.format(self.first, self.last)

    def get_pay(self):
        return '현재 "{}"의 연봉은 "{}"입니다.'.format(self.full_name(), self.pay)

    # 1 클래스 메소드 데코레이터를 사용하여 클래스 메소드 정의
    @classmethod
    def change_raise_amount(cls, amount):
        # 2 인상율이 "1" 보다 작으면 재입력 요청
        while amount < 1:
            print('[경고] 인상율은 "1"보다 작을 수 없습니다.')
            amount = input('[입력] 인상율을 다시 입력하여 주십시오.\n=> ')
            amount = float(amount)
        cls.raise_amount = amount
        print('인상율 "{}"가 적용 되었습니다.'.format(amount))


emp_1 = Employee('Sanghee', 'Lee', 50000)
emp_2 = Employee('Minjung', 'Kim', 60000)

# 연봉 인상 전
print(emp_1.get_pay())
print(emp_2.get_pay())

# 연봉 인상율 변경
Employee.change_raise_amount(0.9)

# 연봉 인상
emp_1.apply_raise()
emp_2.apply_raise()

# 연봉 인상 후
print(emp_1.get_pay())
print(emp_2.get_pay())
$ python oop_4.py
현재 "Sanghee Lee"의 연봉은 "50000"입니다.
현재 "Minjung Kim"의 연봉은 "60000"입니다.
[경고] 인상율은 "1"보다 작을 수 없습니다.
[입력] 인상율을 다시 입력하여 주십시오.
=> 1.2
인상율 "1.2"가 적용 되었습니다.
현재 "Sanghee Lee"의 연봉은 "60000"입니다.
현재 "Minjung Kim"의 연봉은 "72000"입니다.

#1에서 클래스 메소드 데코레이터를 사용하여 클래스 메소드를 정의했습니다. #2에서는 데이터 무결성 검사를 실시한 후, 데이터가 1보다 큰 경우에만 클래스변수를 변경하였습니다. 클래스 메소드는 인스턴스 생성자(constructor)와 같은 용도로 사용하는 경우도 있습니다. 예제를 보도록 하죠.

oop_4.py

class Person:
    def __init__(self, year, month, day, sex):
        self.year = year
        self.month = month
        self.day = day
        self.sex = sex

    def __str__(self):
        return '{}년 {}월 {}일생 {}입니다.'.format(self.year, self.month, self.day, self.sex)


person_1 = Person(1990, 8, 29, '남성')
print(person_1)
$ python oop_4.py
1990년 8월 29일생 남성입니다.

생년월일과 성별 데이터를 가진 클래스를 사용해 봤습니다. 하지만 항상 “년, 월, 일, 성별”을 인자로 받는게 아니라 경우에 따라서는 주민등록번호를 인자로 받아 인스턴스를 생성해야 하는 경우가 있다고 생각을 해보죠. 그럴 경우 다음과 같은 코드를 만들 수 있습니다.

oop_4.py

class Person:
    def __init__(self, year, month, day, sex):
        self.year = year
        self.month = month
        self.day = day
        self.sex = sex

    def __str__(self):
        return '{}년 {}월 {}일생 {}입니다.'.format(self.year, self.month, self.day, self.sex)


ssn_1 = '900829-1034356'
ssn_2 = '051224-4061569'


def ssn_parser(ssn):
    front, back = ssn.split('-')
    sex = back[0]

    if sex == '1' or sex == '2':
        year = '19' + front[:2]
    else:
        year = '20' + front[:2]

    if (int(sex) % 2) == 0:
        sex = '여성'
    else:
        sex = '남성'

    month = front[2:4]
    day = front[4:6]

    return year, month, day, sex


person_1 = Person(*ssn_parser(ssn_1))
print(person_1)

person_2 = Person(*ssn_parser(ssn_2))
print(person_2)
$ python oop_4.py
1990년 08월 29일생 남성입니다.
2005년 12월 24일생 여성입니다.

위와 같은 방법으로 ssn_parser라는 함수를 사용하여 주민등록번호를 분석한 후에 튜플을 클래스 생성자 인자로 전달하여도 되지만, 이것보다 더 좋은 방법은 클래스 메소드로 대체 생성자를 만드는 것입니다. 예제를 보시죠.

oop_4.py

class Person:
    def __init__(self, year, month, day, sex):
        self.year = year
        self.month = month
        self.day = day
        self.sex = sex

    def __str__(self):
        return '{}년 {}월 {}일생 {}입니다.'.format(self.year, self.month, self.day, self.sex)

    @classmethod
    def ssn_constructor(cls, ssn):
        front, back = ssn.split('-')
        sex = back[0]

        if sex == '1' or sex == '2':
            year = '19' + front[:2]
        else:
            year = '20' + front[:2]

        if (int(sex) % 2) == 0:
            sex = '여성'
        else:
            sex = '남성'

        month = front[2:4]
        day = front[4:6]

        return cls(year, month, day, sex)


ssn_1 = '900829-1034356'
ssn_2 = '051224-4061569'

person_1 = Person.ssn_constructor(ssn_1)
print(person_1)

person_2 = Person.ssn_constructor(ssn_2)
print(person_2)
$ python oop_4.py
1990년 08월 29일생 남성입니다.
2005년 12월 24일생 여성입니다.

클래스 밖에서 함수를 사용하였때와 결과는 같지만 함수가 클래스 안에 메소드로 정의되어 조금 더 세련된 코드가 된것을 볼 수 있습니다.

이번에는 스태틱 메소드의 사용방법에 대해서 알아보도록 하겠습니다. 많은 사람들이 클래스 메소드와 스태틱 메소드를 혼동하는데, 이 강좌에서 인스턴스 메소드, 클래스 메소드, 스태틱 메소드 이 세 가지 메소드의 개념을 확실히 잡고 가도록 하죠.

이 세 가지 메소드는 모두 클래스 안에서 정의 됩니다. 인스턴스 메소드는 인스턴스를 통해서 호출이 되고, 첫 번째 인자로 인스턴스 자신을 자동으로 전달합니다. 관습적으로 이 인수를 ‘self’라고 칭합니다. 클래스 메소드는 클래스를 통해서 호출이 되고 “@classmethod”라는 데코레이터로 정의합니다. 첫 번째 인자로는 클래스 자신이 자동으로 전달되고 이 인수를 관습적으로 ‘cls’라고 칭합니다. 스태틱 메소드는 앞서 설명한 두 메소드와는 틀리게 인스턴스나 클래스를 첫 번째 인자로 받지 않습니다. 스태틱 메소드는 클래스 안에서 정의되어 클래스 네임스페이스 안에는 있을뿐 일반 함수와 전혀 다를게 없습니다. 하지만 클래스와 연관성이 있는 함수를 클래스 안에 정의하여 클래스나 인스턴스를 통해서 호출하여 조금 편하게 쓸 수가 있는 것 입니다. 밑의 예를 보시죠.

oop_4.py

import datetime


class Person:
    my_class_var = 'sanghee'

    def __init__(self, year, month, day, sex):
        self.year = year
        self.month = month
        self.day = day
        self.sex = sex

    def __str__(self):
        return '{}년 {}월 {}일생 {}입니다.'.format(self.year, self.month, self.day, self.sex)

    @classmethod
    def ssn_constructor(cls, ssn):
        front, back = ssn.split('-')
        sex = back[0]

        if sex == '1' or sex == '2':
            year = '19' + front[:2]
        else:
            year = '20' + front[:2]

        if (int(sex) % 2) == 0:
            sex = '여성'
        else:
            sex = '남성'

        month = front[2:4]
        day = front[4:6]

        return cls(year, month, day, sex)

    @staticmethod
    def is_work_day(day):
        # weekday() 함수의 리턴값은
        # 월: 0, 화: 1, 수: 2, 목: 3, 금: 4, 토: 5, 일: 6
        if day.weekday() == 5 or day.weekday() == 6:
            return False
        return True


ssn_1 = '900829-1034356'
ssn_2 = '051224-4061569'

person_1 = Person.ssn_constructor(ssn_1)
print(person_1)

person_2 = Person.ssn_constructor(ssn_2)
print(person_2)

# 일요일 날짜 오브젝트 생성
my_date = datetime.date(2016, 10, 9)

# 클래스를 통하여 스태틱 메소드 호출
print(Person.is_work_day(my_date))
# 인스턴스를 통하여 스태틱 메소드 호출
print(person_1.is_work_day(my_date))
$ python oop_4.py
1990년 08월 29일생 남성입니다.
2005년 12월 24일생 여성입니다.
False
False

@staticmethod 데코레이터를 사용하여 ‘is_work_day’라는 이름과 어떤 날짜에 근무 여부를 리턴하는 기능을 가진 스태틱 메소드를 만들어 봤습니다.

다음 강의에서는 클래스의 상속과 서브 클래스에 대해서 알아 보겠습니다.