스터디/파이썬 스터디 강의자료

[1팀/한규림] 8차시 파이썬 스터디 - 객체 지향 프로그래밍

onegyul 2023. 5. 18. 18:56

8차시_강의안_객체지향프로그래밍.pdf
1.12MB
8차시_과제_객체지향프로그래밍.pdf
5.42MB

8차시 강의 주제는 객체 지향 프로그래밍입니다.

01. 객체 지향 프로그래밍

1) 객체 지향 프로그래밍을 배우는 이유

  • 다른 사람이 작성한 코드를 어떻게 사용하면 좋을지에 대한 답
  • 내가 아닌 남이 만든 코드를 재사용하고 싶을 때 사용하는 대표적인 방법
  • 어떤 기능을 수행하는 하나의 단일 프로그램을 객체라고 하는 코드로 만들어 다른 프로그래머가 재사용할 수 있도록 한다

2) 객체와 클래스

객체(object)는 실생활에 존재하는 실제적인 물건 또는 개념을 뜻하며, 속성과 행동으로 구성된다. 객체 지향 프로그래밍은 이러한 객체의 개념을 활용하여 프로그램으로 표현하는 기법이다.

  • 속성(attribute) : 객체가 가지고 있는 변수
  • 행동(action) : 객체가 실제로 작동시키는 함수, 메서드

예) 인공지능 축구 프로그램을 만들 때를 생각해보자. 프로그램을 만들기 위해 어떤 종류의 객체가 필요한지 생각해야 한다. 게임을 구성하는 단위로 심판, 선수, 팀이 있는데 이 개념을 객체라고 할 수 있다. ‘선수’ 객체의 경우 선수 이름, 포지션, 소속팀 등이 하나의 속성이고, ‘공을 차다’, ‘패스하다’와 같은 개념은 행동으로 생각하면 된다.

 

객체는 하나의 프로그램에서 여러 개가 사용될 수도 있다. 따라서 객체들을 위한 설계도를 만들어야 한다. 이를 프로그램에서는 클래스(class)라고 한다.

  • 클래스(class) : 객체가 가져야 할 기본 정보를 담은 코드, 일종의 설계도 코드라고 생각.

예) 축구 선수 객체를 만든다면 선수 이름, 포지션, 소속팀 등은 기본적으로 가져야 할 속성이다. 이러한 정보를 클래스에 담고 실제 생성되는 개체에는 손흥민, 이강인 같은 선수 이름을 할당한다. 이렇게 해서 실제로 생성되는 객체를 인스턴스(instance)라고 한다.

예) ‘DOG’ 라는 클래스가 있다면, 실제 사용하는 종류별로 여러 마리의 Dog 인스턴스를 만들 수 있다.

02. 파이썬의 객체 지향 프로그래밍

1) 클래스 구현하기

💡 class SoccerPlayer(object): 클래스 예약어, 클래스 이름, 상속받는 객체명

아직 상속의 개념을 배우지 않았지만 간단히 설명하면, 기존에 만든 클래스의 특징을 그대로 이어받아 사용하는 것을 말한다. 상속은 객체 지향 프로그래밍의 장점 중 하나인데, 이 특징은 뒤에서 다시 다루도록 하자.

💡 파이썬에서 자주 사용하는 작명 기법 (알아만 두자)

snake_case : 띄어쓰기 부분에 ‘_’를 추가하여 변수의 이름을 지정함. 파이썬 함수나 변수명에 사용된다.

CamelCase : 띄어쓰기 부분에 대문자를 사용하여 변수의 이름을 지정함. 파이썬 클래스명에 사용된다.

 

클래스 선언의 기본을 알았으니 축구 선수 클래스를 구성해보자.

1. 속성의 선언

속성에 대한 정보를 선언하기 위해서는 __init__( ) 라는 예약 함수를 사용한다. 이 외의 예약 함수로 __str__ , __add__ 등이 있다. 이러한 함수들은 약속된 형태의 작업을 수행시켜 준다.

class SoccerPlayer(object):
		def __init__(self, name, position, back_number):
				self.name = name
				self.position = position
				self.back_number = back_number
  • __init__( ) 함수 : 클래스에서 사용할 변수를 정의하는 함수
  • 첫번째 매개변수는 반드시 self 변수를 사용해야 한다.
    self 변수는 클래스에서 생성된 인스턴스에 접근하는 예약어이다. 이해하기 어렵다면 생성된 인스턴스를 지정하는 변수라고 생각하면 된다.
  • self 뒤의 매개변수들은 실제로 클래스가 가진 속성으로 선수 이름, 포지션, 등번호 등이고 이 값들은 실제로 생성되는 인스턴스에 할당된다. 할당되는 코드는 self.name = name (=생성된 인스턴스에 있는 name 변수에 매개변수로 입력된 name 값을 할당한다는 뜻)이다. 클래스의 변수는 ‘self.변수이름’으로 init( ) 함수에서 자유롭게 생성할 수 있다.

2. 함수의 선언

함수는 이 클래스가 할 수 있는 다양한 동작을 정의할 수 있다. 등번호 교체 라는 핻옹을 코드로 표현해보자.

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를 매개변수에 반드시 넣어야 한다는 것이다. self가 있어야만 실제로 인스턴스가 사용할 수 있는 함수로 선언된다. 그 외의 사용법은 일반적인 함수와 같다.

3. _의 쓰임

일반적으로 _의 쓰임은 개수에 따라 여러 가지로 나눌 수 있다.

_ 1개는 이후로 쓰이지 않은 변수에 특별한 이름을 부여하고 싶지 않을 때 사용한다.

for _ in range(10):
		print("Hello, World")
  • ‘Hello, World’를 화면에 10번 출력하는 함수이다. 횟수를 세는 _ 변수는 특별한 용도가 없으므로 뒤에서 사용되지 않는다.

_ 2개를 사용하면 특수한 에약 함수나 변수를 의미한다. str__이나 __ init( ) 같은 함수가 있다. __ str__( ) 함수는 클래스로 인스턴스를 생성했을 때, 그 인스턴스 자체를 print( ) 함수로 화면에 출력하면 나오는 값을 뜻한다.

다양한 용도가 있으니 _의 특수한 용도에 대해서도 인지해두는 것이 좋다.

 

2) 인스턴스 사용하기

  • 인스턴스는 클래스에서 실제적인 데이터가 입력되어 사용할 수 있는 형태의 객체를 뜻한다.

💡 jinhyun = SoccerPlayer(”Jinhyun”, “MF”, 10): 객체명, 클래스 이름, init 함수 인터페이스 초깃값

# 전체 SoccerPlayer 코드
class SoccerPlayer(object):
    def __init__(self, name, position, back_number):
        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. I play in %s in center." % (self.name, self.position)

# SoccerPlayer를 사용하는 instance 코드
jinhyun = SoccerPlayer("Jinhyun", "MF", 10)

print("현재 선수의 등번호는:", jinhyun.back_number)
jinhyun.change_back_number(5)
print("현재 선수의 등번호는:", jinhyun.back_number)

# 출력결과 (각각 16, 17, 18행 실행결과이다)
현재 선수의 등번호는: 10
선수의 등번호를 변경한다: From 10 to 5
현재 선수의 등번호는: 5

3) 클래스를 사용하는 이유

  • 자신의 코드를 다른 사람이 손쉽게 사용할 수 있도록 설계하기 위함
  • 코드를 좀 더 손쉽게 선언할 수 있다는 장점도 있다
# 데이터
names = ["Messi","Ramos","Ronaldo","Park","Buffon"]
positions = ["MF","DF","CF","WF","GK"]
numbers = [10,4,7,13,1]

# 이차원 리스트
players = [[name, position, number] for name, position, number in zip(names, positions, numbers)]
print(players)
print(players[0])

# 전체 SoccerPlayer 코드
class SoccerPlayer(object):
    def __init__(self, name, position, back_number):
        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. I play in %s in center." % (self.name, self.position)

# 클래스-인스턴스
player_objects = [SoccerPlayer(name, position, number) for name, position, number in zip(names, positions, numbers)]
print(player_objects[0])

# 출력결과 (각각 print문에 대한 실행 결과)
[['Messi', 'MF', 10], ['Ramos', 'DF', 4], ['Ronaldo', 'CF', 7], ['Park', 'WF', 13], ['Buffon', 'GK', 1]]
['Messi', 'MF', 10]
Hello, My name is Messi. I play in MF in center.

03. 객체 지향 프로그래밍의 특징

1) 상속

  • 상속(inheritance) : 이름 그대로 무엇인가를 내려받는 것을 뜻하며, 부모 클래스에 정의된 속성과 메서드를 자식 클래스가 물려받아 사용하는 것을 말한다.
class Person(object):
    pass

object가 Person 클래스의 부모 클래스이다. object는 파이썬에서 사용하는 가장 기본 객체(base object)이며, 파이썬 언어가 객체 지향 프로그래밍이므로 모든 변수는 개체이다.

a = "abc"
print(type(a))

<class 'str'>

문자열형이라고 불렀지만 내부적으로는 객체로 처리되었다. 파이썬의 모든 객체는 object 객체를 상속한다.

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
class Korean(Person):
    pass

first_korean = Korean("Sungshul", 35)
print(first_korean.name)  # Sungchul 출력
class Person(object):           # 부모 클래스 Person 선언
    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 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) : 같은 이름의 메서드가 다른 기능을 하는 것을 말함
  • 객체 지향 프로그래밍은 다른 사람의 코드를 쉽게 재사용하기 위해 사용하는데 이를 위해서는 내부적인 구현 과정을 잘 모르더라도 그 함수나 클래스의 역할은 명확히 알 필요가 있음
n_crawler = NaverCrawler()
d_crawler = DaumCrawler()
crawlers = [n_crawler, d_crawler]
news = []
for crawler in crawlers:
    news.append(crawler.do_crawling())
	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) : 객체의 정보를 볼 수 있는 레벨을 조절하여 객체의 정보 접근을 숨기는 것
  • 캡슐화(encapsulation) : 객체의 매개변수 인터페이스만 명확히 알면 객체를 사용할 수 있는데, 객체의 세부 내용은 모른 채 객체의 사용법만 알고 사용한다는 뜻
  • 정보 은닉(information hiding) : 외부에서 코드 내부를 볼 수 없게 하기 위해 내부의 정보를 숨기는 개념

캡슐화와 정보은닉으로 표현은 다르게 하지만 둘 다 코드의 내부 구현을 잘 해서 외부에서 쉽게 사용하게 하고, 코드의 세부적인 내용은 모르게 한다는 측면에서 비슷한 의미로 사용된다.

캡슐화를 사용해야 하는 이유? 여러 가지가 있지만 클래스를 설계할 때 클래스 간 간섭 및 정보 공유를 최소화하여 개별 클래스가 단독으로도 잘 동작할 수 있도록 해야 하기 때문이다. 또한, 사용자 입장에서는 상세한 내용을 모르더라도 인터페이스를 이해하면 클래스를 쉽게 사용할 수 있다. 파이썬에서는 이러한 개념을 가시성이라는 이름으로 적용시키고, 정보 은닉을 어떻게 할 것인가를 코드 레벨에서 조절한다.

실제 파이썬의 가시성 사용 방법에 대해 예시를 들어 설명하겠다. 코드를 작성해야 하는 상황은 다음과 같다.

  • Product 객체를 Inventory 객체에 추가
  • Inventory에는 오직 Product 객체만 들어감
  • Inventory에 Product가 몇 개인지 확인이 필요함
  • Inventory에 Product items는 직접 접근이 불가함
class Product(object):
    pass

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)

my_inventory = Inventory()
my_inventory.add_new_item(Product())
my_inventory.add_new_item(Product())

my_inventory.__items

# 출력결과
new item added
new item added
Traceback (most recent call last):
  File "C:\\Users\\hahnp\\PycharmProjects\\study_only\\dsob_week8\\visibility1.py", line 20, in <module>
    my_inventory.__items
AttributeError: 'Inventory' object has no attribute '__items'
class Inventory(object):
    def __init__(self):
        self.__items = []

    @property
    def itmes(self):
        return self.__items