객체지향 (1)

3 minute read

1. 파이썬 객체지향의 특징

  • object: 파이썬의 최상위 객체
  • 접근제한자가 없음

2. 클래스 생성 / 인스턴스화

클래스 생성

class A:
    a = 1 # class attribute

인스턴스화

  • 사용하기 위해서는 반드시 인스턴스화 시켜야 한다.
  • A.a를 해서도 사용할 수 있지만 인스턴스화한 것은 아님
    • 파이썬은 클래스 자체가 인스턴스된 것이기 때문
# 인스턴스화 하기
aa = A()

3. class attribute / instance attribute

(1) class attribute

  • 클래스가 접근할 수 있는 것
  • dir(클래스이름) 했을 때 나타남
    • 만든 클래스 어트리뷰트 외의 __...__들은 최상위 객체인 object를 상속받기 때문에 생기는 것
  • 클래스 어트리뷰트가 아닌 것을 A.b와 같이 부르려고 하면 Attribute Error 발생

(2) instance attribute

  • 인스턴스는 자신의 고유한 값/메서드를 가질 수 있음
    • instance attribute: 인스턴스의 고유한 값
  • 값의 추가/삭제가 자유로움
  • 인스턴스 어트리뷰트를 따로 만들지 않으면 A.a와 같이 불렀을 때 클래스 어트리뷰트를 찾아서 반환함.
aa.a = 3 # instance attribute

실제로 인스턴스 어트리뷰트는 이렇게 만들 것이 아니라 인스턴스 메서드 안에 만드는 것이 좋다.

4-(2) instance method

4. class method / instance method

(1) class method

class A:
    a = 1
    b = 2
    def __init__(self):
        self.a = 3
        print('init')
    @classmethod
    def bb(cls):    # 관용적으로 cls라고 씀
        cls.b = 4
        print('bb')

A.bb() # bb

aaa = A()

aaa.bb() # bb

(2) instance method

  • 인스턴스 어트리뷰트는 인스턴스 메서드 안에 만들어야 한다.
    • def __init__(self) : 인스턴스화 할 때 실행
class A:
    a = 1
    b = 2
    def __init__(self):
        self.a = 3 # 실행하는 순간 self 대신 aaa가 들어감
        print('__init__')
    def bb(self):
        self.b = 4

aaa = A()

aaa.a # 3

vars(aaa) # {'a': 3}
  • __init__ 외의 메서드 안에 생성한 어트리뷰트는 해당 메서드를 실행해야 인스턴스 어트리뷰트로 사용할 수 있다.
  • 즉, 실행하는 시점에 따라 인스턴스 어트리뷰트가 달라진다.
  • vars()를 이용해 체크한다.
aaa.b # 2

aaa.bb()
aaa.b # 4
vars(aaa) # {'a': 3, 'b': 4}

5. 객체지향 관련 명렁어

(1) instance attribute 확인 명령어

vars(aaa) # {'a': 3}

aaa.__dict__ # {'a': 3}

(2) 부모 확인 명령어

type(aaa) # __main__.A

aaa.__class__ # __main__.A

6. 리터럴 방식 vs. 인스턴스화 방식

# 리터럴 방식
a = 1
b = ['홍', '길', '동']
# 인스턴스화 방식
a = int(1) 
b = list('홍길동')

7. 상속

파이썬은 다중상속을 지원하며, 똑같은 이름이지만 행동을 다르게 하는 다형성을 지원한다.

(1) 상속 기본형태

부모 역할인 클래스명(T)는 괄호를 생략해서 쓸 수 있고 기본적으로 파이썬의 최상위 객체인 object를 상속한다.

class T:
    x = 1

class S(T):
    pass

class K(T):
    x = 2   # overriding
  • S, K는 각각 T를 상속했으므로 T의 x를 사용할 수 있다. ( dir()로 확인 가능 )
  • K는 오바라이딩을 하였으므로 K.x로 호출하면 새로 설정된 2가 나오게 된다.

(2) 상속 순서

똑같은 메서드일 때는 어떤 것을 먼저 실행할지 순서를 정할 수 없다 (consistent MRO를 만들 수 없다) 라고 TypeError가 난다.

MRO : Method Resolution Error

class NO(T, K):
    pass

# TypeError

상속 순서를 확인하기 위해 __mro__를 사용할 수 있다.

  • 단, 상속은 클래스 기준이기 때문에 __mro__는 클래스에만 사용할 수 있다.
  • __mro__는 dir() 했을 때는 보이지 않아도 사용할 수는 있다.
  • __mro__ 대신 mro()도 사용 가능하다. 전자는 튜플로 결과값을 반환하고 후자는 리스트로 반환한다.
K.__mro__  # (__main__.K, __main__.T, object)

kk = K()

kk.__mro__ # AttributeError

(3) 다이아몬드 구조

class A:
    def __init__(self):
        self.x = 'A'
        print('A')

class B(A):
    def __init__(self):
        self.x = 'B'
        print('B')
        
class C(A):
    def __init__(self):
        self.x = 'C'
        print('C')
        
class D(B,C):
    def __init__(self):
        print('D')


d = D() # D
d.x # AttributeError

상속을 하더라도 실행을 하지 않으면 값이 전달되지 않고, x값이 안나온다.

  • A.__init__(self)으로 실행시켜줘야 한다.
class A:
    def __init__(self):
        self.x = 'A'
        print('A')

class B(A):
    def __init__(self):
        self.x = 'B'
        A.__init__(self)
        print('B')
        
class C(A):
    def __init__(self):
        self.x = 'C'
        A.__init__(self)
        print('C')
        
class D(B,C):
    def __init__(self):
        C.__init__(self)   # 실행시켜줘야함
        B.__init__(self)
        print('D')

d = D() # ACABD
d.x # 'A'

여러번 상속되면서 생기는 문제는 super().__init__()을 사용해 상속체계를 내부적으로 정리하게 하여 해결한다.

  • super: 부모클래스의 객체를 대표하는 인스턴스로, 중복 문제를 해결할 수 있다.
class A:
    def __init__(self):
        self.x = 'A'
        print('A')

class B(A):
    def __init__(self):
        self.x = 'B'
        super().__init__()     # 상속체계 내부적으로 정리
        print('B')
        
class C(A):
    def __init__(self):
        self.x = 'C'
        super().__init__()
        print('C')
        
class D(B,C):
    def __init__(self):
        super().__init__()
        print('D')


d = D() # ACBD
D.mro() # [__main__.D, __main__.B, __main__.C, __main__.A, object]

Leave a comment