[Python] 네임드튜플 tutorial

Hyeseong·2020년 12월 1일
1

python

목록 보기
2/22
post-thumbnail

파이썬의 네임드튜플은 인덱스와 네임드 속성을가지고 값에 접근하는 immutable 컨테이너 유형이에요. 특징이 이름 그대로 튜플의 특징과 추가적 특징을 짬봉한거라고 보면되요.
그리고 이녀석은collections.namedtuple factory 함수에서 나와 만들어진거에요.

네임드 튜플은 3가지를 기억하세요.

  • easy-to-create
  • immutable
  • lightweight object type

결국 위 세가지 특징이 매우 Pythonic하고 clean한 코드를 만드는 원리라고 보면되요.

기본 예시


from collections import namedtuple

City = namedtuple('City', 'name population')

c1 = City('Bratislava', 432000)
c2 = City('Budapest', 1759000)

print(c1)
print(c2)

collections 모듈에서 nametuple을 임포트를 먼저하고 이후 nametuple를 정의할거에요.
1번째 인자 argument가 바로 namedtuple의 이름이에요.2번째 인자의 경우는 field names이에요. 'name population' 또는 ['name', 'population']으로 리스트로 감싼 상태로 표현되기도 해요.
결국 네임드 튜플을 City라는 이름으로 바꾸어서 그 기능을 하게 하는 거라고 보면되요.

이후 City('Bratislava', 432000)로 각각 인자 값을 넣어서 도시 이름과 인구수를 인자값으로 주고 c1과 c2를 만들었어요.

네임튜플 접근 방법

네임드튜플은 인덱싱과 속성들로 접근 할 수 있어요.


from collections import namedtuple

City = namedtuple('City' , 'name population')

## 접근 방법 1) 인덱스 2) named attributes
c1 = City('Bratislava', 432000)
c2 = City('Budapest', 1759000)

print(c1[0]) # 인덱스로 접근
print(c1[1]) 

print(c2.name) # named attributes로 접근
print(c2.population)
Bratislava
432000
Budapest
1759000

네임드 튜플 언팩킹

from collections import namedtuple


City = namedtuple('City' , 'name population')

c1 = City('Bratislava', 432000)
c2 = City('Budapest', 1759000)

name, population = c1
print(f'{name}: {population}')

print('----------------------')

print(c2)
print(*c2, sep=': ')

두 개의 변수로 풀어 버린 모습입니다. 아래는.

name, population = c1

그리고 * operator를 통해서 다른 방법으로 unpack할수 있어요.

print(*c2, sep=': ')

물론 ** operator로 네임드 튜플의 인자인 dictionay를 값을 unpack 할 수 있어요.

Bratislava: 432000
----------------------
City(name='Budapest', population=1759000)
Budapest: 1759000

from collections import namedtuple 

City = namedtuple('City' , 'name population')

d = { 'name': 'Bratislava', 'population': 432000}

c = City(**d)
print(c)

위 소스코드는 딕셔너리 타입의 인자 값을 unpack한 모습입니다.

namedtuple 서브클래싱

일반적 클래스 위에 얹어 기능을 더하여 정의 할 수 있어요

from collections import namedtuple
from math import sqrt

class Point(namedtuple('Point', 'x y')):

    __slots__ = ()

    @property
    def hypot(self):
        return sqrt((self.x ** 2 + self.y ** 2))

    def __str__(self):
        return f'Point: x={self.x}  y={self.y}  hypot={self.hypot}'


p = Point(5, 5)
print(p.hypot)
print(p)
$ ./subclassing.py 
7.0710678118654755
Point: x=5  y=5  hypot=7.0710678118654755

Python typing.NamedTuple

파있ㄴ 3.6 이후로는 typing.NamedTuple을 이용하여 기존의 collections.namedtuple을 사용할 수 있게 되었어요.

#!/usr/bin/env python3

from typing import NamedTuple


class City(NamedTuple):
    name: str
    population: int


c1 = City('Bratislava', 432000)
c2 = City('Budapest', 1759000)

print(c1)
print(c2)

City클래스에 NamedTuple을 상속 받고 name: str으로 문자열을 지정, population: int 정수형을 지정하게 되네요.

Python namedtuple defaults(기본값 지정)


from collections import namedtuple
from math import sqrt

class Point(namedtuple('Point', 'x y', defaults=[1, 1])):

    __slots__ = ()

    @property
    def hypot(self):
        return sqrt((self.x ** 2 + self.y ** 2))

    def __str__(self):
        return f'Point: x={self.x}  y={self.y}  hypot={self.hypot}'


p1 = Point(5, 5)
print(p1)

p2 = Point()
print(p2)

defaults 인자값을 지정하여 기본값을 지정 할 수 있어요.

Point: x=5  y=5  hypot=7.0710678118654755
Point: x=1  y=1  hypot=1.4142135623730951

Python namedtuple helpers

파이썬에서는 몇가지 helper 메소드를 namedtuple를 위해 제공해요.

from typing import NamedTuple


class Point(NamedTuple):

    x: int = 1
    y: int = 1


p = Point(5, 5)

print(p._fields)
print(p._field_defaults)
print(p._asdict())
print(p._replace(y=100)

그 중 _fields는 필드 네임을 리스팅하는 문자열 튜플이에요.
_field_defaults는 필드 네임을 기본 값들과 mappig하는 딕셔너리에요.
_asdict 메소드는 필드 네임과 그에 대응하는 값들을 연결하여 딕셔너리로 정렬하려 값을 돌려주는 역할을 해요.

$ ./helpers.py 
('x', 'y')
{'x': 1, 'y': 1}
OrderedDict([('x', 5), ('y', 5)])
Point(x=1, y=100)

namedtuple - serialize to JSON

_asdict() 메소드는 namedtupleds를 -> JSON 형식으로 직렬화 하는데 사용되요.

from typing import NamedTUple
import json

class City(NamedTuple):
	name: str
    population: int
    
c1 = City('Bratislava', 432000)
c2 = City('Budapest', 1759000)
c3 = City('Prague', 1280000)
c4 = City('Warsaw', 1748000)

cities = [c1, c2, c3, c4]

print(json.dumps(c1._asdict()))

json_string = json.dumps([city._asdict() for city in cities])
print(json_string)
$ ./json_output.py 
{"name": "Bratislava", "population": 432000}
[{"name": "Bratislava", "population": 432000}, {"name": "Budapest", "population": 1759000}, 
{"name": "Prague", "population": 1280000}, {"name": "Warsaw", "population": 1748000}]

single city 그리고 cities 리스트를 직렬화해서 json을 임포트하고 json.dump 메소드로 API 구현까지 단숨에 할 수 있을것 같은 느낌 같은 느낌이 드네요.(어렴품이 눈감고 아웅하는 느낌;;)

Python namedtuple sort

sort 메소드를 통해서 말그래도 namedtuple을 정렬 할 수 있어요.


from typing import NamedTuple


class City(NamedTuple):
    id: int
    name: str
    population: int


c1 = City(1, 'Bratislava', 432000)
c2 = City(2, 'Budapest', 1759000)
c3 = City(3, 'Prague', 1280000)
c4 = City(4, 'Warsaw', 1748000)
c5 = City(5, 'Los Angeles', 3971000)
c6 = City(6, 'Edinburgh', 464000)
c7 = City(7, 'Berlin', 3671000)

cities = [c1, c2, c3, c4, c5, c6, c7]

cities.sort(key=lambda e: e.name)

for city in cities:
    print(city)
$ ./sorting.py 
City(id=7, name='Berlin', population=3671000)
City(id=1, name='Bratislava', population=432000)
City(id=2, name='Budapest', population=1759000)
City(id=6, name='Edinburgh', population=464000)
City(id=5, name='Los Angeles', population=3971000)
City(id=3, name='Prague', population=1280000)
City(id=4, name='Warsaw', population=1748000)

결과적으로 확인하면 오름차순 순서로 name이 정렬된걸 확인 할 수 있어요.
눈여겨 봐야할 점은 Cities의 객체드릉ㄹ cities라는 리스트 변수에 담아 attribute인 sort()를 불러서 그 안에 인자로 lambda를 이용하여 내부 변수를 생성하여 name attribute를 이용하여 for문을 돌려 하나씩 값을 출력할때 오름차순 name으로 출력 할 수 있게했어요.
지금...음~ 머리론 다~ 이해했는데! 흠...

namedtuple _make helper


from collections import namedtuple

City = namedtuple('City' , 'name population')

c1 = City._make(('Bratislava', 432000))
c2 = City._make(('Budapest', 1759000))

print('일반 클래스로 상속한 이후 호출시 클래스에 _make 메소드를 이용하여 인자 값 안에 소괄호로 감쌈')
print(type(c1))
print(c1)
print(c2)
일반 클래스로 상속한 이후 호출시 클래스에 _make 메소드를 이용하여 안
에 소괄호로 감쌈
<class '__main__.City'>
City(name='Bratislava', population=432000)
City(name='Budapest', population=1759000)

_make()를 사용한 결과 값이 == 사용하지 않고 namedtuple를 쓴것과 동일하다는것을 확인했네요. 아래에 바로 소개하겠지만 csv data를 읽을때도 사용하게 되요.

namedtuple - read CSV data

아래 콤마로 구분된(comma seperated values) 파일들을 읽을 수 있어요.

Bratislava, 432000
Budapest, 1759000
Prague, 1280000
Warsaw, 1748000
Los Angeles, 3971000
New York, 8550000
Edinburgh, 464000
Berlin, 3671000

어떻게 읽어 드리는지 볼게요.

from collections import namedtuple
import csv


City = namedtuple('City' , 'name population')

f = open('cities.csv', 'r')

with f:

    reader = csv.reader(f)
    
    for city in map(City._make, reader):
        print(city)
$ ./read_csv.py 
City(name='Bratislava', population=' 432000')
City(name='Budapest', population=' 1759000')
City(name='Prague', population=' 1280000')
City(name='Warsaw', population=' 1748000')
City(name='Los Angeles', population=' 3971000')
City(name='New York', population=' 8550000')
City(name='Edinburgh', population=' 464000')
City(name='Berlin', population=' 3671000')

map() 메소드를 이용해서 첫 번째 인자로 City._make 메소드! 두 번째 인자로 csv 파일 값을 갖고 있는 reader 객체를 넣어서 for문을 돌려 출력하게되네요.
주로 map과 _make 메서드는 clean code(pythonic~~!)를 지향할 떄 사용하게되요.

namedtuple - read SQLite database

파일에다가 저장해서 데이터를 불러 올건 아니조? db에 저장된 데이터를 불러올 때도 namedtuple이 사용되요.

아래는 city table을 생성하게 하는 SQL문이에요.

DROP TABLE IF EXISTS cities;
CREATE TABLE cities(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, 
  population INTEGER);

INSERT INTO cities(name, population) VALUES('Bratislava', 432000);
INSERT INTO cities(name, population) VALUES('Budapest', 1759000);
INSERT INTO cities(name, population) VALUES('Prague', 1280000);
INSERT INTO cities(name, population) VALUES('Warsaw', 1748000);
INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000);
INSERT INTO cities(name, population) VALUES('New York', 8550000);
INSERT INTO cities(name, population) VALUES('Edinburgh', 464000);
INSERT INTO cities(name, population) VALUES('Berlin', 3671000);
$ sqlite3 ydb.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> .read cities.sql
from typing import NamedTuple
import sqlite3 as sqlite

class City(NamedTuple):

    id: int
    name: str
    population: int


con = sqlite.connect('ydb.db')

with con:

    cur = con.cursor()

    cur.execute('SELECT * FROM cities')
    
    for city in map(City._make, cur.fetchall()):
        print(city)

차근 차근 본다면 City클래스가 생성되고 각각 데이터 타입을 할당한 변수가 보이네요.
아! sqlite3를 임포트하는거 깜빡하면 안되요.

그리고 임포트한 sqlite(sqlite3)를 이용해서 connect() 메서드에 호출하고자 하는 db 이름을 적을게요. 그리고 con 인스턴스를 만들어요.

with 키워드를 이용하면 open(), close()를 별도로 해줄 필요 없다는거~
이후 execute()를 이용하여 쿼리문을 작성해요.

특히 for문을 돌릴때 map() 메서드로 clean 하게 City._make와 cur.fetchall()를 이용하여 한줄 씩 멋지게 출력하게 만드네요.

Namedtuple은 여기까지~

profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글