본문 바로가기
Python

[Py] collections.namedtuple-Factory Function

by ds31x 2025. 4. 4.

1. 팩토리 함수(Factory Function)란?

팩토리 함수는 객체 생성 과정을 추상화하여 객체를 생성하는 함수 를 가리킴.

즉, 객체를 직접 생성하는 대신 함수를 호출하여 객체를 생성하는 방식으로 다음과 같은 장점이 있음:

  • 객체 생성 로직의 캡슐화.
  • 생성 과정의 세부 사항 은닉.
  • 동일한 인터페이스로 다양한 유형의 객체 생성.
  • 코드의 재사용성 향상.

Python에서 namedtuple은 대표적인 팩토리 함수의 예시.


2. namedtuple 팩토리 함수

namedtuple

  • Python의 collections 모듈에서 제공하는 팩토리 함수로,
  • 이름이 있는 필드를 가진 tuple의 서브클래스를 동적으로 생성.

2023.10.06 - [Python] - [Python] tuple

 

[Python] tuple

tuple Tuple은 immutable list라고 자주 불릴 정도로 list와 유사하다. immutable이기 때문에 한번 assign된 이후 item의 update는 불가함.update가 안되기 때문에 list 보다 적은 수의 methods를 제공.때문에 보다 적

ds31x.tistory.com


2.1. 기본 문법

collections.namedtuple(
    typename,     # 생성될 클래스의 이름 (문자열)
    field_names,  # 필드 이름들 (문자열 리스트 또는 공백/콤마로 구분된 문자열)
    *, 
    rename=False, # 유효하지 않은 필드 이름을 자동으로 변경할지 여부 (기본값: False)
    defaults=None,# 필드의 기본값 (Python 3.7+) 
    module=None,  # 클래스의 `__module__` 속성 값 (기본값: 호출자의 모듈 이름)
)

 


2.2. 반환값

  • namedtuple은 지정된 이름과 필드를 가진 새로운 tuple의 서브클래스를 반환.
  • 반환된 객체를 나중에 인스턴스 생성을 위한 생성자로 사용가능.

3. namedtuple 사용 튜토리얼

3.1. 기본 사용법

from collections import namedtuple

# 1. namedtuple 클래스 생성
Person = namedtuple('Person', ['name', 'age', 'job'])

# 2. 생성된 클래스의 인스턴스 만들기
john = Person('John Doe', 30, 'Developer')

# 3. 인덱스와 이름으로 접근하기
print(john[0])     # 'John Doe' (일반 tuple처럼 인덱스로 접근)
print(john.name)   # 'John Doe' (필드 이름으로 접근)
print(john.age)    # 30 (나이 값)
print(john.job)    # 'Developer' (직업 정보)

# 4. 반복 가능한 객체로 사용하기
for value in john:
    print(value)

# 5. 언패킹하기
name, age, job = john
print(f"{name}{age}살이고 {job}의 직업.")

3.2. 다양한 필드 이름 지정 방법

# 리스트로 필드 지정
Point1 = namedtuple('Point', ['x', 'y', 'z'])

# 공백으로 구분된 문자열로 필드 지정
Point2 = namedtuple('Point', 'x y z')

# 콤마로 구분된 문자열로 필드 지정
Point3 = namedtuple('Point', 'x, y, z')

# 모두 동일한 결과 생성
p1 = Point1(1, 2, 3)
p2 = Point2(1, 2, 3)
p3 = Point3(1, 2, 3)

print(p1)  # Point(x=1, y=2, z=3)
print(p2)  # Point(x=1, y=2, z=3)
print(p3)  # Point(x=1, y=2, z=3)

3.3. rename 매개변수 사용하기

Python 식별자 규칙에 맞지 않는 필드 이름이나 예약어 처리 시 rename=True 사용.

# Python 예약어와 유효하지 않은 식별자가 있는 경우
# class, def, 1stField 등은 유효한 식별자가 아닙니다
Fields = namedtuple('Fields', ['class', 'def', '1stField'], rename=True)

# 자동 이름 변경:
# class -> _0, def -> _1, 1stField -> _2
f = Fields('Math', 'function', 'First')
print(f)     # Fields(_0='Math', _1='function', _2='First')
print(f._0)  # 'Math'
print(f._1)  # 'function'

3.4. defaults 매개변수 사용하기 (Python 3.7+)

# 기본값 설정
Employee = namedtuple('Employee', ['name', 'id', 'dept', 'salary'], defaults=[0, 'N/A'])
# 마지막 두 필드 기본값 적용: dept=0, salary='N/A'

# 기본값 있는 필드 생략 가능
emp1 = Employee('John', 1001)  # dept와 salary는 기본값 사용
print(emp1)  # Employee(name='John', id=1001, dept=0, salary='N/A')

# 모든 값 명시적 제공 가능
emp2 = Employee('Jane', 1002, 'HR', 50000)
print(emp2)  # Employee(name='Jane', id=1002, dept='HR', salary=50000)

# 클래스 기본값 정보 확인
print(Employee._field_defaults)  # {'dept': 0, 'salary': 'N/A'}

3.5. 유용한 메서드들

Point = namedtuple('Point', 'x y z')
p = Point(1, 2, 3)

# 1. _make(): 반복 가능 객체로부터 새 인스턴스 생성
data = [4, 5, 6]
p2 = Point._make(data)
print(p2)  # Point(x=4, y=5, z=6)

# 2. _asdict(): OrderedDict로 변환 (Python 3.1+)
p_dict = p._asdict()
print(p_dict)  # OrderedDict([('x', 1), ('y', 2), ('z', 3)])

# 3. _replace(): 필드 값 변경한 새 인스턴스 생성
p3 = p._replace(y=20, z=30)
print(p)   # Point(x=1, y=2, z=3) - 원본은 변경되지 않음
print(p3)  # Point(x=1, y=20, z=30)

# 4. _fields: 필드 이름 튜플
print(p._fields)  # ('x', 'y', 'z')

3.6. 클래스 메서드 상속과 확장

namedtuple로 생성된 클래스는 일반 클래스처럼 상속이나 메서드 추가 가능.

class PointWithMethods(namedtuple('Point', 'x y z')):
    # 새 메서드 추가 방법
    def distance_from_origin(self):
        return (self.x ** 2 + self.y ** 2 + self.z ** 2) ** 0.5

    # 특수 메서드 재정의 예시
    def __str__(self):
        return f"Point({self.x}, {self.y}, {self.z}) - 원점과의 거리: {self.distance_from_origin():.2f}"

p = PointWithMethods(3, 4, 5)
print(p.distance_from_origin())  # 7.0710678118654755
print(p)  # Point(3, 4, 5) - 원점과의 거리: 7.07

4. 실제 사용 사례

4.1. CSV 데이터 처리

import csv
from collections import namedtuple

# CSV 데이터의 namedtuple 변환
def read_csv_as_namedtuples(file_path):
    with open(file_path, 'r') as f:
        reader = csv.reader(f)
        header = next(reader)  # 첫 번째 행의 헤더 처리
        Row = namedtuple('Row', header)
        return [Row(*row) for row in reader]

# 사용 예시
# 예: users.csv 파일에 name,age,email 열이 있는 경우
# users = read_csv_as_namedtuples('users.csv')
# for user in users:
#     print(f"{user.name}의 이메일은 {user.email}입니다.")

4.2. 데이터베이스 결과 처리

import sqlite3
from collections import namedtuple

def query_as_namedtuples(db_path, query):
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    cursor.execute(query)

    # 결과 컬럼 이름 추출
    column_names = [desc[0] for desc in cursor.description]
    Row = namedtuple('Row', column_names)

    # 결과의 namedtuple 변환
    results = [Row(*row) for row in cursor.fetchall()]

    conn.close()
    return results

# 사용 예시
# results = query_as_namedtuples('example.db', 'SELECT id, name, age FROM users')
# for user in results:
#     print(f"ID: {user.id}, 이름: {user.name}, 나이: {user.age}")

4.3. coordinate와 shape 처리

from collections import namedtuple
import math

Point2D = namedtuple('Point2D', 'x y')

class Circle(namedtuple('CircleBase', 'center radius')):
    @property
    def area(self):
        return math.pi * self.radius ** 2

    @property
    def circumference(self):
        return 2 * math.pi * self.radius

    def contains_point(self, point):
        return math.sqrt((point.x - self.center.x)**2 + 
                        (point.y - self.center.y)**2) <= self.radius

# 사용 예시
center = Point2D(0, 0)
circle = Circle(center=center, radius=5)
print(f"원의 면적: {circle.area:.2f}")
print(f"원의 둘레: {circle.circumference:.2f}")

test_point = Point2D(3, 4)
if circle.contains_point(test_point):
    print(f"점 {test_point}는 원 내부 위치.")
else:
    print(f"점 {test_point}는 원 외부 위치.")

5. 고급 기법: 동적 namedtuple 생성

런타임에 필드를 동적으로 결정해야 하는 경우 사용 가능한 기법.

def create_record_type(schema):
    """
    스키마 딕셔너리에서 동적으로 namedtuple 타입을 생성

    schema: {필드명: 설명} 형태의 딕셔너리
    """
    field_names = list(schema.keys())
    RecordType = namedtuple('Record', field_names)

    # docstring 추가
    field_docs = '\n'.join(f'    {name}: {desc}' for name, desc in schema.items())
    RecordType.__doc__ = f'Record with fields:\n{field_docs}'

    return RecordType

# 사용 예시
user_schema = {
    'id': '사용자 고유 식별자',
    'username': '사용자 로그인 이름',
    'email': '이메일 주소',
    'active': '계정 활성화 상태'
}

UserRecord = create_record_type(user_schema)
print(UserRecord.__doc__)

user = UserRecord(1, 'john_doe', 'john@example.com', True)
print(user)

6. namedtuple vs 다른 데이터 구조 비교

6.1. namedtuple vs 일반 tuple

# 일반 tuple
person_tuple = ('John Doe', 30, 'Developer')
print(person_tuple[0])  # 'John Doe' - 인덱스만으로 접근

# namedtuple
Person = namedtuple('Person', 'name age job')
person_named = Person('John Doe', 30, 'Developer')
print(person_named.name)  # 'John Doe' - 이름으로 접근

# 가독성 비교
age = person_tuple[1]  # 인덱스만으로 데이터 속성 불명확
age = person_named.age  # 명확한 age 데이터 속성 인식

6.2. namedtuple vs dict

# dict
person_dict = {'name': 'John Doe', 'age': 30, 'job': 'Developer'}
print(person_dict['name'])  # 'John Doe'

# namedtuple
Person = namedtuple('Person', 'name age job')
person_named = Person('John Doe', 30, 'Developer')
print(person_named.name)  # 'John Doe'

# 차이점
person_dict['age'] = 31  # dict의 변경 가능성
# person_named.age = 31  # namedtuple의 불변성으로 인한 오류 발생

# 메모리 사용량 비교
import sys
print(f"딕셔너리 크기: {sys.getsizeof(person_dict)} 바이트")
print(f"namedtuple 크기: {sys.getsizeof(person_named)} 바이트")
# namedtuple의 일반적 메모리 사용량 우위

6.3. namedtuple vs class

# 일반 클래스
class PersonClass:
    def __init__(self, name, age, job):
        self.name = name
        self.age = age
        self.job = job

# namedtuple
Person = namedtuple('Person', 'name age job')

# 인스턴스 생성
person_class = PersonClass('John Doe', 30, 'Developer')
person_named = Person('John Doe', 30, 'Developer')

# 접근 방식은 유사
print(person_class.name)  # 'John Doe'
print(person_named.name)  # 'John Doe'

# 주요 차이점 비교
person_class.age = 31  # 클래스 인스턴스의 변경 가능성
# person_named.age = 31  # 오류! namedtuple은 불변

# 비교 동작이 다름
p1 = PersonClass('John Doe', 30, 'Developer')
p2 = PersonClass('John Doe', 30, 'Developer')
print(p1 == p2)  # False (기본적 객체 ID 비교 결과)

n1 = Person('John Doe', 30, 'Developer')
n2 = Person('John Doe', 30, 'Developer')
print(n1 == n2)  # True (값 비교 결과)

7. 정리: namedtuple 팩토리 함수의 장점과 한계

7.1. 장점:

  • 필드 이름 접근을 통한 가독성 향상
  • 일반 tuple의 모든 기능 유지 (인덱싱, 언패킹 등의 기능)
  • 메모리 효율성 (딕셔너리보다 적은 메모리 사용량)
  • 불변성으로 인한 데이터 안전성 확보
  • 값 기반 동등성 비교 (같은 값이면 같은 객체로 간주하는 특성)
  • 간결한 클래스 정의 방식

7.2. 한계:

  • 불변성 (필요에 따른 단점 가능성)
  • 메서드 추가 시 상속 필요성
  • 속성 검증 로직 구현의 어려움
  • 디폴트 값 설정 옵션의 Python 3.7 이상 제한

8. 마무리

  • namedtuple 팩토리 함수는 간결하면서도 명확한 데이터 구조를 만들 수 있는 강력한 도구.
  • 특히 불변성이 필요하고 필드 이름을 통한 접근이 중요한 경우에 적합.
  • 간단한 데이터 모델링, 반환 값 패키징, 다중 값 반환 등 다양한 상황에서의 유용한 활용 방안.

 

그러나 복잡한 비즈니스 로직, 상태 변경 필요 시,
또는 고급 데이터 검증 필요 시에는 일반 클래스나 데이터 클래스(dataclasses 모듈)의 고려 필요성.


같이보면 좋은 자료들

2025.04.04 - [Python] - [Py] collections 모듈 (summary) - 작성중

 

[Py] collections 모듈 (summary) - 작성중

Python의 collections 모듈은 파이썬의 built-in 자료구조를 확장한 special container 클래스들을 제공함.1. Counter요소의 개수를 세는 dictionary의 subclass.해시 가능한 객체의 카운트를 저장함.from collections impor

ds31x.tistory.com


 

'Python' 카테고리의 다른 글

[OpenCV] macOS에서 Qt 지원하도록 빌드.  (0) 2025.04.05
[Py] collections.ChainMap  (0) 2025.04.04
[Py] collection.OrderedDict  (0) 2025.04.04
[Py] collections 모듈 (summary) - 작성중  (0) 2025.04.04
[Py] print 함수  (0) 2025.04.02