사용자 정의 컨테이너 만들기

Beginner

This tutorial is from open-source community. Access the source code

소개

이 랩에서는 Python 컨테이너와 메모리 관리에 대해 배우게 됩니다. Python 이 내장 데이터 구조에 대한 메모리를 어떻게 처리하는지 살펴보고, 메모리 효율적인 사용자 정의 컨테이너 클래스를 만드는 방법을 알아보겠습니다.

이 랩의 목표는 Python 리스트와 딕셔너리의 메모리 할당 동작을 조사하고, 메모리 사용을 최적화하기 위한 사용자 정의 컨테이너 클래스를 만들고, 열 지향 데이터 저장소의 이점을 이해하는 것입니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 93%입니다.학습자들로부터 97%의 긍정적인 리뷰율을 받았습니다.

리스트 메모리 할당 이해

Python 에서 리스트는 특히 요소를 추가해야 할 때 매우 유용한 데이터 구조입니다. Python 리스트는 추가 연산에 효율적으로 설계되었습니다. Python 은 필요한 메모리 양을 정확히 할당하는 대신, 향후 추가를 예상하여 추가 메모리를 할당합니다. 이 전략은 리스트가 증가할 때 필요한 메모리 재할당 횟수를 최소화합니다.

sys.getsizeof() 함수를 사용하여 이 개념을 더 잘 이해해 봅시다. 이 함수는 객체의 크기를 바이트 단위로 반환하므로, 리스트가 다양한 단계에서 얼마나 많은 메모리를 사용하는지 확인할 수 있습니다.

  1. 먼저, 터미널에서 Python 대화형 셸을 열어야 합니다. 이것은 Python 코드를 즉시 실행할 수 있는 놀이터와 같습니다. 열려면 터미널에 다음 명령을 입력하고 Enter 키를 누르십시오.
python3
  1. Python 대화형 셸에 들어가면 sys 모듈을 가져와야 합니다. Python 의 모듈은 유용한 함수를 포함하는 도구 상자와 같습니다. sys 모듈에는 필요한 getsizeof() 함수가 있습니다. 모듈을 가져온 후 items라는 빈 리스트를 만듭니다. 다음은 이를 수행하는 코드입니다.
import sys
items = []
  1. 이제 빈 리스트의 초기 크기를 확인해 보겠습니다. sys.getsizeof() 함수를 items 리스트를 인수로 사용하여 사용합니다. Python 대화형 셸에 다음 코드를 입력하고 Enter 키를 누르십시오.
sys.getsizeof(items)

64 바이트와 같은 값을 볼 수 있습니다. 이 값은 빈 리스트의 오버헤드를 나타냅니다. 오버헤드는 요소가 없는 경우에도 Python 이 리스트를 관리하는 데 사용하는 기본 메모리 양입니다.

  1. 다음으로, 리스트에 항목을 하나씩 추가하고 메모리 할당이 어떻게 변경되는지 관찰합니다. append() 메서드를 사용하여 리스트에 요소를 추가한 다음 크기를 다시 확인합니다. 다음은 코드입니다.
items.append(1)
sys.getsizeof(items)

96 바이트 정도의 더 큰 값을 볼 수 있습니다. 이 크기 증가는 Python 이 새 요소를 수용하기 위해 더 많은 메모리를 할당했음을 보여줍니다.

  1. 리스트에 더 많은 항목을 계속 추가하고 각 추가 후 크기를 확인해 보겠습니다. 다음은 이를 수행하는 코드입니다.
items.append(2)
sys.getsizeof(items)  ## Size remains the same

items.append(3)
sys.getsizeof(items)  ## Size still unchanged

items.append(4)
sys.getsizeof(items)  ## Size still unchanged

items.append(5)
sys.getsizeof(items)  ## Size jumps to a larger value

모든 추가 연산마다 크기가 증가하지 않는다는 것을 알 수 있습니다. 대신, 주기적으로 증가합니다. 이는 Python 이 각 새 항목에 대해 개별적으로가 아닌 청크 단위로 메모리를 할당하고 있음을 보여줍니다.

이 동작은 의도된 것입니다. Python 은 리스트가 증가함에 따라 빈번한 재할당을 피하기 위해 처음에 필요한 것보다 더 많은 메모리를 할당합니다. 리스트가 현재 용량을 초과할 때마다 Python 은 더 큰 메모리 청크를 할당합니다.

리스트는 객체 자체를 저장하는 것이 아니라 객체에 대한 참조를 저장한다는 점을 기억하십시오. 64 비트 시스템에서 각 참조는 일반적으로 8 바이트의 메모리가 필요합니다. 이는 리스트가 서로 다른 유형의 객체를 포함할 때 실제로 사용하는 메모리 양에 영향을 미치므로 이해하는 것이 중요합니다.

딕셔너리 메모리 할당

Python 에서 리스트와 마찬가지로 딕셔너리는 기본적인 데이터 구조입니다. 딕셔너리에 대해 이해해야 할 중요한 측면 중 하나는 메모리를 할당하는 방식입니다. 메모리 할당은 Python 이 딕셔너리의 데이터를 저장하기 위해 컴퓨터의 메모리에 공간을 확보하는 방식을 의미합니다. 리스트와 유사하게 Python 딕셔너리도 청크 단위로 메모리를 할당합니다. 딕셔너리로 메모리 할당이 어떻게 작동하는지 살펴보겠습니다.

  1. 먼저, 작업할 딕셔너리를 만들어야 합니다. 동일한 Python 셸에서 (또는 닫았다면 새 셸을 엽니다) 데이터 레코드를 나타내는 딕셔너리를 만들 것입니다. Python 의 딕셔너리는 키 - 값 쌍의 모음이며, 각 키는 고유하며 해당 값을 액세스하는 데 사용됩니다.
import sys  ## Import sys if you're starting a new session
row = {'route': '22', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}

여기서는 Python 인터프리터에서 사용하거나 유지 관리하는 일부 변수와 인터프리터와 강력하게 상호 작용하는 함수에 대한 액세스를 제공하는 sys 모듈을 가져왔습니다. 그런 다음 네 개의 키 - 값 쌍이 있는 row라는 딕셔너리를 만들었습니다.

  1. 이제 딕셔너리가 있으므로 초기 크기를 확인하려고 합니다. 딕셔너리의 크기는 컴퓨터에서 차지하는 메모리 양을 나타냅니다.
sys.getsizeof(row)

sys.getsizeof() 함수는 객체의 크기를 바이트 단위로 반환합니다. 이 코드를 실행하면 약 240 바이트의 값을 볼 수 있습니다. 이를 통해 딕셔너리가 처음에 얼마나 많은 메모리를 차지하는지 알 수 있습니다.

  1. 다음으로, 딕셔너리에 새로운 키 - 값 쌍을 추가하고 메모리 할당이 어떻게 변경되는지 관찰합니다. 딕셔너리에 항목을 추가하는 것은 일반적인 작업이며, 메모리에 미치는 영향을 이해하는 것이 중요합니다.
row['a'] = 1
sys.getsizeof(row)  ## Size might remain the same

row['b'] = 2
sys.getsizeof(row)  ## Size may increase

첫 번째 키 - 값 쌍 ('a': 1) 을 추가하면 딕셔너리의 크기가 동일하게 유지될 수 있습니다. 이는 Python 이 이미 특정 메모리 청크를 할당했고, 새 항목을 수용할 공간이 해당 청크에 충분할 수 있기 때문입니다. 그러나 두 번째 키 - 값 쌍 ('b': 2) 을 추가하면 크기가 증가할 수 있습니다. 일정 수의 항목을 추가한 후 딕셔너리의 크기가 갑자기 증가하는 것을 알 수 있습니다. 이는 딕셔너리가 리스트와 마찬가지로 성능을 최적화하기 위해 청크 단위로 메모리를 할당하기 때문입니다. 청크 단위로 메모리를 할당하면 Python 이 시스템에서 더 많은 메모리를 요청해야 하는 횟수가 줄어들어 새 항목을 추가하는 프로세스가 빨라집니다.

  1. 딕셔너리에서 항목을 제거하여 메모리 사용량이 감소하는지 확인해 보겠습니다. 딕셔너리에서 항목을 제거하는 것도 일반적인 작업이며, 메모리에 미치는 영향을 확인하는 것이 흥미롭습니다.
del row['b']
sys.getsizeof(row)

흥미롭게도 항목을 제거해도 일반적으로 메모리 할당이 줄어들지 않습니다. 이는 Python 이 항목을 다시 추가하는 경우 재할당을 피하기 위해 할당된 메모리를 유지하기 때문입니다. 메모리 재할당은 성능 측면에서 비교적 비용이 많이 드는 작업이므로 Python 은 가능한 한 이를 피하려고 합니다.

메모리 효율성 고려 사항:

많은 레코드를 만들어야 하는 대규모 데이터 세트로 작업할 때, 각 레코드에 딕셔너리를 사용하는 것이 가장 메모리 효율적인 방법이 아닐 수 있습니다. 딕셔너리는 매우 유연하고 사용하기 쉽지만, 특히 많은 수의 레코드를 처리할 때 상당한 양의 메모리를 소비할 수 있습니다. 다음은 더 적은 메모리를 소비하는 몇 가지 대안입니다.

  • 튜플 (Tuple): 간단한 불변 시퀀스. 튜플은 생성된 후 변경할 수 없는 값의 모음입니다. 키를 저장하고 관련 키 - 값 매핑을 관리할 필요가 없기 때문에 딕셔너리보다 적은 메모리를 사용합니다.
  • 명명된 튜플 (Named tuple): 필드 이름이 있는 튜플. 명명된 튜플은 일반 튜플과 유사하지만, 이름을 사용하여 값에 액세스할 수 있으므로 코드를 더 읽기 쉽게 만들 수 있습니다. 또한 딕셔너리보다 적은 메모리를 사용합니다.
  • __slots__가 있는 클래스: 인스턴스 변수에 딕셔너리를 사용하지 않도록 명시적으로 속성을 정의하는 클래스. 클래스에서 __slots__를 사용하면 Python 은 인스턴스 변수를 저장하기 위해 딕셔너리를 생성하지 않으므로 메모리 사용량이 줄어듭니다.

이러한 대안은 많은 레코드를 처리할 때 메모리 사용량을 크게 줄일 수 있습니다.

열 중심 데이터로 메모리 최적화

전통적인 데이터 저장 방식에서는 각 레코드를 별도의 딕셔너리로 저장하는 경우가 많은데, 이를 행 중심 접근 방식이라고 합니다. 그러나 이 방법은 상당한 양의 메모리를 소비할 수 있습니다. 대안은 데이터를 열로 저장하는 것입니다. 열 중심 접근 방식에서는 각 속성에 대해 별도의 리스트를 만들고, 각 리스트는 해당 특정 속성에 대한 모든 값을 저장합니다. 이는 메모리를 절약하는 데 도움이 될 수 있습니다.

  1. 먼저, 프로젝트 디렉토리에 새 Python 파일을 만들어야 합니다. 이 파일에는 열 중심 방식으로 데이터를 읽는 코드가 포함됩니다. 파일 이름을 readrides.py로 지정합니다. 터미널에서 다음 명령을 사용하여 이를 수행할 수 있습니다.
cd ~/project
touch readrides.py

cd ~/project 명령은 현재 디렉토리를 프로젝트 디렉토리로 변경하고, touch readrides.py 명령은 readrides.py라는 새 빈 파일을 만듭니다.

  1. 다음으로, WebIDE 편집기에서 readrides.py 파일을 엽니다. 그런 다음 다음 Python 코드를 파일에 추가합니다. 이 코드는 CSV 파일에서 버스 운행 데이터를 읽어 각 열을 나타내는 네 개의 별도 리스트에 저장하는 read_rides_as_columns 함수를 정의합니다.
## readrides.py
import csv
import sys
import tracemalloc

def read_rides_as_columns(filename):
    '''
    Read the bus ride data into 4 lists, representing columns
    '''
    routes = []
    dates = []
    daytypes = []
    numrides = []
    with open(filename) as f:
        rows = csv.reader(f)
        headings = next(rows)     ## Skip headers
        for row in rows:
            routes.append(row[0])
            dates.append(row[1])
            daytypes.append(row[2])
            numrides.append(int(row[3]))
    return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides)

이 코드에서는 먼저 필요한 모듈 csv, sys, tracemalloc을 가져옵니다. csv 모듈은 CSV 파일을 읽는 데 사용되고, sys는 시스템 관련 작업에 사용될 수 있으며 (이 함수에서는 사용되지 않음), tracemalloc은 메모리 프로파일링에 사용됩니다. 함수 내부에서, 데이터의 서로 다른 열을 저장하기 위해 네 개의 빈 리스트를 초기화합니다. 그런 다음 파일을 열고, 헤더 행을 건너뛰고, 파일의 각 행을 반복하면서 해당 값을 적절한 리스트에 추가합니다. 마지막으로, 이 네 개의 리스트를 포함하는 딕셔너리를 반환합니다.

  1. 이제 열 중심 접근 방식이 메모리를 절약할 수 있는 이유를 분석해 보겠습니다. Python 셸에서 이 작업을 수행합니다. 다음 코드를 실행합니다.
import readrides
import tracemalloc

## Estimate memory for row-oriented approach
nrows = 577563     ## Number of rows in original file
dict_overhead = 240  ## Approximate dictionary overhead in bytes
row_memory = nrows * dict_overhead
print(f"Estimated memory for row-oriented data: {row_memory} bytes ({row_memory/1024/1024:.2f} MB)")

## Estimate memory for column-oriented approach
pointer_size = 8   ## Size of a pointer in bytes on 64-bit systems
column_memory = nrows * 4 * pointer_size  ## 4 columns with one pointer per entry
print(f"Estimated memory for column-oriented data: {column_memory} bytes ({column_memory/1024/1024:.2f} MB)")

## Estimate savings
savings = row_memory - column_memory
print(f"Estimated memory savings: {savings} bytes ({savings/1024/1024:.2f} MB)")

이 코드에서는 먼저 방금 만든 readrides 모듈과 tracemalloc 모듈을 가져옵니다. 그런 다음 행 중심 접근 방식에 대한 메모리 사용량을 추정합니다. 각 딕셔너리의 오버헤드가 240 바이트라고 가정하고, 이를 원본 파일의 행 수로 곱하여 행 중심 데이터에 대한 총 메모리 사용량을 구합니다. 열 중심 접근 방식의 경우, 64 비트 시스템에서 각 포인터가 8 바이트를 차지한다고 가정합니다. 4 개의 열과 항목당 하나의 포인터가 있으므로 열 중심 데이터에 대한 총 메모리 사용량을 계산합니다. 마지막으로, 열 중심 메모리 사용량에서 행 중심 메모리 사용량을 빼서 메모리 절약량을 계산합니다.

이 계산은 열 중심 접근 방식이 딕셔너리를 사용하는 행 중심 접근 방식에 비해 약 120MB 의 메모리를 절약해야 함을 보여줍니다.

  1. tracemalloc 모듈로 실제 메모리 사용량을 측정하여 이를 확인해 보겠습니다. 다음 코드를 실행합니다.
## Start tracking memory
tracemalloc.start()

## Read the data
columns = readrides.read_rides_as_columns('ctabus.csv')

## Get current and peak memory usage
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current/1024/1024:.2f} MB")
print(f"Peak memory usage: {peak/1024/1024:.2f} MB")

## Stop tracking memory
tracemalloc.stop()

이 코드에서는 먼저 tracemalloc.start()를 사용하여 메모리 추적을 시작합니다. 그런 다음 read_rides_as_columns 함수를 호출하여 ctabus.csv 파일에서 데이터를 읽습니다. 그 후, tracemalloc.get_traced_memory()를 사용하여 현재 및 최대 메모리 사용량을 얻습니다. 마지막으로, tracemalloc.stop()을 사용하여 메모리 추적을 중지합니다.

출력은 열 중심 데이터 구조의 실제 메모리 사용량을 보여줍니다. 이는 행 중심 접근 방식에 대한 이론적 추정치보다 훨씬 적어야 합니다.

상당한 메모리 절약은 수천 개의 딕셔너리 객체의 오버헤드를 제거하여 얻을 수 있습니다. Python 의 각 딕셔너리는 포함된 항목 수에 관계없이 고정된 오버헤드를 갖습니다. 열 중심 스토리지를 사용하면 수천 개의 딕셔너리 대신 몇 개의 리스트만 있으면 됩니다.

사용자 정의 컨테이너 클래스 만들기

데이터 처리에서 열 중심 접근 방식은 메모리 절약에 매우 효과적입니다. 그러나 기존 코드가 데이터를 딕셔너리 목록 형태로 예상하는 경우 문제가 발생할 수 있습니다. 이 문제를 해결하기 위해 사용자 정의 컨테이너 클래스를 만들 것입니다. 이 클래스는 행 중심 인터페이스를 제공하며, 이는 코드에서 딕셔너리 목록처럼 보이고 작동한다는 것을 의미합니다. 그러나 내부적으로는 열 중심 형식으로 데이터를 저장하여 메모리를 절약하는 데 도움이 됩니다.

  1. 먼저, WebIDE 편집기에서 readrides.py 파일을 엽니다. 이 파일에 새 클래스를 추가할 것입니다. 이 클래스는 사용자 정의 컨테이너의 기반이 됩니다.
## Add this to readrides.py
from collections.abc import Sequence

class RideData(Sequence):
    def __init__(self):
        ## Each value is a list with all of the values (a column)
        self.routes = []
        self.dates = []
        self.daytypes = []
        self.numrides = []

    def __len__(self):
        ## All lists assumed to have the same length
        return len(self.routes)

    def __getitem__(self, index):
        return {'route': self.routes[index],
                'date': self.dates[index],
                'daytype': self.daytypes[index],
                'rides': self.numrides[index]}

    def append(self, d):
        self.routes.append(d['route'])
        self.dates.append(d['date'])
        self.daytypes.append(d['daytype'])
        self.numrides.append(d['rides'])

이 코드에서는 Sequence에서 상속받는 RideData라는 클래스를 정의합니다. __init__ 메서드는 각 열을 나타내는 네 개의 빈 리스트를 초기화합니다. __len__ 메서드는 컨테이너의 길이를 반환하며, 이는 routes 리스트의 길이와 같습니다. __getitem__ 메서드를 사용하면 인덱스로 특정 레코드에 액세스하여 딕셔너리로 반환할 수 있습니다. append 메서드는 각 열 리스트에 값을 추가하여 컨테이너에 새 레코드를 추가합니다.

  1. 이제 버스 운행 데이터를 사용자 정의 컨테이너로 읽는 함수가 필요합니다. 다음 함수를 readrides.py 파일에 추가합니다.
## Add this to readrides.py
def read_rides_as_dicts(filename):
    '''
    Read the bus ride data as a list of dicts, but use our custom container
    '''
    records = RideData()
    with open(filename) as f:
        rows = csv.reader(f)
        headings = next(rows)     ## Skip headers
        for row in rows:
            route = row[0]
            date = row[1]
            daytype = row[2]
            rides = int(row[3])
            record = {
                'route': route,
                'date': date,
                'daytype': daytype,
                'rides': rides
            }
            records.append(record)
    return records

이 함수는 RideData 클래스의 인스턴스를 생성하고 CSV 파일의 데이터로 채웁니다. 파일에서 각 행을 읽고, 관련 정보를 추출하고, 각 레코드에 대한 딕셔너리를 생성한 다음, 이를 RideData 컨테이너에 추가합니다. 핵심은 딕셔너리 목록과 동일한 인터페이스를 유지하지만 내부적으로는 열로 데이터를 저장한다는 것입니다.

  1. Python 셸에서 사용자 정의 컨테이너를 테스트해 보겠습니다. 이를 통해 예상대로 작동하는지 확인할 수 있습니다.
import readrides

## Read the data using our custom container
rows = readrides.read_rides_as_dicts('ctabus.csv')

## Check the type of the returned object
type(rows)  ## Should be readrides.RideData

## Check the length
len(rows)   ## Should be 577563

## Access individual records
rows[0]     ## Should return a dictionary for the first record
rows[1]     ## Should return a dictionary for the second record
rows[2]     ## Should return a dictionary for the third record

사용자 정의 컨테이너는 Sequence 인터페이스를 성공적으로 구현하므로 리스트처럼 동작합니다. len() 함수를 사용하여 컨테이너의 레코드 수를 얻을 수 있으며, 인덱싱을 사용하여 개별 레코드에 액세스할 수 있습니다. 각 레코드는 내부적으로 데이터가 열로 저장되어 있음에도 불구하고 딕셔너리처럼 보입니다. 이는 딕셔너리 목록을 예상하는 기존 코드가 수정 없이 사용자 정의 컨테이너로 계속 작동하기 때문에 매우 좋습니다.

  1. 마지막으로, 사용자 정의 컨테이너의 메모리 사용량을 측정해 보겠습니다. 이를 통해 딕셔너리 목록과 비교하여 얼마나 많은 메모리를 절약하는지 확인할 수 있습니다.
import tracemalloc

tracemalloc.start()
rows = readrides.read_rides_as_dicts('ctabus.csv')
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current/1024/1024:.2f} MB")
print(f"Peak memory usage: {peak/1024/1024:.2f} MB")
tracemalloc.stop()

이 코드를 실행하면 메모리 사용량이 열 중심 접근 방식과 유사하다는 것을 알 수 있으며, 이는 딕셔너리 목록이 사용하는 것보다 훨씬 적습니다. 이는 메모리 효율성 측면에서 사용자 정의 컨테이너의 장점을 보여줍니다.

슬라이싱을 위한 사용자 정의 컨테이너 향상

사용자 정의 컨테이너는 개별 레코드에 액세스하는 데 매우 유용합니다. 그러나 슬라이싱과 관련하여 문제가 있습니다. 컨테이너의 슬라이스를 가져오려고 하면 결과가 일반적으로 예상하는 것과 다릅니다.

이러한 현상이 발생하는 이유를 이해해 보겠습니다. Python 에서 슬라이싱은 시퀀스의 일부를 추출하는 데 사용되는 일반적인 연산입니다. 그러나 사용자 정의 컨테이너의 경우 Python 은 슬라이스된 데이터만 사용하여 새 RideData 객체를 만드는 방법을 알지 못합니다. 대신, 슬라이스의 각 인덱스에 대해 __getitem__을 호출한 결과를 포함하는 리스트를 생성합니다.

  1. Python 셸에서 슬라이싱을 테스트해 보겠습니다.
import readrides

rows = readrides.read_rides_as_dicts('ctabus.csv')
r = rows[0:10]  ## Take a slice of the first 10 records
type(r)  ## This will likely be a list, not a RideData object
print(r)  ## This might look like a list of numbers, not dictionaries

이 코드에서는 먼저 readrides 모듈을 가져옵니다. 그런 다음 ctabus.csv 파일에서 데이터를 읽어 rows 변수에 저장합니다. 처음 10 개의 레코드의 슬라이스를 가져와 결과의 유형을 확인하려고 하면 RideData 객체 대신 리스트임을 알 수 있습니다. 결과를 출력하면 딕셔너리 대신 숫자 목록과 같이 예상치 못한 내용이 표시될 수 있습니다.

  1. 슬라이싱을 제대로 처리하도록 RideData 클래스를 수정해 보겠습니다. readrides.py를 열고 __getitem__ 메서드를 업데이트합니다.
def __getitem__(self, index):
    if isinstance(index, slice):
        ## Handle slice
        result = RideData()
        result.routes = self.routes[index]
        result.dates = self.dates[index]
        result.daytypes = self.daytypes[index]
        result.numrides = self.numrides[index]
        return result
    else:
        ## Handle single index
        return {'route': self.routes[index],
                'date': self.dates[index],
                'daytype': self.daytypes[index],
                'rides': self.numrides[index]}

이 업데이트된 __getitem__ 메서드에서는 먼저 index가 슬라이스인지 확인합니다. 슬라이스인 경우 result라는 새 RideData 객체를 만듭니다. 그런 다음 이 새 객체를 원래 데이터 열 (routes, dates, daytypes, numrides) 의 슬라이스로 채웁니다. 이렇게 하면 사용자 정의 컨테이너를 슬라이싱할 때 리스트 대신 다른 RideData 객체를 얻을 수 있습니다. index가 슬라이스가 아닌 경우 (즉, 단일 인덱스인 경우) 관련 레코드를 포함하는 딕셔너리를 반환합니다.

  1. 향상된 슬라이싱 기능을 테스트해 보겠습니다.
import readrides

rows = readrides.read_rides_as_dicts('ctabus.csv')
r = rows[0:10]  ## Take a slice of the first 10 records
type(r)  ## Should now be readrides.RideData
len(r)   ## Should be 10
r[0]     ## Should be the same as rows[0]
r[1]     ## Should be the same as rows[1]

__getitem__ 메서드를 업데이트한 후 슬라이싱을 다시 테스트할 수 있습니다. 처음 10 개의 레코드의 슬라이스를 가져오면 결과의 유형이 이제 readrides.RideData여야 합니다. 슬라이스의 길이는 10 이어야 하며, 슬라이스의 개별 요소에 액세스하면 원래 컨테이너의 해당 요소에 액세스하는 것과 동일한 결과를 얻어야 합니다.

  1. 다양한 슬라이스 패턴으로도 테스트할 수 있습니다.
## Get every other record from the first 20
r2 = rows[0:20:2]
len(r2)  ## Should be 10

## Get the last 10 records
r3 = rows[-10:]
len(r3)  ## Should be 10

여기서는 다양한 슬라이스 패턴을 테스트하고 있습니다. 첫 번째 슬라이스 rows[0:20:2]는 처음 20 개의 레코드에서 두 번째 레코드마다 가져오며, 결과 슬라이스의 길이는 10 이어야 합니다. 두 번째 슬라이스 rows[-10:]는 마지막 10 개의 레코드를 가져오며, 그 길이도 10 이어야 합니다.

슬라이싱을 제대로 구현함으로써 사용자 정의 컨테이너는 열 중심 스토리지를 유지하면서 표준 Python 리스트처럼 더욱 동작합니다.

내장 Python 컨테이너를 모방하지만 내부 표현이 다른 사용자 정의 컨테이너 클래스를 만드는 이 접근 방식은 코드가 사용자에게 제공하는 인터페이스를 변경하지 않고 메모리 사용량을 최적화하는 강력한 기술입니다.

요약

이 Lab 에서 몇 가지 중요한 기술을 배웠습니다. 첫째, Python 리스트와 딕셔너리에서 메모리 할당 동작을 탐구하고 행 중심에서 열 중심 데이터 저장으로 전환하여 메모리 사용량을 최적화하는 방법을 배웠습니다. 둘째, 원래 인터페이스를 유지하면서 메모리를 적게 사용하고 슬라이싱 연산을 제대로 처리하도록 향상된 사용자 정의 컨테이너 클래스를 만들었습니다.

이러한 기술은 대규모 데이터 세트로 작업하는 데 매우 유용하며, 코드의 인터페이스를 변경하지 않고도 메모리 사용량을 크게 줄일 수 있습니다. 내부 표현이 다른 내장 Python 컨테이너를 모방하는 사용자 정의 컨테이너 클래스를 만드는 기능은 Python 애플리케이션을 위한 강력한 최적화 도구입니다. 이러한 개념은 대규모의 정기적으로 구조화된 데이터 세트를 포함하는 프로젝트를 포함하여 메모리가 중요한 다른 프로젝트에 적용할 수 있습니다.