4 minute read

추상화

몰라도 되는 부분은 감추고 꼭 알아야 하는 부분만 드러내는 것을 추상화라고 한다.
변수나 함수를 클래스등을 사용하는 것도 추상화다.

추상화 잘하기

이름 잘 짓기

이름을 잘 지어야 그것이 나타내는게 뭔지 바로 파악할 수 있다.

문서화 하기

파이썬에서는 docstring(documentation string)으로 문서화 한다.
설명할 부분 바로 아래에 작성한다.

"""주석"""
"""
여러줄
작성
가능
"""

help(클래스 이름) 를 사용하면 깔끔하게 docstring을 확인할 수 있다.

docstring중 많이 쓰이는 포맷은 google docstring, reStructuredText(파이썬 공식 문서화 기준), NumPy/SciPy가 있다.

type hinting

정적 타입 언어에서는 변수의 타입을 지정하지만 동적 타입 언어에서는 변수의 타입을 따로 지정하지 않는다.
그래서 어떤 타입의 변수를 사용할지 알기가 어렵다. 이런 단점을 보완하기 위해 type hinting을 쓴다.

age: int = 20

def deposit(self, amount: float) -> None:
    self.balance += amount

변수에 타입 힌팅을 쓰려면 변수 이름 뒤에 콜론을 쓰고 타입을 쓰면 된다.
메소드의 리턴값은 메소드 정의부분 뒤에 화살표를 쓰고 타입을 쓴다.
리턴값이 없으면 None을 쓴다.
타입 힌팅은 실행에 영향을 주지 못하고 타입 힌팅에 맞지 않는 값을 넣어도 동작한다.

캡슐화

객체의 일부 구현 내용에 대한 외부로부터의 직접적인 접근을 차단하고 객체의 속성과 행동을 하나로 묶는다.

객체 내부 숨기기

class User:
    def __init__(self, name, age, address):
        self.name = name
        self.__age = age
        self.__address = address
    
    def __check(self, address):
        ...

__이름 이렇게 하면 외부에서 접근이 불가능 해진다.

접근을 하려면 접근가능한 메서드를 만들어야 한다.

def get_age(self):
    return self.__age

getter메소드로 값을 가져올 수 있다.

def set_age(self, value):
    self.__age = value

setter메소드로 값을 설정할 수 있다.
setter메소드에서 조건을 설정해 적절한 값만 들어오게 통제할 수 있다.

위와 같이 특정 변수에 접근하기 위해선 특정 메소드를 통해서만 접근이 가능한데 이렇게 변수에 접근하는 통로를 메소드로 제한하는 것을 속성과 행동을 하나로 묶는다고 한다.

맹글링

class User:
    def __init__(self, name, age, address):
        self.name = name
        self.__age = age
        self.__address = address

user1 = User("철수", 27, "서울")
print(dir(user1))

dir함수를 사용하면 인스턴스가 가지고 있는 모든 변수와 메소드를 볼 수 있다.
[‘_User__address’, ‘_User__age’, ‘class’, ‘delattr’, ……
이런 내용들이 나오게 되는데 앞 부분을 보면 __age변수와 __addresss변수에 “_클래스이름”이 덧붙여져 있는 것을 볼 수 있는데 밑줄 두개를 붙여 캡슐화를 하면 내부적으로 저런식으로 이름이 바뀌게 된다. 이것을 네임 맹글링이라고 한다. 그래서 기존의 이름으로 접근했을 때는 에러가 나게되고 바뀐 이름으로 접근을 하면 에러가 나지 않는다. 이렇게 클래스 밖에서 접근할 수 있으니 캡슐화가 되지 않았다. 사실 파이썬에서는 언어 차원에서 캡슐화를 지원하지 않는다.

데코레이터로 캡슐화

class User:
    def __init__(self, name, age, address):
        self.name = name
        self.__age = age
        self.__address = address

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        self.__age = value

user1 = User("철수", 27, "서울")
print(user1.age)
user1.age = 30
print(user1.age)

이렇게 데코레이터를 사용하고나서
user1.age, user1.age = 30 을 하면 코드상으로는 그냥 age를 가져오는 것이지만 데코레이터를 사용하고 나면 같은 이름을 가진 age메소드를 실행하라는 의미가 된다.

property데코레이터를 사용하면 캡슐화 전 사용하던 코드를 캡슐화 후 수정할 필요가 없다.

파이썬에서 캡슐화를 그렇게 엄격하게 지키지 않더라도 객체를 사용할 땐 가능한 메소드로 사용하는게 유지보수 측면에서 좋다.
입력값의 조건이 바꼈다면 getter메소드만 수정하면 되기 때문이다.

상속

상속은 두 클래스 사이에 부모 자식 관계를 설정하는 것이다.
클래스들 간에 중복되는 내용을 부모로 만든다.

class A:
    number = 1


class B(A):
    text = "abc"


print(B.number) # 1

A클래스가 부모, B클래스가 자식이 된다.
부모 클래스의 클래스 변수 number를 상속받아 B클래스에서도 사용할 수 있다.

help함수를 사용하면 print(help(B)) 상속관계를 확인할 수 있다.
위의 경우에는

Method resolution order:
B
A
builtins.object

이렇게 상속관계가 나온다. builtins.object는 최상위 클래스다. 모든 클래스들은 builtins.object를 상속받는다.

mro(메소드 검색 순서)

mro함수를 사용해서도 상속관계를 확인할 수 있다.
print(B.mro()) -> [<class ‘main.B’>, <class ‘main.A’>, <class ‘object’>]
왼쪽 부터 오른쪽으로 자식에서 부모 방향이다.
자식이 먼저 검색된다.

isinstance

class A:
    number = 1


class B(A):
    text = "abc"


c = B()

print(isinstance(c, B))
print(isinstance(c, A))

첫 번째 파라미터의 값이 두 번째 파라미터의 인스턴스인지 확인한다.
상속관계의 클래스에서 자식 클래스로 만든 인스턴스는 부모 클래스의 인스턴스이기도 하다.

issubclass

class A:
    number = 1


class B(A):
    text = "abc"


print(issubclass(B, A))

첫 번째 파라미터의 값이 두 번째 파라미터의 자식 클래스인지 확인한다.

오버라이딩

오버라이딩은 물려 받은 내용을 각 클래스에 맞게 수정한다는 의미다.

class A:
    def __init__(self, name, age):
        self.name = name
        self.age = age


class B(A):
    def __init__(self, name, age, email):
        super().__init__(name, age)
        self.email = email

super함수로 부모 클래스의 메소드를 호출 가능하다. 이 때 self는 생략한다.

mro로 메소드 검색 순서를 확인해보면 자식이 먼저 검색되고 부모가 그 다음 순서로 검색된다. 그래서 부모와 자식에 같은 이름의 메소드가 있더라도 자식이 먼저 검색되므로 오버라이딩한 메소드가 실행된다.

다중 상속

하나의 자식이 여러 부모를 상속받을 수 있다.

class A:
    number = 1


class B:
    number = 2


class C(A, B):
    pass


print(C.number) # 1

앞 쪽의 파라미터가 더 우선권을 가진다. mro로 확인해 보면 된다.

다형성

같은 형태의 코드가 서로 다른 동작을 하는 것을 말한다.
자식의 공통되는 부분들을 부모에 모아두면 유지보수에 유리해 진다.

추상 클래스

여러 클래스들의 공통점을 추상화해서 모아놓은 클래스를 말한다.
추상클래스로 정의하면 그 자식클래스들이 추상클래스에서 상속받는 메소드를 오버라이딩하도록 강제한다.

from abc import ABC, abstractmethod


class A(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

추상 클래스를 만들려면 ABC를 상속받고 적어도 하나 이상의 추상 메소드를 가져야 한다. 추상 메소드를 만들려면 @abstractmethod를 사용한다. 이 추상메소드는 자식 클래스가 반드시 오버라이딩 해야한다.

추상 클래스는 인스턴스를 만들 수 없다.
인스턴스를 직접 생성하려고 만든 클래스가 아니라 공통점을 모으기 위함이다.

여러개의 자식이 공통점을 가지고 있을 때 추상 메소드를 정의한다. 그리고 자식은 그 공통점을 반드시 오버라이딩 하도록 강제되기 때문에 공통되는 부분을 빼먹을 수 가 없어진다.

추상 메서드에는 타입 힌팅을 해주는 게 좋다.

추상 클래스와 추상화

추상 클래스로 추상화된 부분 까지만 고려하고 개발을 진행할 수 있다.

추상 클래스에 일반 메소드

추상 클래스에도 일반 메소드를 같이 적을 수 있다.

추상 메소드 내용 채우기

추상 메소드는 어차피 오버라이딩 되니 내용이 필요없을 것 같지만 추상 메소드 안에서 공통되는 부분은 작성해두면 자식에서 super로 재사용 할 수 있다.

추상 클래스 다중 상속

일반 클래스에서 다중 상속은 자제하는게 좋지만 추상 클래스 다중 상속은 상관없다.
어차피 자식에서 오버라이딩하니 겹치는 문제가 없기 때문이다.
추상 메소드로만 이루어져 있다면 상관 없지만 만약 추상 메소드와 일반 메소드가 섞여 있으면 문제가 생길 수 있다.

Leave a comment