Python - class, function, method

2022. 12. 23. 02:22기술 공부/Python

이번엔 class, function, method를 알아보자.

자료를 찾다 보니 깨달은 사실인데 이 셋을 어떻게 만들고 쓰는지에 대해서는 자료가 가득하지만,
얘들의 정체가 무엇이며 언제 어떻게 써야 할지는 많이 없는 것 같아, 이 부분을 위주로 작성하려고 한다.


일단 class, function, methods가 누구인가. type()을 이용해서 알아보자.

def hello_world():
    print('Hello World!')


print(type(hello_world))
print(type(int()))
print(type(print))

예시를 들기 위해, hello_world라는 함수를 선언하여 우리가 선언한 def는 function, method인지 알아보자.
그리고 가장 많이 쓰는 int와 print는 function, method인지 알아보자.

출력 결과 :

<class 'function'>
<class 'int'>
<class 'builtin_function_or_method'>

다소 충격적인 결과가 나왔다.

우선 직접 만든 hello_world는 function이라고 나왔다.
그리고 함수처럼 쓰던 int()는 class였다.
마지막으로 print는 builtin_function_or_method이라고 나왔다.

 

 

class는 잠시 뒤로 미루고,
function과 builtin_function_or_method를 일단 알아보자.
같은 def로 선언한 함수인데 왜 다른 결과가 나왔을까.

 

 

가장 간단한 function부터 살펴보자.
Python에서 규정한 function이란 무엇인지, Docs에서 찾아보자.

function (함수)
호출자에게 어떤 값을 돌려주는 일련의 문장들.
없거나 그 이상의 인자 가 전달될 수 있는데, 바디의 실행에 사용될 수 있습니다. 

호오. 일단 '호출자에게 어떤 값을 돌려주는 일련의 문장들.'이 가장 눈에 띄인다.

 

 

다음으로 builtin_function_or_method를 살펴보자.
builtin이란 번역하면 내장이란 뜻이다. Python에서 기본적으로 제공한다는 의미가 되겠다.

function은 위에서 찾아봤으니, 
Python에서 규정한 method란 무엇인지, Docs에서 찾아보자.

method (메서드)
클래스 바디 안에서 정의되는 함수.
그 클래스의 인스턴스의 어트리뷰트로서 호출되면,
그 메서드는 첫 번째 인자 (보통 self라고 불린다)로 인스턴스 객체를 받습니다.

그렇다. method란, '클래스 바디 안에서 정의되는 함수.'이다.

정리해 보자면, print는 class 안에서 정의된 function은 아니니,
builtin_function이라고 보는 것이 맞겠다.

아무래도 builtin을 많이 제공하는 Python에서는
built에 한정하여 function과 method를 묶어서 분류하는 모양이다.

 

 

마지막으로 class를 찾아보자.

class (클래스)
사용자 정의 객체들을 만들기 위한 주형.
클래스 정의는 보통 클래스의 인스턴스를 대상으로 연산하는 메서드 정의들을 포함합니다.

여러 어려워 보이는 단어가 많지만 여기서 주목해야 하는 부분은
'클래스의 인스턴스를 대상으로 연산하는 메서드 정의들을 포함합니다.'라는 문장이다.

일단은 class란 class의 instance(인스턴스)를 대상으로 연산하는 method들이라고 볼 수 있겠다.

 

 

Python에서 규정한 class, function, method를 모두 찾아보았다.
다음으로 넘어가기 전에 정리를 해보자.

function 어떤 값을 돌려주는 일련의 문장(코드)들.
method 클래스 바디 안에서 정의되는 함수(function).
class 클래스의 인스턴스를 대상으로 연산하는 메서드들

우리는 앞서 class, function, method의 정체를 알아보았다.
이번에는 function, 그리고 class와 method, 2개를 묶어서  언제 어디에 쓰면 좋을지 알아보자.

 

 

function을 써야 할 것 같은 간단한 예시 코드를 준비해 보았다.
실제로 필드에서는 상상도 할 수 없을 만큼 긴 코드들이 실존한다.
여기서는 예시를 위해 짧게 준비하였다는 것을 감안하자.

 

 

회사에 갔더니, a와 b는 둘 다 10이고,
a와 b를 더해서 5번 출력하는 프로그램을 만들어달라는 요청이 왔다.

다 만들었더니, a + b를 출력한 후 원하는 곳에서 a를 1씩 늘려달라고 하길래 추가했다.

a = 10
b = 10

print(a + b)
a = a + 1
print(a + b)
a = a + 1
print(a + b)
print(a + b)
a = a + 1
print(a + b)

그렇게 완성된 코드이다.
a + b가 반복되고, a = a + 1도 반복되는 것이 눈에 띈다.

근데 다음 날, a와 b를 더하는 게 아니라 곱해달라고 한다.

 

 

우린 이런 경우, 사용자 정의 function(def)를 이용하여 노동 시간을 단축시킬 수 있다.
우선 다섯 번 수정해야 하는 a + b를 묶어보자.

a = 10
b = 10


def calculate_print(x, y):
    print(x * y)


calculate_print(a, b)
a = a + 1
calculate_print(a, b)
a = a + 1
calculate_print(a, b)
calculate_print(a, b)
a = a + 1
calculate_print(a, b)

훌륭한 calculate_print function이 만들어졌다.
이로서 우리는 + 를 * 연산으로 바꾸는 데, 대략 5초 걸릴 일을 1초 만에
끝내고 4초간 회사에 앉아 웹툰을 볼 수 있는 시간을 벌었다.

그다음 날, 마지막에 a에 1을 한 번 더 더해달라고 한다.

그래서 수정하려고 하는데,
문득 원하는 곳에 a = a + 1을 추가해 달라고 했었으니,
나중에 또 이런 요청이 올지 모른다고 생각이 들었다.

어차피 a + b를 출력한 후에 a = a + 1을 한다고 했으니...

a = 10
b = 10


def calculate_print_plus(x, y, z):
    # 입력받은 x와 y를 곱하여 print()한다.
    print(x * y)
    
    if z:
        # z가 True 면, x + 1을 돌려준다.
        return x + 1
    else:
        # z가 False 면, x를 돌려준다.
        return x


a = calculate_print_plus(a, b, True)
a = calculate_print_plus(a, b, True)
a = calculate_print_plus(a, b, False)
a = calculate_print_plus(a, b, True)
a = calculate_print_plus(a, b, True)

훌륭한 calculate_print_plus function이 완성되었다.
이제 언제든 True와 False만 바꾸기만 하면,
변덕 심한 요청을 들어주면서 동시에 생색내며 커피를 얻어 마실 수 있게 되었다.

이게 뭔 개판 스토리인지 이해할 필요 전혀 없다.
중요한 건, 중간중간 function이 추가되며 노동 시간이 단축된 것이다.

그렇다. 번지르르한 말을 빼고 나면,
반복되는 코드를 반복해서 타이핑하는 시간을 줄이기 위해 function이 사용된다.

실제로 필드에서는 print(a + b)나 a = a + 1 같은 한 줄 코드가 아닌,
수십, 수백 줄의 코드가 몇 번이나 반복되는 경우가 많다.

그래서 개발을 하기 전에 반복되는 코드를 미리 파악해
function으로 짜고, 이것을 모아서 모듈을 만들어 둔다.

이해가 됐을지는 모르겠다.
하지만 나중에 긴 코드를 직접 자다 보면 뼈저리게 느끼게 될 것이다.


다음으로 class와 method를 언제 어디에 쓰면 좋을지 알아보자.
이해를 돕기 위해 또다시 개판 스토리를 준비해 보았다.
오글거리더라도 나름 괜찮은 설명이라고 개인적으로 생각한다.

 

 

호그와트의 점심시간이 찾아왔다.
해리와 헤르미온느, 론은 학식을 먹으려고 한다.

호그와트의 학식에서는 급식판을 사용하는데,
마법사들 답게 학식에서 '급식판 소환!'을 외치면,

철로 만든 급식판이 짠하고 나타난다.
급식판 맨 위 오른쪽에는 이름도 새겨져 있고,
맨 위 왼쪽에는 포크 수납장과 수납장을 여는 버튼이 있다.

호그와트 급식판 상상도

다시 현생으로 돌아와서,
그렇다. 우리는 이 호그와트 급식판을 class로 만들 것이다.

 

 

만들어진 급식판 코드는 이렇다.

class 급식판소환:
    # 급식판은 철로 만들어져 있다.
    소재 = '철'

    # 기본으로 포크수납장이 있다.
    포크수납장 = ['포크', '이쑤시개']

    # 이 메소드는 class 가 생성될 때 *무조건* 실행된다.
    # 전문 용어로 생성자 라고 한다.
    def __init__(self, 이름):
        # 소환한 사람의 이름을 급식판에 새긴다.
        self.소환자 = 이름

        # 3개의 반찬칸과 밥칸, 국칸이 있고, 무엇을 받을 지 모른다.
        self.반찬칸1 = ''
        self.반찬칸2 = ''
        self.반찬칸3 = ''
        self.밥칸 = ''
        self.국칸 = ''

    def 포크수납장_버튼(self):
        # 포크수납장 버튼을 누르면 포크와 이쑤시개를 얻을 수 있다.
        return self.포크수납장

    def 반찬칸1_받기(self, 반찬):
        # 반찬칸1에 받은 반찬을 채울 수 있다.
        self.반찬칸1 = 반찬

    def 반찬칸2_받기(self, 반찬):
        # 반찬칸2에 받은 반찬을 채울 수 있다.
        self.반찬칸2 = 반찬

    def 반찬칸3_받기(self, 반찬):
        # 반찬칸3에 받은 반찬을 채울 수 있다.
        self.반찬칸3 = 반찬

    def 밥칸_받기(self, 밥):
        # 밥칸에 받은 밥을 채울 수 있다.
        self.밥칸 = 밥

    def 국_받기(self, 국):
        # 국칸에 받은 국을 채울 수 있다.
        self.국칸 = 국

    def 비우기(self):
        # 급식판을 모두 비운다.
        self.반찬칸1 = ''
        self.반찬칸2 = ''
        self.반찬칸3 = ''
        self.밥칸 = ''
        self.국칸 = ''


급식판_해리 = 급식판소환('해리')
급식판_헤르미온느 = 급식판소환('헤르미온느')
급식판_론 = 급식판소환('론')

 

아직 왜 class를 써야 하는지 불분명하다.
class가 없을 때 해리, 헤르미온느, 론의 급식판 코드를 봐보자.

 

 

급식판_소재_해리 = '철'
급식판_소재_헤르미온느 = '철'
급식판_소재_론 = '철'
포크수납장_해리 = ['포크', '이쑤시개']
포크수납장_헤르미온느 = ['포크', '이쑤시개']
포크수납장_론 = ['포크', '이쑤시개']
.
.
.

그만 알아보자... 더 큰 문제는 3명 급식판만도 벌써 힘든데, 호그와트에는 3명만 다니지 않는다.

 

 

확인해보니 class 역시 반복되는 코드를 반복해서 타이핑하는 시간을 줄이기 위해 사용된다.
그리고 def와는 다르게 데이터 값을 인스턴스(객체)에 저장할 수 있다는 것을 알 수 있다.

갑자기 인스턴스라는 친구가 등장했다.

인스턴스라는 놈은 클래스가 실체화된 것을 의미한다.
즉, 클래스가 실체를 가지고 구현된다는 것을 의미한다.
그렇다면 클래스는 실체가 없다는 말일까? 그렇다. 클래스는 추상적인 존재다.

해리가 '급식판소환!'을 외치기 전에는 급식판을 볼 수도 만질 수도 없는 상상 속의 존재인 것이다.
(그리고 상상 속의 존재이기 때문에 무한한 발전 또는 변화가 가능하다.)
'급식판소환!'을 외쳐야만 그 실체가 생겨 볼 수도 만질 수도 있는 것이다.
(붕어빵틀과 붕어빵을 인용하려다, 이야기를 바꾼 주된 이유이다.)

또한 해리가 소환한 급식판과 헤르미온느가 소환한 급식판은 다른 존재이다.
이미 급식판 위에 새겨진 이름도 다르거니와, 각자 담은 반찬도 다를 것이다.

 

 

요약 정리해 보자면,

class는 추상적인(실체가 없는) 존재이다.
class를 생성(급식판 소환)하게 되면 인스턴스(실체)가 생긴다.
이렇게 생성된 인스턴스는 각자 다른 존재다.
각자 다른 존재라서, 각자 다른 데이터를 저장할 수 있다.

 

 

그리고 왜, 언제 쓰는지를 설명하자면,

반복되는 코드를 반복해서 타이핑하는 시간을 줄이기 위해 사용된다.
상상 속의 존재이기 때문에 내가 원하는 데로 상상할(만들거나 변경할) 수 있다.
인스턴스에 각자 다른 데이터 값을 연산, 처리 및 저장하기 위해 사용된다.

라고 말할 수 있겠다.

 

 

번지르르한 말을 빼고 설명하자면,
function만 쓰다가 어느 날 갑자기 'function도 반복되는구나' 하고,
변수의 이름들이 잘 기억나지 않고 오락 가락 하면서,
귀찮음과 불편함을 느끼게 되면 class를 써야 할 때가 온 것이다.

그리고 class의 신세계를 느끼면 되겠다.
Python의 모든 것은 class와 인스턴스로 만들어져 있다고 해도 과언이 아니니까.

 

설명이 잘 되었을지는 사실 잘 모르겠다.

실제로 필드에서 신입이 직접 클래스를 만드는 경우는
없다고 말할 수 있다. 있다면 진심으로 박수를 쳐주자.


번외로 클래스의 간단한 생김새와 사용 방법을 살펴보자.

class HogwartsLunchPlate:
    # 클래스, 인스턴스의 공통적인 데이터를 저장한다.
    # material, fork_cabinet과 같은 변수들을 '클래스 변수'라고 부른다.
    # 인스턴스가 생성되지 않아도 사용할 수 있다.

    # 해리와 헤르미온느, 론의 급식판 소재는 철로 되어 있는 것은 모두 동일하다.
    material = 'Iron'

    # 마찬가지로 모든 식판에 포크 수납장이 있다.
    fork_cabinet = ['Fork', 'Toothpick']

    # HogwartsLunchPlate()가 실행될 때 무조건 실행된다.
    # 이 것을 '생성자'라고 부른다.
    # initial(초기화)의 약자이다.
    def __init__(self, name):
        # 인스턴스의 변수를 생성하고 저장한다.
        # 이를 '인스턴스 변수'라고 부른다.
        # 인스턴스마다 다른 값을 저장할 수 있다.
        # 인스턴스가 생성되어야 사용할 수 있다.
        self.owner = name
        self.side_1 = ''
        self.side_2 = ''
        self.side_3 = ''
        self.rice = ''
        self.soup = ''

    # 이런 메소드를 classmethod 라고 부른다.
    # 인스턴스가 생성되어야 사용할 수 있다.
    def open_fork_cabinet(self):
        return self.fork_cabinet

    def set_side_1(self, side):
        self.side_1 = side

    def set_side_2(self, side):
        self.side_2 = side

    def set_side_3(self, side):
        self.side_3 = side

    def set_rice(self, rice):
        self.rice = rice

    def set_soup(self, soup):
        self.soup = soup

    def flush(self):
        self.side_1 = ''
        self.side_2 = ''
        self.side_3 = ''
        self.rice = ''
        self.soup = ''

    # 이런 메소드를 스태틱 메소드 라고 부른다.
    # 인스턴스가 생성되지 않아도 사용할 수 있다.
    @staticmethod
    def return_hi():
        return 'Hi'


# 클래스 변수는 이렇게 사용할 수 있다.
print('클래스의 클래스 변수 호출')
print('HogwartsLunchPlate.material :', HogwartsLunchPlate.material)
print()

print('클래스의 클래스 변수 호출')
print('HogwartsLunchPlate.fork_cabinet) :', HogwartsLunchPlate.fork_cabinet)
print()

# 스태틱 메소드는 이렇게 사용할 수 있다.
print('클래스의 스태틱 메소드 호출')
print('HogwartsLunchPlate.return_hi()) :', HogwartsLunchPlate.return_hi())
print()

# 이렇게 사용하면 오류가 나온다.
# 인스턴스를 생성하지 않고, 인스턴스 변수를 사용하는 경우,
# print(HogwartsLunchPlate.owner)

# 이렇게 사용하면 오류가 나온다.
# 인스턴스를 생성하지 않고, 클래스 메소드를 사용하는 경우,
# print(HogwartsLunchPlate.set_side_1())

# 급식판을 소환해보자. (인스턴스를 만들어보자.)
harry_plate = HogwartsLunchPlate('Harry')

# 인스턴스 변수는 이렇게 사용할 수 있다.
print('인스턴스의 인스턴스 변수 호출')
print('harry_plate.owner :', harry_plate.owner)
print()
print('인스턴스의 인스턴스 변수 호출 (값 넣기 전)')
print('harry_plate.side_1 :', harry_plate.side_1)
print()
harry_plate.side_1 = '김치'
print("harry_plate.side_1 = '김치' 실행")
print('인스턴스의 인스턴스 변수 호출 (값 넣은 후)')
print('harry_plate.side_1 :', harry_plate.side_1)
print()

# 클래스 메소드는 이렇게 사용할 수 있다.
print('인스턴스의 인스턴스 변수 호출 (값 넣기 전)')
print('harry_plate.side_2 :', harry_plate.side_2)
print()
harry_plate.set_side_2('두부 조림')
print("harry_plate.set_side_2('두부 조림') 실행")
print('인스턴스의 인스턴스 변수 호출 (값 넣은 후)')
print('harry_plate.side_2 :', harry_plate.side_2)
print()

# 인스턴스에서도 클래스 변수, 스태틱 변수도 사용할 수 있다.
print('인스턴스의 클래스 변수 호출')
print('harry_plate.material :', harry_plate.material)
print()
print('인스턴스의 클래스 변수 호출')
print('harry_plate.fork_cabinet :', harry_plate.fork_cabinet)
print()
print('인스턴스의 스태틱 메소드 호출')
print('harry_plate.return_hi() :', harry_plate.return_hi())
print()
출력 결과 : 

클래스의 클래스 변수 호출
HogwartsLunchPlate.material : Iron

클래스의 클래스 변수 호출
HogwartsLunchPlate.fork_cabinet) : ['Fork', 'Toothpick']

클래스의 스태틱 메소드 호출
HogwartsLunchPlate.return_hi()) : Hi

인스턴스의 인스턴스 변수 호출
harry_plate.owner : Harry

인스턴스의 인스턴스 변수 호출 (값 넣기 전)
harry_plate.side_1 : 

harry_plate.side_1 = '김치' 실행
인스턴스의 인스턴스 변수 호출 (값 넣은 후)
harry_plate.side_1 : 김치

인스턴스의 인스턴스 변수 호출 (값 넣기 전)
harry_plate.side_2 : 

harry_plate.set_side_2('두부 조림') 실행
인스턴스의 인스턴스 변수 호출 (값 넣은 후)
harry_plate.side_2 : 두부 조림

인스턴스의 클래스 변수 호출
harry_plate.material : Iron

인스턴스의 클래스 변수 호출
harry_plate.fork_cabinet : ['Fork', 'Toothpick']

인스턴스의 스태틱 메소드 호출
harry_plate.return_hi() : Hi

'기술 공부 > Python' 카테고리의 다른 글

Decorators  (0) 2023.02.01
custom_min() vs 내장 함수 min(), 그리고 그 차이  (0) 2023.01.02
Python은 어떻게 작동할까? (2)  (2) 2022.12.30
Python은 어떻게 작동할까? (1)  (1) 2022.12.25