[부스트캠프 AI Tech 5기 Pre-Course] 1-2. Python Object Oriented Programming

bee·2023년 1월 12일
0
post-thumbnail

해당 시리즈의 모든 내용은 TeamLab Director 최성철 교수님의 부스트캠프 pre-course 강의 내용입니다.

만들어 놓은 코드를 재사용하고 싶다!

ex) 수강신청 프로그램을 작성해보자.
방법①. 수강신청의 시작부터 끝까지 순서대로 작성
방법②. 수강신청 관련 주체들(교수, 학생, 관리자)행동(수강신청하기, 과목 입력하기)데이터(수강과목, 강의과목)들을 중심으로 프로그램 작성 후 연결

객체지향 프로그래밍 (OOP; Object-Oriented Programming)

  • 객체 개념을 프로그램으로 표현
  • python 역시 객체 지향 프로그램 언어이다.
    ex) 인공지능 축구 프로그램을 작성한다고 가정해보자.

객체 (Object)

  • 실생활에서 일종의 물건
  • 속성과 행동을 가짐
    ex) 객체 : 팀, 선수, 심판, 공

속성 (Attribute)

  • 변수(variable)로 표현된다.
    ex) 속성 : 선수 - 선수 이름, 포지션, 소속팀 ; 팀 - 팀 이름, 팀 연고지, 팀소속 선수

행동 (Action)

  • 함수(method)로 표현된다.
    ex) 행동 : 선수 - 공을 차다, 패스하다 ; 심판 - 휘슬을 불다, 경고를 주다

클래스 (Class)

  • 설계도
    ex) 붕어빵틀

인스턴스 (instance)

  • 실제 구현체
    ex) 붕어빵

파이썬으로 '클래스' 구현하기

1. Class 선언하기

  • object는 python3에서 자동 상속

python naming rule

  • snake_case : 띄워쓰기 부분에 " _ "를 추가
    뱀처럼 늘여쓰기, 파이썬 함수/변수명에 사용
  • CamelCase : 띄워쓰기 부분에 "대문자"
    낙타의 등 모양, 파이썬 클래스명에 사용

2. Attribute 추가하기

  • Attribute 추가는 __init__,self 와 함께 사용한다.
  • __init__ : 객체 초기화 예약 함수
  • 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
        
# 같은 클래스에서 생성된 3개의 객체 생성
>>>	son = SoccerPlayer("son", "FW", 7)
>>> park = SoccerPlayer("park", "WF", 13)


>>> print(son) # 주소값이 출력된다.
<__main__.SoccerPlayer object at 0x000002870F4FaE80>
  • 파이썬에서 __의 의미 : 특수한 예약함수나 변수, 함수명 변경(맨글링)으로 사용한다.
    ex) __main__, __add__, __str__, __eq__
>>> 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 __str__(self) : # print문을 입력했을때 호출된다. 
>>>	    	return "Hello, My name is %s. My back number is %d." %\(self.name, self.back_number)


>>> 	def __add__(self, other) : 
>>>			return self.name + other.name

>>>	son = SoccerPlayer("son", "FW", 7)
>>>	park = SoccerPlayer("park", "WF", 13)

>>> print(son) # 리턴값이 출력된다. 
Hello. My name is son. My back number is 7.

>>> son + park
'sonpark'

더 많은 매직 매소드는 아래의 링크 참고!
링크텍스트

3. method 구현하기

  • method 추가는 기존 함수와 같으나, 반드시 self 를 추가해야만 class 함수로 인정된다.

4. object(instance) 사용하기

  • object 이름 선언과 함께 초기값 입력하기
  • 객체명 = 클래스명(__init__함수 interface, 초기값)
  • self: 생성된 instance 자기 자신을 가리킬 때 사용
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 #여기서의 self는 jinhyun이 된다.
        
# 인스턴스 선언
jinhyun = SoccerPlayer("Jinhyun", "MF", 10)
print("현재 선수의 등번호는 : ", jinhyun.back_number)
jinhyun.change_back_number(5)
print("현재 선수의 등번호는 : ", jinhyun.back_number)

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

- Note를 정리하는 프로그램
- 사용자는 Note에 뭔가를 적을 수 있다.
- Note에는 Content(str)가 있고, 내용을 제거할 수 있다.
- 두 개의 노트북을 합쳐 하나로 만들 수 있다.
- Note는 Notebook에 삽입된다.
- Notebook은 Note가 삽입될 때 페이지를 생성하며,최대 300페이지까지 저장 가능하다.
- 300페이지가 넘으면 더 이상 노트를 삽입할 수 없다.
class Note(object) : 
	def __init__(self, content = None) :
    	self.content = content
        
    def write_content(self, content) : 
    	self.content = content
        
    def remove_all(self):
    	self.content = ""
        
    def__add__(self, other):
		return self.content + other.content
        
    def __str__(self):
    	return self.content
        

class NoteBook(object):
	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)
        else:
        	print("해당 페이지는 존재하지 않습니다.")
            
    def get_number_of_pages(self):
    	return len(self.notes.keys())    
# from 파일명 import 클래스명
>>> from bee_note import Note
>>> from bee_note import NoteBook

>>> my_notebook = NoteBook("꿀벌 강의노트")
>>> my_notebook
<bee_note.Notebook at 파일주소>

>>> my_notebook.title
'꿀벌 강의노트'

>>> 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)
>>> my_notebook.notes
{1: <bee_note.Note at 주소>,
 100: <bee_note.Note at 주소>}
 
>>> my_notebook.get_number_of_pages()
2

>>> my_notebook.notes[2] = Note("안녕")
>>> print(my_notebook.notes[2])
노트에 적힌 내용입니다 : 안녕

OOP Characteristics

1. Inheritance 상속

  • 부모클래스로 부터 attribute와 method를 물려받은 자식클래스를 생성하는 것
# 부모클래스
class Person(object): 
	def __init__(self, name, age):
    	self.name = name
        self.age = age
        
    def __str__(self):
    	return "저의 이름은 {0} 입니다. 나이는 {1} 입니다." .format(self.name, self.age)

# 자식클래스 (부모클래스인 Person으로부터 상속받음)
class Korean(Person):
	pass
    
first_korean = Korean("Sungchul", 35) # Korean에는 아무런 attribute도 없지만 Person으로부터 상속받았기 때문에 Person의 attribute를 사용할 수 있다.
print(first_korean.name)
class Person: # 초기의 상속은 object가 기준이다. 안써줘도 알아서 상속받음
	def __init__(self, name, age, gender):
    	self.name = name
        self.age = age
        self.gender = gender
        
    def about_me(self) : 
    	print("저의 이름은 ", self.name, "이구요, 제 나이는 ", str(self.age), "살 입니다.")
        
    def __str__(self):
    	return "저의 이름은 ", self.name, "이구요, 제 나이는 ", str(self.age), "살 입니다."

# 자식클래스 (부모클래스인 Person으로부터 상속받음)
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("제 급여는 ", self.salary, "원 이구요, 제 입사일은 ", self.hire_date, " 입니다.")

2. Polymorphism 다형성

  • 같은 이름의 method를 쓰되, 목적에 따라서 각각의 내부 구현을 다르게 하는 것
  • Dynamic Typing 특성으로 인해 파이썬에서는 같은 부모 클래스의 상속에서 주로 발생한다.
class Animal:
	def __init__(self, name):
    	self.name = name
        
    def talk(self) : 
    	raise NotImplementedError("Subclass must implement abstract method")

# 상속
class Cat(Animal):
	def talk(self):
    	return 'Meow!'
        
class Dog(Animal):
	def talk(self):
    	return 'Woof! Woof!'

animals = [Cat('Missy'), Cat('Mr. Mistoffelees'), Dog('Lassie')]

for animal in animals :
	print(animal.name + ': ' + animal.talk())

Missy: Meow!
Mr. Mistoffelees: Meow!
Lassie: Woof! Woof!

3. Visibility 가시성

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

Encapsulation 캡슐화 (정보은닉 ; Information Hiding)

  • Class를 설계할 때, Class 간 간섭/정보공유의 최소화
  • 캡슐을 던지듯, 인터페이스만 알아서 써야한다.
    ex) 심판 Class가 축구선수 Class의 가족 정보를 알아야 하나? => No

Visibility Example1

  • Product 객체를 Inventory 객체에 추가
  • Inventory에는 오직 Product 객체만 들어감
  • Inventory에 Product가 몇 개인지 확인 필요
  • Inventory에 Product items는 직접 접근 불가
class Product(Object) :
	pass
    
class Inventory(object):
	def __init__(self):
    	self.__items = [] # Private 변수로 선언 (타객체 접근 불가)
        
    def add_new_item(slef, product):
    	if type(product) == Product :
        	self.__items.append(product)
            print("new item added")
        else:
        	raise ValueError("Invalid Item")
            
    def get_number_of_items(self):
    	return len(self.__items)
        
>>> my_inventory = Inventory()
>>> my_inventory.add_new_item(Product())
>>> my_inventory.add_new_item(Product())
>>> my_inventory
new item added
new item added

<__main__.Inventory at 0x1439df0b850>

>>> my_inventory.__items # Private 변수에 접근 시도 => 불가(에러)
AttributeError : 'Inventory' object ahs no attribute '__items'

Visibility Example2

  • Product 객체를 Inventory 객체에 추가
  • Inventory에는 오직 Product 객체만 들어감
  • Inventory에 Product가 몇 개인지 확인 필요
  • Inventory에 Product items 접근 허용
class Product(Object) :
	pass
    
class Inventory(object):
	def __init__(self):
    	self.__items = [] # Private 변수로 선언 (타객체 접근 불가)
     
    @property # property decorator : 숨겨진 변수를 봔한하게 해줌
    def items(self):
    	return self.__items # 외부에서는 접근 불가 but, 내부에서는 접근 가능
        
>>> my_inventory = Inventory()
>>> my_inventory.add_new_item(Product())
>>> my_inventory.add_new_item(Product())
>>> print(my_inventory.get_number_of_items())

>>> items = my_inventory.items # Property decorator로 함수를 변수처럼 호출
>>> items.append(Product())
>>> print(my_inventory.get_number_of_items())

>>> my_inventory.__items # Private 변수에 접근 시도 => 불가(에러)
AttributeError : 'Inventory' object ahs no attribute '__items'

>>> my_inventory.items # 내부에서 접근 가능
[<__main__.Product at 0x1439dfc81f0>, <__main__.Product at 0x1439dfc83d0>]

decorate 이해하기

1) first-class objects 일급객체(일등함수)

  • 변수나 데이터 구조에 할당이 가능한 객체
  • parameter로 전달 가능 + return 값으로 사용
  • 파이썬의 모든 함수는 일급함수다.
>>> def square(x):
		return x * x
    
>>> f = square # 함수를 변수로 사용
>>> f(5)
25
>>> def square(x):
		return x * x
>>> def cube(x):
	return x * x * x
    
>>> def formula(method, argument_list) : # 함수를 파라미터로 사용
	return [method(value) for value in argument_list]

2) inner function

  • 함수 내에 또 다른 함수가 존재
>>> def print_msg(msg):
		def printer():
        	print(msg)
        printer()

>>> print_msg("Hello, Python")

closures

  • inner funciton을 return 값으로 반환
  • 비슷한 목적을 가졌지만 다양한 변형이 된 함수를 만들어낼 수 있음
>>> def print_msg(msg):
		def printer(): # another를 호출하면 printer이 리턴된다
        	print(msg)
        printer()

>>> another = print_msg("Hello, Python")
>>> another()
>>> 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")

3) decorator funciton

  • 복잡한 closure 함수를 간단하게 한다
>>> def star(func):
		def inner(*args, **kwargs):
    		print("*", * 30)
        	func(*args, **kwargs)
        	print("*" * 30)
    	return inner

>>> @star
	def printer(msg): # printer함수 => func
		print(msg)
>>> printer("Hello") # "Hello" => msg
******************************
Hello
******************************
>>> 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", "T") 
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
Hello
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
>>> def generate_power(exponent):
	def wrapper(f):
    	def inner(*args):
        	result = f(*args)
            return exponent**result
        return inner
    return wrapper
    
>>> @generate_power(2) # => exponent
>>> def raise_two(n): # => f, n => *args
		return n**2  
>>> print(raise_two(7)) # 7**2
562949953421312
profile
벌집처럼 밀도있게 차곡차곡 쌓아나가는중

0개의 댓글