Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 305 – CSV 파일 API

Author:
Kevin Altis <altis at semi-retired.com>, Dave Cole <djc at object-craft.com.au>, Andrew McNamara <andrewm at object-craft.com.au>, Skip Montanaro <skip at pobox.com>, Cliff Wells <LogiplexSoftware at earthlink.net>
Discussions-To:
Csv list
Status:
Final
Type:
Standards Track
Created:
26-Jan-2003
Python-Version:
2.3
Post-History:
31-Jan-2003, 13-Feb-2003

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

쉼표로 구분된 값(Comma Separated Values, CSV) 파일 형식은 스프레드시트와 데이터베이스에서 가장 흔히 쓰이는 가져오기 및 내보내기 형식입니다. 많은 CSV 파일은 파싱하기 쉽지만, 이 형식은 안정적인 명세로 공식 정의되어 있지 않습니다. 또한 line.split(",") 같은 방식으로 CSV 파일의 줄을 파싱하면 결국 실패할 수밖에 없을 만큼 미묘합니다. 이 PEP는 CSV 파일을 읽고 쓰기 위한 API를 정의합니다. 이 API를 구현하는 모듈도 함께 제공됩니다.

할 일 (관심 있고 의욕 있는 분들을 위한 메모)

적용 범위

이 PEP는 한 가지 일을 잘 해내는 데 집중합니다. 바로 다양한 필드 구분자, 인용 문자, 인용 부호 이스케이프 방식, 줄 끝 문자를 사용할 수 있는 표 형식 데이터를 파싱하는 일입니다. 저자들은 제안된 모듈이 이 한 가지 파싱 문제를 효율적으로 해결하도록 하려고 합니다. 저자들은 다음과 같은 관련 주제는 다룰 생각이 없습니다:

  • 데이터 해석 (문자열 “10”을 담은 필드는 문자열이어야 합니까, float이어야 합니까, int여야 합니까? 10진수입니까, 16진수입니까, 2진수입니까? 따옴표로 둘러싸인 숫자는 숫자입니까, 문자열입니까?)
  • 로케일별 데이터 표현 (숫자 1.23을 “1.23”으로 써야 합니까, “1,23”으로 써야 합니까, 아니면 “1 23”으로 써야 합니까?) – 이 부분은 언젠가 다룰 수도 있습니다.
  • 고정 폭 표 형식 데이터 - 이미 안정적으로 파싱할 수 있습니다.

근거

CSV 파일은 형식이 충분히 단순해서, 한 줄씩 읽은 다음 필드를 구분하는 쉼표를 기준으로 나누기만 해도 그럭저럭 처리할 수 있는 경우가 많습니다. 읽어 들이는 데이터가 모두 숫자라면 특히 그렇습니다. 이 방법은 한동안은 통하겠지만, 누군가 데이터에 쉼표처럼 예상하지 못한 것을 넣는 순간 뒤통수를 맞게 됩니다. 문제를 파고들다 보면 정규 표현식을 사용해서 해결할 수 있겠다는 결론에 이르게 될지도 모릅니다. 이 방법도 한동안은 통하겠지만, 어느 날 원인을 알 수 없이 망가집니다. 문제는 점점 커지고, 더 깊이 파고든 끝에 결국 이 형식을 위해 특별히 만든 파서가 필요하다는 것을 깨닫게 됩니다.

CSV 형식은 명확하게 정의되어 있지 않으며, 구현마다 미묘한 예외 상황이 여럿 있습니다. 약어의 “V”가 “Values”가 아니라 “Vague”(모호한)를 뜻한다는 말까지 있습니다. 서로 다른 구분자와 인용 문자는 시작에 불과합니다. 어떤 프로그램은 각 구분자 뒤에 공백을 넣는데, 이 공백은 뒤따르는 필드에 속하지 않습니다. 필드 안에 포함된 인용 문자를 두 번 겹쳐 써서 인용하는 프로그램도 있고, 앞에 이스케이프 문자를 붙여서 인용하는 프로그램도 있습니다. 이렇게 별난 처리 방식의 목록은 끝이 없어 보일 정도입니다.

이처럼 다양한 방식이 존재하기 때문에, 프로그래머가 해당 출처와 프로그램을 깊이 이해하지 않고서는 여러 출처의 CSV 파일을 안정적으로 파싱하거나 특정 외부 프로그램에 입력하도록 만든 CSV 파일을 생성하기가 어렵습니다. 이 PEP와 함께 제공되는 소프트웨어는 이 과정을 덜 취약하게 만들고자 합니다.

기존 모듈

이 문제는 이전에도 다루어진 적이 있습니다. 현재 Python 커뮤니티에서 사용할 수 있는 모듈 중 적어도 세 가지가 프로그래머가 CSV 파일을 읽고 쓸 수 있게 해 줍니다:

  • Object Craft(객체 크래프트)의 CSV 모듈 [2]
  • Cliff Wells의 Python-DSV 모듈 [3]
  • Laurence Tratt의 ASV 모듈 [4]

각 모듈은 API가 서로 달라서 프로그래머가 모듈 사이를 오가기가 다소 어렵습니다. 더 큰 문제는 이들 모듈이 CSV의 일부 경계 사례를 서로 다르게 해석한다는 점일 수 있습니다. 그래서 프로그래머는 모듈 API 간의 차이를 극복한 뒤에도 패키지 간의 의미상 차이까지 처리해야 합니다.

모듈 인터페이스

이 PEP는 세 가지 기본 API를 지원합니다. 하나는 CSV 파일을 읽고 파싱하는 API이고, 하나는 CSV 파일을 쓰는 API이며, 나머지 하나는 리더와 라이터에 서로 다른 CSV 방언(dialect)을 식별해 주는 API입니다.

CSV 파일 읽기

CSV 리더는 reader 팩토리 함수로 생성합니다:

obj = reader(iterable [, dialect='excel']
             [optional keyword args])

리더 객체는 줄을 반환하는 이터러블 객체를 유일한 필수 매개변수로 받는 이터레이터입니다. 그 이터러블이 바이너리 모드를 지원한다면(파일 객체는 지원합니다), reader 함수에 전달하는 이터러블 인자는 반드시 바이너리 모드로 열려 있어야 합니다. 이렇게 하면 리더 객체가 파일 내용의 해석을 완전히 제어할 수 있습니다. 선택적 dialect 매개변수는 아래에서 설명합니다. reader 함수는 파서의 특정 형식 설정을 정의하는 여러 선택적 키워드 인자도 받습니다(“형식 매개변수” 절을 참조하십시오). 리더는 일반적으로 다음과 같이 사용합니다:

csvreader = csv.reader(file("some.csv"))
for row in csvreader:
    process(row)

리더 객체가 반환하는 각 행은 문자열 또는 유니코드 객체의 리스트입니다.

dialect 매개변수와 개별 형식 매개변수가 모두 생성자에 전달되면, 먼저 방언에서 형식 매개변수를 조회한 다음 개별 형식 매개변수를 검사합니다.

CSV 파일 쓰기

라이터 생성도 이와 비슷합니다:

obj = writer(fileobj [, dialect='excel'],
             [optional keyword args])

라이터 객체는 (바이너리 모드와 텍스트 모드를 구분하는 경우) 바이너리 쓰기 모드로 열린 파일류 객체를 감싸는 래퍼입니다. 라이터 객체는 리더 생성자와 동일한 선택적 키워드 매개변수를 받습니다.

라이터는 일반적으로 다음과 같이 사용합니다:

csvwriter = csv.writer(file("some.csv", "w"))
for row in someiterable:
    csvwriter.writerow(row)

CSV 파일의 첫 번째 행으로 필드 이름 집합을 생성하려면 프로그래머가 이를 명시적으로 기록해야 합니다. 예를 들면 다음과 같습니다.:

csvwriter = csv.writer(file("some.csv", "w"), fieldnames=names)
csvwriter.write(names)
for row in someiterable:
    csvwriter.write(row)

또는 기록되는 이터러블에서 이것이 첫 번째 행이 되도록 구성해야 합니다.

여러 방언 관리하기

CSV는 다소 모호하게 정의된 형식이므로, 두 CSV 파일이 정확히 같은 데이터를 담고 있으면서도 서로 다를 수 있는 경우가 많습니다. 표 형식 데이터를 가져오거나 내보낼 수 있는 많은 도구는 사용자가 필드 구분자, 인용 문자, 줄 종결자 및 파일의 기타 특성을 지정할 수 있게 해 줍니다. 이러한 특성은 비교적 쉽게 판별할 수 있지만 알아내기가 여전히 다소 번거롭고, 개별적으로 지정하면 함수 호출이 상당히 길어집니다.

여러 서식 매개변수를 파악하고 지정하는 어려움을 줄이기 위해, reader 객체와 writer 객체는 dialect 인자를 지원합니다. 이 인자는 이러한 하위 수준 매개변수 묶음을 편리하게 가리키는 핸들일 뿐입니다. 방언이 문자열로 주어지면 모듈의 등록 함수를 통해 모듈에 알려진 방언 중 하나를 가리키며, 그렇지 않으면 아래에서 설명하는 Dialect 클래스의 인스턴스여야 합니다.

방언의 이름은 일반적으로 특정한 형식 제약 조건 집합을 정의하는 애플리케이션이나 조직의 이름을 따서 짓습니다. 이 글을 쓰는 시점에 모듈에는 두 가지 방언이 정의되어 있습니다. 하나는 Excel 97과 Excel 2000에서 CSV 파일을 내보낼 때의 기본 형식 제약 조건을 기술하는 “excel”이고, 다른 하나는 “excel”과 같지만 필드 구분자로 ASCII TAB 문자를 지정하는 “excel-tab”입니다.

방언은 속성만 있는 클래스로 구현되므로, 사용자는 서브클래싱을 통해 변형 방언을 만들 수 있습니다. “excel” 방언은 Dialect의 서브클래스이며 다음과 같이 정의됩니다:

class Dialect:
    # placeholders
    delimiter = None
    quotechar = None
    escapechar = None
    doublequote = None
    skipinitialspace = None
    lineterminator = None
    quoting = None

class excel(Dialect):
    delimiter = ','
    quotechar = '"'
    doublequote = True
    skipinitialspace = False
    lineterminator = '\r\n'
    quoting = QUOTE_MINIMAL

“excel-tab” 방언은 다음과 같이 정의됩니다:

class exceltsv(excel):
    delimiter = '\t'

(개별 서식 매개변수에 대한 설명은 “서식 매개변수” 절을 참조하십시오.)

특정 방언을 문자열로 참조할 수 있도록 모듈은 다음과 같은 여러 함수를 정의합니다:

dialect = get_dialect(name)
names = list_dialects()
register_dialect(name, dialect)
unregister_dialect(name)

get_dialect()는 주어진 이름과 연결된 방언 인스턴스를 반환합니다. list_dialects()는 등록된 모든 방언 이름의 리스트를 반환합니다. register_dialects()는 문자열 이름을 방언 클래스와 연결합니다. unregister_dialect()는 이름/방언 연결을 삭제합니다.

서식 매개변수

reader와 writer 생성자는 모두 dialect 인자 외에도 키워드 매개변수로 지정하는 여러 개별 서식 매개변수를 받습니다. 인식되는 서식 매개변수는 다음과 같습니다:

  • quotechar는 인용 문자로 사용할 한 글자 문자열을 지정합니다. 기본값은 ‘“‘입니다. 이 값을 None으로 설정하면 quoting을 csv.QUOTE_NONE으로 설정한 것과 같은 효과가 있습니다.
  • delimiter는 필드 구분자로 사용할 한 글자 문자열을 지정합니다. 기본값은 ‘,’입니다.
  • escapechar는 quotechar가 None으로 설정되었을 때 구분자를 이스케이프하는 데 사용할 한 글자 문자열을 지정합니다.
  • skipinitialspace는 구분자 바로 뒤에 오는 공백을 어떻게 해석할지 지정합니다. 기본값은 False이며, 이는 구분자 바로 뒤에 오는 공백이 다음 필드의 일부라는 뜻입니다.
  • lineterminator는 행을 끝맺을 문자 시퀀스를 지정합니다.
  • quoting은 writer가 언제 따옴표를 생성할지 제어합니다. 다음 모듈 상수 중 하나를 값으로 가질 수 있습니다:
    • csv.QUOTE_MINIMAL은 필요할 때만, 예를 들어 필드에 quotechar나 구분자가 포함된 경우에만 따옴표를 붙인다는 뜻입니다
    • csv.QUOTE_ALL은 필드를 항상 따옴표로 감싼다는 뜻입니다.
    • csv.QUOTE_NONNUMERIC은 숫자가 아닌 필드를 항상 따옴표로 감싼다는 뜻입니다.
    • csv.QUOTE_NONE은 필드를 절대 따옴표로 감싸지 않는다는 뜻입니다.
  • doublequote는 필드 안의 따옴표 처리 방식을 제어합니다. True이면 읽을 때는 연속된 두 따옴표를 하나로 해석하고, 쓸 때는 각 따옴표를 두 개의 따옴표로 씁니다.

dialect 설정과 하나 이상의 다른 선택적 매개변수를 함께 처리할 때는 dialect 매개변수를 개별 서식 매개변수보다 먼저 처리합니다. 따라서 새 dialect 클래스를 정의하지 않고도 dialect를 선택한 다음 설정을 하나 이상 쉽게 재정의할 수 있습니다. 예를 들어 Excel 2000에서 작은따옴표를 인용 문자로, 콜론을 구분자로 사용해 CSV 파일을 생성했다면 다음과 같이 리더를 만들 수 있습니다.:

csvreader = csv.reader(file("some.csv"), dialect="excel",
                       quotechar="'", delimiter=':')

“excel” dialect를 참조하므로 Excel이 CSV 파일을 생성하는 방식의 나머지 세부 사항은 자동으로 처리됩니다.

리더 객체

리더 객체는 이터러블이며, 이 객체의 next() 메서드는 행의 필드마다 문자열을 하나씩 담은 문자열 시퀀스를 반환합니다.

라이터 객체

라이터 객체에는 writerow()와 writerows()라는 두 가지 메서드가 있습니다. 전자는 출력에 기록할 필드들의 이터러블(일반적으로 리스트)을 받습니다. 후자는 이터러블의 리스트를 받아 각 이터러블에 대해 writerow()를 호출합니다.

구현

샘플 구현이 제공됩니다. [1] 이 구현의 목표는 이 PEP에서 설명하는 API를 효율적으로 구현하는 것입니다. 이 구현은 Object Craft(객체 크래프트)의 csv 모듈을 크게 참고하였습니다. [2]

테스트

샘플 구현 [1]에는 테스트 케이스 모음이 포함되어 있습니다.

문제점

  1. 연속된 구분자를 해석하는 방식을 매개변수로 제어해야 합니까? 저희 생각은 “아니요”입니다. 연속된 구분자는 항상 빈 필드를 나타내야 합니다.
  2. 유니코드는 어떻습니까? codecs.open()으로 얻은 파일 객체를 전달하는 것으로 충분합니까? 예를 들면 다음과 같습니다.:
    csvreader = csv.reader(codecs.open("some.csv", "r", "cp1252"))
    
    csvwriter = csv.writer(codecs.open("some.csv", "w", "utf-8"))
    

    첫 번째 예에서는 텍스트가 cp1252로 인코딩되어 있다고 가정합니다. 시스템이 적극적으로 유니코드로 변환해야 합니까, 아니면 필요한 경우에만 유니코드 문자열을 반환해야 합니까?

    두 번째 예에서는 파일이 디스크에 기록하기 전에 유니코드 문자열을 utf-8로 자동으로 인코딩합니다.

    참고: 이 글을 쓰는 시점에 csv 모듈은 유니코드 데이터를 처리하지 않습니다.

  3. 다른 이스케이프 방식은 어떻습니까? 사용 중인 dialect에 None이 아닌 escapechar 매개변수가 있고 quoting 매개변수가 QUOTE_NONE으로 설정되어 있으면, 필드 안에 나타나는 구분자는 기록할 때 앞에 이스케이프 문자가 붙습니다. 읽을 때도 이런 구분자 앞에는 이스케이프 문자가 붙어 있어야 합니다.
  4. 기록할 때 쓰는 “모두 인용” 모드가 있어야 합니까? “숫자 값을 제외하고 모두 인용”하는 모드는 어떻습니까? 두 모드 모두 구현되어 있습니다(각각 QUOTE_ALL과 QUOTE_NONNUMERIC).
  5. 줄 끝(end-of-line)은 어떻게 처리합니까? Unix 시스템에서 CSV 파일을 생성하면 Excel이 LF만 있는 줄 종결자를 제대로 인식합니까? 파일은 읽기용이든 쓰기용이든 용도에 맞게 바이너리 모드로 열어야 합니다. lineterminator 시퀀스를 '\r\n'으로 지정하십시오. 그러면 결과 파일이 올바르게 작성됩니다.
  6. 리더가 딕셔너리를 생성하고 라이터가 딕셔너리를 받아들이는 옵션은 어떻습니까? csv.py의 DictReader 및 DictWriter 클래스를 참고하십시오.
  7. 인용 문자와 구분자는 단일 문자로 제한됩니까? 당분간은 그렇습니다.
  8. 길이가 서로 다른 행은 어떻게 처리해야 합니까? 데이터 해석은 애플리케이션이 맡을 일입니다. 이 수준에서는 “짧은 행”이나 “긴 행” 같은 것이 존재하지 않습니다.

참고 문헌

웹에는 다른 CSV 관련 프로젝트에 대한 참고 자료가 많이 있습니다. 여기에는 그중 몇 가지를 소개합니다.