파이썬의 클래스와 객체지향

jkky98·2023년 4월 16일
0

Python

목록 보기
1/1

클래스는 왜 필요한가?

많은 파이썬 입문자들이 파이썬을 배우면서 잘 이해가 안되고 생소한 개념이 있다. 바로 객체지향이란 개념과 클래스 개념이다. 객체지향언어는 클래스개념을 구현 가능한 언어라고 볼 수 있다. 파이썬의 문법또한 타 언어들과 차이가 많지만, 강조점을 객체지향에 두어 파이썬이라는 언어를 "객체지향" 언어라고 하는 것은 바로 클래스를 구현하는 것이 중요하기 때문이다.

클래스는 def선언을 가지는 함수보다 더 복잡해보인다. 그렇기에 이 둘을 헷갈려하고 무엇이 더 나은 선택인지 모르는 경우 쉬운 구조인 함수를 선택하곤 한다.

많은 예시로 제시되는 계산기 클래스 만들기 예제를 볼 때, 함수로 계산기의 기능을 모두 포함할 수 있을까? 계산기의 필수 필요구조는 다음과 같다.

  1. 연산능력( + , - , x , % , ....)
  2. 숫자 누적능력
  3. 계산 숫자 초기화

파이썬의 함수구조는 입력을 받고 내부로직을 통한 출력을 내보내는 기능이 전부이다. 계산기를 구현하려면 함수 내부에서 모두 처리할 수 없을 것이다. 클래스를 활용하면 클래스 하나로 연속적인 사건을 처리할 수 있다. 즉 사건1(ex.계산기에서의 연산) 사건2(ex.계산기에서의 숫자 초기화)등등 여러 사건을 연결할 수 있다.

함수로도 가능한가?
하나의 파이썬 파일 내에서 결국 클래스 없이 위의 예제인 계산기 구조를 if나 함수선언을 통해 그럴듯해보이게 제작이 가능하다. 우선 결론적으로 코드가 굉장히 더러워 진다. 또한 함수내에서의 변수로만 가능한 것이 아닌 전역변수들을 또한 활용해서 구현해야만 할 것이다. 사실 예제로 제시된 계산기는 매우 저수준의 쉬운 이해를 위한 예시일 뿐이다.

클래스, 객체, 인스턴스

클래스와 객체 인스턴스라는 영어단어에서 내가 무엇을 이해해야하는지, 처음 이 단어들을 볼 때 느낄 수 없다.

class Calculator:
    def __init__(self):
        self.result = 0

    def add(self, num):
        self.result += num
        return self.result

    def sub(self, num):
        self.result -= num
        return self.result
        
a = Calculator()
a.add(300)
a.sub(300)
a.result

이 코드에서 우리는 Calculator라는 클래스를 만들었고 a변수에 Calculator()를 할당했다. a = 클래스, 즉 a는 이제 객체가 된 것이다. a는 Calculator()가 클래스이기 때문에 객체가 된 것이다. 객체라는 의미는 이 형식에 따른 것이 전부이며, a는 이제 객체라는 신분이 된 것이다. a는 이제 선언이전에 불가능했던 기능(메서드호출, 속성호출)들을 사용한다. 클래스를 할당하기 이전에 불가능했던 뒤에 .(dot)을 붙이고 함수,변수같은 형식의 기능을 사용한다(300의 덧셈과 300의 뺄셈)

기능들을 참고하면 클래스 내부에서 만들어진 함수들을 이용하고 있다. 클래스 내부에서 만들어진 함수들을 메서드(method)라고 하며, 점으로 연결해서 사용하는 모습을 볼 수 있다. 클래스없이 순수하게 만들어진 함수와는 분명히 구분되는 형식이다. 즉 a라는 변수이자 객체는 계산기 기능을 가진 컨테이너가 된 것이다.

인스턴스는 클래스를 관점으로 한 단어이며, Calculator클래스의 인스턴스가 a인 것이다.

정리하면

  • a는 a=Calculator() 코드를 통해 객체가 된다. 그 이유는 Calculator()가 클래스이기 때문이다. 즉 클래스를 변수에 담으면 변수는 객체인 것이다.
  • a는 Calculator클래스의 인스턴스이다.

표현의 예시로 a,b,c,d를 모두 Calculator클래스로 객체화 했다면 Calculator의 인스턴스들은 a,b,c,d이다.

_ init _()과 self에 대한 이해

클래스를 만들 때 보면 항상 __init__() 이라는 함수를 선언한다. 필수적이다. 왜 필수적인지 이해해야한다. a가 객체가 되기 위해 a = Calculator()를 통해 객체가 된다. 이때 a가 처음 객체가 될 때 init이라는 것을 통해 초기 설정을 하는 것이다. init메서드도 분명 아래의 add와 sub처럼 같은 함수이지만, 조금 특별하다. init메서드는 a가 객체가 되자마자 자동적으로 실행된다. 즉 위의 예제에서는 self.result = 0라는 것이 바로 실행되는 것이다.(예제에서의 의미적으로는 계산기에서의 처음 값을 0으로 초기 설정하는 것이다)

  • 우리는 회사에 입사하기전에 초기교육을 받는다, 우리는 첫 아르바이트 근무 이전에 인수인계를 받는다. 이런 관점에서 변수도 객체가 되기 위해 처음 설정을 해야한다고 생각하면 편하다.

a = Calculator()만 해서 a를 객체로만 만들었는데 print(self.result)가 실행된 모습이다. 0을 내어준거보니 바로 위 코드인 self.result = 0또한 바로 실행된 것으로 보인다.

그렇다면 self는 무엇인가? self뒤에는 왜 메서드처럼 점이 찍히고 어떤 변수명이 적히는가?(self.result = 0 의 관점에서) self는 즉 이 클래스 내부에서 쓰인다는 뜻이다. 우리가 생각하는 계산기에서 처음 값은 0이고 300을 더하면 300이 된다. 이렇게 값은 계속해서 저장되어야 하고 업데이트되어야한다. 만약 self.result가 아니라 그냥 result라면, 즉 result = 0 으로 선언한다면 추가적으로 아래의 add, sub메서드 내부의 self.result또한 모두 result로 바꾼다면 result는 함수에서의 개념처럼 내부의 변수로만 인식되어 저장 및 업데이트가 불가능하다.

self.result = 0을 result로 바꾼 결과 print(self.result)를 하니 에러가 발생했다. 어쩌면 문법적으로 당연하다.그렇다면 객체인 a를 통해 .result를 print하였더니 에러가 발생한다.(Calculator클래스는 result를 속성으로 가지지 않음이라는 문구와 함께) 즉 점을 찍고 객체 내부에 저장되어있는 속성을 사용하기 위해서는 self를 구성해야한다는 뜻이다. self.result의 self는 자신과의 연결이다.

모든 메서드에는 역시 처음 입력인자로 self를 사용한다. 우리는 메서드를 실행할 때 입력인자에 self라는 것을 쓰지도 않는데 에러가 뜨지도 않는다. self는 여기서 본체 컨테이너(a객체)와의 연결의 역할만 하는 것이다.

a.result 와 a.add() 두개의 괄호의 차이는 무엇인가? 이는 무엇이 다른지 클래스 내부를 보면 쉽게 파악가능하다. result의 경우 self.result = 0이고 add나 sub를 실행하면 값이 업데이트 된다. a.add()는 클래스 내부의 함수이다. 즉 a.result는 클래스 내부의 변수이고(속성이라고 함) a.add()는 클래스 내부의 함수이다.(메서드라고 함)

self는 연결을 위한 것이라고 생각할 수 있다. def add(self, num)에 존재하는 self가 없다면 a.add()가 실행 불가능하다. 즉 a는 현재 연결의 뜻을 보이는 점(.)을 통해 a.add()로 실행했는데 함수 입력인자에 self가 없다면 점의 역할이 사라지는 것이다. 동일한 관점으로 속성인 a.result또한 그렇다.

이해를 위해 이렇게 생각할 수 있다
a.result는 a(self).result 이며, a.add()는 a(self).add()인 것이다. 괄호안에 self를 쓴 것은 a를 self로 생각하자는 것이다. 이제 self의 영어단어 본 뜻과 연관이 꽤 깊다는 것을 느낄 수 있을 것이다.

self를 첫 인자로 쓰지 않는다면?
에러가 뜬다. 에러를 읽어보면 입력인자가 한개만 입력되어야 한다는데, 두개가 입력되었다는 에러문구를 볼 수 있다. 분명 300이라는 입력인자 하나만 전달했는데 말이다. 우리는 a.add(300)을 실행한 것이다. 이때 점은 입력인자에 자동적으로 self를 전달하고 있는 것이다. 즉 우리는 300만 입력인자로 사용했지만, a.add()라는 문법때문에 self와 300이 같이 입력으로 전달된 것이다.

즉 자기자신이라는 self의 뜻이 왜 입력인자에 쓰이는지를 이해가 갈 것이다. 클래스가 아닌 순수한 함수선언에서 함수 내부의 변수는 함수 밖에서 사용이 불가능 했다. 클래스 내부의 함수도 동일하다. 하지만 self로 쓰인 변수는 가능하다. 그렇기 때문에 클래스 내부함수의 로직에 self.result += num이라고 쓰인 것이 return에서만 반영되는 것이 아닌 self.result가 먼저 업데이트되고 return 되는 것이다. 순서를 서술해보면,

  1. a=Calculator()를 실행하면 init메서드에 의해 init내의 내부로직들이 자동실행되어 self.result = 0이 실행되어 self.result = 0이 된다 우리는 a=Calculator()만 썼는데도, a.result를 프린트하면 0이라는 결과를 볼 수 있다.
  2. a.add(300)을 실행하면, self.result += 300이 적용되어 self.result가 바로 300으로 업데이트되고, 그 후 return self.result를 통해 300을 반환받는다. return에 대한 개념도 정확해야한다. 만약 return이 없다면, print(a.add(300))을 하면 300을 띄우지 못한다. 그렇다면 300을 띄우기 위해서는 print(a.result)를 해야할 것이다. return은 순수한 함수에서의 기능과 똑같다.(그렇기에 return이 필수는 아니다)
  • def add(self, num)에 들어가는 num은 클래스 내부의 변수(속성)으로 작용하지 않는다. add메서드를 위한 내부로직을 돕는 것이 끝이다. 즉 순수한 함수에서의 내부변수와 같은 역할을 하는 것이다.

init내부인자를 추가한다면?

현재 계산기 코드예제에서는 init메서드 안에 self외에 어떤 인자도 없다. 만약 계산기에 로그인 기능이 필요해서 ID만을 받는다고 가정한다면 다음과 같이 코드를 수정가능하다.

class Calculator:
    def __init__(self, ID):
        self.result = 0
        self.ID = ID

    def add(self, num):
        self.result += num
        return self.result

    def sub(self, num):
        self.result -= num
        return self.result
        
a = Calculator(jkky98)
a.add(300)
a.sub(300)
a.result
a.ID

상식적으론 이 ID가 회원이 맞는지 아닌지 판단하는 로직도 필요하다고 생각하지만 일단은 계산기가 ID를 확보해야한다고만 가정하자.

init메서드는 우리가 직접 실행하는 것이 아니었다.(a를 객체를 만들자마자 바로 실행되니까) ID는 self.ID라는 변수에 다시 담긴다.

a를 객체로 만들 때, 변화가 일어났다. 원래는 괄호안에 아무것도 쓰지 않았지만 jkky98이라는 임의의 ID를 썼다. 이것을 쓰지 않는다면 어떻게 될까? 당연히 에러가난다.
init이 실행이 되는데, self.ID=ID가 실행이 안될 것이다. 당연하다 ID를 받아오는 과정이 없기 때문이다.

  • add메서드의 경우 a.add(300)처럼 메서드 괄호안에 직접 변수를 넣었다. 하지만 init의 경우 클래스 선언시 클래스 괄호안에 변수를 넣어야 한다.

a = 1 , a = [1,2,3,4]는 객체인가?

이제 우리가 당연하듯이 생각했던 개념에 대한 전환이 필요하다. 방금까지는 이해를 위해 클래스를 선언받는 변수만이 객체라고 말했다. 하지만 이는 이해를 위한 선의의 거짓말이었다.

a = 1를 선언하면 a는 객체인가?라는 물음에 1이 클래스가 아니기 때문에 객체가 아니라고 답할 수 있지만, 실제로 그렇지 않다. "아니 1은 클래스가 아니고 내가 만든적도 없는데 왜 객체라는 것이냐?"라고 질문할 수 있다. 우리가 선언한 것 이외에 파이썬에는 우리에게 보이지 않는 기본 클래스들이 존재한다. 즉 파이썬은 이러한 모든 클래스들로 이루어진 것이다. 1은 int라는 클래스이고 우리는 a에 1을 할당하자마자 int라는 클래스를 할당받은 객체를 생성한 것이다. 다만 처음 배울 때 이를 가르쳐주지 않는 것 뿐이다.
정말로 a를 1로 선언하고 점을 찍어보니 메서드나 속성같은 것들이 목록으로 뜬다.(주피터 노트북의 기능으로 점만 찍어도 미리 클래스 내부의 기능들을 소개해줌)

그렇다면 a = [1,2,3,4] 리스트도 객체인 것이다.
그렇다. 소개되는 메서드가 a=1일때랑 다르다. 1로 선언했을 때와 다른 객체가 된 것을 알 수 있다. 아마 리스트를 공부할 때 리스트를 변수에 받고 그 뒤에 점을 찍고 메서드들을 사용할 때가 있었을 것이다. 이제 그 의미를 파악하게 된 것이다.

a를 쓰고 mac기준 shift+tab을 누르면 객체에 대한 정보가 나온다. 그렇다면 계산기 객체는 어떻게 뜰까?
역시 리스트와 다르게 뜬다. 어떠한 선언도 하지 않은 변수를 쓰고 shift+tab을 누르면 어떤 정보도 뜨지 않는다.

  • 모든 것은 클래스였다. 이제 왜 파이썬이 객체지향 언어인지 파악할 수 있었다. 그리고 우리는 객체를 계속 만들어왔던 것이다.
  • 우리는 이때동안 파이썬의 기본내장 클래스를 클래스인줄 모르고 사용해왔다. 그리고 지금은 원하는 기능의 클래스를 직접 만들고 있다.

그래도 이해가 안된다면
계산기 클래스를 예시보다 조금 더 복잡하게 설계하고 함수와 클래스로 만들어보고 여러번 계산기를 작동하고 초기화하는 코드를 짜보면 어느 것이 편한지 느낄 수 있을 것이다. 클래스제작은 큰 프로그램을 위한 작은 프로그램 한 단위를 만드는 것이라고 생각할 수도 있다. 직접 클래스를 설계해보고 여러번 구동해보고,다시 읽어본다면 빠르게 습득할 수 있을 것이라 생각한다.

이 포스팅은 기본적인 클래스의 이해를 위한 것이며, init메서드 이외에도 특수한 메서드 형식들이 더 존재한다. 이는 직접 검색하여 역할을 파악하면 된다. 가장 중요한 것은 객체와 클래스에 대한 이해와 클래스의 필요성에 대한 이해, self가 왜 들어가는지, 무슨 역할을 하는지에 대한 이해이다.

profile
기록은 귀찮지만 머리와 db가 연결되어 있지 않아 슬피 존재하는 blog

0개의 댓글