Python API

Python API

DuckDB Python API는 duckdb.sql 명령으로 SQL 쿼리를 가장 간단하게 실행할 수 있게 해줘요. 결괏값은 Relation 객체로 반환되고, Pandas/Polars/Arrow 데이터프레임도 직접 쿼리할 수 있답니다. 함께 살펴볼까요?

출처: 문서

본문

설치: DuckDB Python 클라이언트를 사용하려면 [Python 설치 페이지]({% link install/index.html %}?environment=python)를 방문하세요.

DuckDB Python 클라이언트의 최신 안정 버전은 {{ site.current_duckdb_version }}이에요.

설치 (Installation)

DuckDB Python API는 pip로 설치할 수 있어요: pip install duckdb. 자세한 내용은 [설치 페이지]({% link install/index.html %}?environment=python)를 참고하세요. conda로도 설치할 수 있어요: conda install python-duckdb -c conda-forge.

Python 버전: DuckDB는 Python 3.9 이상이 필요해요.

기본 API 사용법 (Basic API Usage)

DuckDB로 SQL 쿼리를 실행하는 가장 간단한 방법은 duckdb.sql 명령을 사용하는 거예요.

import duckdb

duckdb.sql("SELECT 42").show()

이것은 Python 모듈 안에 전역으로 저장된 인메모리 데이터베이스를 사용해 쿼리를 실행해요. 쿼리 결과는 Relation으로 반환돼요. relation은 쿼리의 기호적(symbolic) 표현이에요. 쿼리는 결과를 가져오거나 화면에 출력을 요청할 때까지 실행되지 않아요.

Relation은 변수에 저장하고 테이블로 사용해서 이후 쿼리에서 참조할 수 있어요. 이렇게 하면 쿼리를 점진적으로 구성할 수 있답니다.

import duckdb

r1 = duckdb.sql("SELECT 42 AS i")
duckdb.sql("SELECT i * 2 AS k FROM r1").show()

데이터 입력 (Data Input)

DuckDB는 디스크에 있든 인메모리에 있든 다양한 형식의 데이터를 받아들일 수 있어요. 자세한 내용은 [데이터 수집 페이지]({% link docs/current/clients/python/data_ingestion.md %})를 참고하세요.

import duckdb

duckdb.read_csv("example.csv")                # CSV 파일을 Relation으로 읽기
duckdb.read_parquet("example.parquet")        # Parquet 파일을 Relation으로 읽기
duckdb.read_json("example.json")              # JSON 파일을 Relation으로 읽기

duckdb.sql("SELECT * FROM 'example.csv'")     # CSV 파일 직접 쿼리
duckdb.sql("SELECT * FROM 'example.parquet'") # Parquet 파일 직접 쿼리
duckdb.sql("SELECT * FROM 'example.json'")    # JSON 파일 직접 쿼리

데이터프레임 (DataFrames)

DuckDB는 Pandas DataFrame, Polars DataFrame, Arrow 테이블을 직접 쿼리할 수 있어요. 이것들은 읽기 전용이라는 점을 주의하세요. 즉 [INSERT]({% link docs/current/sql/statements/insert.md %})나 [UPDATE 문]({% link docs/current/sql/statements/update.md %})으로 이 테이블을 편집하는 것은 불가능해요.

Pandas

Pandas DataFrame을 직접 쿼리하려면:

import duckdb
import pandas as pd

pandas_df = pd.DataFrame({"a": [42]})
duckdb.sql("SELECT * FROM pandas_df")
┌───────┐
│   a   │
│ int64 │
├───────┤
│    42 │
└───────┘
Polars

Polars DataFrame을 직접 쿼리하려면:

import duckdb
import polars as pl

polars_df = pl.DataFrame({"a": [42]})
duckdb.sql("SELECT * FROM polars_df")
┌───────┐
│   a   │
│ int64 │
├───────┤
│    42 │
└───────┘
PyArrow

PyArrow 테이블을 직접 쿼리하려면:

import duckdb
import pyarrow as pa

arrow_table = pa.Table.from_pydict({"a": [42]})
duckdb.sql("SELECT * FROM arrow_table")
┌───────┐
│   a   │
│ int64 │
├───────┤
│    42 │
└───────┘

결과 변환 (Result Conversion)

DuckDB는 쿼리 결과를 다양한 형식으로 효율적으로 변환하는 것을 지원해요. 자세한 내용은 [결과 변환 페이지]({% link docs/current/clients/python/conversion.md %})를 참고하세요.

import duckdb

duckdb.sql("SELECT 42").fetchall()   # Python 객체
duckdb.sql("SELECT 42").df()         # Pandas DataFrame
duckdb.sql("SELECT 42").pl()         # Polars DataFrame
duckdb.sql("SELECT 42").arrow()      # Arrow Table
duckdb.sql("SELECT 42").fetchnumpy() # NumPy Arrays

데이터를 디스크에 쓰기 (Writing Data to Disk)

DuckDB는 Relation 객체를 다양한 형식으로 디스크에 직접 쓰는 것을 지원해요. 대안으로 [COPY 문]({% link docs/current/sql/statements/copy.md %})을 SQL로 사용해 데이터를 디스크에 쓸 수도 있어요.

import duckdb

duckdb.sql("SELECT 42").write_parquet("out.parquet") # Parquet 파일로 쓰기
duckdb.sql("SELECT 42").write_csv("out.csv")         # CSV 파일로 쓰기
duckdb.sql("COPY (SELECT 42) TO 'out.parquet'")      # Parquet 파일로 복사

연결 옵션 (Connection Options)

애플리케이션은 duckdb.connect() 메서드로 새 DuckDB 연결을 열 수 있어요.

인메모리 데이터베이스 사용 (Using an In-Memory Database)

duckdb.sql()으로 DuckDB를 사용하면 인메모리 데이터베이스에서 동작해요. 즉 어떤 테이블도 디스크에 저장되지 않아요. 인자 없이 duckdb.connect() 메서드를 호출하면 연결을 반환하는데, 이것도 인메모리 데이터베이스를 사용해요:

import duckdb

con = duckdb.connect()
con.sql("SELECT 42 AS x").show()

영속 저장소 (Persistent Storage)

duckdb.connect(dbname)영속 데이터베이스에 대한 연결을 만들어요. 그 연결에 쓰는 어떤 데이터든 지속되고, Python과 다른 DuckDB 클라이언트 모두에서 같은 파일에 다시 연결해서 다시 불러올 수 있어요.

import duckdb

# 'file.db'라는 파일에 대한 연결 생성
con = duckdb.connect("file.db")
# 테이블을 만들고 데이터 로드
con.sql("CREATE TABLE test (i INTEGER)")
con.sql("INSERT INTO test VALUES (42)")
# 테이블 쿼리
con.table("test").show()
# 연결을 명시적으로 닫기
con.close()
# 참고: 연결은 범위를 벗어날 때 암시적으로 닫히기도 해요

연결이 닫히는 것을 보장하기 위해 컨텍스트 매니저를 사용할 수도 있어요:

import duckdb

with duckdb.connect("file.db") as con:
    con.sql("CREATE TABLE test (i INTEGER)")
    con.sql("INSERT INTO test VALUES (42)")
    con.table("test").show()
    # 컨텍스트 매니저가 자동으로 연결을 닫아요

구성 (Configuration)

duckdb.connect()config 딕셔너리를 받는데, 여기서 [구성 옵션]({% link docs/current/configuration/overview.md %}#configuration-reference)을 지정할 수 있어요. 예를 들어:

import duckdb

con = duckdb.connect(config = {'threads': 1})

[저장 버전]({% link docs/current/internals/storage.md %})을 지정하려면 storage_compatibility_version 옵션을 전달하세요:

import duckdb

con = duckdb.connect(config = {'storage_compatibility_version': 'latest'})

연결 객체와 모듈 (Connection Object and Module)

연결 객체와 duckdb 모듈은 같은 메서드를 지원하기 때문에 서로 바꿔 쓸 수 있어요. 유일한 차이는 duckdb 모듈을 사용할 때는 전역 인메모리 데이터베이스를 사용한다는 점이에요.

다른 사람이 쓰도록 설계한 패키지를 개발하고 있고, 그 패키지에서 DuckDB를 사용한다면, duckdb 모듈의 메서드를 쓰는 대신 연결 객체를 만드는 것이 좋아요. 그 이유는 duckdb 모듈이 공유된 전역 데이터베이스를 사용하는데, 여러 다른 패키지에서 쓰면 디버깅하기 어려운 이슈가 생길 수 있기 때문이에요.

병렬 Python 프로그램에서 연결 사용 (Using Connections in Parallel Python Programs)

duckdb.sql()의 스레드 안전성과 전역 연결 (Thread Safety of duckdb.sql() and the Global Connection)

duckdb.sql()duckdb.connect(':default:')은 공유된 전역 인메모리 연결을 사용해요. 이 연결은 스레드 안전하지 않아서, 여러 스레드에서 쿼리를 실행하면 문제가 생길 수 있어요. DuckDB를 병렬로 실행하려면 각 스레드가 자신만의 연결을 가져야 해요:

def good_use():
    con = duckdb.connect()
    # 새 연결 사용
    con.sql("SELECT 1").fetchall()

반대로 다음은 전역 연결에 의존하기 때문에 동시성 문제를 일으킬 수 있어요:

def bad_use():
    con = duckdb.connect(':default:')
    # 전역 연결 사용
    return con.sql("SELECT 1").fetchall()

또는:

def also_bad():
    return duckdb.sql("SELECT 1").fetchall()
    # 전역 연결 사용

duckdb.sql()을 사용하거나 단일 연결을 스레드 간에 공유하는 것은 피하세요.

cursor()에 대하여 (About cursor())

[DuckDBPyConnection.cursor() 메서드]({% link docs/current/clients/python/reference/index.md %}#duckdb.DuckDBPyConnection.cursor)는 같은 연결에 대한 또 다른 핸들을 만들어요. 새 연결을 열지 않아요. 따라서 하나의 연결에서 만든 모든 커서는 동시에 쿼리를 실행할 수 없어요.

커뮤니티 확장 (Community Extensions)

[커뮤니티 확장]({% link community_extensions/index.md %})을 로드하려면 install_extension 메서드에 repository="community" 인자를 사용하세요.

예를 들어 h3 커뮤니티 확장을 다음과 같이 설치하고 로드하세요:

import duckdb

con = duckdb.connect()
con.install_extension("h3", repository="community")
con.load_extension("h3")

서명 없는 확장 (Unsigned Extensions)

[서명 없는 확장]({% link docs/current/extensions/overview.md %}#unsigned-extensions)을 로드하려면 다음과 같이 하세요:

con = duckdb.connect(config={"allow_unsigned_extensions": "true"})

경고 신뢰하는 소스에서만 서명 없는 확장을 로드하세요. HTTP로 서명 없는 확장을 로드하는 것은 피하세요. DuckDB를 안전하게 설정하는 방법에 대한 지침은 [Securing DuckDB 페이지]({% link docs/current/operations_manual/securing_duckdb/securing_extensions.md %})를 참고하세요.

더 알아보기 (Learn more)