[파이썬 기초]Python Object Oriented Programming

차보경·2022년 8월 5일
0

TIL

목록 보기
9/37
post-thumbnail
post-custom-banner

부스트코스 영상 링크

배우기 앞서, 그냥 돌아가기만 하면 되는거 아닌가!?
객체 지향 프로그래밍 왜 배워야하는데!! 라고 생각한다면 아래의 예시를 생각해보자.

나는야 잘나가는 멋쟁이 개발자1.
근데 실상은 Po구글러wer & 복붙러...! 다른 똑똑이들이 작성해놓은 코드 그냥 갖다 재사용하고 시포...
요구사항에 맞춰 조금만 바꿔서 쓰고 싶당 말이양...!

나는야 멋쟁이 개발자2!
어디 내놔도 부끄럽지 않은 잘키운 내 소스코드면 다른 사람도 편하게 쓸 수 있을 것 같은데!?

모두 코드의 재사용성이 좋아야한다는 것이 포인트다!

☞ 어떻게 잘 구조화 시켜야 할까? 라고 할 때 떠오르는 것이 객체 지향 프로그래밍!

☞ 왜냐면 객체지향 프로그래밍은 수정시 클래스 내부 변수 혹은 메서드로 존재하기 때문에 해당 부분만 수정하면 된다(↔절차 지향 프로그래밍). 다 클래스 단위로 모듈화 시켜 개발했기 때문(이래서 프로젝트로 나눠 개발 하기도 좋지)

☞ 그리고 개발자1 같은 경우엔 다들 객체 지향으로 쓰는데 나도 객체 지향을 알아야 수정하기 쉽다는 코드 쓱 바꿔서 쓰지 & 나도 그렇게 써야하고요 (는 나야나)

그럼 왜 배워야하는지 알았으니 계속 가보자고!

클래스와 객체

~ 객체 지향 언어의 이해 ~

Q. 수강신청 프로그램을 작성한다! 어떻게 해야할까?

  1. 수강신청이 시작부터 끝까지 순서대로 작성 (옛날 방식)

  2. 수강신청 관련 주체들(교수, 학생, 관리자)의 행동(수강신청, 과목입력)과 데이터들을 중심으로 프로그램 작성 후 연결
    → 최근 방식. '객체 지향 프로그램 기법'

객체 지향 프로그래밍 개요

  • Object Oriented Programming (OOP)
  • 객체 : 실생활에서 일종의 물건 속성(Attribute)와 행동(Action)을 가짐
  • OOP는 이런 객체 개념을 프로그램으로 표현
    (속성은 변수(Variable), 행동은 함수(method)로 표현됨)
  • 파이썬 역시 객체 지향 프로그램 언어

Ex.

인공지능 축구 프로그램을 작성한다고 가정시,

  • 객체 종류 : 팀, 선수, 심판, 공
  • Action :
    • 선수 - 공 차기, 패스
    • 심판 - 휘슬불기, 경고주기
  • Attribute :
    • 선수 - 선수 이름, 포지션, 소속팀
    • 팀 - 팀 이름, 연고지, 소속 선수
  • OOP는 설계도에 해당하는 클래스(class)와 실제 구현체인 인스턴스(instance)로 나눔

    붕어빵틀은 설계도, 찍어 나온 결과는 Instance!
    → 결과는 팥붕, 슈붕, 피붕 등등 다양하게 나올 수 있다.

Objects in python

  • class 선언, object는 python3에서는 자동 상속

Attribute 추가하기

  • Attribute추가는 __init__, self와 함께 →__init__은 객체 초기화 예약함수
  • 속성 정보 → __init__, 행동 메서드
  • self.객체 (self에 소속된 객체)으로 객체의 초기정보 입력
  • init시 객체의 정보 힌트를 줄 수 있음
        def __init__(self, name : str, position : str, back_number: int):

★★ 상식) 파이썬에서 __의 의미

  • __은 특수한 예약 함수나 변수, 함수명 변경(맨글링)으로 사용함
  • 매직매소드 숙지하기!
    ex. __main__, __add__(더해줌), __str__(문자 print), __eq__
class SoccerPlayer(object):
	def __str__(self):
    return "hi, my name is %s. I play in %s in center" % (self.name, self.position)

# 객체      클래스
chabbo = SoccerPlayer("chabbo", "MF", 10)
print(chabbo)

>>> hi, my name is chabbo. I play in MF in center
  • 각 변수에 동일한 class를 할당하더라도 각각의 변수는 다 다른 것들 (like 팥붕, 슈붕)
chabbo = SoccerPlayer("chabbo", "MF", 10)
Son = SoccerPlayer("Son", "FW", 7)

chabbo is Son
>>> False
  • 그리고 class 할당시 init만 하고 안에 다른 def를 하지 않으면 print(chabbo)
    <__main__.SoccerPlayer object at 0x000002D541267C40>이런 식으로 메모리 주소값이 출력됨

Method 구현하기

  • method(Action) 추가는 기존 함수와 같으나, 반드시 self를 추가해야만 class 함수로 인정됨!
class SoccerPlayer(object):
    def change_back_number(self, new_number):
        print('선수의 등 번호를 변경합니다 : from %d to %d' %(self.back_number, new_number))
        self.back_number = new_number

Object(instance) 사용하기

  • Object 이름 선언과 함께 초기값 입력하기.
chabbo = SoccerPlayer("chabbo", "MF", 10)
print('현재 선수의 등번호는 :', chabbo.back_number)

★★중요) self가 모야?

  • 생성된 instance 자신을 의미
  • 위에서 chabbo 라는 instance를 생성되면(chabbo = SoccerPlayer("chabbo", "MF", 10)), 밖에서는 chabbo.back_number로 변수명(chabbo)으로 쓰지만, code안에서는 self가 chabbo가 됨. 말그래도 자기 자신을 가리킬때 self를 사용.
class SoccerPlayer(object):
    def __init__(self, name : str, position : str, back_number: int):
        self.name = name
        self.position = position
        self.back_number = back_number

    def change_back_number(self, new_number):
        print('선수의 등 번호를 변경합니다 : from %d to %d' %(self.back_number, new_number))
        self.back_number = new_number

    def __str__(self):
        return "hi, my name is %s. I play in %s in center. back number is %d" % (self.name, self.position, self.back_number)

chabbo = SoccerPlayer("chabbo", "MF", 10)
print(chabbo)

chabbo.change_back_number(7)
print(chabbo)

>>> hi, my name is chabbo. I play in MF in center. back number is 10
>>> 선수의 등 번호를 변경합니다 : from 10 to 7
>>> hi, my name is chabbo. I play in MF in center. back number is 7

OOP Implementation Example

구현 가능한 OOP 만들기 - 노트북

  • Note를 정리하는 프로그램
  • 사용자는 Note에 뭔가를 적을 수 있다.
  • Note에는 Content가 있고, 내용을 제거할 수 있다.
  • 두 개의 노트북을 합쳐 하나로 만들 수 있다.
  • Note는 Notebook에 삽입된다.
  • Notebook은 Note가 삽입 될 때 page를 생성하며, 최고 300page 까지 저장 가능하다.
  • 300page가 넘으면 더 이상 노트를 삽입하지 못한다.

  • 생성 Code
class NoteBook():
    def __init__(self, title):
        self.title = title
        self.page_number = 1
        self.notes = {}

    def add_note(self, note, page = 0):
        if self.page_number < 300:
            if page == 0:
                self.notes[self.page_number] = note
                self.page_number += 1
            else:
                self.notes = {page:note}
                self.page_number += 1
        else:
            print('page가 모두 채워졌습니다.')

    def remove_note(self, page_number):
        if page_number in self.notes.keys():
            return self.notes.pop(page_number) # 와 여기에 pop을..
        else: 
            print('해당 페이지는 존재하지 않습니다.')


    def get_number_of_pages(self):
        return len(self.notes.keys()) 

class Note():
    def __init__(self, content):
        self.content = content

    def creat_content(self, content):
        self.content = content
        return content

    def remove_content(self):
        self.content = ''
        
    def __add__(self, other):
        return self.content + other.content

    def __str__(self):
        return self.content
  • 사용방법
    ★중요! ipyb file에서 py 파일 불러와 사용하는 방법
from fileimport 함수명
from creat_note import Note
from creat_note import NoteBooK

my_notebook = NoteBook('타이틀')
new_note = Note('오 신기하다')
print(new_note) 
>>> 오 신기하다
new_note2 = Note('파이썬')
print(new_note2) 
>>> 파이썬
  • 출력 코드
my_notebook = NoteBook('타이틀')
new_note = Note('오 신기하다')
print(new_note) 
new_note2 = Note('파이썬')
print(new_note2)
my_notebook.add_note(new_note)
my_notebook.add_note(new_note2, 100)
print(my_notebook.notes[100])
print(my_notebook.get_number_of_pages())
my_notebook.notes[0] = Note('이게 왜안돼')
print(my_notebook.notes[0])


OOP characteristics

(몰라도 코딩은 할 수 있다)ㅋㅋㅋㅋ

  • 객체 지향 언어의 특징 : 실제 세상을 모델링
  • 필요한 것들 : Inheritance(상속성), Polymorphism(다형성), Visibility(가시성 = hidden class)

Inheritance(상속성)

  • 부모클래스로부터 속성과 Method를 물려받은 자식 클래스를 생성하는 것
  • Korean class에는 아무것도 없지만, Person을 상속받아 그 내부 method를 사용할 수 있다.
class Employee(Person):
    def __init__(self, name, age, gender, salary, hire_date):
        super().__init__(name, age, gender) # 부모객체 사용
        self.salary = salary
        self.hire_date = hire_date
  • 여기서 super()은 부모클래스(person)의 객체를 불러오는 것. self로 자기자신을 불러오듯이, super은 부모클래스를 불러온다.
class Person():
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def about_me(self):
        print(f'제 이름은 {self.name}이구요, 제 나이는 {str(self.age)}살 입니다.')
    
    def __str__(self):
        return (f'제 이름은 {self.name}이구요, 제 나이는 {str(self.age)}살 입니다.')


class Employee(Person):
    def __init__(self, name, age, gender, salary, hire_date):
        super().__init__(name, age, gender) # 부모객체 사용
        self.salary = salary
        self.hire_date = hire_date

    def do_work(self):
        print('열일!')
    
    def about_me(self): # 부모클래스 함수 재정의
        super().about_me() # 부모클래스 함수 사용
        print(f'제 급여는 {self.salary}원 이구요, 제 입사일은 {self.hire_date}입니다.')

chabbo = Person('chabbo', 31, 'Female')
chiwoo = Employee('chiwoo', 31, "Male", 10000000000, '1992.01.01')

chabbo.about_me()
chiwoo.about_me()

출력 결과

>>> 제 이름은 chabbo이구요, 제 나이는 31살 입니다.
############ 자식 클래스의 출력 결과 #############
# 먼저 super().about_me() 출력
>>> 제 이름은 chiwoo이구요, 제 나이는 31살 입니다.
# 새로 추가한 부분 출력
>>> 제 급여는 10000000000원 이구요, 제 입사일은 1992.01.01입니다.  

Polymorphism(다형성)

  • 같은 이름 메소드의 내부 로직을 다르게 작성
  • Dynamic Typing의 특성으로 인해 파이썬에서는 같은 부모클래스의 상속에서 주로 발생함
  • 중요한 OOP의 개념. 그러나 너무 깊이 알 필요 없음
  • 함수명은 같으나 interface와 code에 의해서 code를 다르게 짤 수 있는 것을 말함. 개념적으로는 같은 일을 하나, 세부적으로 구현이 다를 때, 그럴때 사용.
class Animal():
    def __init__(self, name):
        self.name = name
    
    def talk(self):
        raise NotImplementedError('서브클래스 메소드가 구현되야합니다')
    
class Cat(Animal):
    def talk(self):
        return '야옹!'

class Dog(Animal):
    def talk(self):
        return '멈무!멈무!'

animals = [Cat('냥이'), Cat('옹이'), Dog('멈무미')]

for animal in animals:
    print(f'{animal.name} : {animal.talk()}')
  • 출력 결과
냥이 : 야옹!
옹이 : 야옹!
멈무미 : 멈무!멈무!

이렇게 비슷한 일을 할 때! 같은 이름을 쓰되, 내부 코드를 다르게 해서 쉽게 가동할 수 있음

Visibility(가시성)

  • 객체의 정보를 볼 수 있는 레벨을 조절하는 것
  • 누구나 객체 안에 모든 변수를 볼 필요가 없음
    1) 객체를 사용하는 사용자가 임의로 정보 수정
    2) 필요 없는 정보에는 접근할 필요가 없음
    3) 만약 제품으로 판매한다면? 소스의 보호

※참고) Encapsulation 이라고도함

  • 캡슐화 또는 정보 은닉(Information Hiding)
  • Class를 설계할 때, 클래스 간 간섭/정보 공유의 최소화
  • 심판 클래스가 축구 선수 클래스 가족 정보를 알아야하나?
  • 캡슐을 던지듯, 인터페이스만 알아서 써야함

Visibility Ex 1

  • Product 객체를 Inventory 객체에 추가

  • Inventory에는 오직 Product 객체만 들어감

  • Inventory에 Product가 몇 개인지 확인이 필요

  • Inventory에 Product items는 직접 접근이 불가
    __items처리

  • Bad case Code

class Product():
    # 빈 더미클래스 생성
    pass

class Inventory():
    def __init__(self):
        self.items = []
        self.test = 'abc'

    def add_new_item(self, product):
        if type(product) == Product:
            self.items.append(product)
            print('새로운 아이템을 추가했습니다')
        else:
            raise ValueError('Invalid Item')
    
    def get_number_of_items(self):
        return len(self.items)

코드 생성 후 입출력 진행

my_inven = Inventory()
my_inven.add_new_item(Product())
my_inven.add_new_item(Product())
print(my_inven)
print(my_inven.items)
my_inven.items.append('virus')
print(my_inven.items)
  • 개발자가 원한 것은 my_inven.add_new_item(Product()) Product를 이용해서 inventory에 들어가길 원했으나,
    my_inven.items.append('virus') 이런식으로 다른 사용자가 직접적으로 inventory에 접근해 추가할 수 있다.

  • 결과 : Product 객체외의 다른 item이 추가됨

새로운 아이템을 추가했습니다
새로운 아이템을 추가했습니다
<__main__.Inventory object at 0x000001734527BCA0>
[<__main__.Product object at 0x000001734527BAF0>, <__main__.Product object at 0x000001734527BAC0>]
[<__main__.Product object at 0x000001734527BAF0>, <__main__.Product object at 0x000001734527BAC0>, 'virus']
  • Good Code (캡슐화)
class Product():
    # 빈 더미클래스 생성
    pass

class Inventory():
    def __init__(self):
        self.__items = [] #private 변수로 선언 타객체가 접근 못함
        self.test = 'abc'

    def add_new_item(self, product):
        if type(product) == Product:
            self.__items.append(product)
            print('새로운 아이템을 추가했습니다')
        else:
            raise ValueError('Invalid Item')
    
    def get_number_of_items(self):
        return len(self.__items)

이렇게 하면,
AttributeError: 'Inventory' object has no attribute 'items'
이런식으로 접근할 수 없게 됨.

Visibility Ex 2

  • Product 객체를 Inventory 객체에 추가
  • Inventory에는 오직 Product 객체만 들어감
  • Inventory에 Product가 몇 개인지 확인이 필요
  • Inventory에 Product items 접근 허용
    @property라는 decorator사용!!
class Inventory():
    def __init__(self):
        self.__items = [] #private 변수로 선언 타객체가 접근 못함
        
    @property   #property decorator 숨겨진 변수를 반환하게 해줌
    def items(self):
        return self.__items
  • 외부에서는 접근이 안되지만, 내부에서는 접근이 가능하면서 반환해줄 수 있음
  • 함수명을 변수명처럼 쓸 수 있게 함. 내부 객체를 접근할 수 있게 만들어줌으로 내부 정보를 빼서 외부에 넘겨줄 때 사용할 수 있음.
  • 보통 그대로 return해주기보단 copy해서 보내줌

decorate

  • 이해하기 위한 정보 : first-class objects(1급객체), inner function, decorator

first-class objects

  • 일등함수또는 일급 객체
  • 변수나 데이터 구조에 할당이 가능한 객체
  • 파라미터로 전달 가능 + 리턴 값으로 사용
  • 파이썬의 모든 함수는 일급함수라 파라미터로 전달가능
def square(x):
	return x * x

f = square # 메모리주소 할당
f(5) >> 25

#################################

def cube(x):
	return x * x * x

def formula(method, argument_list):
	return [method(value) for value in argument_list]
  • 함수를 파라미터로 사용 (계산할 때 x^2, x^3 쓰는 경우가 있을 때 각각 넣어서 사용하면 편함!)

Inner function

  • 함수 내에 또 다른 함수가 존재
  • 실제 업무에서 흔하게 쓰이는 구조임!!!
def print_massage(msg):
	def printer():
    	print(msg)
    printer()
    
print_massage('Hello, Python')
  • closures : Inner function을 return값으로 반환
def print_massage(msg):
	def printer():
    	print(msg)
    return printer()
    
another = print_massage('Hello, Python')
another()
  • another()은 항상 Hello, Python 메세지가 return됨?
  • 파이썬은 일급함수를 쓰기 때문에 함수 자체가 return이 가능.
    java에서 많이 씀!
  • 어디에 쓰는데여??
def tag_func(tag, text):
	text = text
    tag = tag
    
    def inner_func():
    	return '<{0}>{1}<{0}>'.format(tag, text)
    return inner_func
    
h1_func = tag_func('title', 'this is python class')
p_func = tag_func('p', 'data academy')
  • 같은 이름으로 다양한 목적의 function을 만들 수 있음. 다양한 변형이 된 함수를 만들 수 있는게 closure의 장점!!

decorator function

  • 복잡한 클로져 함수를 간단하게!
def star(func):
	def inner(*args, **kwargs):
    	print("*" * 30)
        func(*args, **kwargs)
        print("*" * 30)
    return inner
    
@star
def printer(msg):
	print(msg)
printer('Hello')
  • 그럼 star 이하의 printer()함수가 앞에 star()함수 안으로 들어가게됨
  • 출력값
******************************
Hello
******************************
  • 응용 code
def star(func):
    def inner(*args, **kwargs):
        print (args[1] * 30)
        func(*args, **kwargs)
        print (args[1] * 30)
    return inner
    
@star
def printer(msg, mark):
	print(msg)
printer('Hello', '=')

출력값

==============================
Hello
==============================
profile
차보의 Data Engineer 도전기♥ (근데 기록을 곁들인)
post-custom-banner

0개의 댓글