[부스트캠프 AI Tech] U-stage. 1-4

느린 개발자·2021년 1월 21일
1

부스트캠프 AI Tech

목록 보기
5/35

📌 Life is short, you need python


📄 Python Object-Oriented Programming

절차지향 프로그래밍의 경우 순차적인 전개 로 과정 을 쉽게 파악할 수 있는 장점이 있지만 유지보수 및 코드의 재사용성은 어려움이 있을 수 있다. 이러한 문제를 해결하기 위해 기능별로 묶어 모듈화하고 모듈 재활용을 통해 코드의 재사용성을 높인 객체지향 프로그래밍 이 등장하게 되었다.

객체 는 실생활에서 일종의 물건으로 속성(Attribute)행동(Action) 을 가지며 OOP에서는 속성 -> 변수(Variable) , 행동 -> 함수(Method) 로 표현된다.

✏️ Class vs Instance

클래스는 객체를 정의하는 템플릿 이며, 클래스(템플릿)을 바탕의 실제 구현체를 인스턴스 라 한다.

✏️ Practice

class Note:
    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): # Magic method
        return self.content+other.content
    
    def __str__(self): # Magic method
        return self.content
    
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)
        else:
            print('해당 페이지는 존재하지 않습니다.')
    
    def get_number_of_pages(self):
        return len(self.notes.keys())
 

note1=Note('첫번째 학습정리.')
note2=Note('두번째 학습정리.')
print(note1+note2) # __add__ ,첫번째 학습정리.두번째 학습정리.
print(note1) # __str__ , 첫번째 학습정리.

notebook=NoteBook('부스트캠프')
notebook.add_note(note1,1) # note 를 page=1 에 추가, [1:note1]
notebook.add_note(note2,3) # note 를 page=3 에 추가, [3:note2]
print(notebook.notes[1]) # 첫번째 학습정리.
print(notebook.notes[3]) # 두번째 학습정리.
              

위에서 언급된 Magic method를 자세히 알고 싶다면 여기를 클릭해주세요.

✏️ Inheritance & Polymorphism & Visibility

상속(Inheritance) : 공통적인 속성(Attribute) 와 행동(Action)을 가지는 클래스들이 여러 개 존재한다면, 각 클래스 마다 구현하는 것은 매우 비효율적이다. 이러한 문제는 부모클래스로 부터 속성(Attribute) 와 행동(Action) 을 물려받은 자식 클래스를 생성함으로서 해결할 수 있다.

class Unit:
    # 생성자
    def __init__(self,name,power):
        # 인스턴스 변수(Attribute)
        self.name=name 
        self.power=power

    def attack(self): # 메서드(Action)
        print(f'{self.name} , 이(가) [공격력 : {self.power}] 공격합니다.')

    # 소멸자 
    def __del__(self):
        print(f'{self.name}, 이(가) 삭제되었습니다.')



class Monster(Unit):

     # 클래스 변수, 각 인스턴스들이 공유하는 변수
    count=0 

    def __init__(self,name,power,difficulty):
        super(Monster,self).__init__(name,power)
        self.difficulty=difficulty

    def show_info(self):  
        print(f'이름 : {self.name} / 공격력: {self.power} / 난이도 : {self.difficulty}')

    


monster1=Monster('몬스터1','20','초급')
Monster.count+=1 # 클래스 변수 
monster2=Monster('몬스터2','30','중급')
Monster.count+=1 # 클래스 변수 



monster1.attack()
monster1.show_info()
print('#############################')
monster2.attack()
monster2.show_info()

print(f'현재 생성된 몬스터 수 : {Monster.count}')

# 몬스터1 , 이(가) [공격력 : 20] 공격합니다.
# 이름 : 몬스터1 / 공격력: 20 / 난이도 : 초급
# #############################
# 몬스터2 , 이(가) [공격력 : 30] 공격합니다.
# 이름 : 몬스터2 / 공격력: 30 / 난이도 : 중급
# 현재 생성된 몬스터 수 : 2
# 몬스터1, 이(가) 삭제되었습니다.
# 몬스터2, 이(가) 삭제되었습니다.

다형성(Polymorphism) : 기능의 큰 틀은 같지만 , 내부의 세부적인 기능은 조금씩 다를 수 있다. 이러한 경우에는 오버라이딩(Overriding) 또는 추상메서드(Abstract method)을 통해 해결할 수 있다.


from abc import abstractmethod,ABCMeta

class Animal(metaclass=ABCMeta):

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

    @abstractmethod
    def talk(self): # Abstract method, defined by convention only
        pass
        

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(f'{animal.name} : {animal.talk()}')

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

가시성(Visibility) : 객체의 모든 정보를 파악할 필요는 없다. 만약 중요한 정보가 누군가에 의해서 값이 변경되어 기능에 큰 악영향을 끼친다면 문제가 생길것이다. 따라서 객체의 정보를 볼수 있는 레벨을 조절할 필요가 있다.

class Inventory:
    def __init__(self):
        self.__items = [] # private 변수로 

    #getter
    @property 
    def items(self):
        return self.__items
    #setter
    @items.setter
    def items(self,items):
        self.__items=items
    

my_inventory=Inventory()
items=my_inventory.items
items.append(4) # 리스트의 메모리 참조로 self.__items 값의 영향을 끼치게 되는 문제가 생깁니다.
print(items) # 4
print(my_inventory.items) # 4

위에서 private 변수를 통해 중요한 정보를 숨긴것 같지만, my_inventory 인스턴스의 items를 가져와서 바로 수정하게 되면 메모리 참조 로 리스트 변화가 영향을 끼치게 되어 의도치 않은 동작이 발생할 수 있다. 그래서 다음과 같이 deepcopy 를 통해 리스트 복사본을 넘겨주는것이 좋다.


from copy import deepcopy
class Inventory:
    def __init__(self):
        self.__items = [] # private naming convention

    #getter
    @property 
    def items(self):
        return deepcopy(self.__items)
    #setter
    @items.setter
    def items(self,items):
        self.__items=deepcopy(items)
    
        


my_inventory = Inventory()
items=my_inventory.items # getter
items.append(4)
my_inventory.items=items # setter
print(my_inventory.items) # [4]
items.append(5)
print(my_inventory.items) # [4]

<참고>
추가적으로 파이썬은 변수 자체를 private 속성으로 바꾸는 것이 아닌, 네이밍 컨벤션을 통해 외부에서 우연히 발견할수 없도록 이름을 네임맹글링 된것이다.

my_inventory._Inventory__items  # 접근가능.

✏️ First-class objects

일등함수 또는 일급객체라 불리며, 변수나 데이터 구조에 할당이 가능한 객체를 말한다. 즉, 파라미터 전달 + 리턴 값으로 사용이 가능하다. 한 예로 파이썬의 함수의 경우 일급함수이다.


def add_and_square(f,data1,data2): # parameter로 f 전달 
    return f(data1,data2)**2

def add(data1,data2):
    return data1+data2

print(add_and_square(add,2,3)) #(2+3)^2=25

✏️ Inner function & Closure

함수내에 또 다른 함수 선언할수 있다. inner function은 상위 부모함수에서만 호출이 가능하다. 즉, 부모함수를 벗어나 호출할 수 없다.

def parent_function(msg):
    def child_function():
    	print(f'내부 함수입니다. 부모함수의 파라미터 "{msg}" 에 접근할수 있습니다.')
    child_function() 

parent_function('내부함수 작동.')
# 내부 함수입니다. 부모함수의 파라미터 "내부함수 작동." 에 접근할수 있습
니다.

그렇다면 nested function(inner function) 은 왜 사용하는 것일까?

  1. 가독성
    함수를 사용하는 이유중 하나는 반복되는 코드블록을 하나로 정의해서 효율적인 코드작성을 하기 위해서다. 마찬가지로 중첩함수도 부모함수안에 반복되는 코드가 있다면 내부함수를 정의하여 코드를 효과적으로 관리하고 가독성을 높일수 있다.
  2. Closure
    폐쇄 의 의미를 가지는 closure 는 외부로부터 격리되어 사용되는 것을 의미한다. 즉, 부모함수가 자신의 내부에 함수나 변수를 가두어 사용하는것이다. 그리고 부모함수는 내부함수를 리턴함으로써 부모함수의 변수를 외부로부터 직접적인 접근은 격리하면서도 부모함수의 변수를 사용한 연산은 가능하게 해주는것이다.
def generate_power(base_number):
    def nth_power(power):
    	return base_number**power
    return nth_power
    
calculate_power_of_two=generate_power(2) # base_number=2
print(calculate_power_of_two(7)) #2^7=128

✏️ Decorator

데코레이터 패턴은 자신의 방을 예쁜 벽지나 커튼으로 장식을 하듯이, 기존의 코드를 수정하지 않고도 여러가지 기능을 추가할 수 있게 한다. 위에서 살펴본 closure 와 아주 비슷한데, 차이점은 함수를 다른 함수의 인자로 전달한다는 점이다.

def decorator_function(original_function):
    def wrapper_function(*args,**kwargs):
        print (f'{original_function.__name__} 함수가 호출되기전 입니다.')
        return original_function(*args,**kwargs)
    return wrapper_function


def display():
    print ('display함수가 실행됐습니다.')


def display_info(name,age):
    print (f'display_info({name},{age}) 함수가 실행됐습니다.')

display_1 = decorator_function(display)  
display_2 = decorator_function(display_info)  

display_1()
display_2('lots-o','5')

# display 함수가 호출되기전 입니다.
# display함수가 실행됐습니다.
# display_info 함수가 호출되기전 입니다.     
# display_info(lots-o,5) 함수가 실행됐습니다.

파이썬에서는 다음과 같이 @을 사용하여 간단하게 데코레이터 구문을 제공한다.

def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):  #1
        print (f'{original_function.__name__} 함수가 호출되기전 입니다.')
        return original_function(*args, **kwargs)  #2
    return wrapper_function


@decorator_function
def display():
    print ('display함수가 실행됐습니다.')


@decorator_function
def display_info(name,age):
    print (f'display_info({name},{age}) 함수가 실행됐습니다.')

display()
display_info('lots-o', '5')

# display 함수가 호출되기전 입니다.
# display함수가 실행됐습니다.
# display_info 함수가 호출되기전 입니다.
# display_info(lots-o,5) 함수가 실행됐습니다.

📄 Module and Package

✏️ Module

프로그램에서는 작은 프로그램 조각들이며, 모듈들을 모아서 하나의 큰 프로그램을 개발한다. 또한 모듈화를 통해서 다른 프로그램에서도 사용가능하게 한다.

파이썬에서의 모듈은 함수나 변수 또는 클래스를 모아놓은 *.py 파일을 의미한다.

✏️ Package

하나의 대형 프로젝트를 만드는 코드의 묶음!이며, 다양한 모듈들의 합,폴더로 연결된다.

주목할것은 __init__.py 인데, 현재 폴더가 패키지 임을 알리는 초기화 스크립트 이다. 물론 3.3 버전 이상부터는 작성하지 않아도 패키지로 간주해주지만, 일반적으로 하위버전의 방식대로 작성한다.

#game/__init__.py
__all__=['image','sound','stage']

from . import image
from . import sound
from . import stage

# game/image/__init__.py
__all__=['character','object']

from . import character
from . import object
# game/sound/__init__.py
__all__=['bgm','echo']

from . import bgm
from . import echo
# game/sound/echo.py
def echo_play():
    print('echo_play')
# game/stage/__init__.py
__all__=['main','sub']

from . import main
from . import sub

# game/__main__.py
from sound import echo
if __name__=="__main__":
    print('Hello game')
    echo.echo_play()
    
>>> python game
Hello game
echo

📚 Reference

파이썬 Magic method
Inner function & Closure
Decorator
Module
Package

profile
남들보단 느리지만, 끝을 향해

0개의 댓글