ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 11. 파이썬 클래스
    파이썬(Python) 강의 2019. 2. 17. 09:21

     이전 포스트를 통해서 여러가지 자료형과 오퍼레이션 그리고 함수를 사용하는 법을 알아보았다. 자료형에는 숫자형인 정수형(Integer), 플로트(Float), 문자열인 스트링(String), 리스트, 튜플, 딕셔너리 등등을 알아보았다. 그리고 각각의 자료형이 "함수"들을 가지고 있다는 것도 알게되었다. 우리는 각각의 자료형이 가지고있는 함수를 이용해서 리스트에 아이템을 덧붙이거나 딕셔너리에 아이템을 추가할 수 있었다. 이런 값과 함수를 가진 자료형을 오브젝트라고 부른다. 그리고 오브젝트는 클래스를 이용해 만들어진다. 이 포스트를 통해 오브젝트가 무엇이고 클래스가 무엇인지 알아보도록 하겠다.

    들어가기전에

    이 포스트는 여러분이 파이썬을 설치했고 각자 원하는 IDE를 이용해 파이썬을 개발한다고 가정한다. 혹시 파이썬을 설치하지 않았다면  1.파이썬 설치 및 개발환경 설정 이 포스트에서 파이썬과 파이썬 개발환경을 먼저 셋업하길 바란다. 앞서 말했듯이 반드시 실습 해 보길 바란다. 이 포스트는 여러분이 파이썬의 주요 자료형과 함수를 알고있고 사용 할 수 있다고 가정한다.

    목표

    • 오브젝트란(Object)?
    • 클래스가 필요한 이유
    • 클래스 정의
    • 클래스 생성
    • 클래스 사용

    오브젝트란(Object)?

    추상적 정의

     일상에서 오브젝트란 무엇인가? 오브젝트를 번역하면 물건 또는 물체/객체라고 할 수 있다. 물체에는 물체의 고유한 값이나 기능이 존재한다. 파이썬에서는 그것을 자료형 또는 함수라고 부른다. 예를들어서 String은 문자열을 값으로 가지는 오브젝트이다. 예를들어서 3은 숫자를 값으로 가지는 오브젝트이다. 예를들어서 len(..)이란 함수는 시퀀스의 길이를 리턴하는 기능을 가진 오브젝트이다. 파이썬에서는 모든 것들이 오브젝트이다.  파이썬에서는 모든것들이 오브젝트라고 말하는 것 또는 객체 지향 개발론에서 말하는 오브젝트라는 것(객체 지향을 모른다면 넘어가도 좋다)은 굉장히 추상적인 느낌을 준다. 따라서 오브젝트가 소프트웨어에서 구체적으로 무슨 뜻인지 설명 해 보고자 한다. 

    소프트웨어적 정의

     파이썬에게는 메모리 상에서 사용중인 또는 실행중인 모든 자료형과 함수가 오브젝트이다. 프로그램이란 코드이고, 이 코드는 CPU에 의해 한 줄씩 실행된다. 

    위의 그림처럼 cpu가 fruit = "apple"을 불러와 실행을 시키면 메모리 공간에 "apple"이 할당된다. 이 할당된 메모리 공간을 오브젝트라고 부른다. 따라서 위에서 언급 한 것 처럼 오브젝트란 메모리 상에서 사용중인 또는 실행중인 모든 자료형과 함수를 의미한다. 이제 함수의 예를 보자.

    func()이라는 함수를 실행시키면, cpu는 메모리상에 존재하는 func함수의 정의부분을 참고하여 func이라는 파이썬 오브젝트를 만든다. 질문이 생기지 않았는가? 왜 apple 오브젝트를 생성 할 때는데 아무 정의부분도 보지 않았는데 함수 오브젝트를 생성 할 때는 함수의 정의 부분을 참고 해야 하는가? 엄밀히 말하면 apple 오브젝트를 생성 할 때도 분명 어떤 틀/정의를 참고한다. 하지만 문자열 오브젝트는 built-in오브젝트로, 그 정의부분이 파이썬 인터프리터의 일부이다. 즉, 문자열 오브젝트의 정의부분은 파이썬 언어로 작성된게 아니라는 뜻이다. 하지만 우리가 작성한 파이썬 함수는 우리의 코드부분에 있다. 따라서 파이썬 인터프리터가 해당 코드부분을 참고하지 않으면 어떻게 실행해야 하는지 모른다. 파이썬은 CPU에게 def func의 정의부분을 참고하여 오브젝트를 만들라고 명령한다.  

    #define PY_SSIZE_T_CLEAN

    #include "Python.h" #include <ctype.h>
    #include <stddef.h>
    #ifdef COUNT_ALLOCS Py_ssize_t null_strings, one_strings; #endif
    static PyStringObject* characters[UCHAR_MAX + 1];
    static PyStringObject* nullstring;

    <참고: 실제 파이썬 인터프리터가 built-in 오브젝트인 문자열을 구현한 C 코드의 첫 부분 - PyStringObject라는 포인터를 확인 할 수 있다.>


    클래스가 필요한 이유

     프로그래밍을 하다보면 문자열이나 숫자보다 더 많은 정보를 한꺼번에 포함해야 하는 경우가 찾아온다. 예를들어서 연락처 프로그램을 만든다고 생각 해 보자. 이 연락처 프로그램에서는 이름, 연락처 두 정보를 입력받아 저장한다고 한다. 1을 누르면 입력을, 2를 누르면 현재 연락처 리스트를 볼 수 있고, 3을 누르면 연락처를 지울 수 있고, 4를 누르면 어플리케이션이 종료된다. 이를 어떻게 구현 할 것인가?

    리스트를 이용한 구현

    name_list = []

    phone_list = []


    while True:
    command = int(input("Press 1 to insert\nPress 2 to list all\nPress 3 to delete\nPress 4 to exit\n"))
    if command == 1:
    name = input("Name : ")
    phone = input("Phone : ")
    name_list.append(name)                         # 얘도 삽입해야 하고

    phone_list.append(phone)                       # 얘도 삽입해야 하고..


    elif command == 2:
    for index, item in enumerate(name_list):

    print(index, item, phone_list[index])      # 리스트 출력 부분


    elif command == 3:
    phone = input("Phone to delete : ")
    for index, item in enumerate(name_list):

    print("Deleting " + item + " : " + str(phone_list[index]))

    if phone == phone_list[index]:

    name_list.remove(item)                 # 얘도 지워야 하고

    phone_list.remove(phone_list[index])    # 얘도 지워야 하고..


    elif command == 4:
    break

    간단하게 위와같이 구현 할 수 있을 것이다. nameList와 phoneList 두 개를 만들어 index를 이용해 관리 할 수 있을 것이다. 하지만 만약 여기에 이메일을  추가하고 싶다면 어떻게 하겠는가? 이메일 추가 후 생년월일을 추가하고 싶다면 어떻게 하겠는가? 이외에도 15가지 다른 특성들을 추가하고 싶다면 어떻게 하겠는가? 리스트를 15개를 만들 것인가? 만약 누군가 실수로 phone만 삭제하고 name이나 email은 삭제하지 않으면 어떻게 되는가? 만약에 이 코드가 은행 전산시스템에 들어가는 코드이고 "balanceList"로 각 고객의 통장의 돈을 관리한다고 치자. 누군가 실수로 name만 지운다면 인덱스가 꼬이면서 심각한 전산 오류를 야기 할 수 있다.

    딕셔너리를 이용한 구현

    contact_list = []

    while True:
    command = int(input("Press 1 to insert\nPress 2 to list all\nPress 3 to delete\nPress 4 to exit\n"))
    if command == 1:
    name = input("Name : ")
    phone = input("Phone : ")
    email = input("Email : ")
    c = {
    "name": name,
    "phone": phone,
    "email": email
    }
    contact_list.append(c) # 리스트 두 개 대신 딕셔너리를 가진 리스트로 관리
    elif command == 2:
    for contact in contact_list:
    print(contact["name"], contact["phone"], contact["email"])
    elif command == 3:
    phone = input("Phone to delete : ")
    for contact in contact_list:
    print("Deleting " + contact["name"] + " : " + contact["phone"] + " : " + contact["email"])
    if phone == contact["phone"]:
    contact_list.remove(contact)    # 이제 하나만 지우면 된다.
    elif command == 4:
    break

    파이썬 자료형에대해 익숙하다면 이런 딜레마를 딕셔너리를 이용해 조금더 간단하게 유지보수 할 수 있을 것이다. 그렇다면 문자열의 형태가 아닌 좀 더 정형화된 방식으로 이런 자료들을 관리 할 순 없는 것일까? 눈치 챘겠지만 바로 클래스가 이런 역할을 하도록 도와준다. 파이썬에서는 클래스를 이용해 멤버 변수(name, phone, email..)와 함수를 정의 할 수 있다.

    클래스란 인간이 언어를 더 쉽게 사용 할 수 있도록 해준다. 객체 지향 프로그래밍(Object Oriented Programming)에서는 모든 것을 객체(오브젝트)로 정의하고 그 객체가 가지고 있는 특성(멤버 변수)와 기능(함수)을 프로그래밍 언어로 구현한다. 이러한 객체의 특성과 기능을 정의 할 수 있게 해주는 것이 바로 클래스이다. 클래스는 파이썬에만 존재하는 개념이 아닌 객체 지향 프로그래밍을 지원하는 거의 모든 언어들(C++, C#, Java)이 지원한다.

    만약 클래스가 너무 싫고 사용하기 싫다면 사용하지 않아도 된다. 다만 개발자로서 클래스를 사용하지 않았을 경우 발생 할 수 있는 상황들을 미리 예방 해야 할 것이다. 

    클래스(Class)

    class Contact:
    name = ""
    phone = ""
    email = ""

    예를들어서 이렇게 name, phone, email을 멤버 변수로 가진 Contact라는 클래스를 정의 할 수 있다. class 키워드를 이용해 지금부터 class의 정의가 나올 것임을 알려준다. 함수 정의부에서 그랬듯이 이름을 적어주고 :를 누른 후 클래스의 내부에 멤버 변수를 적는다. 파이썬 함수와 마찬가지로 들여쓰기(indention)에 주의 해야 한다는 점을 유의하자. 더 나아가,

    class Contact:
    name = ""
    phone = ""
    email = ""

    def print_contact_info(self): # self는 자기 자신이라는 뜻이다.
    print(self.name + " : " + self.phone + " : " + email)

    이렇게 클래스가 실행 할 수 있는 함수도 내부에 정의 할 수 있다. 다시 말해 클래스는 멤버 변수 + 함수로 구성된 번들(Bundle)이다.

    클래스 생성

    클래스는 그렇다면 어떻게 생성하는가? 클래스는 그 클래스의 생성자(Constructor)를 실행하면 생성 할 수 있다. 생성자는 어디 있는가? 클래스는 우리가 직접 구현하지 않아도 디폴트로 1개의 생성자를 가지고 있다. 또는 직접 생성자를 작성 할 수 있다.

    class Contact:
    name = ""
    phone = ""
    email = ""

    def __init__(self, name, phone, email): #생성자 : name, phone, email을 받아 Contact를 생성한다.
    self.name = name
    self.phone = phone
    self.email = email

    def print_contact_info(self):
    print(self.name + " : " + self.phone + " : " + email)

    생성자 함수는 __init__이라는 함수이다. 반드시 __init__이라는 함수 이름을 가지고 있어야 한다. 안그러면 파이썬이 생성자라는 것을 이해하지 못한다.

    self

    기본적으로 self(자기자신인 오브젝트)을 인자로 받는데 self는 프로그래머들이 신경 쓰지 않아도 된다. self라고 적어만 주면 파이썬이 알아서 self를 찾아서 넣어준다. 현재 실행중인 오브젝트를 의미한다.

    클래스 사용

    클래스를 정의 했으니 이제 어떻게 사용하는지 알아보자. 여러분이 함수를 정의 한 후 함수를 사용하기 위해 어떻게 했는가? 그렇다 함수를 func()하고 실행했다. 클래스도 마찬가지이다. 앞서 말했듯 해당 클래스의 생성자를 실행시켜야한다. 생성자는 어떻게 실행시키는가? 다음을 보자.

    class Contact:
    name = ""
    phone = ""
    email = ""

    def __init__(self, name, phone, email):
    self.name = name
    self.phone = phone
    self.email = email

    def print_contact_info(self):
    print(self.name + " : " + self.phone + " : " + self.email)


    c = Contact() # 생성자
    print(c.name, c.phone, c.email)

    생성자는 다름아닌 클래스이름에 ()괄호를 붙인 것이다. 이 코드를 실행하면 다음과 같은 에러가 날 것이다.

    c = Contact()
    TypeError: __init__() missing 3 required positional arguments: 'name', 'phone', and 'email'

    무슨 뜻인가? name, phone, email이 없다고 한다. 왜? 위에서 우리는 __init__생성자가 name, phone, email을 매개변수로 받도록 지정했다. 그러나 생성자 실행시 이 인자들을 넘겨주지 않았기 때문에 에러가 나는 것이다. 

    ########## 클래스 정의 시작 class Contact:
    name = ""
    phone = ""
    email = ""

    def __init__(self, name, phone, email):
    self.name = name
    self.phone = phone
    self.email = email

    def print_contact_info(self):
    print(self.name + " : " + self.phone + " : " + self.email)
    ########## 클래스 정의 끝
    c = Contact(name="fsoftwareengineer", phone="1121132231", email="f.softwareengineer@gmail.com")
    print(c.name, c.phone, c.email) fsoftwareengineer 1121132231 f.softwareengineer@gmail.com

    다시 실행 시켜보면 오류 없이 실행 되는 것을 확인 할 수 있다. 위의 예에서 멤버 변수에 어떻게 접근했는가?

    class Contact:
    name = ""
    phone = ""
    email = ""

    def __init__(self, name, phone, email):
    self.name = name
    self.phone = phone
    self.email = email

    def print_contact_info(self):
    print(self.name + " : " + self.phone + " : " + self.email)

    c = Contact(name="fsoftwareengineer", phone="1121132231", email="f.softwareengineer@gmail.com")
    c.name = "Something else" # 멤버변수 접근은 . 로
    c.phone = "no number"
    c.email = "?"
    print(c.name, c.phone, c.email) Something else no number ?

    멤버변수는 마침표를 이용해 접근 가능하다. 위의 오브젝트와 연관시켜 생각 해 보면 다음과 같다.

    클래스의 정의부분은 우리가 작성한 코드 부분에 있고, c = Contact()하고 생성자를 부르는 순간 파이썬 인터프리터가 해당 클래스의 생성자가 있는 부분을 찾아 메모리에 Contact라는 공간을 할당하고 넘어온 매개변수를 집어 넣는다.

    이번엔 print를 사용하는 대신 Contact클래스의 함수 print_contact_info를 사용해보자.

    class Contact:
    name = ""
    phone = ""
    email = ""

    def __init__(self, name, phone, email):
    self.name = name
    self.phone = phone
    self.email = email

    def print_contact_info(self):
    print(self.name + " : " + self.phone + " : " + self.email)


    c = Contact(name="fsoftwareengineer", phone="1121132231", email="f.softwareengineer@gmail.com")
    c.print_contact_info() fsoftwareengineer : 1121132231 : f.softwareengineer@gmail.com

    성공적으로 이름, 폰번호, 이메일이 출력되는 것을 확인 할 수 있다. 이제 정보를 확인 하고 싶을 때 마다 print(c.name, c.phone, c.email)을 복사 붙여넣기 할 필요가 없는 것이다!

    class Contact:
    name = ""
    phone = ""
    email = ""

    def __init__(self, name, phone, email):
    self.name = name
    self.phone = phone
    self.email = email

    def print_contact_info(self):
    print(self.name + " : " + self.phone + " : " + self.email)


    c = Contact(name="fsoftwareengineer", phone="1121132231", email="f.softwareengineer@gmail.com")
    c2 = Contact(name="Mary", phone="0349324504", email="mary.merry@marry.com")
    c.print_contact_info()
    c2.print_contact_info() fsoftwareengineer : 1121132231 : f.softwareengineer@gmail.com Mary : 0349324504 : mary.merry@marry.com

    c와 c2를 이용해 self가 무엇인지 좀 더 생각해 보도록 하자. 분명 클래스 안에 정의되어있는 print_contact_info는 self를 매개변수로 받는다. 하지만 이 함수를 실행 시킬 때는 아무런 매개변수도 필요 없다. 이는 파이썬이 self라는 매개변수에 이 함수를 부르는 각 오브젝트를 알아서 넣어주기 때문이다. 

    간단하게 말해 c가 자신의 print_contact_info를 부르면 self = c이고 c2가 자신의 print_contact_info를 부르면 c2이다. 따라서 self라는 단어는 정말로 자기 자신을 의미하는 것이다.

    클래스를 이용한 구현

    이제 맨 위에서 우리가 리스트, 딕셔너리로 구현 했던 코드를 이제 클래스를 이용해 구현 할 수 있다.

    class Contact:
    name = ""
    phone = ""
    email = ""

    def __init__(self, name, phone, email):
    self.name = name
    self.phone = phone
    self.email = email

    def print_contact_info(self):
    print(self.name + " : " + self.phone + " : " + self.email)


    def run_contact_App():
    contact_list = []

    while True:
    command = int(input("Press 1 to insert\nPress 2 to list all\nPress 3 to delete\nPress 4 to exit\n"))
    if command == 1:
    name = input("Name : ")
    phone = input("Phone : ")
    email = input("Email : ")
    c = Contact(name, phone, email) # Contact 생성
    contact_list.append(c)

    elif command == 2:
    for contact in contact_list:
    contact.print_contact_info()

    elif command == 3:
    phone = input("Phone to delete : ")
    for contact in contact_list:
    print("Deleting ")
    contact.print_contact_info()
    if phone == contact.phone:
    contact_list.remove(contact) # Contact 삭제

    elif command == 4:

    break


    run_contact_App()

     이번 포스트를 이용해 파이썬 언어로 클래스를 정의하고 사용하는 방법을 알아보았다. 더 나아가 클래스와 오브젝트라는게 소프트웨어 세계에서 어떤 의미인지도 알아보았다. 클래스와 오브젝트 또는 파이썬 인터프리터를 더 이해하기 위해서는 컴파일러와 운영체제를 조금 알아야 한다. 따라서 어느 순간 여러분이 파이 언어에 익숙해지면 다른 언어도 배워보고, 여러분이 새로운 언어를 만든다면 어떻게 만들지 고민 해 보면서 컴파일러/운영체제에 대한 지식을 쌓는 것이 소프트웨어 엔지니어로 성장하는데 많은 도움이 될 것이다.

    댓글

f.software engineer @ All Right Reserved