Byeonguk Kim

안녕하세요. 29살의 조금은 늦은 나이로 새롭게 개발자로 시작하는 신입 개발자입니다. 포트폴리오 [https://deaguowl.github.io]

파이썬 16. 객체지향(OOP)

23 Mar 2019 » Python

2019.03.23 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기.

  1. 객체지향이란?
  2. 객체지향의 특징 3가지 3.

OOP를 시작하기 전에 보면 좋은 것을 지난 번에 TIL한 것에서 가져와보았다.

class - 객체를 만들어 내기 위한 틀

class는 객체를 만들어내기 위한 약속이다.
class는 객체의 속성(property)과 동작(method)을 정의한다.

object - class라는 틀을 가지고 만들어낸 실물(ex. 자동차)

파이썬에서의 객체

  • 컴퓨터가 보는 객체 : 클래스를 이용해 만들어진 변수와 함수를 가진 메모리 공간
  • 우리가 보는 객체 : 현실 세계의 사물을 모델링 한 것

object란?

  1. 관련 있는 변수와 함수를 한곳에 모아(bundling) 놓은 곳(메모리의 영역)
    1. 변수 - 상태정보
    2. “관련 있는” 변수, 함수” “한곳에 모아” 가 중요하다.
  2. class를 통해 생성되므로 class의 속성을 가지고 있어야 한다.
  3. 속성(attribute)
    1. instance member(변수)
      1. 각자의 객체가 가지는 값(값 = 상태정보)
      2. __init__에 보면 변수가 나와 있음
      3. 같은 속성을 가지고 있으나 속성 값이 다르다.
      4. 현재 객체의 담고 있는 인스턴스 멤버 정보 보기 acnt.__dict__
    2. instance method(함수)
      1. 객체가 할 수 있는 행동, 기능

—> 절차지향에서는 전체 프로그램을 다 봐야하지만.
—> 객체지향에서는 단위로 프로그램을 유추해볼 수 있다.

절차지향 vs 객체지향

###procedural vs object-oriented

객체지향 절차지향

2개 다 추상화를 하는 기법이다.

  • 추상화란?
    • 상세한 정보는 무시하고 필요성에 의해 있어야할 정보만을 추림. why?
    • 우리가 구현하고 싶은 것들을 설계하기 위해 멤버와 메서드를 뽑아내는 일

procedural : 함수를 이용해 추상화를 했다. 추상화의 도구로 함수를 이용 (절차지향)

  1. 이 프로그램은 어떤 일을 하는가?
  2. 기계와 비슷한 사고 방식
  3. 함수를 이용

object-oriented : 객체를 통해 추상화를 했다.

  • 현실세계에서 객체를 나타내려면 변수와 함수만 있으면 된다. 현실 세계를 모델링하거나 프로그램을 구현하는데 이처럼 변수와 함수를 가진 객체를 이용하는 패러다임을 객체지향이라고 한다.
  1. 현실 세계(에 존재하는 객체(object)를 어떻게 모델링(modeling)할 것인가?
    • 객체(object) : 사람, 동물, 자동차 등을 어떻게 프로그램으로 모델링 할 것인가?
  2. 사람과 비슷한 사고 방식
  3. 객체를 이용

OOP의 3대 특징

  • 캡슐화
  • 정보은닉
  • 다형성

1. Encapsulation(캡슐화)

정의 : 관련 있는 멤버(변수=데이터)와 메서드(함수=행동)를 하나의 단위(대부분 클래스)로 묶는 것

  • 관련 있는 멤버와 메서드가 중요하다.

  • 하나의 단위 : java, python, c++ 에서는 모두 클래스로 묶지만 꼭 클래스가 아니라도 하나의 단위로만 묶을 수 있다면 OOP로 볼 수 있다.

  • 정보 은닉을 포함

    • 멤버와 메서드를 묶으면서 어떤 멤버를 공개 혹은 공개 하지 않을 것인가 결정
    • 어떤 메서드를 공개 혹은 비공개 할 것인가?
    • 멤버과 메서드를 공개하지 않을 것을 정보 은닉이라고 한다.
    • 정보은닉은 캡슐화 안에서 일어나므로 캡슐화에 포함되었다고 생각

2. 정보은닉

정의 : 특정 멤버와 메서드를 공개하지 않을 것

  1. 파이썬에서는 완벽한 정보은닉을 지원하지 않는다.
    • 그럼 완벽한 정보은닉은 무엇이냐?
      1. c에서는 private를 통해 정보를 은닉시킴
      2. 접근조차 못하게 한다. java, c#, c++모두 지원
  2. 하지만 약간의 기법을 통해 특정 멤버와 메서드가 정보은닉을 바라는 것인지 알 수 있다.

    1. name-mungling
      • self.__ balance = balance —> acnt.__ balance를 하면 실행이 안됨
        • __ balance 를 _Account__balance로 바꾸어서 저장하는 것이다.
      • 함수에도 __를 붙여서 외부에서 쉽게 호출하지 못하도록 한다.
        • 지원을 할려고 노력은 하되 100%지원은 하지 않음
    2. property
      • OOP 호출 전제 조건
        • OOP에서는 외부에서 변수(멤버)에 직접 접근하는 것은 절대 금지이다.
        • 객체의 멤버에 접근이나 수정할 떄는 반드시 메서드를 이용해야 한다.
        • 따라서 객체의 멤버에 접근하기 위해서는 get_ or set_등과 같은 함수를 이용해야 한다.
      • 하지만 property를 이용하여 접근을 일관되게 할 수 있다.
        • 멤버를 호출하면 내부적으로는 함수를 호출하도록 설계한다.
        • 함수를 설계할 때 특정한 정보은닉을 할 수 있다.
  • name-mungling과 멤버에 대한 접근
>>> class Account:
>>> 	def __init__(self, name, money):
>>>			self.user = name
>>>			self.__balance = money
>>>    
>>>		def get_balance(self):
>>>			return self.__balance
>>>    
>>>		def set_balance(self, money):
>>>			if money < 0:
>>>			    return
>>>			self.__balance = money
>>>    
>>> if __name__ == '__main__':
>>>		my_acnt = Account("greg", 5000)
>>>		my_acnt.__balance = -3000
>>>    
>>>		print(my_acnt.get_balance())
>>>		print(my_acnt.set_balance(6000))
>>>		print(my_acnt.get_balance())
>>>		print(my_acnt.__dict__)
>>>
>>> 5000
>>> None
>>> 6000
>>> {"user" : "greg", '_Account_balance' : 6000, '__balance : - 3000'}
  • name-mungling을 통해 바로 접근할 수 없어졌고, 클래스안에서 멤버 앞에 언더바 2개를 붙이게 되면 이후에 이 객체가 만들어 질 때 멤버의 이름은 _ 클래스 이름 __ 멤버 로 설정된다.
  • 객체의 멤버에 접근하기 위해 get_balance와 set_balance 함수를 통해 접근하였다.
  • property를 활용해 이를 해결 할 수 있다.
>>>	class Person:
>>>    def __init__(self, name, money):
>>>       self.name = name
>>>			self.money = money
>>>
>>>		@property
>>>		def money(self):
>>>			print('getter executed')
>>>			self._money = money
>>>
>>>		@money.setter
>>>		def money(self, money):
>>>			print('setter executed')
>>>			if money < 0:
>>>				print("음수보다 큰 값을 넣어주세요")
>>>				return
>>>			
>>>			self._money = money	
>>>			
>>>	if__name__ == "__main__":
>>>		john = Person('john' , 5000)
>>>		print(john.__dict__)
>>>		john.money = -5000
>>>		print(john.money)	
>>>
>>> setter excuted
>>> {'name': 'john', '_money': 5000}
>>> setter excuted
>>> 음수보다  값을 넣어주세요
>>> getter excuted
>>> 5000
  • \ __ init __ 을 실행하여 객체를 만들 때 money 멤버에 오면 setter 메서드가 실행되면서 _money로 저장이 된다.
  • 이러한 방법을 통해 우리는 정보를 은닉하고 멤버에 대한 접근을 일관되게 할 수 있게 된다.
  1. 멤버는 어느 메서드에서도 생길 수 있다!!!!
    1. 파이썬에서는 다른 어떤 함수에서도 멤버가 생성될 수 있다.
    2. 따라서 안쓰더라도 모든 멤버들은 __init__에 넣어줘라!
    3. 인스턴스 멤버를 만든다고 하면 되도록이면 생성자에서 None으로 두고 만들어라
  2. setter를 통해 특정 조건에 대해 멤버 생성을 조절 할 수 있다.
    1. -가 들어오는게 막고 싶다면(특정 조건) 그 기능을 한 함수에 모아서 setter에 집약시킨다.

생성자에 모든 인스턴스 멤버들을 만들기 위해 노력하되 특정 생성자에 대한 생성 조건이 있다고 하면 그것은 하위에 한 함수를 통해 조절한다.

정보은닉은 name-mungling과 property를 함께 사용하여 확실한 은닉을 하기도 한다.

3. polymorphism(다형성)

정의 : 상속받은 같은 인스턴스 메서드를 호출하는데 호출의 주체인 object가 서로 다른 클래스에서 온 인스턴스기 때문에 다른 behavior을 나타낼 때 다형성이라고 한다.

  • 상속을 전제로 한다.
  • 다형성은 추상클래스로 부터 오기 때문에 추상클래스에 대해 먼저 알아야 한다.
  • 추상클래스를 통해 추상메서드를 만들어내면 이후에 만들어지는 객체에서는 추상메서드를 오버라이딩하여 새로운 인스턴스 메서드를 만들어내고, 각각의 메서드들은 다른 behavior을 나타내게 되는데 이를 다형성이라고 한다.
>>>from abc import ABCMeta, abstractmethod
>>>
>>>	class Animal(metaclass=ABCMeta):
>>>	    @abstractmethod
>>>	    def say(self):
>>>
>>>	        print('noting')
>>>
>>>
>>>	class Lion(Animal):
>>>	    # def say(self):
>>>	    #     print("어흥")
>>>	    pass  # 이렇게 say를 빼먹으면 상속받은 메서드를 쓰지 않으면 Lion도 추상 클래스가 된다.
>>>
>>>	class Duck(Animal):
>>>	    def say(self):
>>>	        print("꽥꽥")
>>>
>>>	class Dog(Animal):
>>>	    def say(self):
>>>        print("멍멍")
>>>
>>>	class Deer(Animal):
>>>	    def say(self):
>>>	        print("사슴")
>>>
>>>	if __name__ == "__main__":
>>>	    animals = []
>>>	    animals.extend((Lion(), Duck(), Deer(), Dog(), Duck()))
>>>
>>>	    for animal in animals:
>>>	        animal.say()                 #"어흥", "꽥꽥", "멍멍", "사슴"
>>>
>>>		ani = Animal()  # 이 세상에 그냥 동물은 없으므로 이것을 못되게 해야 한다.
>>>		ani.say()
    
  • from abc import ABCMeta (abstract base class)
  • 추상클래스는 매개변수로 metacalss = ABCMeta를 받아야 한다.
  • 추상매서드를 1개 이상 포함하고 있어야 한다.
  • 추상클래스를 상속받아 생성된 클래스는 모두 추상매서드를 오버라이딩해야한다.

추상클래스

정의 : 동물은 고양이과, 개과로 나눌 수 있다. 또 고양이과에는 사자, 고양이, 호랑이 등등 더 세분화 되어 들어갈 수 있다. 하지만 단순히 고양이과의 객체를 만들어서는 그 객체를 구체화 시킬 수 없다. 따라서 객체를 만들기에 너무나 추상적이라 객체를 만들 수 없도록 한 것이 추상클래스이다..

  • [참고] (https://itewbm.tistory.com/entry/%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4abstract-class%EC%9D%98-%EC%A1%B4%EC%9E%AC-%EC%9D%B4%EC%9C%A0) : 추상클래스의 존재 이유

  • abstract class의 특징
    1. 인스턴스를 만들 수 없다.
    2. abstract method를 가져야 한다.
      1. 추상 메서드 (= 순수 가상 함수)는 몸체가 없어야 한다.
      2. 파생 클래스는 추상메서드를 반드시 overriding을 해야 한다.!!!!!!!!!!
      3. 상속받은 메서드를 사용하지 않으면 그 클래스도 추상 클래스가 된다.(상위)—> 구현을 강제 할 수 있다.
      4. 함수 시그니처는 함수 인터페이스 , 함수 사용법이다.
        1. 객체는 공개된 메서드만 가져와서 사용한다.
        2. 유저가 사용할 수 있는 메서드의 목록 및 사용방법을 보여주는 것을 인터페이스라고 한다.
        3. 객체에서 인터페이스라고 하면 유저프로그래머는 내부 프로그램을 알 필요가 없다. 공개되어 있는 메서드만 가져다 쓰면된다.
        4. 이게 객체에서의 인터페이스이다.
        5. 인터페이스를 공개한다는 말은
          1. 유저에게는 인터페이스를 제공하고
          2. 클래스를 상속받아 쓰는 사람들에게는 다형성을 제공해준다.
  • 메서드 오버라이딩(메서드의 재정의)
    1. 상속 일때만 해당된다.
      1. 정의 : 상속을 하는데 자식 클래스가 어떤 함수(이미 상속을 받아서 이미 있는 함수)를 replace한 경우
      2. 부모로부터 부여 받은 함수를 자신에게 맞추어 메서드를 재정의 하는 것
    2. 메서드 오버라이딩을 통해 구현 가능 한 것
      1. 객체에 따라 함수 결과 값을 다르게 바꿀 수 있다.
        1. 함수를 호출하는 객체가 다르므로
    3. 다형성을 구현하기 위한 기술적인 특징
    4. 이것을 기반으로 다양한 패턴이 나온다.
    5. 메서드 오버라이딩을 하는 순간 다 가상 함수라고 생각해도 된다.
  • 연산자 오버로딩(메서드의 중복정의)
    1. 다형성의 한 종류
    2. 직접 정의한 클래스에서 생성된 객체는 기본적으로 연산이 불가능하다.
    3. 직접 정의한 클래스의 객체에 +,-,*와 같은 일반 연산을 적용하려면 연산자 오버로딩을 통해 객체를 연산 가능한 상태로 만들어야 한다. (기존에 있던 연산자의 기능을 바꾸어 중복으로 정의)
    4. 파이썬에서는 다른 자료형에 적용되는 모든 연산을 사용자가 정의하는 클래스에서 동작할 수 있도록 구현할 수 있다.