함수의 정의적 측면

Beginner

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

소개

이 랩에서는 Python 함수와 메서드의 기본적인 측면을 탐구하는 방법을 배우게 됩니다. 또한 매개변수를 효과적으로 설계하여 함수를 더욱 유연하게 만들 것입니다.

더 나아가, 코드 가독성과 안전성을 향상시키기 위해 타입 힌트 (type hints) 를 구현할 것입니다. 이는 고품질 Python 코드를 작성하는 데 매우 중요합니다.

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

컨텍스트 이해

이전 연습에서 CSV 파일을 읽고 데이터를 다양한 데이터 구조에 저장하는 코드를 접했을 수 있습니다. 이 코드의 목적은 CSV 파일에서 원시 텍스트 데이터를 가져와 딕셔너리 또는 클래스 인스턴스와 같은 더 유용한 Python 객체로 변환하는 것입니다. 이 변환은 Python 프로그램 내에서 데이터를 더 구조적이고 의미 있는 방식으로 작업할 수 있게 해주기 때문에 필수적입니다.

CSV 파일을 읽는 일반적인 패턴은 종종 특정 구조를 따릅니다. 다음은 CSV 파일을 읽고 각 행을 딕셔너리로 변환하는 함수의 예입니다.

import csv

def read_csv_as_dicts(filename, types):
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = { name: func(val)
                       for name, func, val in zip(headers, types, row) }
            records.append(record)
    return records

이 함수가 어떻게 작동하는지 살펴보겠습니다. 먼저, Python 에서 CSV 파일 작업을 위한 기능을 제공하는 csv 모듈을 가져옵니다. 이 함수는 두 개의 매개변수를 사용합니다. filename은 읽을 CSV 파일의 이름이고, types는 각 열의 데이터를 적절한 데이터 유형으로 변환하는 데 사용되는 함수의 목록입니다.

함수 내부에서는 CSV 파일의 각 행을 나타내는 딕셔너리를 저장하기 위해 records라는 빈 목록을 초기화합니다. 그런 다음 with 문을 사용하여 파일을 엽니다. 이 문은 코드 블록이 실행된 후 파일이 제대로 닫히도록 보장합니다. csv.reader 함수는 CSV 파일의 각 행을 읽는 반복자를 생성하는 데 사용됩니다. 첫 번째 행은 헤더로 간주되므로 next 함수를 사용하여 검색됩니다.

다음으로, 함수는 CSV 파일의 나머지 행을 반복합니다. 각 행에 대해 딕셔너리 컴프리헨션을 사용하여 딕셔너리를 생성합니다. 딕셔너리의 키는 열 헤더이고, 값은 types 목록에서 해당 유형 변환 함수를 행의 값에 적용한 결과입니다. 마지막으로, 딕셔너리가 records 목록에 추가되고 함수는 딕셔너리 목록을 반환합니다.

이제 CSV 파일에서 클래스 인스턴스로 데이터를 읽는 유사한 함수를 살펴보겠습니다.

def read_csv_as_instances(filename, cls):
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = cls.from_row(row)
            records.append(record)
    return records

이 함수는 이전 함수와 유사하지만 딕셔너리를 생성하는 대신 클래스의 인스턴스를 생성합니다. 이 함수는 두 개의 매개변수를 사용합니다. filename은 읽을 CSV 파일의 이름이고, cls는 인스턴스가 생성될 클래스입니다.

함수 내부에서는 이전 함수와 유사한 구조를 따릅니다. 클래스 인스턴스를 저장하기 위해 records라는 빈 목록을 초기화합니다. 그런 다음 파일을 열고, 헤더를 읽고, 나머지 행을 반복합니다. 각 행에 대해 클래스 clsfrom_row 메서드를 호출하여 행의 데이터를 사용하여 클래스의 인스턴스를 생성합니다. 그런 다음 인스턴스가 records 목록에 추가되고 함수는 인스턴스 목록을 반환합니다.

이 랩에서는 이러한 함수를 리팩토링하여 더 유연하고 강력하게 만들 것입니다. 또한 함수의 매개변수와 반환 값의 예상 유형을 지정할 수 있는 Python 의 타입 힌트 (type hinting) 시스템을 탐구할 것입니다. 이를 통해 코드를 더 읽기 쉽고 이해하기 쉽게 만들 수 있으며, 특히 코드를 함께 작업할 수 있는 다른 개발자에게 유용합니다.

reader.py 파일을 만들고 이러한 초기 함수를 추가하는 것으로 시작해 보겠습니다. 다음 단계로 넘어가기 전에 이러한 함수가 제대로 작동하는지 확인하기 위해 테스트하십시오.

기본 CSV 리더 함수 생성

CSV 데이터를 읽기 위한 두 개의 기본 함수를 사용하여 reader.py 파일을 먼저 만들어 보겠습니다. 이러한 함수는 데이터를 딕셔너리 또는 클래스 인스턴스로 변환하는 등 다양한 방식으로 CSV 파일을 처리하는 데 도움이 됩니다.

먼저, CSV 파일이 무엇인지 이해해야 합니다. CSV 는 쉼표로 구분된 값 (Comma-Separated Values) 의 약자입니다. 각 줄이 행을 나타내고 각 행의 값이 쉼표로 구분된 테이블 형식 데이터를 저장하는 데 사용되는 간단한 파일 형식입니다.

이제 reader.py 파일을 만들어 보겠습니다. 다음 단계를 따르세요.

  1. 코드 편집기를 열고 /home/labex/project 디렉토리에 reader.py라는 새 파일을 만듭니다. 여기에서 CSV 데이터를 읽는 함수를 작성합니다.

  2. reader.py에 다음 코드를 추가합니다.

## reader.py

import csv

def read_csv_as_dicts(filename, types):
    '''
    선택적 유형 변환을 사용하여 CSV 데이터를 딕셔너리 목록으로 읽기

    Args:
        filename: CSV 파일 경로
        types: 각 열에 대한 유형 변환 함수 목록

    Returns:
        CSV 파일의 데이터가 있는 딕셔너리 목록
    '''
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = { name: func(val)
                       for name, func, val in zip(headers, types, row) }
            records.append(record)
    return records

def read_csv_as_instances(filename, cls):
    '''
    CSV 데이터를 클래스 인스턴스 목록으로 읽기

    Args:
        filename: CSV 파일 경로
        cls: 인스턴스를 생성할 클래스

    Returns:
        CSV 파일의 데이터가 있는 클래스 인스턴스 목록
    '''
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = cls.from_row(row)
            records.append(record)
    return records

read_csv_as_dicts 함수에서 먼저 open 함수를 사용하여 CSV 파일을 엽니다. 그런 다음 csv.reader를 사용하여 파일을 줄 단위로 읽습니다. next(rows) 문은 일반적으로 헤더가 포함된 파일의 첫 번째 줄을 읽습니다. 그 후 나머지 행을 반복합니다. 각 행에 대해 헤더가 키이고 해당 행의 해당 값이 값인 딕셔너리를 생성합니다. 선택적으로 types 목록을 사용하여 유형 변환을 수행합니다.

read_csv_as_instances 함수는 유사하지만 딕셔너리를 생성하는 대신 주어진 클래스의 인스턴스를 생성합니다. 이 함수는 클래스에 데이터 행에서 인스턴스를 생성할 수 있는 from_row라는 정적 메서드가 있다고 가정합니다.

  1. 이러한 함수가 제대로 작동하는지 테스트해 보겠습니다. 다음 코드를 사용하여 test_reader.py라는 새 파일을 만듭니다.
## test_reader.py

import reader
import stock

## Test reading CSV as dictionaries
portfolio_dicts = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("First portfolio item as dictionary:", portfolio_dicts[0])
print("Total items:", len(portfolio_dicts))

## Test reading CSV as class instances
portfolio_instances = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
print("\nFirst portfolio item as Stock instance:", portfolio_instances[0])
print("Total items:", len(portfolio_instances))

test_reader.py 파일에서 방금 생성한 reader 모듈과 stock 모듈을 가져옵니다. 그런 다음 portfolio.csv라는 샘플 CSV 파일로 두 함수를 호출하여 테스트합니다. 함수가 예상대로 작동하는지 확인하기 위해 포트폴리오의 첫 번째 항목과 총 항목 수를 출력합니다.

  1. 터미널에서 테스트 스크립트를 실행합니다.
python test_reader.py

출력은 다음과 유사해야 합니다.

First portfolio item as dictionary: {'name': 'AA', 'shares': 100, 'price': 32.2}
Total items: 7

First portfolio item as Stock instance: Stock('AA', 100, 32.2)
Total items: 7

이는 두 함수가 올바르게 작동함을 확인합니다. 첫 번째 함수는 CSV 데이터를 적절한 유형 변환을 사용하여 딕셔너리 목록으로 변환하고, 두 번째 함수는 제공된 클래스에서 정적 메서드를 사용하여 클래스 인스턴스를 생성합니다.

다음 단계에서는 파일 이름뿐만 아니라 모든 반복 가능한 데이터 소스에서 작동하도록 허용하여 이러한 함수를 더 유연하게 리팩토링할 것입니다.

함수 유연성 향상

현재, 우리의 함수는 파일 이름으로 지정된 파일에서만 읽도록 제한되어 있습니다. 이는 사용성을 제한합니다. 프로그래밍에서 함수가 다양한 유형의 입력을 처리할 수 있도록 더 유연하게 만드는 것이 종종 유익합니다. 이 경우, 함수가 파일 객체 또는 기타 소스와 같은 줄을 생성하는 모든 반복 가능 항목으로 작동할 수 있다면 좋을 것입니다. 이렇게 하면 압축 파일 또는 기타 데이터 스트림에서 읽는 것과 같은 더 많은 시나리오에서 이러한 함수를 사용할 수 있습니다.

이러한 유연성을 활성화하기 위해 코드를 리팩토링해 보겠습니다.

  1. reader.py 파일을 엽니다. 몇 가지 새로운 함수를 포함하도록 수정할 것입니다. 이러한 새로운 함수를 통해 코드가 다양한 유형의 반복 가능 항목으로 작동할 수 있습니다. 추가해야 할 코드는 다음과 같습니다.
## reader.py

import csv

def csv_as_dicts(lines, types):
    '''
    반복 가능 항목에서 CSV 데이터를 딕셔너리 목록으로 구문 분석

    Args:
        lines: CSV 줄을 생성하는 반복 가능 항목
        types: 각 열에 대한 유형 변환 함수 목록

    Returns:
        CSV 줄의 데이터가 있는 딕셔너리 목록
    '''
    records = []
    rows = csv.reader(lines)
    headers = next(rows)
    for row in rows:
        record = { name: func(val)
                  for name, func, val in zip(headers, types, row) }
        records.append(record)
    return records

def csv_as_instances(lines, cls):
    '''
    반복 가능 항목에서 CSV 데이터를 클래스 인스턴스 목록으로 구문 분석

    Args:
        lines: CSV 줄을 생성하는 반복 가능 항목
        cls: 인스턴스를 생성할 클래스

    Returns:
        CSV 줄의 데이터가 있는 클래스 인스턴스 목록
    '''
    records = []
    rows = csv.reader(lines)
    headers = next(rows)
    for row in rows:
        record = cls.from_row(row)
        records.append(record)
    return records

def read_csv_as_dicts(filename, types):
    '''
    선택적 유형 변환을 사용하여 CSV 데이터를 딕셔너리 목록으로 읽기

    Args:
        filename: CSV 파일 경로
        types: 각 열에 대한 유형 변환 함수 목록

    Returns:
        CSV 파일의 데이터가 있는 딕셔너리 목록
    '''
    with open(filename) as file:
        return csv_as_dicts(file, types)

def read_csv_as_instances(filename, cls):
    '''
    CSV 데이터를 클래스 인스턴스 목록으로 읽기

    Args:
        filename: CSV 파일 경로
        cls: 인스턴스를 생성할 클래스

    Returns:
        CSV 파일의 데이터가 있는 클래스 인스턴스 목록
    '''
    with open(filename) as file:
        return csv_as_instances(file, cls)

코드를 리팩토링한 방식을 자세히 살펴보겠습니다.

  1. csv_as_dicts()csv_as_instances()라는 두 개의 더 일반적인 함수를 만들었습니다. 이러한 함수는 CSV 줄을 생성하는 모든 반복 가능 항목으로 작동하도록 설계되었습니다. 즉, 파일 이름으로 지정된 파일뿐만 아니라 다양한 유형의 입력 소스를 처리할 수 있습니다.

  2. 이러한 새로운 함수를 사용하도록 read_csv_as_dicts()read_csv_as_instances()를 다시 구현했습니다. 이렇게 하면 파일 이름으로 파일에서 읽는 원래 기능은 여전히 사용할 수 있지만 이제 더 유연한 함수를 기반으로 구축됩니다.

  3. 이 접근 방식은 기존 코드와의 이전 버전과의 호환성을 유지합니다. 즉, 이전 함수를 사용하던 모든 코드는 예상대로 계속 작동합니다. 동시에, 라이브러리는 이제 다양한 유형의 입력 소스를 처리할 수 있으므로 더 유연해집니다.

  4. 이제 이러한 새로운 함수를 테스트해 보겠습니다. test_reader_flexibility.py라는 파일을 만들고 다음 코드를 추가합니다. 이 코드는 다양한 유형의 입력 소스를 사용하여 새로운 함수를 테스트합니다.

## test_reader_flexibility.py

import reader
import stock
import gzip

## Test opening a regular file
with open('portfolio.csv') as file:
    portfolio = reader.csv_as_dicts(file, [str, int, float])
    print("First item from open file:", portfolio[0])

## Test opening a gzipped file
with gzip.open('portfolio.csv.gz', 'rt') as file:  ## 'rt' means read text
    portfolio = reader.csv_as_instances(file, stock.Stock)
    print("\nFirst item from gzipped file:", portfolio[0])

## Test backward compatibility
portfolio = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("\nFirst item using backward compatible function:", portfolio[0])
  1. 테스트 파일을 만든 후 터미널에서 테스트 스크립트를 실행해야 합니다. 터미널을 열고 test_reader_flexibility.py 파일이 있는 디렉토리로 이동합니다. 그런 다음 다음 명령을 실행합니다.
python test_reader_flexibility.py

출력은 다음과 유사해야 합니다.

First item from open file: {'name': 'AA', 'shares': 100, 'price': 32.2}

First item from gzipped file: Stock('AA', 100, 32.2)

First item using backward compatible function: {'name': 'AA', 'shares': 100, 'price': 32.2}

이 출력은 함수가 이제 다양한 유형의 입력 소스에서 작동하면서 이전 버전과의 호환성을 유지함을 확인합니다. 리팩토링된 함수는 다음에서 데이터를 처리할 수 있습니다.

  • open()으로 연 일반 파일
  • gzip.open()으로 연 압축 파일
  • 텍스트 줄을 생성하는 다른 모든 반복 가능 객체

이렇게 하면 코드가 훨씬 더 유연해지고 다양한 시나리오에서 사용하기 쉬워집니다.

헤더가 없는 CSV 파일 처리

데이터 처리 세계에서 모든 CSV 파일이 첫 번째 행에 헤더를 포함하는 것은 아닙니다. 헤더는 CSV 파일의 각 열에 지정된 이름으로, 각 열이 어떤 종류의 데이터를 담고 있는지 이해하는 데 도움이 됩니다. CSV 파일에 헤더가 없으면 이를 적절하게 처리할 방법이 필요합니다. 이 섹션에서는 헤더가 있는 CSV 파일과 헤더가 없는 CSV 파일을 모두 사용할 수 있도록 호출자가 헤더를 수동으로 제공할 수 있도록 함수를 수정합니다.

  1. reader.py 파일을 열고 헤더 처리를 포함하도록 업데이트합니다.
## reader.py

import csv

def csv_as_dicts(lines, types, headers=None):
    '''
    반복 가능 항목에서 CSV 데이터를 딕셔너리 목록으로 구문 분석

    Args:
        lines: CSV 줄을 생성하는 반복 가능 항목
        types: 각 열에 대한 유형 변환 함수 목록
        headers: 선택적 열 이름 목록. None 인 경우 첫 번째 행이 헤더로 사용됨

    Returns:
        CSV 줄의 데이터가 있는 딕셔너리 목록
    '''
    records = []
    rows = csv.reader(lines)

    if headers is None:
        ## 제공된 헤더가 없으면 첫 번째 행을 헤더로 사용
        headers = next(rows)

    for row in rows:
        record = { name: func(val)
                  for name, func, val in zip(headers, types, row) }
        records.append(record)
    return records

def csv_as_instances(lines, cls, headers=None):
    '''
    반복 가능 항목에서 CSV 데이터를 클래스 인스턴스 목록으로 구문 분석

    Args:
        lines: CSV 줄을 생성하는 반복 가능 항목
        cls: 인스턴스를 생성할 클래스
        headers: 선택적 열 이름 목록. None 인 경우 첫 번째 행이 헤더로 사용됨

    Returns:
        CSV 줄의 데이터가 있는 클래스 인스턴스 목록
    '''
    records = []
    rows = csv.reader(lines)

    if headers is None:
        ## 헤더가 제공되지 않은 경우 첫 번째 행 건너뛰기
        next(rows)

    for row in rows:
        record = cls.from_row(row)
        records.append(record)
    return records

def read_csv_as_dicts(filename, types, headers=None):
    '''
    선택적 유형 변환을 사용하여 CSV 데이터를 딕셔너리 목록으로 읽기

    Args:
        filename: CSV 파일 경로
        types: 각 열에 대한 유형 변환 함수 목록
        headers: 선택적 열 이름 목록. None 인 경우 첫 번째 행이 헤더로 사용됨

    Returns:
        CSV 파일의 데이터가 있는 딕셔너리 목록
    '''
    with open(filename) as file:
        return csv_as_dicts(file, types, headers)

def read_csv_as_instances(filename, cls, headers=None):
    '''
    CSV 데이터를 클래스 인스턴스 목록으로 읽기

    Args:
        filename: CSV 파일 경로
        cls: 인스턴스를 생성할 클래스
        headers: 선택적 열 이름 목록. None 인 경우 첫 번째 행이 헤더로 사용됨

    Returns:
        CSV 파일의 데이터가 있는 클래스 인스턴스 목록
    '''
    with open(filename) as file:
        return csv_as_instances(file, cls, headers)

이러한 함수에 적용한 주요 변경 사항을 이해해 보겠습니다.

  1. 모든 함수에 headers 매개변수를 추가했으며 기본값을 None으로 설정했습니다. 즉, 호출자가 헤더를 제공하지 않으면 함수가 기본 동작을 사용합니다.

  2. csv_as_dicts 함수에서 headers 매개변수가 None인 경우에만 첫 번째 행을 헤더로 사용합니다. 이를 통해 헤더가 있는 파일과 헤더가 없는 파일을 자동으로 처리할 수 있습니다.

  3. csv_as_instances 함수에서 headers 매개변수가 None인 경우에만 첫 번째 행을 건너뜁니다. 이는 자체 헤더를 제공하는 경우 파일의 첫 번째 행이 헤더가 아닌 실제 데이터이기 때문입니다.

  4. 헤더가 없는 파일로 이러한 수정을 테스트해 보겠습니다. test_headers.py라는 파일을 만듭니다.

## test_headers.py

import reader
import stock

## Define column names for the file without headers
column_names = ['name', 'shares', 'price']

## Test reading a file without headers
portfolio = reader.read_csv_as_dicts('portfolio_noheader.csv',
                                     [str, int, float],
                                     headers=column_names)
print("First item from file without headers:", portfolio[0])
print("Total items:", len(portfolio))

## Test reading the same file as instances
portfolio = reader.read_csv_as_instances('portfolio_noheader.csv',
                                        stock.Stock,
                                        headers=column_names)
print("\nFirst item as Stock instance:", portfolio[0])
print("Total items:", len(portfolio))

## Verify that original functionality still works
portfolio = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("\nFirst item from file with headers:", portfolio[0])

이 테스트 스크립트에서 먼저 헤더가 없는 파일의 열 이름을 정의합니다. 그런 다음 헤더가 없는 파일을 딕셔너리 목록과 클래스 인스턴스 목록으로 읽는 것을 테스트합니다. 마지막으로 헤더가 있는 파일을 읽어 원래 기능이 여전히 작동하는지 확인합니다.

  1. 터미널에서 테스트 스크립트를 실행합니다.
python test_headers.py

출력은 다음과 유사해야 합니다.

First item from file without headers: {'name': 'AA', 'shares': 100, 'price': 32.2}
Total items: 7

First item as Stock instance: Stock('AA', 100, 32.2)
Total items: 7

First item from file with headers: {'name': 'AA', 'shares': 100, 'price': 32.2}

이 출력은 이제 함수가 헤더가 있는 CSV 파일과 헤더가 없는 CSV 파일을 모두 처리할 수 있음을 확인합니다. 사용자는 필요할 때 열 이름을 제공하거나 첫 번째 행에서 헤더를 읽는 기본 동작에 의존할 수 있습니다.

이러한 수정을 통해 CSV 리더 함수는 이제 더 다양해지고 더 광범위한 파일 형식을 처리할 수 있습니다. 이는 코드를 더 강력하게 만들고 다양한 시나리오에서 유용하게 만드는 중요한 단계입니다.

타입 힌트 추가

Python 3.5 이상 버전에서는 타입 힌트가 지원됩니다. 타입 힌트는 코드에서 변수, 함수 매개변수 및 반환 값의 예상 데이터 유형을 나타내는 방법입니다. 코드가 실행되는 방식은 변경하지 않지만 코드를 더 읽기 쉽게 만들고 코드가 실제로 실행되기 전에 특정 유형의 오류를 포착하는 데 도움이 될 수 있습니다. 이제 CSV 리더 함수에 타입 힌트를 추가해 보겠습니다.

  1. reader.py 파일을 열고 타입 힌트를 포함하도록 업데이트합니다.
## reader.py

import csv
from typing import List, Callable, Dict, Any, Type, Optional, TextIO, Iterator, TypeVar

## Define a generic type for the class parameter
T = TypeVar('T')

def csv_as_dicts(lines: Iterator[str],
                types: List[Callable[[str], Any]],
                headers: Optional[List[str]] = None) -> List[Dict[str, Any]]:
    '''
    반복 가능 항목에서 CSV 데이터를 딕셔너리 목록으로 구문 분석

    Args:
        lines: CSV 줄을 생성하는 반복 가능 항목
        types: 각 열에 대한 유형 변환 함수 목록
        headers: 선택적 열 이름 목록. None 인 경우 첫 번째 행이 헤더로 사용됨

    Returns:
        CSV 줄의 데이터가 있는 딕셔너리 목록
    '''
    records: List[Dict[str, Any]] = []
    rows = csv.reader(lines)

    if headers is None:
        ## 제공된 헤더가 없으면 첫 번째 행을 헤더로 사용
        headers = next(rows)

    for row in rows:
        record = { name: func(val)
                  for name, func, val in zip(headers, types, row) }
        records.append(record)
    return records

def csv_as_instances(lines: Iterator[str],
                    cls: Type[T],
                    headers: Optional[List[str]] = None) -> List[T]:
    '''
    반복 가능 항목에서 CSV 데이터를 클래스 인스턴스 목록으로 구문 분석

    Args:
        lines: CSV 줄을 생성하는 반복 가능 항목
        cls: 인스턴스를 생성할 클래스
        headers: 선택적 열 이름 목록. None 인 경우 첫 번째 행이 헤더로 사용됨

    Returns:
        CSV 줄의 데이터가 있는 클래스 인스턴스 목록
    '''
    records: List[T] = []
    rows = csv.reader(lines)

    if headers is None:
        ## 헤더가 제공되지 않은 경우 첫 번째 행 건너뛰기
        next(rows)

    for row in rows:
        record = cls.from_row(row)
        records.append(record)
    return records

def read_csv_as_dicts(filename: str,
                     types: List[Callable[[str], Any]],
                     headers: Optional[List[str]] = None) -> List[Dict[str, Any]]:
    '''
    선택적 유형 변환을 사용하여 CSV 데이터를 딕셔너리 목록으로 읽기

    Args:
        filename: CSV 파일 경로
        types: 각 열에 대한 유형 변환 함수 목록
        headers: 선택적 열 이름 목록. None 인 경우 첫 번째 행이 헤더로 사용됨

    Returns:
        CSV 파일의 데이터가 있는 딕셔너리 목록
    '''
    with open(filename) as file:
        return csv_as_dicts(file, types, headers)

def read_csv_as_instances(filename: str,
                         cls: Type[T],
                         headers: Optional[List[str]] = None) -> List[T]:
    '''
    CSV 데이터를 클래스 인스턴스 목록으로 읽기

    Args:
        filename: CSV 파일 경로
        cls: 인스턴스를 생성할 클래스
        headers: 선택적 열 이름 목록. None 인 경우 첫 번째 행이 헤더로 사용됨

    Returns:
        CSV 파일의 데이터가 있는 클래스 인스턴스 목록
    '''
    with open(filename) as file:
        return csv_as_instances(file, cls, headers)

코드에서 변경한 주요 사항을 이해해 보겠습니다.

  1. typing 모듈에서 유형을 가져왔습니다. 이 모듈은 타입 힌트를 정의하는 데 사용할 수 있는 일련의 유형을 제공합니다. 예를 들어, List, DictOptional은 이 모듈의 유형입니다.

  2. 클래스 유형을 나타내기 위해 제네릭 유형 변수 T를 추가했습니다. 제네릭 유형 변수를 사용하면 타입 안전 방식으로 다양한 유형으로 작동할 수 있는 함수를 작성할 수 있습니다.

  3. 모든 함수 매개변수와 반환 값에 타입 힌트를 추가했습니다. 이를 통해 함수가 어떤 유형의 인수를 예상하고 어떤 유형의 값을 반환하는지 명확하게 알 수 있습니다.

  4. List, DictOptional과 같은 적절한 컨테이너 유형을 사용했습니다. List는 목록을 나타내고, Dict는 딕셔너리를 나타내며, Optional은 매개변수가 특정 유형을 갖거나 None일 수 있음을 나타냅니다.

  5. 유형 변환 함수에 Callable을 사용했습니다. Callable은 매개변수가 호출할 수 있는 함수임을 나타내는 데 사용됩니다.

  6. csv_as_instances가 전달된 클래스의 인스턴스 목록을 반환한다는 것을 표현하기 위해 제네릭 T를 사용했습니다. 이는 IDE 및 기타 도구가 반환된 객체의 유형을 이해하는 데 도움이 됩니다.

  7. 이제 모든 것이 제대로 작동하는지 확인하기 위해 간단한 테스트 파일을 만들어 보겠습니다.

## test_types.py

import reader
import stock

## The functions should work exactly as before
portfolio = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("First item:", portfolio[0])

## But now we have better type checking and IDE support
stock_portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
print("\nFirst stock:", stock_portfolio[0])

## We can see that stock_portfolio is a list of Stock objects
## This helps IDEs provide better code completion
first_stock = stock_portfolio[0]
print(f"\nName: {first_stock.name}")
print(f"Shares: {first_stock.shares}")
print(f"Price: {first_stock.price}")
print(f"Value: {first_stock.shares * first_stock.price}")
  1. 터미널에서 테스트 스크립트를 실행합니다.
python test_types.py

출력은 다음과 유사해야 합니다.

First item: {'name': 'AA', 'shares': 100, 'price': 32.2}

First stock: Stock('AA', 100, 32.2)

Name: AA
Shares: 100
Price: 32.2
Value: 3220.0

타입 힌트는 코드가 실행되는 방식을 변경하지 않지만 몇 가지 이점을 제공합니다.

  1. 코드 완성 기능으로 더 나은 IDE 지원을 제공합니다. PyCharm 또는 VS Code 와 같은 IDE 를 사용하는 경우 타입 힌트를 사용하여 변수에 대한 올바른 메서드와 속성을 제안할 수 있습니다.
  2. 예상 매개변수 및 반환 유형에 대한 더 명확한 문서를 제공합니다. 함수 정의만 봐도 어떤 유형의 인수를 예상하고 어떤 유형의 값을 반환하는지 알 수 있습니다.
  3. mypy 와 같은 정적 타입 검사기를 사용하여 오류를 조기에 포착할 수 있습니다. 정적 타입 검사기는 코드를 실행하지 않고 분석하여 코드를 실행하기 전에 타입 관련 오류를 찾을 수 있습니다.
  4. 코드 가독성과 유지 관리성을 향상시킵니다. 개발자 또는 다른 개발자가 나중에 코드로 돌아오면 코드가 무엇을 하는지 더 쉽게 이해할 수 있습니다.

대규모 코드베이스에서 이러한 이점은 버그를 크게 줄이고 코드를 더 쉽게 이해하고 유지 관리할 수 있습니다.

참고: 타입 힌트는 Python 에서 선택 사항이지만 전문적인 코드에서 점점 더 많이 사용되고 있습니다. Python 표준 라이브러리의 라이브러리와 많은 인기 있는 타사 패키지에는 이제 광범위한 타입 힌트가 포함되어 있습니다.

요약

이 Lab 에서 Python 의 함수 설계와 관련된 몇 가지 주요 측면을 배웠습니다. 먼저, CSV 데이터를 다양한 데이터 구조로 처리하는 함수를 작성하는 방법을 포함하여 기본적인 함수 설계를 배웠습니다. 또한, 코드의 다용성과 재사용성을 향상시키기 위해 모든 반복 가능한 소스에서 작동하도록 함수를 리팩토링하여 함수 유연성을 탐구했습니다.

더욱이, 헤더가 있거나 없는 CSV 파일과 같이 다양한 사용 사례를 처리하기 위해 선택적 매개변수를 추가하고, 코드 가독성과 유지 관리성을 향상시키기 위해 Python 의 타입 힌트 시스템을 사용하는 방법을 익혔습니다. 이러한 기술은 강력한 Python 코드를 작성하는 데 매우 중요하며, 프로그램이 더 복잡해짐에 따라 이러한 설계 원칙은 코드를 체계적이고 이해하기 쉽게 유지해 줄 것입니다. 이러한 기술은 CSV 처리를 넘어 Python 프로그래밍 도구 상자에서 가치 있게 활용될 수 있습니다.