본문 바로가기

Computer Science

[Computer Science] 파이썬 with OOP(Object-Oriented Programming)

 

OOP(Object-Oriented Programming)
최소비용으로 최대효율을 지향하며.
세상에 있는 실체가 있는 모든 물체를 클래스와 인스턴스, 함수, 변수라는 object로 변화시켜서 프로그램을 구성.
기본 전제는 기능(함수, 변수) 재사용이 가능한 설계 및 프로그래밍.

기본 개념  

  1. 설계(사람이 이해하는 방식)와 구현할 소스코드(컴퓨터가 이해하는 방식) 간의 상호이해가 중요하다.
  2. HW&SW 성능증가 (CPU성능 증가, 소프트웨어 다중실행) 덕분에 OOP의 모든 기능을 활용할 필요는 없다.
  3. OOP의 개념을 무분별하게 활용하면 유지보수가 어려워질 수도 있기때문에 설계방향 및 서비스기능에 따라 사용해야 한다.
  4. OOP를 제대로 하는 법은 프로그래밍뿐만 아니라 다양한 도메인에서 재사용 가능한 클래스, 메소드(기능) 설계가 중요.
  5. OOP의 필요성은 data-driven(데이터기반 의사결정), 컴퓨터하드웨어성능, 데이터양 증가에 따라 OOP활용도 증가
  6. OOP는 소프트웨어 서비스 설계 방향에 영향을 많이 받는 주관성이 높은 프로그래밍 패러다임일 뿐임.

 

프로그래밍 패러다임 

  • OOP
    기능이 아닌 객체가 중심이 되며 "누가 어떤 일을 할 것인가?"가 핵심이 된다. 즉, 객체를 도출하고 각각의 역할을 정의해 나가는 것에 초점을 맞춘다.
    클래스 선언 순서에 관계없이 실행된다.각각의 클래스가 독립적 으로 작동.
    절차 프로그래밍과 다르게 기능별로 수행되기 때문에 기능에 따라 클래스 안에 있는 함수와 변수만 추가해주면 된다.
    [대형프로그래밍에 좀 더 적합 but 절대적으로 좋은 프로그래밍은 아님]
''' Bus와 Car라는 클래스가 존재 한다.
Bus 클래스는 속도(speed)와 색(color)을 선언 받아(__init__)  
정해진 속도(70)에 맞춰운전(drive_bus)을 한다.

Car 클래스는 속도(speed)와 색(color)과 차종(model)을 선언받아(__init__) 
정해진 속도(50)에 맞춰 운전(drive)한다. '''

class Bus:
  def __init__(self, speed, color):
      self.speed = speed
      self.color = color
  
  def drive_bus(self):
    self.speed = 70

class Car:
  def __init__(self, speed, color, model):
    self.speed = speed
    self.color = color
    self.model = model
  
  def drive(self):
    self.speed = 50
  

''' 클래스 이용하여 인스턴스 생성 '''

myCar = Car(0,"green", "testCar")
myBus = Bus(0,'black')
print("Car Object Create Complete")
print("Car Speed ", myCar.speed)
print("Car Color ", myCar.color)
print("Car Model ", myCar.model)
print("Bus color ", myBus.color)

''' 각 클래스의 메소드 호출하여 인스턴스 실행'''
#Car object method Call
myCar.drive()
myBus.drive_bus()
print("Car Speed by drive ", myCar.speed) # 입출력순서에 상관없이 speed 개체를 변경하여 볼 수 있다.
print("Bus Speed by drive ", myBus.speed)

 

  • 절차 프로그래밍(Procedural Programming, 일명 PP)
    절차 지향 모델링은 프로그램을 기능중심으로 바라보는 방식으로 "무엇을 어떤 절차로 할 것인가?"가 핵심이 된다. 즉, 어떤 기능을 어떤 순서로 처리하는가에 초점을 맞춘다.
    조건 또는 기능이 증가할 때마다 함수와 변수 같은 요소가 계속 증가할 수 있으므로 비효율적이다.
    [소형프로그램에 좀더 적합]
# 케이스 1 : 함수활용
def carAttribute():
  speed = 50
  color = 'black'
  model = 'CarModel'
  return speed,color,model


# 케이스 2 : 변수활용
# speed = 50
# color = 'black'
# model = 'CarModel'

print("Car Procedural Create Complete")
#print("Car Attribute ", speed,color,model) # 변수를 통해서만 값을 호출하므로, 소스코드가 증가한다.
print("Car Attribute ", carAttribute()) # return값을 통해 함수 요소값을 확인할 수 있다.
  • 함수형 프로그래밍(Functional Programming)
    함수형은 함수의 사용을 극대화시켜서 코드의 가독성을 높여주는 형태이다. 프로그래밍 코드를 특정 상황에 특정 함수를 사용하기위해 고안된 방법이다. 특정 컴퓨터환경과 특정 대용량 개발환경에서 많이 쓰이고 있지만 절대적으로 좋은 프로그래밍은 아니

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


1. 추상화(abstraction)


객체들의 공통적인 특징(기능, 속성)을 도출하는 것
객체지향적 관점에서는 클래스를 정의하는 것을 추상화라고 할 수 있다.(클래스가 없는 객체지향 언어도 존재 ex.JavaScript)

  • 기본개념 : 객체들의 공통적인 특징(기능, 속성)을 도출. 즉 추상화(abstraction)는 복잡한 내용에서 핵심적인 개념 및 기능을 요약하는 것.
    • object의 기능에 따라 추상클래스(상위클래스, 부모클래스)를 상속받아 개별적으로 클래스(하위클래스, 자식클래스)를 생성.
      • 추상클래스(상위클래스, 부모클래스)
        - 기본적으로 추상메소드를 선언하며 실제 실행되는 기능은 없어. 
        - 대형 프로젝트를 진행하는 경우 또는 프로그램이 복잡해지는 경우 1차적인 설계를 위해 기능을 추상화 시키고 활용여부는 차후 결정 하기 위해 사용.
        - 특징:
        1. 추상 클래스는 인스턴스를 생성할 수 없다.
        2. 자식 클래스들이 각각의 독립된 공통적인 기능을 갖출 때 사용 한다.
        3. 각각의 독립된 공통적인 기능이 값을 공유하면 안되므로 구현하지 않은 빈 메소드를 사용 한다.
      • 상속클래스(하위클래스, 자식클래스)
        - 실제로 기능이 실행되는 메소드.
        - 추상클래스를 상속받아 각각의 상속 클래스에서 각각의 기능을 한다. 
      • 추상 클래스를 사용할때는 자식 클래스의 메소드와 추상 클래스의 메소드가 일치 해야 합니다
# 추상화 코드

from abc import *    # abc 모듈의 클래스와 메소드를 갖고온다.(abc : abstract base class)

# 추상 클래스
class People(metaclass=ABCMeta):  

# 추상 메소드
    @abstractmethod # 추상 메소드에는 @abstractmethod를 선언해줘야 함
    def charecter(self): 
        pass        # 추상 메소드는 기능 내 실제 실행내용은 없다.

# 상속받는 클래스
class Student(People):
    def charecter(self, pow, think):
        self.pow = pow
        self.think = think

        print('체력: {0}'.format(self.pow))
        print('생각: {0}'.format(self.think))

# 상속받는 클래스
class Driver(People):
    def charecter(self, pow, think):
        self.pow = pow
        self.think = think

        print('체력: {0}'.format(self.pow))
        print('생각: {0}'.format(self.think))

# Student object 생성      
peo1 = Student()
print('Student : ')

# Student object 실행
peo1.charecter(30, 10)

print()

# Driver object 생성
peo2 = Driver()
print('Driver : ')

# Driver object 실행
peo2.charecter(10, 10)

2. 다형성(polymorphism)

약간 다른 방법으로 동작하는 함수를 동일한 이름으로 호출하는 것
동일한 명령의 해석을 연결된 객체에 의존하는 것

  • 기본개념: 다형성은 구현되는 하위클래스에 따라 클래스를 다르게 처리하는 기능이다.
    • 상속과 유사하다고 느껴질 수 있지만, 상속은 상위클래스의 기능(함수, 변수)을 재사용한다.
    • 위의 그림과 같이 다형성은 상위클래스의 기능을 변경하여 사용하는 것이다.(그대로 재사용하지 않는다.)
class Person:
  def run(self):
    print('run')

  def play(self):
    print('play')
  
class Student(Person):
  def run(self):
    print('fast run')
  
  def play(self):
    print('play')
  
class teacher(Person):
  def teach(self):
    print('teach')

  def play(self):
    print('teach play')


# 리스트를 생성한다.
number = list()
# 생성한 리스트에 다형성 개념을 위해 다른 클래스(Student, teacher)가 상위 클래스(Person)를 참조할 수 있도록 한다.
number.append(Student())  # 리스트 끝에 서브 클래스 Student()를 넣습니다. 
number.append(teacher())  # 다시 리스트 끝에 서브 클래스 teacher()를 넣습니다.

print("=========")
for Student in number:
    Student.run()     # 상위클래스인 Person의 run은 상속하여 사용하지만 내용은 다르다.


print("=========")
for teacher in number: 
    teacher.play()    # 상위클래스인 Person의 play는 상속하여 사용하지만 내용은 다르다.


3. 캡슐화(encapsulation)

 

기본개념: object 및 소스코드 구현에 대한 상세정보를 분리하는 과정. 파이썬의 변수나 함수를 감춰주는 기능으로 외부의 무분별한 접근을 막는다, 이를 통해 오류를 줄일 수 있다. 

장점:

  1. 모듈화가 가능(함수, 메소드, 클래스 등을 활용한 기능의 분리.)
     - 디버깅에 용이하다. 
  2. 접근제어 가능

    : 파이썬은 object접근제어를 위한 접근제어자를 제공하지 않기 때문에 변수, 메소드, 함수에 직접 접근이 가능하다 따라서 위의 표화 같이 접근제어규칙을 만들어서 사용함
    - Notation(접근 정도를 나타내는 명칭): Private>>>Protected>>>>Public 순으로 접근이 어려움
    - 데이터를 보이지 않고 외부와 상호작용을 할 때는 메소드를 이용하여 통신을 한다. 보통 라이브러리로 만들어서 업그레이드해 사용할 수 있다.
# 캡슐화코드

class Encap:
  def __init__(self,value):
    self.value = value
    print('init :', self.value)

  def _set(self):
    print('set :', self.value)

  def printTest(self):
    print('printTest :', self.value)

  # def __printTest2(self):
  #   print('printTest :', self.value)

# object 생성
e = Encap(10)

# object 실행 
# 케이스1
e.__init__(20)
e._set()
e.printTest()
#e.__printTest2()


print('\n')

# 케이스2
e.__init__(30)
e._set()
e.printTest()
init : 10
init : 20
set : 20
printTest : 20

init : 30
set : 30
printTest : 30

 

클래스 생성:

underscore를 통해 형태 변화하여 부모클래스와 자식클래스의 변수나 메소드 이름을 구분짓는다.

class Point: 
   def __init__(self, x, y):
       self.x = x
       self.y = y
       self.__private_name = "private 접근"

class Point_sub(Point):
  def __init__(self,x, y):
    Point.__init__(self,x,y)
    self.x = x
    self.y = y
    
  def __sub(self):
    self.__x = 10
    self.__y = 20
  def sub(self):
    self.__sub()
    print(self.__x)
    print(self.__y)
 
my_point = Point(1, 2)

my_point_sub = Point_sub(10,20)


 # case 1 - error case
#print(my_point.__private_name)

 # case 2
#print(my_point._Point__private_name)    # 클래스 생성자에 있는 private변수에 접근하기위해 '_클래스이름__private변수' 를 활용한다.

 # case 3
my_point_sub.sub()    # 변환된 이름에 대해 값 출력​

 

 

속성(attribute)충돌:

  • 위에서 설명한 _와 __의 활용에 대해 생각해보자.
  • 프로그램이 길어지고 다양한 변수를 선언하는 경우 클래스의 속성이 충돌(변수의 중복)할 수 있다.
  • 성의 충돌(같은 값)이 발생하는 이유는 대체로 프로그램에서 중복되는 속성(attribute)을 활용하는 경우
  • 속성명을 다르게 해줘도 되지만 파이썬에서 활용할 수 있는 '비공개 속성'( '__'를 활용한다.) 을 활용할 수 있다.
''' 속성충돌의 예 '''

class parent_class:
  def __init__(self):
    self.value = 30
    self._value = 40
    self.__value = 50

  def get(self):
    return self.value

class sub_class(parent_class):
  def __init__(self):
    super().__init__()
    self.__value = 20    # 위의 parent_class의 _value와 충돌(값의 중복)발생

s = sub_class()
print(s.value) # public
print(s._value)# protected
#print(s.__value)
#print('parent_class value:',s.get(),' sub_class value:', s.value)


''' 속성충돌방지(언더바와 접근제어의 개념을 활용하여 같은 변수이름끼리 헷갈리지 않도록 한다.) '''

class parent_class:
  def __init__(self):
    self.__value = 10   # parent_class는 '__'

  def get(self):
    return self.__value # parent_class는 '__'

class sub_class(parent_class):
  def __init__(self):
    super().__init__()  # parent_class 호출을 위해 super() 사용
    self._value = 20    # sub_class는 '_'


s = sub_class()
print('parent_class value:',s.get(),' sub_class value:', s._value)

 

getter, setter로서의 property 사용:

  • 파이썬에서는 getter,setter에 대해서 명시적으로 표현하지 않기 때문에 property 키워드를 사용한다.
  • 따라서 내장함수와 데코레이터(@)를 활용하여 코드를 간결화하고 기존 인스턴스 속성에 새로운 기능을 제공할 수 있다.
    • 파이썬의 내장함수 property() : getter/setter에 대해서 변수처럼 활용할 수 있게 해준다.
    • 데코레이터로서의 @property : 기존의 메소드에 대해서 새로운 기능을 제공할 수 있다.
    • (단, 데코레이터를 적용한 메소드를 재사용할 수 없다.)
''' case 1 - _get_name에서 property(_get_name, _set_name) (내장함수로서의 property활용)'''

class Color:
     def __init__(self, rgb_value, name):
         self.rgb_value = rgb_value
         self._name = name
     def _set_name(self, name):
         if not name:
             raise Exception("Invalid Name")
         self._name = name

     def _get_name(self):
         return self._name
     name = property(_get_name, _set_name)    # 내장함수 property 활용
     
c = Color("#0000ff", "bright red")
print(c.name)   # property를 통해 변수처럼 getter가 가능하다.

c.name = "red"   # property를 통해 변수처럼 setter가 가능하다.
print(c.name)


''' case 2 - @property 활용(데코레이터로서의 property활용) '''

class Color:
     def __init__(self, rgb_value, name):
         self.rgb_value = rgb_value
         self._name = name
    #  def _set_name(self, name):
    #      if not name:
    #          raise Exception("Invalid Name")
    #      self._name = name

     @property                # 데코레이터 property 활용
     def _get_name(self):
         return self._name
     
c = Color("#0000ff", "bright red")
print(c._name)
# print(c.name)   # 이 경우는 에러이다. 왜 일까?

c.name = "red"
print(c.name)

 

4. 상속성(inheritance) & 포함(Composition)

하나의 클래스가 가진 특징(함수, 데이터)을 다른 클래스가 그대로 물려받는 것
이미 작성된 클래스를 받아서 새로운 클래스를 생성하는 것
기존 코드를 재활용해서 사용함으로써 객체지향 방법의 중요한 기능 중 하나에 속한다.

  • 상속(Inheritance):
    "개는 동물이다." 또는 "선생님은 직장인이다."라는 관계로서 설명된다.
    기본개념 : 상위 클래스의 모든 기능(함수, 변수)을 재사용할 수 있다.
# 상속코드

# 클래스 선언
class Person:
    def __init__(self, name):
        self.name = name
        
class Student(Person):      # Person 클래스 상속받음(name 변수를 파라미터로 재사용)
    def study(self):
        print (self.name + " studies hard")

class Employee(Person):     # Person 클래스 상속받음(name 변수를 파라미터로 재사용)
    def work(self):
        print (self.name + " works hard")

# object 생성
s = Student("Dave")
e = Employee("David")

# object 실행
s.study()
e.work()
  • 포함(Composition):
    "개는 몸을 갖고 있다." 라는 관계로서 설명된다.
    기본개념 : 다른 클래스의 일부 기능(함수)만을 재사용한다.
# 포함코드 예시 1

# 클래스 선언
class Person:
    def __init__(self, age):
        self.age = age

    def printPerson(self):          # 포함을 사용하기 위한 Person 클래스의 다른 함수
        print('Person_printPerson')

class Student:
    def __init__(self, age):
        self.age = age
        self.p = Person(self)       # Student가 Person에 포함되기 위해, Person 클래스에 대한 object 생성

    def aging(self):
        return self.age

    def personComposit(self,age):
        return age, self.p.printPerson()  # 포함개념적용된 Student 클래스의 Person object의 함수

# object 생성
s = Student(10) # 한 번 생성된 object는 파라미터가 변하지 않는 이상 출력값 또한 변하지 않는다.
p = Person(20)

# object 실행
print("s.aging() :", s.aging())   # result : 10
print('\n')


print('test')
print('print with test', "s.personComposit() :", s.personComposit(40)) 
# result 출력 순서 : Person.printPerson -> print with test -> s.personComposit() 


print('\n')
print('test2')
print('print with test2', "p.printPerson() :", p.printPerson()) 
# result 출력 순서 : Person.printPerson -> print with test2 -> p.printPerson()

# 포함코드 예시 2 - 오리의 몸

# 클래스 선언
class Bill():
    def __init__(self, description):
        self.description = description


class Tail():
    def __init__(self, length):
        self.length = length


class Duck():
    def __init__(self, bill, tail):
        self.bill = bill
        self.tail = tail

    def about(self):
        print(
            f"This duck has a {self.bill.description} and a {self.tail.length}.")

# object 생성
duck = Duck(Bill('bill object'), Tail('tail object'))

# object 실행
duck.about()

5. 오버라이딩(Overriding), 오버로딩(Overloading)

  • 오버라이딩(Overriding) - 메소드 오버라이딩은 상속개념을 기반으로 한 개념이다.(다형성 개념의 한종류 이기도 함) 부모클래스의 메소드와 같은 이름을 사용하며 매개변수도 같되 내부 소스를 재정의(재사용)하는 것
    • 오버라이딩: 우선시하다라는 의미, 상속처럼 부모클래스의 메소드를 채호출하는것이 아니라 같은 이름의 메소드를 신규 생성하는 것이다. 
      • 중복되는 기능(메소드)은 기존 부모클래스의 메소드로 재사용하고, 다르게 사용하려면 재2정의 하는 개념으로 활용할 수 있다. 

  • class Bicycle():
         def exclaim(self):
           print("부모클래스 자전거")
    
    class Specialized(Bicycle):            # 부모클래스를 상속받음
        def exclaim(self, specialized):    # 메소드 오버라이딩(specialized 파라미터 추가, return 추가)
             print("자식클래스 재정의",specialized)
             return 'return 자식클래스 재정의'+specialized
    
    
    a_bike = Bicycle()
    a_specialized = Specialized()
    
    # 출력1 - 부모클래스 메소드
    a_bike.exclaim()
    
    # 출력2 - 오버라이딩된 자식클래스 메소드(파라미터 추가, return 추가)
    a_specialized.exclaim('specialized test')
  • super의 사용
    • 자식클래스는 부모클래스가 가지는 모든 매개변수를 사용한다. 
      • 상속을 통한 재사용을 하는 경우, 다른 매개변수도 신규 생성 가능. 
class Student:    # 부모클래스
     def __init__(self, name):
         self.name = name
         print(self.name)

class Graduate(Student):    # 부모클래스 상속받음
     def __init__(self, name, graduation_date): 
         super().__init__(name)   # super를 활용하여 부모 메소드를 호출하여 name 매개변수 재사용(super를 사용하면 부모클래스의 상속받는다는 것을 의미함)
         self.graduation_date = graduation_date

a_student = Student('stu_class')
a_graduate = Graduate('gradu_class',11)

a_student.__init__('stu')
a_graduate.__init__('gradu',11)

# graduate 인스턴스 생성
a_graduate.name             #
a_graduate.graduation_date  #
stu_class
gradu_class
stu
gradu
11


오버로딩(Overloading) - 같은 이름의 함수를 여러 개 정의한 후 매개변수를 다르게 하여 같은 이름을 경우에 따라 호출하여 사용하는 것


동적바인딩(Dynamic Binding)

  • 실행 시간 중에 일어나거나 실행 과정에서 변경될 수 있는 바인딩
  • 정적 바인딩(컴파일 시간에 완료되어 변화하지 않는)과 대비되는 개념.
  • 동적 바인딩은 프로그램의 한 개체나 기호를 실행 과정에 여러 속성이나 연산에 바인딩함으로써 다형 개념을 실현.
    • 다형 개념: 프로그래밍의 유연성을 높여주며 파생 클래스에서 재정의한 함수의 호출을 보장
  • 함수를 호출하면 동적 바인딩을 통해 파생 클래스에 오버라이딩 된 함수가 실행

용어 정리

객체는 클래스라는 틀에서 생겨난 실체(instance)이다.


Object(객체) : 속성과 기능을 최소단위로 분리한것. 클래스, 인스턴스, 함수, 변수 등 실체가 있는 모든 개체
Class : 객체를 분류해주는 설계도와 같은 것.
Instance : class를 통해 객체를 실체화 시킨 것, object가 생성된 이후, object가 소프트웨어의 메모리할당이 되면 인스턴스가 된다.

 


ref. 코딩 스탠다드

https://docs.popekim.com/ko/coding-standards/cpp

 

C++ 코딩 표준 | 포프의 문서창고

마지막 수정일: 2021-02-24

docs.popekim.com

ref. 객체지향

http://www.incodom.kr/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5#h_9bf884dacd40599823d1d2a59bfb9f78

 

객체 지향

# 객체 지향([[Object oriented]]) 프로그래밍이란?

www.incodom.kr

ref. 클래스상속

https://nirsa.tistory.com/115

 

[Python 3.7] 파이썬 클래스 상속 개념과 오버라이딩, super()

클래스 상속 클래스 상속은 말 그대로 클래스를 물려받는 개념 입니다. 기반이 되는 클래스를 부모 클래스(parent class) 또는 기반 클래스(base class), 슈퍼 클래스(super class) 라고 부르며, 물려받은

nirsa.tistory.com

ref. 매직메소드

https://zzsza.github.io/development/2020/07/05/python-magic-method/

 

Python Magic Method 사용법

Python Magic Method 사용법에 대해 작성한 글입니다 키워드 : Python Magic Method, Python Special Method

zzsza.github.io