반응형

Python의 추상 클래스와 MutableMapping을 활용한 데이터 구조 설계

Python은 객체 지향 프로그래밍(OOP) 언어로서, 추상 클래스를 통해 명확한 인터페이스를 정의하고 이를 기반으로 다양한 데이터 구조를 설계할 수 있는 기능을 제공합니다. 특히, Python의 collections.abc 모듈에서 제공하는 MutableMapping 추상 클래스를 활용하면, 딕셔너리와 같은 유연한 데이터 구조를 커스터마이즈하고 확장할 수 있습니다.

이 글에서는 Python의 추상 클래스 개념과 MutableMapping을 활용한 데이터 구조 설계 방법을 설명하고, 실질적인 예제를 통해 그 활용 방법을 소개합니다.

추상 클래스 (Abstract Class)

추상 클래스는 하나 이상의 추상 메서드를 포함하는 클래스입니다. 추상 메서드는 선언만 되어 있고, 실제 구현은 제공하지 않습니다. 추상 클래스는 객체를 직접 생성할 수 없으며, 반드시 상속받아 추상 메서드를 구현해야 합니다. 이를 통해 일관된 인터페이스를 강제할 수 있습니다.

추상 클래스의 정의

from abc import ABC, abstractmethod

class AbstractDataStructure(ABC):
    @abstractmethod
    def add(self, item):
        pass

    @abstractmethod
    def remove(self, item):
        pass

    @abstractmethod
    def find(self, item):
        pass

 

위 예제에서 AbstractDataStructure는 추상 클래스이며, add, remove, find 메서드는 추상 메서드로 정의되어 있습니다. 이 클래스는 상속받아 구체적인 구현을 제공해야 합니다.

MutableMapping 추상 클래스

collections.abc 모듈의 MutableMapping 추상 클래스는 딕셔너리와 같은 매핑 타입을 정의하는 데 사용됩니다. MutableMapping은 Mapping을 상속받아 변경 가능한 매핑 타입의 인터페이스를 정의합니다.

MutableMapping을 활용한 사용자 정의 클래스

MutableMapping을 상속받아 커스터마이즈된 딕셔너리 클래스를 구현할 수 있습니다. 이를 통해 기본 딕셔너리 기능을 확장하거나 새로운 기능을 추가할 수 있습니다.

from collections.abc import MutableMapping

class CustomDict(MutableMapping):
    def __init__(self):
        self._store = {}

    def __getitem__(self, key):
        return self._store[key]

    def __setitem__(self, key, value):
        self._store[key] = value

    def __delitem__(self, key):
        del self._store[key]

    def __iter__(self):
        return iter(self._store)

    def __len__(self):
        return len(self._store)

# CustomDict 사용 예제
custom_dict = CustomDict()
custom_dict['a'] = 1
custom_dict['b'] = 2

print(custom_dict['a'])  # 출력: 1
print(custom_dict)  # 출력: {'a': 1, 'b': 2}

del custom_dict['a']
print(custom_dict)  # 출력: {'b': 2}
print(len(custom_dict))  # 출력: 1

 

CustomDict에 예제 메서드 추가 및 활용

CustomDict 클래스에 예제 메서드를 추가하여 실제로 어떻게 활용할 수 있는지 살펴보겠습니다. 이 예제에서는 increment라는 메서드를 추가하여, 특정 키의 값을 증가시키는 기능을 구현해보겠습니다.

CustomDict 클래스 정의 및 예제 메서드 추가

 

from collections.abc import MutableMapping

class CustomDict(MutableMapping):
    def __init__(self):
        self._store = {}

    def __getitem__(self, key):
        return self._store[key]

    def __setitem__(self, key, value):
        self._store[key] = value

    def __delitem__(self, key):
        del self._store[key]

    def __iter__(self):
        return iter(self._store)

    def __len__(self):
        return len(self._store)

    # 예제 메서드 추가: 특정 키의 값을 증가시키는 메서드
    def increment(self, key, amount=1):
        if key in self._store:
            self._store[key] += amount
        else:
            self._store[key] = amount

# CustomDict 사용 예제
custom_dict = CustomDict()
custom_dict['a'] = 1
custom_dict.increment('a')
custom_dict.increment('b', 5)

print(custom_dict['a'])  # 출력: 2
print(custom_dict['b'])  # 출력: 5
print(custom_dict)  # 출력: {'a': 2, 'b': 5}

 

위 예제에서 CustomDict 클래스에 increment 메서드를 추가하여, 특정 키의 값을 증가시키는 기능을 구현했습니다.

Dictionary에서 비슷하게 구현

기본 dict를 사용하여 비슷한 기능을 구현할 수도 있습니다. 이를 위해 별도의 함수를 정의해보겠습니다.

# 기본 dict를 사용하여 increment 기능을 제공하는 함수
def increment(d, key, amount=1):
    if key in d:
        d[key] += amount
    else:
        d[key] = amount

# dict 사용 예제
basic_dict = {'a': 1}
increment(basic_dict, 'a')
increment(basic_dict, 'b', 5)

print(basic_dict['a'])  # 출력: 2
print(basic_dict['b'])  # 출력: 5
print(basic_dict)  # 출력: {'a': 2, 'b': 5}

CustomDict와 기본 dict를 사용한 구현의 장단점

CustomDict의 장점

  1. 메서드 추가 및 확장 용이:
    • 클래스 메서드로 기능을 구현하면, 객체 지향적인 접근이 가능하고, 상태를 쉽게 관리할 수 있습니다.
    • 메서드를 통해 기능을 캡슐화하여 코드의 재사용성과 유지보수성을 높일 수 있습니다.
  2. 인터페이스 일관성:
    • MutableMapping을 상속받아 딕셔너리와 동일한 인터페이스를 제공하므로, 기존의 딕셔너리 사용 패턴과 호환됩니다.
  3. 추가 기능 구현의 용이성:
    • CustomDict 클래스를 확장하여 새로운 기능을 쉽게 추가할 수 있습니다.

CustomDict의 단점

  1. 복잡성 증가:
    • 기본 dict를 사용하는 것보다 클래스를 정의하고 관리하는 데 더 많은 코드와 복잡성이 필요합니다.
  2. 성능 저하 가능성:
    • 추가된 추상화 계층으로 인해 성능이 약간 저하될 수 있습니다.

기본 dict의 장점

  1. 단순성:
    • 별도의 클래스를 정의하지 않고, 단순한 함수로 기능을 구현할 수 있어 코드가 간결합니다.
    • 기본 dict는 Python의 내장 자료형이므로 추가적인 학습이나 설정 없이 바로 사용할 수 있습니다.
  2. 성능:
    • Python의 기본 dict는 C로 구현되어 있어 매우 빠릅니다.

기본 dict의 단점

  1. 재사용성 및 유지보수성 저하:
    • 기능을 함수로 구현하면, 상태와 기능이 분리되어 있어 재사용성과 유지보수성이 낮아질 수 있습니다.
    • 여러 함수로 기능을 확장하는 경우, 코드의 일관성을 유지하기 어렵습니다.
  2. 객체 지향 프로그래밍의 한계:
    • 객체 지향 프로그래밍의 이점을 활용하지 못하므로, 복잡한 상태 관리나 기능 확장이 어렵습니다.

결론

  • CustomDict: 객체 지향적인 접근으로, 상태와 기능을 캡슐화하여 코드의 재사용성과 유지보수성을 높일 수 있습니다. 복잡한 기능을 확장하는 데 유리하지만, 기본 dict보다 구현과 관리가 더 복잡합니다.
  • 기본 dict: 간단하고 성능이 우수하지만, 복잡한 기능을 확장하거나 유지보수할 때 어려움이 있을 수 있습니다.

두 접근 방식 모두 장단점이 있으므로, 특정 상황과 요구 사항에 따라 적절한 방법을 선택하는 것이 중요합니다.

반응형

'Computer Science > python' 카테고리의 다른 글

동적 속성과 프로퍼티  (0) 2024.08.16
연산자 오버로딩  (0) 2024.07.09
프로토콜과 'abc' 모듈  (0) 2024.06.11
Python 데코레이터  (0) 2024.05.21
seaborn clustermap color label  (0) 2022.05.24
반응형

프로토콜 (Protocol)

프로토콜은 특정 메소드나 속성을 구현해야 하는 비공식적인 인터페이스입니다. Python은 강력한 덕 타이핑(duck typing) 덕분에 공식적으로 인터페이스를 정의하지 않아도 됩니다. 객체가 특정 메소드나 속성을 가지고 있다면, 그 객체는 해당 프로토콜을 구현한다고 간주합니다.

예를 들어, Python의 시퀀스 프로토콜은 __len__과 __getitem__ 메소드를 요구합니다. 리스트, 튜플, 문자열 등은 모두 이 프로토콜을 구현합니다.

 

 

인터페이스를 정의한다는 것은 클래스가 가져야 할 메소드와 속성을 명시하는 것을 의미합니다. 이는 객체 지향 프로그래밍에서 매우 중요한 개념입니다. 인터페이스는 클래스가 특정한 기능을 제공할 것이라는 계약(Contract)을 정의하며, 이를 통해 코드의 일관성을 유지하고 재사용성을 높일 수 있습니다.

인터페이스의 개념

인터페이스는 일반적으로 다음을 포함합니다:

  • 메소드 서명(Signature): 메소드의 이름, 매개변수, 반환 타입 등을 명시합니다.
  • 속성(Property): 클래스가 가져야 할 속성을 정의합니다.

인터페이스는 구현을 포함하지 않으며, 인터페이스를 상속받는 클래스는 해당 인터페이스에서 정의된 모든 메소드와 속성을 구현해야 합니다.

 

class CustomSequence:
    def __len__(self):
        return 10
    
    def __getitem__(self, index):
        return index * 2

seq = CustomSequence()
print(len(seq))        # 10
print(seq[3])          # 6

 

abc 모듈 (추상 기반 클래스)

Python의 abc 모듈은 추상 기반 클래스를 정의하는 기능을 제공합니다. 추상 클래스는 하나 이상의 추상 메소드를 포함할 수 있으며, 이러한 메소드는 서브클래스에서 반드시 구현해야 합니다. 이를 통해 공통 인터페이스를 정의하고 구현 강제를 할 수 있습니다.

추상 클래스 정의

추상 클래스는 ABC 클래스를 상속받고, 추상 메소드는 @abstractmethod 데코레이터를 사용하여 정의합니다.

 

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

class Dog(Animal):
    def sound(self):
        return "Bark"

 

추상 클래스의 역할

  1. 공통 인터페이스 정의: 추상 클래스는 여러 구체적인 클래스가 반드시 구현해야 하는 메소드와 속성을 정의합니다. 이를 통해 모든 서브클래스가 동일한 인터페이스를 가지게 됩니다.
  2. 코드 재사용성 증가: 추상 클래스는 공통 기능을 한 곳에 모아서 구현할 수 있습니다. 이를 상속받는 구체적인 클래스들은 이러한 공통 기능을 재사용할 수 있습니다.
  3. 유지보수성 향상: 코드를 수정할 때, 공통 기능은 추상 클래스에서 한 번만 수정하면 되므로 유지보수성이 높아집니다.
  4. 다형성 지원: 동일한 인터페이스를 통해 다양한 클래스와 상호작용할 수 있게 하여 다형성을 지원합니다.

 

from abc import ABC, abstractmethod

class Animal(ABC):
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def sound(self):
        return "Some generic animal sound"

    @abstractmethod
    def move(self):
        return "Some generic animal movement"

    def display_info(self):
        print(f"{self.name} says {self.sound()} and {self.move()}")

class Dog(Animal):
    def sound(self):
        return "Bark"

    def move(self):
        return "Runs"

class Cat(Animal):
    def sound(self):
        return "Meow"

    # move 메소드를 재정의하지 않음

class Fish(Animal):
    # sound 메소드를 재정의하지 않음

    def move(self):
        return "Swims"

# 동물원 관리 시스템
class Zoo:
    def __init__(self):
        self.animals = []

    def add_animal(self, animal: Animal):
        self.animals.append(animal)

    def show_all_animals(self):
        for animal in self.animals:
            animal.display_info()

# 동물원에 동물 추가
zoo = Zoo()
zoo.add_animal(Dog("Buddy"))
zoo.add_animal(Cat("Whiskers"))
zoo.add_animal(Fish("Nemo"))

# 모든 동물 정보 표시
zoo.show_all_animals()

 

실행 결과

Buddy says Bark and Runs
Whiskers says Meow and Some generic animal movement
Nemo says Some generic animal sound and Swims

 

예제에서는 추상클래스 "Animal"을 선언하고 각각 Dog, Cat 그리고 Fish 클래스가 이를 상속하도록 했습니다.

 

구체적인 클래스 Zoo 에서 Dog, Cat 그리고 Fish 오브젝트를 생성하였으며 여기서 모든 동물들에게 상속받은 추상 클래스 display_info를 호출하면 실행 결과를 출력하게 됩니다.

 

 

추상클래스는 다중 상속도 가능합니다.

from abc import ABC, abstractmethod

class Printable(ABC):
    @abstractmethod
    def print(self):
        pass

class Scannable(ABC):
    @abstractmethod
    def scan(self):
        pass

class MultifunctionPrinter(Printable, Scannable):
    def print(self):
        return "Printing document"

    def scan(self):
        return "Scanning document"

# MultifunctionPrinter 클래스는 Printable과 Scannable의 추상 메소드를 모두 구현해야 합니다
mfp = MultifunctionPrinter()
print(mfp.print())  # "Printing document"
print(mfp.scan())   # "Scanning document"

 

MultifunctionPrinter에 Printable와 Scannable 추상 클래스를 다중 상속 하고 있습니다.

 

일반 메서드의 경우, 서브클래스에서 재정의하지 않으면 상속된 메서드를 그대로 사용할 수 있습니다. 즉, 서브클래스는 필요에 따라 메서드를 재정의할 수도 있고, 그렇지 않으면 상위 클래스에서 정의된 메서드를 사용할 수 있습니다.

 

그러나 추상 클래스에서 정의된 추상 메서드는 특별한 경우입니다. 추상 메서드는 기본적으로 구현되지 않은 메서드입니다. 따라서 추상 클래스에서 추상 메서드를 정의하면, 이를 상속받는 클래스는 반드시 그 추상 메서드를 재정의하여 구현해야 합니다. 그렇지 않으면 해당 서브클래스는 추상 클래스로 간주되어 인스턴스화할 수 없습니다.

 

from abc import ABC, abstractmethod

class AbstractClass(ABC):
    @abstractmethod
    def abstract_method(self):
        pass

    def concrete_method(self):
        print("This is a concrete method from the abstract class.")

class ConcreteClass(AbstractClass):
    def abstract_method(self):
        print("This is the implementation of the abstract method.")

# 인스턴스 생성
instance = ConcreteClass()
instance.abstract_method()  # This is the implementation of the abstract method.
instance.concrete_method()  # This is a concrete method from the abstract class.

 

추상 클래스의 목적은 인터페이스를 정의하고, 특정 메서드를 서브클래스에서 반드시 구현하도록 강제하는 것입니다. 이를 통해 코드의 일관성을 유지하고, 서브클래스가 필요한 기능을 올바르게 구현하도록 보장합니다.

 

 

 

직접 ABC를 정의할 수 있지만 가능하면 collections.abc나 파이썬 표준 라이브러리의 모듈에서 상속해서 써라!

 

 

반응형

'Computer Science > python' 카테고리의 다른 글

연산자 오버로딩  (0) 2024.07.09
추상클래스의 활용  (0) 2024.06.12
Python 데코레이터  (0) 2024.05.21
seaborn clustermap color label  (0) 2022.05.24
flask_sqlalchemy  (0) 2022.05.23

+ Recent posts