[파이썬] OOP(Object Oriented Programming)란?

OOP(Object Oriented Programming)

기본 개념

  • 세상에 실체가 있는 모든 물체를 클래스와 인스턴스, 함수, 변수라는 object로 변화시켜서 프로그램을 구성한다.
  • OOP의 기본 전제는 기능(함수, 변수) 재사용이 가능하도록 설계 및 프로그래밍 했는 지이다.
  • 최소비용으로 최대효율을 얻기 위한 개념의 일부이다.

의견

  • 용어보다는, 현실에서 발생할 수 있는 특정 객체(object)를 컴퓨터라는 도구에 인식시키는 것이다.
  • 대부분의 분야에서 OOP의 개념을 적용하여 프로그래밍을 수행한다.
  • 기본개념: 설계(사람이 이해하는 방식)와 구현할 소스코드(컴퓨터가 이해하는 방식) 간의 상호이해가 중요하다.
    • HW, SW의 성능증가(CPU 성능, SW 다중 실행) 덕에 OOP의 모든 기능을 활용할 필요는 없다.
    • OOP를 무분별하게 활용하면 유지보수가 어려워질 수도 있기 때문에 설계방향 및 서비스 기능에 따라 사용해야 한다.

OOP의 어려운 점

  • 하나의 패러다임이기 때문에 우열을 가릴 필요는 없다.
  • 주관성이 높으므로, 보편적으로 활용되는 개념만 배운다.
    (소프트웨어 서비스 설계 방향에 영향을 많이 받는다.)
  • 프로그래밍 뿐 아니라 다양한 도메인에서 재사용 가능한 클래스, 메소드 설계가 중요하다.

OOP 이전

image

  • OOP 개념이 나오기 전에는, 배열과 함수, 변수를 많이 생성하고 활용하여 최대한 많은 기능을 적은 양의 소스코드 파일에 담았다.
  • 속성과 기능이 증가할 때마다 배열과 함수를 계속 생성해야했기에 소스코드를 관리하는데 비효율적이었다.
    • 이에 따라 속성과 기능을 object라는 최소 단위로 분리하는 OOP의 개념이 나오기 시작했다.

필요성

image

  • 데이터 기반 의사결정(data-driven), 컴퓨터 하드웨어 성능, 데이터량 증가에 따라 OOP 활용도 증가하였다.
    • 프로그래밍 패러다임: OOP, Procedural Programming, functional Programming
    • 함수형은 함수의 사용을 극대화시켜 코드의 가독성을 높여주는 형태이다.
    • 프로그래밍 코드를 특정 상황에 특정 함수를 사용하기 위해 고안된 방법이다.
    • 절대적으로 좋은 프로그래밍은 아니다.

일상 생활

  • 일상생활에서 볼 수 있는 것, 실제로 머리속에서 떠올릴 수 있는 것을 프로그래밍하는 것이 OOP의 중요한 점이다.
  • 기능별로 개체가 효율적으로(재사용가능하도록) 분리되어야 한다. 그러므로 설계가 중요하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 절차 프로그래밍(Procedural Programming, 일명 PP)
# 조건 또는 기능이 증가할 때마다 함수와 변수 같은 요소가 계속 증가할 수 있으므로 비효율적이다.

# 케이스 1 : 함수활용
def carAttribute():
  speed = 50
  color = 'black'
  model = 'CarModel'
  return speed,color,model

# return값을 통해 함수 요소값을 확인할 수 있다.
print("Car Attribute: ", carAttribute())

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

# 해당 변수를 각각 명시해주어야 한다.
print("Car Attribute: ", speed,color,model)
'''
Car Attribute:  (50, 'black', 'CarModel')
Car Attribute:  50 black CarModel
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# OOP / 기능에 따라 Car클래스 안에 있는 함수와 변수만 추가해주면 된다.
# 아래와 같이 클래스 선언 순서에 관계없이 실행된다.
# 절차 프로그래밍과 다르게 기능별로 수행되기 때문이다.
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")
print("--------Car Object Create Complete--------")
print("Car Speed: ", myCar.speed)
print("Car Color: ", myCar.color)
print("Car Model: ", myCar.model)

myBus = Bus(0, 'black')
print("--------Bus Object Create Complete--------")
print("Bus color: ", myBus.color)


# 운전 중이 아니므로 speed는 0을 출력한다.
print("--------Car/Bus Speed 1--------")
print("Car Speed by drive: ", myCar.speed)
print("Bus Speed by drive: ", myBus.speed)

# Car/Bus object method Call
myCar.drive()
myBus.drive_bus()

# 각각의 개체가 각자의 속도로 움직이고 있다.
print("--------Car/Bus Speed 2--------")
print("Car Speed by drive: ", myCar.speed)
print("Bus Speed by drive: ", myBus.speed)
'''
--------Car Object Create Complete--------
Car Speed:  0
Car Color:  green
Car Model:  testCar
--------Bus Object Create Complete--------
Bus color:  black
--------Car/Bus Speed 1--------
Car Speed by drive:  0
Bus Speed by drive:  0
--------Car/Bus Speed 2--------
Car Speed by drive:  50
Bus Speed by drive:  70
'''

OOP의 구성

1. 캡슐화

image

  • 기본개념: 내부 속성(변수)과 함수를 하나로 묶어서 클래스로 선언하는 일반적인 개념이다.
    • 캡슐화 형태로 코드를 작성하지 않으면 특정 기능(함수, 변수)에 직접 접근하게 되는 상황이 된다.
    • 기능이 많아질수록 재사용의 개념을 활용하기 어렵다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 캡슐화코드

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
'''

2. 상속과 포함(Inheritance & Composition)

image

  • 객체(object)의 종류는 현실 세계에 있는 대부분이기 때문에, 설계될 수 있는 다양한 객체(object)가 있다.

상속(Inheritance)

  • ‘개는 동물이다.’ 또는 ‘선생님은 직장인이다.’
  • 기본개념: 상위 클래스의 모든 기능(함수, 변수)을 재사용할 수 있다.

포함(Composition)

  • ‘개는 몸을 갖고 있다.’
  • 기본개념: 다른 클래스의 일부 기능(함수)만을 재사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# 상속코드

# 클래스 선언
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()
'''
Dave studies hard
David works hard
'''


# 포함코드

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

    def printPerson(self):
        print('Person_printPerson')

class Student(Person):
    def __init__(self, name, age, id):
        Person.__init__(self, name, age)
        Person.printPerson(self)
        self.id = id

    def test(self, score):
        if score > 80:
            print (self.name + " studies hard")
        else:
            print (self.name + " needs supplementary lessons")


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

# object 실행
print("s.age:", s.name)   # result : Dave
print("s.age:", s.age)   # result : 20
print("s.id:", s.id)   # result : 1
print('\n')

# 객체 하나 더 생성하기
s2 = Student("Jamie", 25, 2)
print("s2.age:", s2.name)   # result : Jamie
print("s2.age:", s2.age)   # result : 25
print("s2.id:", s2.id)   # result : 2
print('\n')

# 점수입력
print('-- test score result --')
s.test(52)
s2.test(88)
'''
Person_printPerson
s.age: Dave
s.age: 20
s.id: 1


Person_printPerson
s2.age: Jamie
s2.age: 25
s2.id: 2


-- test score result --
Dave needs supplementary lessons
Jamie studies hard
'''


# 포함코드

# 클래스 선언
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()
'''
This duck has a bill object and a tail object.
'''

3. 추상화

image

  • 기본개념: 추상화(abstraction)는 복잡한 내용에서 핵심적인 개념 및 기능을 요약하는 것이다.
    • object의 기능에 따라 추상클래스(상위클래스)를 상속받아 개별적으로 클래스(하위클래스)를 생성한다.
    • 기본적으로 추상메소드를 선언하며 실제 실행되는 기능은 보여지지 않는다.
    • 실제 실행되는 기능은 선언된 추상 클래스를 상속받은 다른 클래스의 메소드에서 확인할 수 있다.
    • 추상클래스를 사용하는 이유: 대형 프로젝트를 진행하는 경우 또는 프로그램이 복잡해지는 경우 1차적인 설계를 위해 기능을 추상화시켜놓고, 활용여부는 차후에 결정하기 위함이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 추상화 코드

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)
'''
Student : 
체력: 30
생각: 10

Driver : 
체력: 10
생각: 10
'''

4. 다형성

image

  • 다형성은 구현되는 하위클래스에 따라 클래스를 다르게 처리하는 기능이다.
    • 상속과 유사하다고 느낄 수 있지만, 상속은 사위 클래스의 기능을 재사용한다.
    • 위 그림과 같이 다형성은 상위클래스의 기능을 변경하여 사용하는 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Person:
  def run(self):
    print("I'm a human: ", end='')
    print('run')

  def play(self):
    print("I'm a human: ", end='')
    print('play')
  
class Student(Person):
  def run(self):
    print("I'm a student: ", end='')
    print('fast run')
  
  def play(self):
    print("I'm a student: ", end='')
    print('play')
  
class teacher(Person):
  def teach(self):
    print("I'm a teacher: ", end='')
    print('teach')

  def play(self):
    print("I'm a teacher: ", end='')
    print('teach play')


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

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


print("=========")
for Player in number: 
    Player.play()    # 상위클래스인 Person의 play는 상속하여 사용하지만 내용은 다르다.
'''
=========
I'm a student: fast run
I'm a human: run
=========
I'm a student: play
I'm a teacher: teach play
'''

OOP 설계

클래스 설계와 사용

  • 클래스 설계가 중요한 이유는 코드 재사용성이다.
  • 코드 블록을 구성한다.

1단계

  • 코드 설계 시 사용할 object
  • Users
    • Customers
    • Vendors
    • Admin
  • Products
  • Purchases

2단계

  • 코드 작성 전, 각 object별로 요구되는 속성과 어떤 기능을 위해 생성되었는지 설계한다.
  • Users
    • Attributes(속성)
      • 이름
      • 사용자가 관리자인지?
    • Customers
      • Attributes
        • 이름
        • 구매목록
    • Vendors
      • Attributes
        • 이름
        • 상품목록
    • Admin
      • 이름
      • 사용자가 관리자임을 나타내는 구분값
  • Products
    • Attributes
      • 이름
      • 가격
      • 공급업체
  • Purchases
    • Attributes
      • 제품
      • 고객
      • 가격
      • 구매완료기간
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# 기본이 될 수 있는 User 클래스 생성

class User:
    def __init__(self, name, is_admin=False):
        self.name = name
        self.is_admin = is_admin


# User로부터 상속받는 3개의 클래스를 정의
"""
부모 클래스에서 정의한 함수와 변수는 자식 클래스에서 정의한 것처럼 사용할 수 있다.

부모 클래스에서 정의한 함수와 자식 클래스에서 정의한 함수 이름이 같은 경우,
부모 클래스의 함수를 호출하려면 두 가지 방법 중 하나를 사용해야 한다.
1) 부모클래스명.함수명()
2) super().함수명()

즉, super()는 자식클래스에서 부모클래스에 있는 함수를 사용하고 싶고,
플러스 해당 함수명이 자식클래스에 중복되어 있을 때 사용할 수 있는 코드이다.
"""

class User:
    def __init__(self, name, is_admin=False):
        self.name = name
        self.is_admin = is_admin

class Admin(User):
    def __init__(self, name):
        super().__init__(name, is_admin=True)

# 부모 클래스(User)로부터 상속을 받으려면 클래스를 선언할 때,
# 클래스 이름 다음에 있는 소괄호 안에 부모 클래스의 이름을 넣어주면 됩니다.
class Customer(User):
    def __init__(self, name):
        super().__init__(name)
        self.purchases = []

class Vendor(User):
    def __init__(self, name):
        super().__init__(name)
        self.products = []


# 위의 코드에서 제품(Product)과 구매(Purchase) 클래스 생성 코드 추가

from datetime import datetime

class User:
    def __init__(self, name, is_admin=False):
        self.name = name
        self.is_admin = is_admin

class Admin(User):
    def __init__(self, name):
        super().__init__(name, is_admin=True)

class Customer(User):
    def __init__(self, name):
        super().__init__(name)
        self.purchases = []

class Vendor(User):
    def __init__(self, name):
        super().__init__(name)
        self.products = []

class Product:
    def __init__(self, name, price, vendor):
        self.name = name
        self.price = price
        self.vendor = vendor

class Purchase:
    def __init__(self, product, customer):
        self.product = product
        self.customer = customer
        self.purchase_price = product.price
        self.purchase_data = datetime.now()


"""
현재 생성된 클래스: User(Admin, Customer, Vendor), Product, Purchase

1) 고객(Customer): 제품(Product)을 구매(Purchase)하는 행동
   => Customer 클래스에 purchase_product 메소드 추가

2) 공급업체(Vendor): 제품(Product)을 생산(Product)하는 행동
   => Vendor 클래스에 create_product 메소드 추가
"""

from datetime import datetime

class User:
    def __init__(self, name, is_admin=False):
        self.name = name
        self.is_admin = is_admin

class Admin(User):
    def __init__(self, name):
        super().__init__(name, is_admin=True)

class Customer(User):
    def __init__(self, name):
        super().__init__(name)
        self.purchases = []
    # 함수 추가
    def purchase_product(self, product):
        purchase = Purchase(product, self)
        self.purchases.append(purchase)

class Vendor(User):
    def __init__(self, name):
        super().__init__(name)
        self.products = []
    # 함수 추가
    def create_product(self, product_name, product_price):
        product = Product(product_name, product_price, self)
        self.products.append(product)

# 모델링을 위한 추가 소스코드
class Product:
    def __init__(self, name, price, vendor):
        self.name = name
        self.price = price
        self.vendor = vendor

class Purchase:
    def __init__(self, product, customer):
        self.product = product
        self.customer = customer
        self.purchase_price = product.price
        self.purchase_data = datetime.now()

클래스의 인스턴스화

image

  • 클래스를 생성했으면, 그것을 활용하기 위한 인스턴스화가 필요하다.
    • object가 생성된 이후, object가 소프트웨어의 메모리 할당이 되면 인스턴스가 되는 것이다.
      • object는 인스턴스를 포함할 수 있으며, 포괄적 의미를 갖는다.
      • object는 프로그래밍 전체에서 쓰이는 포괄적인 의미를 가지므로 인스턴스와 비교하면서 학습하는 대상은 아니다.
1
2
3
4
5
6
7
8
# 가장 기본적인 클래스를 생성해보고 값을 확인해본다.

class MyFirstClass:
    pass

a = MyFirstClass()  # 인스턴스화(메모리할당됨)

print(a)    # 주소값은 일반적인 정수값과 다르게 나온다.

파이썬 활용 및 OOP

데이터 캡슐화와 접근제어

image

  • 캡슐화는 object 및 소스코드 구현에 대한 상세정보를 분리하는 과정이다.
  • 모듈화가 가능해진다.(함수, 메소드, 클래스 등을 활용한 기능 분리)
  • 기능이 분리되어있으니 디버깅을 하는 경우 편리하다.
  • 프로그램이 기능별로 분리되어 있어 소스코드의 목적을 알기 쉽다.
  • 접근제어자를 제공하지 않기 때문에 변수, 메소드, 함수에 직접 접근할 수 있다.
    • 상단 표와 같은 직접 접근을 허용하지 않는 규칙이 있다.
  • 파이썬의 변수나 함수를 감춰주는 기능으로, 외부에서 무분별한 접근을 막기 위해 위와 같은 개념이 생겨났다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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: private 으로 해당코드로 접근할 수 없다
# print(my_point.__private_name)

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

# case 3
print('case3 ------------')
my_point_sub.sub()    # 변환된 이름에 대해 값 출력
'''
private 접근
case3 ------------
10
20
'''
  • 클래스이름과 속성에 대해 알고 있으면, 변형된 이름을 사용해 접근 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class A(object):
  def __init__(self):
    self.__X = 3        # self._A__X 로 변환

  def __test(self):     # _A__test() 로 변환
    print('self.__X' ,self.__X)

  def bast(self):
    self.__test()

class B(A):
  def __init__(self):
    A.__init__(self)
    self.__X = 20       # self._B__X 로 변환

  def __test(self):     # _B__test() 로 변환
    print(self.__X)

  def bast(self):
    self.__test()

# 객체 생성
a = A()

# 오류 발생 코드
# a.__test()

# private 메소드 접근방법
# 1) 변형된 이름을 통해 접근가능: '_클래스이름__private메소드'
print('a ----------------------')
a._A__test()

# 2) 우회경로로 접근가능
a.bast()

# 객체 생성
b = B()

# private 메소드 접근
print('b ----------------------')
b._B__test()
b.bast()

# 오류 발생 코드
# print(a.__X)

# __X 변수 접근
print('X ----------------------')
print(a._A__X)
print(b._A__X)
print('\n')

# 상속을 받았기 때문에 B클래스의 인스턴스에서 A클래스의 함수와 변수도 확인가능하다.
# dir(객체): 파이썬 내장메소드로 해당 객체가 어떤 변수와 메소드를 가지고 있는지 나열
print('[부모클래스 A를 사용해 생성한 객체 a의 변수와 메소드]')
print(dir(a))

print('[부모클래스 B를 사용해 생성한 객체 b의 변수와 메소드]')
print(dir(b))     
'''
a ----------------------
self.__X 3
self.__X 3
b ----------------------
20
20
X ----------------------
3
3


[부모클래스 A를 사용해 생성한 객체 a의 변수와 메소드]
['_A__X', '_A__test', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bast']
[부모클래스 B를 사용해 생성한 객체 b의 변수와 메소드]
['_A__X', '_A__test', '_B__X', '_B__test', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bast']
'''
  • 프로그램이 길어지고 다양한 변수를 선언하는 경우 클래스의 속성이 충돌할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 속성충돌

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('어떤 클래스에서 값을 받아오냐에 따라 값의 정보가 바뀜--')
print(s._parent_class__value)
print(s._sub_class__value)
print('parent_class value:',s.get(),' sub_class value:', s.value)
'''
30
40
어떤 클래스에서 값을 받아오냐에 따라 값의 정보가 바뀜--
50
20
parent_class value: 30  sub_class value: 30
'''
  • 충돌이 발생하는 이유는 대체로 프로그램에서 중복되는 속성(attribute)을 활용하는 경우이다.
  • 속성명을 다르게 해줘도 되지만 파이썬에서 활용할 수 있는 ‘비공개 속성’을 활용할 수 있다.
  • 비공개 속성은 ‘__‘를 활용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 속성충돌방지(언더바와 접근제어의 개념을 활용하여 같은 변수이름끼리 헷갈리지 않도록 한다.)

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) 
'''
parent_class value: 10  sub_class value: 20
'''

메소드 오버라이딩

image

  • 메소드 오버라이딩은 같은 이름의 메소드를 신규 생성하는 것이다.
  • 중복되는 기능은 기존 부모클래스의 메소드로 재사용하고, 다르게 사용하려면 재정의하는 개념으로 활용할 수 있다.
    • 다형성 개념의 한 종류이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 - 부모클래스 메소드
print('출력1:')
a_bike.exclaim()

# 출력2 - 오버라이딩된 자식클래스 메소드(파라미터 추가, return 추가)
print('출력2:')
a_specialized.exclaim('specialized test')
class Bicycle():
    def exclaim(self):  
        print("부모클래스 자전거")

class Specialized(Bicycle): # 부모클래스를 상속받음
    def exclaim(self):    # 메소드 오버라이딩
        super().exclaim() # super는 위의 부모클래스 메소드를 그대로 활용한다.
        print('super를 활용한 부모클래스의 메소드입니다.')
         


a_bike = Bicycle()
a_specialized = Specialized()

# 출력1 - 부모클래스 메소드
print('출력1:')
a_bike.exclaim()

# 출력2 - super : 부모클래스 메소드 그대로 활용
print('출력2:')
a_specialized.exclaim()

super

  • Graduate(자식클래스)는 Student(부모클래스)가 가지고 있는 모든 매개변수(파라미터, arguments)를 사용한다.
  • 상속을 통한 재사용을 하는 경우, 아래와 같이 다른 매개변수(graduation_date)도 신규 생성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

print('출력1: ', end='')
a_student = Student('stu_class')
print('출력2: ', end='')
a_graduate = Graduate('gradu_class',11)

print('출력3: ', end='')
a_student.__init__('stu')
print('출력4: ', end='')
a_graduate.__init__('gradu',11)

# graduate 인스턴스 생성
print('출력5: ', end='')
print(a_graduate.name)

print('출력6: ', end='')
print(a_graduate.graduation_date)

참조

0%