이중 연결 리스트(Doubly Linked List)는 각 노드가 이전 노드와 다음 노드를 참조하는 구조입니다. 이중 연결 리스트를 구현하기 위해서는 노드 클래스(Node)를 정의하고, 이 노드들을 관리하는 리스트 클래스(DoublyLinkedList)를 정의해야 합니다.

파이썬의 list는 배열 기반 자료구조이므로, 직접적으로 상속해서 이중 연결 리스트를 구현하는 것은 권장되지 않습니다. 대신 이중 연결 리스트를 클래스로 직접 구현할 수 있습니다. 여기에서는 list를 상속하지 않고, 이중 연결 리스트의 구조를 더 자연스럽게 구현하는 방식을 제안합니다.

1. Node 클래스 정의

각 노드는 세 가지 속성을 가집니다:

  • data: 저장되는 값
  • prev: 이전 노드를 가리키는 참조
  • next: 다음 노드를 가리키는 참조

2. DoublyLinkedList 클래스 정의

이중 연결 리스트는 여러 노드를 관리하며, 주로 다음과 같은 작업을 지원합니다:

  • 앞이나 뒤에 노드 삽입
  • 앞이나 뒤에서 노드 삭제
  • 리스트 순회

코드 구현

class Node:
    def __init__(self, data):
        self.data = data
        self.prev = None
        self.next = None

class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    def append(self, data):
        """리스트의 끝에 데이터를 추가"""
        new_node = Node(data)
        if self.head is None:  # 리스트가 비어 있는 경우
            self.head = new_node
            self.tail = new_node
        else:
            new_node.prev = self.tail
            self.tail.next = new_node
            self.tail = new_node

    def prepend(self, data):
        """리스트의 앞에 데이터를 추가"""
        new_node = Node(data)
        if self.head is None:  # 리스트가 비어 있는 경우
            self.head = new_node
            self.tail = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node

    def delete_from_front(self):
        """앞에서 노드 삭제"""
        if self.head is None:  # 빈 리스트
            return None
        removed_node = self.head
        if self.head == self.tail:  # 노드가 하나만 있는 경우
            self.head = None
            self.tail = None
        else:
            self.head = self.head.next
            self.head.prev = None
        return removed_node.data

    def delete_from_back(self):
        """뒤에서 노드 삭제"""
        if self.tail is None:  # 빈 리스트
            return None
        removed_node = self.tail
        if self.head == self.tail:  # 노드가 하나만 있는 경우
            self.head = None
            self.tail = None
        else:
            self.tail = self.tail.prev
            self.tail.next = None
        return removed_node.data

    def display(self):
        """리스트의 모든 요소를 출력"""
        current = self.head
        while current:
            print(current.data, end=" <-> " if current.next else "\n")
            current = current.next

    def display_reverse(self):
        """리스트를 역순으로 출력"""
        current = self.tail
        while current:
            print(current.data, end=" <-> " if current.prev else "\n")
            current = current.prev

# 테스트 코드
dll = DoublyLinkedList()
dll.append(1)
dll.append(2)
dll.append(3)
dll.prepend(0)

print("리스트 출력:")
dll.display()

print("역순 리스트 출력:")
dll.display_reverse()

print("앞에서 삭제:", dll.delete_from_front())
dll.display()

print("뒤에서 삭제:", dll.delete_from_back())
dll.display()

코드 설명

  • Node 클래스: 각 노드가 데이터(data), 이전 노드(prev), 다음 노드(next)를 갖습니다.
  • DoublyLinkedList 클래스: 이 클래스는 이중 연결 리스트를 관리합니다. 이 클래스의 head는 첫 번째 노드를, tail은 마지막 노드를 가리킵니다.
    • append: 리스트 끝에 노드를 추가합니다.
    • prepend: 리스트 앞에 노드를 추가합니다.
    • delete_from_front: 리스트의 앞에서 노드를 삭제합니다.
    • delete_from_back: 리스트의 뒤에서 노드를 삭제합니다.
    • display: 리스트의 모든 요소를 출력합니다.
    • display_reverse: 리스트를 역순으로 출력합니다.

실행 결과 예시

리스트 출력:
0 <-> 1 <-> 2 <-> 3
역순 리스트 출력:
3 <-> 2 <-> 1 <-> 0
앞에서 삭제: 0
1 <-> 2 <-> 3
뒤에서 삭제: 3
1 <-> 2

이 코드로 이중 연결 리스트를 생성하고, 삽입 및 삭제 작업을 수행할 수 있습니다.

Python의 object 클래스는 모든 파이썬 클래스의 기본 클래스이자 최상위 클래스입니다. 즉, 파이썬에서 작성된 모든 클래스는 명시적으로 상속하지 않더라도 암묵적으로 object 클래스를 상속받습니다. object 클래스는 파이썬의 객체 지향 시스템을 뒷받침하는 핵심 역할을 하며, 파이썬의 모든 객체는 object로부터 상속된 속성 및 메서드를 가지고 있습니다.

object 클래스의 역할

  1. 모든 클래스의 기반 클래스: 모든 파이썬 클래스는 기본적으로 object를 상속받습니다. 이는 클래스의 일관성을 유지하고, 파이썬의 객체 지향 프로그래밍을 지원하는 기초 구조를 제공합니다.
  2. 기본 메서드 제공: object 클래스는 몇 가지 기본 메서드를 제공합니다. 이 메서드들은 모든 객체가 상속받아 사용할 수 있는 메서드들입니다.
    • __init__(self): 생성자 메서드로, 객체가 생성될 때 호출됩니다.
    • __new__(cls): 인스턴스 생성 전에 호출되는 메서드로, 주로 객체의 메모리를 할당하는 역할을 합니다.
    • __str__(self): 객체의 문자열 표현을 반환하는 메서드로, print() 함수가 호출될 때 사용됩니다.
    • __repr__(self): 객체의 공식적인 문자열 표현을 반환하는 메서드입니다. 개발자가 객체를 디버깅할 때 주로 사용됩니다.
    • __eq__(self, other): 두 객체가 같은지 비교하는 메서드입니다.
    • __hash__(self): 객체의 해시값을 반환하는 메서드로, 해시 테이블과 관련된 자료구조에서 사용됩니다.
  3. 메모리 관리: object 클래스는 파이썬의 메모리 관리 시스템에서 중요한 역할을 합니다. 예를 들어, 객체가 더 이상 필요하지 않을 때 메모리에서 해제되는 과정을 관리하는 데 사용됩니다.
  4. 상속 구조의 루트: object는 파이썬의 모든 상속 구조의 최상위에 있으며, 모든 클래스는 직접적이든 간접적이든 object로부터 상속됩니다. 이는 다형성과 같은 객체 지향 프로그래밍의 원리를 지원합니다.

object 클래스 내부 구조

파이썬의 object 클래스는 C로 구현된 매우 경량의 클래스입니다. 내부적으로는 최소한의 속성과 메서드를 가지며, 이를 기반으로 다양한 파생 클래스를 만들 수 있도록 설계되었습니다. object 클래스는 CPython 구현체의 Objects/typeobject.c 파일에서 정의되어 있습니다.

주요 메서드의 내부 구조 (개략적 설명)

  1. __new__(cls): 인스턴스를 생성하기 위한 메서드로, 메모리 할당을 담당합니다. 새로운 객체를 생성할 때 이 메서드가 먼저 호출됩니다. 일반적으로 이 메서드는 super()를 사용하여 상위 클래스의 __new__를 호출함으로써 동작합니다.
  2. class MyClass(object): def __new__(cls, *args, **kwargs): instance = super(MyClass, cls).__new__(cls) return instance
  3. __init__(self): 인스턴스 초기화를 담당하는 메서드입니다. __new__가 객체의 메모리를 할당한 후에, __init__이 호출되어 객체의 초기 상태를 설정합니다.
  4. class MyClass(object): def __init__(self, value): self.value = value
  5. __repr__(self)__str__(self): 객체의 문자열 표현을 반환하는 두 메서드입니다. __repr__은 주로 개발자를 위한, __str__은 사용자에게 보여주기 위한 출력 형식을 정의합니다.
  6. class MyClass(object): def __repr__(self): return f"MyClass(value={self.value})" def __str__(self): return f"Value is {self.value}"

예시: object 클래스 상속

다음은 object 클래스를 명시적으로 상속받는 간단한 클래스입니다.

class MyClass(object):
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, {self.name}!"

obj = MyClass("Alice")
print(obj.greet())  # 출력: Hello, Alice!

MyClassobject 클래스를 명시적으로 상속받고 있으며, __init__ 메서드를 재정의하여 초기화를 처리하고 있습니다.

결론

Python의 object 클래스는 파이썬 객체 지향 프로그래밍의 기초를 이루는 클래스입니다. 모든 클래스는 이 object 클래스를 상속받으며, 객체 생성 및 메모리 관리와 관련된 핵심 메서드를 제공합니다. 이 클래스를 이해하면 파이썬의 클래스 시스템과 객체 지향 프로그래밍에 대해 깊이 있는 이해를 할 수 있습니다.

파이썬에서 리스트를 상속받아 "명명된 자료구조(Named Data Structure)"를 만들 수 있습니다. 리스트는 순차적으로 데이터를 저장하는 자료구조이기 때문에, 이를 상속받아 각 항목을 이름을 통해 접근할 수 있는 자료구조를 만들면, 직관적인 데이터 관리를 할 수 있습니다. 리스트는 일반적으로 인덱스를 통해 데이터를 접근하지만, 명명된 자료구조를 사용하면 항목을 이름으로 접근할 수 있는 유연성을 얻을 수 있습니다.

예제: 리스트를 상속한 NamedList 클래스

이 예제에서는 리스트를 상속받아 각 항목에 이름을 부여하고, 이 이름을 통해 데이터를 조회하거나 수정할 수 있는 NamedList 클래스를 구현해 봅니다.

예제 코드:

# 리스트 상속하여 명명된 자료구조 정의
class NamedList(list):
    def __init__(self, names, values):
        # 리스트 초기화
        super().__init__(values)
        if len(names) != len(values):
            raise ValueError("Names and values must have the same length.")
        self.names = names  # 각 항목의 이름 저장

    # 이름을 사용하여 항목을 조회하는 메서드
    def get_by_name(self, name):
        if name in self.names:
            index = self.names.index(name)
            return self[index]
        else:
            raise KeyError(f"Name '{name}' not found.")

    # 이름을 사용하여 항목을 수정하는 메서드
    def set_by_name(self, name, value):
        if name in self.names:
            index = self.names.index(name)
            self[index] = value
        else:
            raise KeyError(f"Name '{name}' not found.")

    # 모든 이름과 값을 출력하는 메서드
    def display(self):
        for name, value in zip(self.names, self):
            print(f"{name}: {value}")

# 사용 예제
# NamedList 객체 생성
person_info = NamedList(
    names=["name", "age", "occupation"],
    values=["John Doe", 30, "Engineer"]
)

# 이름을 사용하여 값 조회
print(f"Name: {person_info.get_by_name('name')}")
print(f"Age: {person_info.get_by_name('age')}")
print(f"Occupation: {person_info.get_by_name('occupation')}")

# 이름을 사용하여 값 수정
person_info.set_by_name("age", 31)
print(f"\nUpdated Age: {person_info.get_by_name('age')}")

# 모든 항목 출력
print("\nAll Info:")
person_info.display()

설명:

  1. NamedList 클래스는 list를 상속받아, 기본적인 리스트의 기능을 유지하면서 각 항목에 이름을 부여하고, 이 이름을 통해 데이터를 관리할 수 있게 만듭니다.
  2. __init__() 메서드에서 리스트를 초기화하며, names 리스트는 각 항목의 이름을 저장하고, values 리스트는 해당 값들을 저장합니다.
  3. get_by_name() 메서드는 주어진 이름에 해당하는 값을 반환합니다.
  4. set_by_name() 메서드는 주어진 이름에 해당하는 값을 수정합니다.
  5. display() 메서드는 각 이름과 값의 쌍을 출력합니다.
  6. 이름과 값의 리스트가 동일한 길이를 가져야 한다는 검사를 포함하고 있습니다.

실행 결과:

Name: John Doe
Age: 30
Occupation: Engineer

Updated Age: 31

All Info:
name: John Doe
age: 31
occupation: Engineer

주요 포인트:

  • NamedList 클래스는 리스트의 인덱스 기반 접근을 이름 기반 접근으로 변환하여, 더 직관적인 데이터 관리가 가능합니다.
  • 항목을 이름으로 조회하거나 수정할 수 있기 때문에, 코드를 작성할 때 가독성이 높아지고 유지보수가 쉬워집니다.
  • names 리스트와 values 리스트의 길이가 일치해야 하며, 그렇지 않으면 오류를 발생시켜 데이터를 일관되게 관리합니다.

이와 같은 명명된 자료구조는 복잡한 데이터를 다룰 때 유용하며, 특히 구조적으로 관리해야 하는 상황에서 직관적인 접근 방식을 제공합니다.

파이썬에서 딕셔너리를 상속받아 "명명된 자료구조(Named Data Structure)"를 만들면, 키-값 구조를 가지는 데이터를 보다 직관적으로 다룰 수 있습니다. 이는 특히 키 이름을 데이터의 속성처럼 사용하고 싶을 때 유용합니다. 파이썬의 namedtuple과 유사한 방식으로 딕셔너리를 활용할 수 있지만, 딕셔너리 상속을 통해 커스텀 메서드나 동적 속성 추가 등이 가능합니다.

예제: 딕셔너리를 상속한 NamedDataCard 클래스

NamedDataCard 클래스는 딕셔너리를 상속받아 각 키를 속성처럼 접근할 수 있도록 하며, 데이터가 명확히 정의된 구조를 갖도록 할 수 있습니다. 예를 들어, 사람의 정보를 저장하는 명명된 자료구조를 만들 수 있습니다.

예제 코드:

# 딕셔너리 상속하여 명명된 자료구조 정의
class NamedDataCard(dict):
    # 데이터 카드 생성자
    def __init__(self, **kwargs):
        # 딕셔너리 초기화
        super().__init__(**kwargs)

    # 속성 접근을 가능하게 하기 위해 __getattr__과 __setattr__ 메서드 구현
    def __getattr__(self, name):
        # 딕셔너리 키를 속성처럼 접근 가능
        if name in self:
            return self[name]
        raise AttributeError(f"'NamedDataCard' object has no attribute '{name}'")

    def __setattr__(self, name, value):
        # 속성 설정을 딕셔너리 항목으로 저장
        self[name] = value

    # 특정 속성 출력 메서드
    def display_attributes(self):
        for key, value in self.items():
            print(f"{key}: {value}")

# 사용 예제
# NamedDataCard 객체 생성 (사람의 정보를 저장한다고 가정)
person = NamedDataCard(name="John Doe", age=30, occupation="Engineer")

# 속성처럼 데이터를 접근
print(f"Name: {person.name}")
print(f"Age: {person.age}")
print(f"Occupation: {person.occupation}")

# 데이터 수정 (속성처럼)
person.age = 31
print(f"Updated Age: {person.age}")

# 새로운 속성 추가
person.location = "New York"
print(f"Location: {person.location}")

# 모든 속성 출력
print("\nAll Attributes:")
person.display_attributes()

설명:

  1. NamedDataCard 클래스는 dict를 상속받고, 딕셔너리의 기본 기능을 유지하면서 키를 속성처럼 사용할 수 있도록 __getattr____setattr__ 메서드를 재정의했습니다.
  2. __getattr__person.name과 같은 형태로 딕셔너리의 키에 접근할 수 있도록 합니다.
  3. __setattr__은 새로운 속성을 추가하거나 값을 변경할 때 딕셔너리의 항목으로 자동 저장되도록 합니다.
  4. display_attributes() 메서드는 모든 속성(딕셔너리 항목)을 출력합니다.

실행 결과:

Name: John Doe
Age: 30
Occupation: Engineer
Updated Age: 31
Location: New York

All Attributes:
name: John Doe
age: 31
occupation: Engineer
location: New York

주요 포인트:

  • 이 클래스는 딕셔너리처럼 데이터를 저장하면서, 객체의 속성처럼 각 키에 접근할 수 있도록 만듭니다.
  • 이를 통해 명명된 자료구조(Named Data Structure)를 유연하게 사용할 수 있으며, 직관적인 인터페이스를 제공합니다.
  • 새로운 속성도 동적으로 추가할 수 있기 때문에, 데이터 구조를 확장하거나 수정하는 것이 간편합니다.

이 방식은 주로 직관적인 데이터 접근이 필요하거나, 데이터의 이름을 명확히 명명하고 관리하는 경우에 유용합니다.

파이썬의 명명된 자료구조는 데이터를 더 명확하고 가독성 있게 관리하기 위한 구조입니다. 이러한 자료구조는 값에 접근할 때 이름을 통해 접근할 수 있기 때문에 코드의 이해와 유지보수를 용이하게 합니다.

파이썬에서는 다음과 같은 명명된 자료구조를 사용할 수 있습니다.

  1. namedtuple (from collections)
  2. dataclass (from dataclasses)

각각을 설명하고 예제 코드를 제공하겠습니다.


1. namedtuple (from collections)

namedtuple은 일반적인 튜플과 유사하지만, 각 항목에 이름을 붙일 수 있는 튜플입니다. 따라서, 인덱스 번호가 아닌 필드 이름으로 값에 접근할 수 있습니다. 불변(immutable)하다는 점에서는 튜플과 동일합니다.

사용법:

from collections import namedtuple

# namedtuple 정의
Person = namedtuple('Person', ['name', 'age', 'city'])

# 객체 생성
p = Person(name="John", age=30, city="New York")

# 값에 필드 이름으로 접근
print(p.name)  # 출력: John
print(p.age)   # 출력: 30
print(p.city)  # 출력: New York

# 튜플처럼 인덱스로도 접근 가능
print(p[0])  # 출력: John

설명:

  • namedtuple('Person', ['name', 'age', 'city']): Person이라는 명명된 튜플을 정의합니다. 세 개의 필드 이름 name, age, city를 가지며, 각 필드에 이름으로 접근할 수 있습니다.
  • 불변성: namedtuple은 기본적으로 불변(immutable)합니다. 값을 변경할 수 없습니다.

장점:

  • 필드 이름으로 데이터에 접근할 수 있어 가독성이 높습니다.
  • 메모리 사용이 효율적이고 성능이 뛰어납니다.

2. dataclass (from dataclasses)

파이썬의 dataclass는 3.7 버전부터 도입된 기능으로, 클래스를 더 간편하게 정의할 수 있도록 도와줍니다. 특히, 데이터 저장과 관련된 클래스에 자주 사용되는 __init__, __repr__, __eq__와 같은 메서드를 자동으로 생성해줍니다.

사용법:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    city: str

# 객체 생성
p = Person(name="Alice", age=25, city="Los Angeles")

# 값에 필드 이름으로 접근
print(p.name)  # 출력: Alice
print(p.age)   # 출력: 25
print(p.city)  # 출력: Los Angeles

# __repr__ 메서드 자동 생성됨
print(p)  # 출력: Person(name='Alice', age=25, city='Los Angeles')

# __eq__ 메서드 자동 생성됨
p2 = Person(name="Alice", age=25, city="Los Angeles")
print(p == p2)  # 출력: True

설명:

  • @dataclass: dataclass 데코레이터를 사용하면, 클래스 정의 시 각 필드의 타입과 이름만 적어주면 됩니다. 파이썬이 자동으로 생성자(__init__), 문자열 표현(__repr__), 비교(__eq__) 등을 처리해 줍니다.
  • 변경 가능성: dataclass는 기본적으로 변경 가능(mutable)합니다.

장점:

  • 클래스를 더 간결하게 작성할 수 있습니다.
  • 자동으로 생성되는 메서드를 통해 코드의 중복을 줄일 수 있습니다.
  • 기본 값, 필드 간 타입 검사 등을 쉽게 지원합니다.

dataclass의 추가 기능:

  • 기본값 설정: 필드에 기본값을 설정할 수 있습니다.
  • 필드 순서 변경: 기본값이 없는 필드는 기본값이 있는 필드 앞에 위치해야 합니다.
  • 불변성 설정: dataclass에서 불변성을 설정할 수도 있습니다. 이를 위해 frozen=True를 사용합니다.
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int

p = Point(10, 20)
print(p.x)  # 출력: 10

# 불변성으로 인해 값을 변경하려고 하면 오류 발생
# p.x = 15  # AttributeError: cannot assign to field 'x'

namedtupledataclass 비교

특성 namedtuple dataclass
불변성 기본적으로 불변 기본적으로 변경 가능 (불변성 설정 가능)
메서드 자동 생성 일부 (_replace(), _asdict()) 여러 메서드 (__init__, __repr__, __eq__ 등)
필드 기본값 설정 가능 가능
가독성 필드 이름으로 접근 가능 필드 이름으로 접근 가능
성능 메모리 사용이 더 적음 기능이 많아 다소 무거움

결론

파이썬의 명명된 자료구조는 가독성을 높이고, 명시적으로 데이터를 관리할 수 있는 좋은 도구들입니다. namedtuple은 메모리 효율적이고 불변성을 가진 자료 구조가 필요할 때 유용하고, dataclass는 보다 유연하고 클래스를 빠르게 정의하고 싶을 때 매우 유용합니다.

상황에 맞는 도구를 선택하여 데이터를 관리하면 코드의 유지 보수성과 효율성을 모두 높일 수 있습니다.

+ Recent posts