본문 바로가기
프로그래밍/Python

[인공지능(AI) 기초 다지기] 객체 지향 언어의 이해

by hi-rachel 2023. 1. 30.

* Python Object Oriented Programming

 

💡 아래 내용은 부스트코스(boostcourse) 인공지능(AI) 기초 다지기 강의를 듣고 공부하며 내용을 정리한 글입니다. 더 자세한 내용은 실제 강의를 들어보길 추천합니다 😃

 

📝 Contents

- 객체 지향 프로그래밍(Object Oriented Programming_OOP)

- Class 구현 방법

- OOP 구현 방법

- 객체 지향 언어의 특징:  상속(Inheritance), 다형성(Polymorphism), 가시성(Visibility)

- decorate 이해하기 위한 개념들(일급 객체(first-class object), Inner Function, Closer)

 

 


🐍 객체 지향 프로그래밍(Object Oriented Programming_OOP)

- 객체: 실생활에서 일종의 물건, 속성(Attribute)과 행동(Action)을 가짐

- OOP는 이러한 객체 개념을 프로그램으로 표현

=> 속성은 변수(variable), 행동은 함수(method)로 표현됨

- 파이썬 역시 객체 지향 프로그래밍 언어

자주 나오는 예시의 {클래스와 인스턴스 : 붕어빵틀과 붕어빵}

처음 코딩 책을 볼 때 자꾸 '인스턴스(Instance)'라는 용어가 나오는데 어렵게 느껴져 그 개념을 잘 이해 못 했었다. 하지만 위 붕어빵 예시처럼 나는 Instance를 원래 영어 자체가 '사례, 예시'라는 의미로 정해진 (Class) 코드 안에서 나오는 다양한 예시들, 경우라고 나는 내 식대로 받아들였다.

 

다양한 붕어빵(사례들)이 나올 수 있다 🤣

 

 

실제 구현해보기 - 축구 선수 정보를 Class로 구현하기

# Class 함수명은 CamelCase 사용
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 "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)

son is park    # false (서로 다른 객체)

son + park    # 'sonpark'

print(son)    # Hello, My name is son, My back number is 7

choi = SoccerPlayer("Jinhyun", "mf", 10)
print(choi)    # Hello, My name is Jinhyun, My back number is 10

choi.change_back_number(7)
print(choi)
# 선수의 등번호를 변경합니다 : From 10 to 7
# Hello, My name is Jinhyun, My back number is 7

# 이렇게 객체의 속성을 직접 바꿔줄 수도 있지만 위 방식을 권장함
choi.back_number = 13
print(choi)    # Hello, My name is Jinhyun, My back number is 13

 


class 구현

class 선언

class 이름(상속받는 객체명):

Attribute 추가

Attribute 추가는 __init__, self와 함께!

__ 의미

'__' 는 특수한 예약 함수나 변수, 함수명 변경(맨글링)으로 사용

method 구현

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

objects(instance) 사용

 


OOP 구현

조건

 

scheme

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.total_page = 1
        self.notes = {}
        
    def add_note(self, note, page = 0):
        if self.page_number <= 300 and page <= 300:
            if page == 0:
                self.notes[self.page_number] = note
                self.page_number += 1
                self.total_page += 1
            else:
                self.notes[page] = note
                #self.notes = {page : note}
                self.total_page += 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())

 


🌟 객체 지향 언어의 특징 -  상속(Inheritance), 다형성(Polymorphism), 가시성(Visibility)

: 실제 세상을 모델링

 

상속(Inheritance)

- 부모 클래스로부터 속성과 Method를 물려받은 자식 클래스를 생성하는 것

class Person(object):
    def __init__(self):
        self.name = "SJ"
        self.age = 20

class Player(Person):
    pass

the_player = Player()
print(the_player.name)    # SJ

클래스 Player는 Person 부모 클래스로부터 속성, Method를 물려받는다.

 

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), "살 입니다.")

class Korean(Person):
    pass

first_korean = Korean("sungchul", 35, "man")
print(first_korean.about_me())    # 저의 이름은 sungchul 이구요, 제 나이는 35 살 입니다.

(함수의 리턴값이 없기 때문에 None도 뒤에 같이 출력되지만 이는 생략)

 

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, " 입니다.")
manager = Employee("choi", 23, "women", 450, "22/12/20")

print(manager.do_work())
# 열심히 일을 합니다.

print(manager.about_me())
# 저의 이름은  choi 이구요, 제 나이는  23 살 입니다.
# 제 급여는  450 만원이고, 제 입사일은  22/12/20  입니다.

 

 

다형성(Polymorphism)

- 같은 이름 메소드내부 로직을 다르게 작성

- 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 "mung! mung!"

같은 이름 메서드(talk)의 내부 로직(return 값)을 다르게 작성!

animals = [Cat('Cheese'),
          Cat("Samsag"),
          Dog("Kimza")]
for animal in animals:
    print(animal.name + ": " + animal.talk())
"""
Cheese: Meow!
Samsag: Meow!
Kimza: mung! mung!
"""

 

 

가시성(Visibility)

- 객체의 정보를 볼 수 있는 레벨을 조절하는 것

- 🌟 누구나 객체 안에 모든 변수를 볼 필요가 없음 => 캡슐화(Encapsulation) 또는 정보 은닉(Information Hiding)

1) 객체를 사용하는 사용자가 임의로 정보 수정하면 x
2) 필요 없는 정보에는 접근할 필요가 없음
3) 만약 제품으로 판매한다면? 소스의 보호

- 클래스를 설계할 떼, 클래스 간 간섭, 정보 공유의 최소화

- __ mangling 접근 못함
- @property 함수명을 변수명처럼 사용하게 해 줌 => 접근 가능

 

(가시성 예시)

조건

class Product(object):
    def __init__(self):
        self.__items = []

        
class Inventory(object):
    def __init__(self):
        self.__items = []
        
    def add_new_item(self, 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)

self.__items => Private 변수로 선언, 타 객체가 접근 못함

 

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 # <------ ⚠️

items.append(Product())
print(my_inventory.get_number_of_items())
"""
new item added
new item added
2
AttributeError: 'Inventory' object has no attribute 'items'
"""

 

조건 수정 : Inventory에 Product items 접근 허용

class Product(object):
    def __init__(self):
        self.__items = []

        
class Inventory(object):
    def __init__(self):
        self.__items = []
        
    def add_new_item(self, 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)
    @property
    def items(self):
        return self.__items

@property

property decorator 숨겨진 변수를 반환하게 해 준다.

 

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())
"""
new item added
new item added
2
3
"""

 


decorate 이해하기 위한 개념들

1. 일등 함수 또는 일급 객체 (first-class object)

- 변수나 데이터 구조에 할당이 가능한 객체

- 파라미터로 전달이 가능, 리턴 값으로 사용

- 파이썬의 함수는 일급 함수

# 함수를 변수로 사용
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]
formula(cube, [1, 2, 3])    # [1, 8, 27]

 

2. Inner Function

# 함수 내에 또 다른 함수가 존재
def print_msg(msg):
    def printer():
        print(msg)
    printer()
print_msg("Hello, Python")    # Hello, Python

 

 

- Closer: inner function을 return값으로 반환

def print_msg(msg):
    def printer():
        print(msg)
    return printer

another = print_msg("Hello, Python")
another()    # Hello, Python
클로저란?
자신을 둘러싼 scope(name space)의 상태값을 기억하는 함수

어떤 함수가 클로저이기 위해서는 다음의 세 가지 조건을 만족해야 한다.
1. 해당 함수는 어떤 함수 내의 중첩된 함수여야 한다.
2. 해당 함수는 자신을 둘러싼(enclose) 함수 내의 상태값을 반드시 참조해야 한다.
3. 해당 함수를 둘러싼 함수는 이 함수를 반환해야 한다.

[출처 : https://shoark7.github.io/programming/python/closure-in-python#3a]

 

클로저 예시)

def tag_func(tag, text):
    text = text
    tag = tag
    
    def inner_func():
        return '<{0}>{1}<{0}>'.format(tag, text)
    return inner_func

hi_func = tag_func('title', 'This is Python Class')
p_func = tag_func('p', "Data Academy")

print(hi_func())    # <title>This is Python Class<title>
p_func()    # '<p>Data Academy<p>'

 

3. decorator

# 복잡한 클로져 함수를 간단하게!
def star(func):
    def inner(*args, **kwargs):
        print("*"*30)
        func(*args, **kwargs)
        print("*" * 30)
    return inner

def percent(func):
    def inner(*args, **kwargs):
        print("%" * 30)
        func(*args, **kwargs)
        print("%" * 30)
    return inner

@star
@percent
def printer(msg):
    print(msg)
    
printer("Hello")
"""
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
"""

 

def generate_power(exponent):
    def wrapper(f):
        def inner(*args):
            result = f(*args)
            return exponent**result
        return inner
    return wrapper

@generate_power(2)
def raise_two(n):
    return n**2
    
print(raise_two(1)) 
print(raise_two(2)) # 2 ** 2 = 4 => 2 ** 4승 = 16
print(raise_two(7)) # 7 ** 2 = 49 => 2 ** 49승 = 562949953421312

[키워드 가변인자(*args) 참고: 2023.01.23 - [Python] - [인공지능(AI) 기초 다지기] Pythonic Code 파이썬다운 코드를 작성해보자! - list comprehension, enumerate, zip, lambda, generator, asterisk(*) ..]

 

 


+ 그동안 강의 중 꽤 어려운 내용이 나왔다. 내용이 꽤 많아 정리할까 고민했지만, 역시 정리하면서 다시 보면 스스로 공부가 제일 많이 된다(내 블로그를 제일 자주 찾는 건 나다). 같은 강의도 세부적으로 보면 공부해야 할 내용이 정말 많다.
이 강의는 원래 이쪽 분야 지식이 없는 사람들도 들을 수 있도록 전반적으로 설명해 주어 여러 가지 면에서 복습도 되고 개념을 명확히 해줘 너무 좋다 :)

 

 

🙂 공감과 피드백 환영합니다.