객체지향 (2)

3 minute read

1. 상속 - 부모 찾기

부모를 찾는 명령어

ex. C가 A, B를 다중상속받았을 때

  • issubclass(C, A) : True 반환
  • C.__base__ : A만 알려줌.
  • C.__bases__ : A, B를 알려줌
  • C.mro()

기타 명령어 정리

  • is : 메모리까지 같은지 비교. Boolean 반환
  • isisntacne : 인스턴스인지 확인
c = C()
isinstance(c, C) # True

isistance(1, int) # True

2. 상속 - 오버라이딩

상속받은 것 외의 새로운 기능을 추가할 수도 있고, 상속받은 기능을 변경할 수도 있다.

class X:
    def xx(self):
        print('X')

class Y(X):
    def xx(self):
        print('Y')

y = Y()
y.xx()  # Y

3. Delegation (위임)

위임의 매커니즘 1 : 상속

  • 파이썬에서는 상속이 위임으로 돌아간다.
  • 암시적으로 부모에게 일을 위임한다.
  • 적은 코드로 깔끔하게 수행할 수 있다.
  • 속도를 희생하지만 메모리 차지를 덜할 수 있다.
class A:
    def xx(self):
        x = 1
        print('xx')

class B(A):
    pass

A.xx # <function __main__.A.xx(self)>
B.xx # <function __main__.A.xx(self)>
# 일은 A에서 함
class X:
    def xx(self):
        print('X')

class Y(X):
    def xx(self):
        print('----')
        X.xx(self)  

y = Y()
y.xx()
# ----
# X

다중상속일 때 겹치는 부분을 제외하고 싶다면 X.xx(self) 대신 super().xx()super(Y, self).xx()를 사용한다.

  • super().xx(): 파이썬 3부터 지원하는 방식
  • super(Y, self).xx(): 파이썬 2 방식

위임의 매커니즘 2 : Composition (컴포지션)

  • 상속을 사용하지 않고 원하는 기능을 가져오는 것으로, 실제 객체를 인스턴스하여 사용한다.
  • 명시적으로 일을 위임한다.
  • 상속보다 복잡하다.
  • 사용 이유: 디커플링 (강한 결합을 피할 수 있음)
  • 즉, 변경하고자 하는 기능이 많거나 너무 많이 유기적으로 연결되어 있을 때는 상속보다 컴포지션이 유연하다.
class Door:
    colour = 'brown'

    def __init__(self, number, status):
        self.number = number
        self.status = status

    def open(self):
        self.status = 'open'
        
    def close(self):
        self.status = 'closed'

class SecurityDoor:
    colour = 'gray'
    locked = True
    
    def __init__(self, number, status):
        # Door 객체 인스턴스화
        self.door = Door(number, status)
        
    def open(self):
        if self.locked:
            return
        self.door.open()
        
    def close(self):
        self.door.close()
  • __getattr__
class A:
    def __init__(self, x):
        self.x = x
    def __getattr__(self, t):     # 호출한 attriute가 없으면 실행
        print('__getattr__ 실행: ',t)

a = A(3)
a.x # 3

# __getattr__ 없을 때는 AttributeError 발생
a.y # __getattr__ 실행: y
class SecurityDoor:
    locked = True
    
    def __init__(self, number, status):
        self.door = Door(number, status)
        
    def open(self):
        if self.locked:
            return
        self.door.open()
        
    def __getattr__(self, attr):
        return getattr(self.door, attr)
    
    s = SecurityDoor(5, 'default')
    # __getattr__ 없을 때는 아래 코드를 실행하면 AttributeError 발생  
    s.status # default

__getattr__ != getattr() == __getattribute__

class S:
  x = 2
  def __getattribute__(self, a):
      print('getattribute:',a)

s = S()
s.x # getattribute: x
getattr(s, 'x') # getattribute: x

4. Descriptor (디스크립터)

.의 행동을 바꾸는 것을 디스크립터라고 한다.

디스크립터를 만드는 3가지 방식

  • composition 방식
  • property 방식
  • decorator 방식

(1) composition 방식

__get__, __set__, __del__를 사용한다.

class Value:
    def __init__(self, x):
        print('init')
        self.x = x
    def __get__(self, a, b):
        print('get')
        return self.x
    def __set__(self, x):
        print('set')
        self.x = x

# Myclass에서 Value를 초기화
class Myclass:
    t = Value(1)
# get 호출
Myclass.t
# get
# 1

(2) property 방식

사용자가 임의로 값을 추가 / 삭제하는 것을 방지할 수 있다.

class C:
    def __init__(self, x):
        self.__x = x
    def getx(self): 
        return self.__x
    def setx(self, value): 
        self.__x = value
    def delx(self): 
        del self.__x
    x = property(getx, setx, delx, "I'm the 'x' property.")

c = C(4)

# get 호출
c.x  # 4

# set 호출
c.x = 3

# del 호출
del c.x

Mangling

  • ex. __a
  • __가 붙으면 호출할 때 사용되어야 하는 이름을 바꿀 수 있다.
  • 중요한 정보를 감출 수 있어 private 기능으로 활용할 수 있다. (information hiding 기법)

(3) decorator 방식

@property를 사용

  • 파이썬은 오버로딩을 지원하지 않으므로, 이름이 같은 메서드가 있다면 나중에 실행된 메서드가 이전에 실행된 메서드를 덮어쓰게 된다.
  • 하지만, 해당 데코레이터를 사용하면 내부적으로 다르게 처리하게 만들기 때문에 이름이 같아도 덮어쓰지 않는다.
class D:
    
    def __init__(self, x):
        self.__x = x
    
    @property        # 해당 데코레이터가 callable -> not callable로 변하게 함
    def x(self):
        print('xxx')
        return self.__x
    
    @x.setter
    def x(self, x):
        print('set')
        self.__x = x

d = D(3)

d.x
# xxx
# 3

d.x = 5
# set

5. _ 용법 정리

  • 맨 마지막 output 값 반환
  • 숫자 사이에 들어가는 _
    • 100_000가 의미하는 것은 100,000이다.
  • import * 했을 때 임포트 안되는 것 만들 때
  • Mangling
    • 뒤섞인 형태로 변형
    • class T:
        __x = 1
          
      T.__x  # AttributeError
      T._T__a  # 1
      
  • dunder = magic method = special method
    • ex. __init__
  • 이름 같은 것을 쓸 수 없지만 유사한 기능을 제공하고자 할 때
    • ex. isin_

6. Metaclasses

  • 클래스의 클래스로, 클래스의 행동을 제약한다.
  • 메타클래스의 인스턴스는 클래스이다.
  • type을 상속받으면 메타클래스
  • type(오브젝트)는 클래스 이름을, type(클래스)는 type(메타클래스)를 반환한다.
# 클래스 만들기
t = type('int2', (int,), {})
issubclass(t, int) # True

지금까지 __base__, __bases__, __mro__같은 것들은 dir()로 보이지 않았다. 바로 메타클래스에서 관리하는 것들이기 때문이었고, 나오게 하기 위해서는 dir(클래스명.__class__)와 같이 사용한다.

Leave a comment