[천재교육] 프로젝트 기반 빅데이터 서비스 개발자 양성 과정 9기
학습일 : 2024.10.25
📕 학습 목록
- 데이터 엔지니어링
- 데이터 파이프라인 모듈 구성
- Connection Pool
- ORM
- DB Connection 살습
📗 기억할 내용
1. 데이터 엔지니어링
1) 데이터 엔지니어링이란?
- 데이터 엔지니어링: 데이터를 수집하고 활용할 수 있도록 시스템을 구축하는 것
- 대규모 데이터를 효율적으로 수집, 저장, 처리 및 전송하기 위한 기술과 인프라를 개발하는 작업
2) 데이터 관리와 분석
- Data Lake(DL): 다양한 형태의 데이터를 원시 형태로 대규모로 저장하는 시스템
- 구조화된 데이터 뿐만 아니라 비구조화된 데이터까지 모두 수용
- 원시 데이터저장, 스키마 미리 정의하지 않음, 대용량 데이터 처리에 유리
- Data Warehouse(DW): 여러 소스에서 데이터를 가져와 분석에 적합하도록 정리된 구조화된 데이터 저장소
- 정제된 데이터 저장, 스키마 정의 필요
- 비즈니스 인텔리전스(BI), 보고서 생성에 사용
- Data Mart(DM): 특정 부서에서 사용할수 있도록 데이터 웨어하우스의 일부 데이터를 추출해 소규모로 저장한 것
- 특정 목적에 맞춘 소규모 데이터 저장소
- 빠르고 간편한 접근 가능, 특정 비즈니스 기능에 최적화
- Data Governance: 데이터의 품질, 보안, 규정 준수를 보장하기 위해 데이터를 관리하고 정책을 수립하는 체계적인 프로세스
- 데이터의 일관성, 무결성, 접근 권한 등을 관리
- 기업의 데이터 활용 가치를 극대화
- Data Pipeline: 데이터를 수집, 처리, 저장하는 일련의 자동화된 과정(≒ ETL)
- 데이터 파이프라인의 주요 단계
- 데이터 수집: 다양한 소스(데이터베이스, 파일, API, IoT 디바이스 등)로 부터 데이터를 가져옴
- 데이터 처리: 수집한 데이터를 변환, 정제, 통합
- 데이터 저장 및 전달: 처리된 데이터를 DL, DW, DM 또는 분석 시스템에 저장∙전달하여 분석∙의사결정에 활용할 수 있게 함
- 데이터 파이프라인의 활용 예시
- 웹 로그 분석: 웹사이트 로그 데이터를 수집하여 사용자 행동을 분석하고, 마케팅 전략 수립에 활용
- 데이터 통합: 여러 시스템의 데이터를 통합하여 하나의 저장소에 저장해, 전체 데이터를 통합적으로 분석
- 머신러닝 데이터 준비: 데이터를 수집하고 전처리하여 머신러닝 모델에 적합한 데이터셋을 제공
- 데이터 파이프라인의 주요 단계
3) Data vs Meta Data
- 데이터
- 실제 내용이나 정보 그 자체
- 분석, 저장, 처리의 대상
- 메타데이터
- 데이터를 설명하는 데이터; 데이터에 대한 설명 또는 속성 정보
- 데이터를 더 잘 이해하고 관리할 수 있도록 도움
- 검색∙분류를 더 쉽게 해줌
4) OLTP vs OLAP
- OLTP (Online Transaction Processing, 온라인 트랜잭션 처리)
- 트랜잭션을 실시간으로 처리하고 업데이트하는 시스템
- 은행, 쇼핑몰 등에서 발생하는 실시간 데이터 작업에 적합
- OLAP (Online Analytical Processing, 온라인 분석 처리)
- 대량의 데이터를 분석하고 비즈니스 의사결정을 돕는 시스템
- 매출이나 고객 행동 패턴 분석같이 의사결정을 위한 데이터 분석에 적합
5) Batch vs Stream
- Batch Processing: 많은 양의 데이터를정해진 시간에 일괄적으로 처리하는 것
- Stream Processing: 실시간으로 들어오는 데이터를 계속 처리하는 것
2. 데이터 파이프라인 모듈 구성
1) 데이터 파이프라인 모듈 파일의 각 역할
- 데이터 파이프라인이 데이터를 수집, 처리, 저장하는 과정을 어떻게 나누고 있는지 확인
2) 데이터 파이프라인 모듈 구성 설계 원칙
- 객체 지향적 프로그래밍: 파이프라인 모듈이 객체 지향적 방식으로 설계. 즉, 각 기능을 클래스와 객체 단위로 나누어 관리하고 재사용성을 높이는 방식으로 설계
- 하나의 실행 파일은 하나의 기능만 담당: 각 파일이 특정한 하나의 역할에만 집중하도록 설계. 예를 들어, extract.py 는 데이터 추출만 담당하는 식으로 모듈화 됨
- 기능과 연관 있는 직관적인 명명: 각 파일이 하는 기능을 직관적으로 알 수 있도록 명명. 예를 들어, query.py는 쿼리와 관련된 기능을 맡고 있음
- 관련성 있는 실행 파일은 같은 폴더 아래에: 관련된 파일들(예: 데이터베이스 관련 파일, 파이프라인 관련 파일 등)은 같은 폴더에 그룹화되어 구조적으로 정리됨
3) 절차적 프로그래밍 vs 객체 지향 프로그래밍
- 절차적 프로그래밍 (Procedural Programming): 순차적 흐름에 따라 프로그램을 작성하는 방식
- 함수(or 프로시저)를 사용해 코드를 모듈화
- 함수가 데이터에 접근해 작업을 수행
- 객체지향 프로그래밍(OOP, Object-Oriented Programming): 객체라는 개념을 통해 프로그램을 구조화하는 방식
- 클래스와 객체를 사용해 데이터를 캡슐화
- 객체 간 상호작용을 통해 작업을 수행
절차적 프로그래밍 | 객체지향 프로그래밍 | |
구조 | 순차적 흐름, 함수 기반 | 객체와 클래스 기반 |
데이터 관리 | 함수와 데이터가 분리되어 있음 | 데이터와 메서드를 객체 단위로 묶어서 관리 |
재사용성 | 함수 단위로 재사용 가능 | 상속과 다형성을 통한 높은 재사용성 |
유지보수성 | 복잡한 프로그램에서 유지보수가 어려움 | 코드 구조가 모듈화되어 유지보수가 쉬움 |
적용 예시 | 작은 규모의 프로젝트, 단순한 스크립트 | 대규모 프로젝트, 복잡한 시스템 |
예제 언어 | C, Pascal | Java, Python, C++ |
3. Connection Pool
1) 정의
- 데이터베이스와의 연결을 효율적으로 관리하기 위해 일정량의 Connection 객체를 미리 생성하여 풀에 저장해 두는 구조
2) 작동 원리
- 클라이언트가 데이터베이스 연결을 요청할 때마다 새 연결을 생성하는 대신, 이미 만들어진 Connection 객체를 빌려줌
- 작업이 끝난 후에는 Connection 객체가 반납되어 다시 Pool에 저장됨
- 데이터베이스 연결은 시스템 자원을 사용하므로, 작업이 끝난 후 반드시 풀로 반납해야 함
- 이를 위해 Python의 with문을 사용하면 자동으로 연결이 반납되어 자원 누수를 방지할 수 있음
- 적정 Connection 수 공식: https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing#the-formula
- 큰 Connection Pool 은 메모리 소모가 큰 대신 대기 시간이 적어짐
- 작은 Connection Pool은 메모리 소모가 적은 대신 대기 시간이 길어짐
3) 파이썬 with문 활용 예시
- 이 방식은 Connection Pool 내의 Connection 자원을 효율적으로 관리하고, 성능을 최적화 함
import mysql.connector
with mysql.connector.connect(...) as connection:
with connection.cursor() as cursor:
# 작업 수행
# with문이 끝나면 자동으로 connection이 풀에 반납됨
4. ORM(Object Relational Mapping)
1) 정의
- 프로그래밍 언어(ex: 파이썬)의 객체를 관계형 데이터베이스의 테이블에 매핑해주는 기술
- 데이터베이스와의 상호작용을 객체 지향적으로 데이터를 처리할 수 있음
2) 작동 방식
- SQL 쿼리를 직접 작성하지 않고 객체를 통해 데이터베이스에 접근하고 데이터를 조작함
- 파이썬에서는 Django ORM, SQLAlchemy ORM이 대표적임
3) 장점
- 코드 기반 데이터 조작: SQL 쿼리 대신 코드로 데이터 조작이 가능하여 프로그래밍에 더 집중할 수 있음
- 재사용성과 유지보수: 데이터베이스 스키마 변경 시에도 ORM 모델만 수정하면 되므로, 재사용 및 유지보수가 편리
- DBMS 종속성 감소: 특정 데이터베이스 관리 시스템(DBMS)에 종속되지 않아 다양한 DBMS로 손쉽게 교체할 수 있음
📘코드 실습
[파이썬 클래스 선언]
1) 필요한 패키지 한 번에 설치
# requirements.txt
psycopg2==2.9.9
pandas==2.1.1
numpy==1.26.1
ipykernel==6.25.2
SQLAlchemy==2.0.22
python-dotenv==1.0.0
pymysql==1.1.0
pyarrow==14.0.0
click==8.1.7
Faker
easydict
boto3
shortuuid==1.0.11
streamlit==1.31.1
streamlit-autorefresh==1.0.1
altair==4.0
vega_datasets==0.9.0
① ctrl shift ` : requirements.txt 파일 위치로 경로 이동하여 터미널 실행
② pip install -r requirements.txt : requirements.txt 파일 내 패키지들 한 번에 설치
2) 외부에서 파일 실행하기
① 폴더(test_folder) 생성 → 폴더 내에 두 가지 파일 생성
- __init__.py : __init__.py 파일이 있어야 파이썬은 해당 폴더를 패키지로 인식함 → import로 불러올 수 있게 됨
- test.py : 클래스(test_class)를 정의할 파일
[tip] __xxx__ 표기법 (Dunder Name, 던더 네임)
: 파이썬에서 특별한 의미나 내부 동작을 정의하는 데 사용됨. __init__이나 __tablename__처럼, 파이썬이 자동으로 인식하고 특정 기능을 수행하도록 만들어진 이름
② test.py 에 코드 입력 후 실행
# test.py
print("오늘은 날씨가 좋습니다.")
print("Hello World")
"""
오늘은 날씨가 좋습니다.
Hello World
"""
③ test_folder 폴더와 같은 경로에 test2.py 생성
④ test2.py에서 test_folder>test.py 가 실행되게 만들기
# test2.py
from test_folder import test
"""
오늘은 날씨가 좋습니다.
Hello World
"""
3) 클래스 정의 후, 외부에서 호출하기
[tip] 클래스와 객체 개념 정리
* 클래스와 객체의 개념
- 클래스(Class): 객체를 생성하기 위한 청사진(설계도). 클래스에는 특정 유형의 객체가 가지는 속성(데이터)과 메서드(기능)가 정의됨
- 객체(Object): 클래스에 의해 생성된 구체적인 인스턴스.하나의 클래스에서 여러 객체를 만들 수 있으며, 각 객체는 독립적인 상태와 속성을 가질 수 있음
* __init__ 메서드의 의미와 역할
- 초기화 (Initialization): __init__ 메서드는 객체가 생성될 때 자동으로 실행됨. 주로 객체의 초기 속성을 설정하는 역할을 하며, 이를 통해 객체가 생성되자마자 필요한 초기 값들을 가질 수 있게 함
- 매개변수 전달: 객체를 생성할 때 필요한 초기값들을 __init__의 매개변수로 받을 수 있음. 이 값들은 각각의 객체마다 다를 수 있으며, 객체가 생성될 때 사용자가 원하는 초기 상태로 설정할 수 있음
- 인스턴스 변수 설정: self 키워드를 사용해 인스턴스 변수에 접근하고 초기값을 할당. 인스턴스 변수는 각 객체마다 독립적으로 존재하는 변수로, 하나의 클래스에서 생성된 여러 객체가 각기 다른 상태를 가질 수 있도록 도움
- 예를 들어, 다음과 같은 __init__ 메서드를 가진 클래스가 있을 때 이 클래스에서 생성된 객체는 name과 age 라는 인스턴스 변수를 가지며, 각 객체는 서로 다른 이름과 나이를 가질 수 있음class Person: def __init__(self, name, age): self.name = name self.age = age
* __init__ 메서드를 사용하는 이유
- 객체의 독립성 유지: __init__ 메서드를 통해 각 객체가 생성될 때 필요한 초기 값을 설정하여 객체마다 독립적인 속성을 가질 수 있게 함. 이는 객체 지향 프로그래밍의 중요한 원칙인 객체 독립성을 유지하는 데 필수적임
- 자동 초기화: 객체를 생성할 때마다 동일한 초기 설정이 필요하다면, __init__ 메서드에 정의해두어 객체 생성과 동시에 초기 설정이 자동으로 이루어지도록 할 수 있음
- 예를 들어, 각 객체의 name이나 age와 같은 기본 속성을 미리 설정하여 생성 후 별도의 설정 없이 사용할 수 있게 함
- 코드 가독성 및 유지보수성 향상: __init__ 메서드로 초기값 설정이 체계적으로 이루어지면, 코드의 구조가 명확해지고, 유지보수성이 향상됨
* __init__ 메서드 정의가 필수인가?
- __init__ 메서드는 필수가 아니며, 클래스에 특별한 초기화 작업이 필요 없을 경우 생략할 수 있음
- 단, __init__을 정의하지 않으면 객체 생성 후 초기 속성을 수동으로 설정해야 하기 때문에, 매번 객체 생성 시 추가 설정을 반복해야 할 수 있음
① 클래스(test_class)가 정의된 파일(test.py) 만들기
- 클래스 : 겍체(인스턴스)를 생성할 수 있는 일종의 툴을 제공
- 클래스 변수 : 클래스에 속해 있는 변수로, 모든 인스턴스가 공통으로 사용하게 됨
- __init__ 메서드 : 생성자 메서드. 클래스의 인스턴스(객체)가 생성될 때 자동으로 호출되어 객체의 초기 설정을 해줌
- self : 생성된 인스턴스 자신을 가리킴. 인스턴스 내부에 데이터를 저장하려면 self를통해 접근해야 함
- A, B, C : 생성 시 외부에서 전달받는 값. self.a = A 와 같이, 전달받은 값들은 인스턴스 변수 a, b, c에 저장함
- self.a, self.b, self.c : 인스턴스 변수. 각각 인스턴스별로 독립적으로 존재. 인스턴스마다 다른 값을 가질 수 있음
- 나머지 메서드들 : 클래스 내부에 있는 함수들은 "메서드"라고 부름
- text_concat 메서드 : self.a 와 self.b 의 값을 문자열로 연결해 반환
- text_concat_v2/v3 메서드 : self.a, self.b, self.c에 클래스 변수 temp0, temp1을 덧붙여 연결한 문자열을 반환
- temp=temp0 : text_concat_v2 메서드를 호출할 때 기본적으로 temp0 값을 사용할 것을 지정한 것
# text_concat_v2() 메서드에 매개변수 입력/미입력시 반환되는 결과
obj = test_class("A", "B", "C")
print(obj.text_concat_v2()) # 출력: "AB0" (기본값 사용)
print(obj.text_concat_v2("X")) # 출력: "ABX" (매개변수 값 사용)
# test.py
class test_class: # 클래스 선언
temp0 = "0" # 클래스 변수
temp1 = "1"
def __init__(self, A, B, C): # __init__() 메서드
self.a = A
self.b = B
self.c = C
def text_concat(self): # 인스턴스의 매개변수를 받지 않음(인스턴스에서 해당 메서드 호출 시 매개변수 입력할 경우, 오류 발생)
return self.a + self.b
def text_concat_v2(self, temp=temp0): # 인스턴스의 매개변수를 받음. 받지 않을 경우 기본값(클래스 변수 temp0)으로 대체
return self.a + self.b + temp
def text_concat_v3(self, temp=temp1):
return self.c + temp
② 다른 파일(test2.py)에서 클래스 불러오기 및 인스턴스 생성(example)
# test2.py
from test import test_class # test.py 파일에서 test_class 클래스를 불러옴
# 인스턴스 생성
example = test_class("안녕하세요", "!", "밥 먹으러 가요.")
# 메서드 호출
result1 = example.text_concat()
result2 = example.text_concat_v2()
result3 = example.text_concat_v3()
# 결과 출력
print(result1) # 출력: 안녕하세요!
print(result2) # 출력: 안녕하세요!0
print(result3) # 출력: 밥 먹으러 가요.1
[파이썬에서 DB Connection]
1. psycopg2 라이브러리를 통한 Connection
[요약]
- psycopg2: PostgreSQL 데이터베이스와의 연결을 제공
- cursor.execute(): 연결된 데이터베이스에서 SQL을 실행하고 데이터를 처리
- db.commit(): 데이터베이스에 대한 모든 변경 작업을 확정; 변경된 데이터를 데이터베이스에 영구적으로 저장
- psycopg2 : 파이썬에서 PostgreSQL 데이터베이스와 연결하고, SQL 쿼리를 실행할 수 있도록 해주는 라이브러리
- PostgreSQL 연결: psycopg2.connect()를 사용해 PostgreSQL 데이터베이스에 연결할 수 있으며, 연결된 데이터베이스에서 쿼리를 실행하고 결과를 받을 수 있음
- SQL 실행: psycopg2를 통해 SELECT, INSERT, UPDATE, DELETE 등 다양한 SQL 쿼리를 실행할 수 있음
- 트랜잭션 관리: psycopg2는 트랜잭션을 자동으로 관리하며, 변경사항을 커밋하거나 롤백할 수 있는 기능을 제공
- cursor : 데이터베이스와의 연결을 통해 SQL 쿼리문을 실행하고 결과를 처리하는 객체
- 쿼리 실행: cursor.execute() 메서드를 사용하여 SQL 쿼리문을 실행할 수 있음. 예를 들어, cursor.execute("SELECT * FROM table_name")과 같이 쓸 수 있음
- 결과 처리: 쿼리 실행 후 반환된 데이터를 가져오기 위해 fetchone(), fetchall(), fetchmany() 등의 메서드를 사용할 수 있음
- fetchone(): 한 행씩 결과를 가져옴
- fetchall(): 모든 결과를 리스트로 가져옴
- fetchmany(size): 지정한 크기만큼의 결과를 가져옴
- 메모리 공간 관리: cursor는 데이터베이스에서 반환된 결과값을 메모리에 저장하고, 이를 순서대로 가져오도록 지원함
- 리소스 해제: 데이터베이스 작업이 완료되면 cursor.close()로 cursor를 닫아 메모리와 데이터베이스 리소스를 해제하는 것이 좋음
1) 데이터베이스 Connection
import psycopg2
# 데이터베이스 연결 → 객체(db)에 저장
db = psycopg2.connect(
host = '127.0.0.1',
dbname = 'postgres',
user = 'postgres',
password = '1234',
port=5432
)
# 커서 생성 > 특정 SQL 문장을 처리한 결과를 담고 있는 영역을 가리키는 일종의 포인터 / 쿼리문에 의해서 반환되는 결과값들을 저장하는 메모리 공간
cursor=db.cursor()
2) CRUD 실행
① SQL 쿼리문을 변수에 할당
# SQL 쿼리문을 변수에 할당
# CREATE TABLE 쿼리
create_query = """
CREATE TABLE lecture (
id SERIAL PRIMARY KEY,
name VARCHAR(32),
year INT,
gender VARCHAR(10),
count INT
);
"""
# INSERT 쿼리
insert_query = """
INSERT INTO lecture (id, name, year, gender, count)
VALUES (1, 'Tom', 2024, 'M', 1100);
"""
# UPDATE 쿼리
update_query = """
UPDATE lecture
SET id = 1,
name = 'CHUNJAE',
year = 1994,
gender = 'M',
count = 1000
WHERE id = 1;
"""
# DELETE 쿼리
delete_query = "DELETE FROM lecture WHERE id = 1;"
# DROP TABLE 쿼리
drop_query = "DROP TABLE lecture;"
② SQL 쿼리문 실행
- CREATE TABLE : 테이블 생성
# 'lecture'라는 테이블을 생성하고, 각 열의 데이터 타입을 정의함
cursor.execute(create_query)
db.commit() # 테이블 생성 후 변경사항을 커밋하여 저장
- INSERT ROW : 데이터 넣기
# 'lecture' 테이블에 id, name, year, gender, count 값을 가진 한 행을 추가
cursor.execute(insert_query)
db.commit() # 데이터 삽입 후 변경사항을 커밋하여 저장
- SELECT : 넣은 데이터확인
# 'lecture' 테이블에 추가된 데이터를 확인하기 위해 전체 데이터 조회
cursor.execute("SELECT * FROM lecture")
cursor.fetchall() # 결과를 모두 가져옴
- UPDATE ROW : 테이블에서 id=1인 행의 데이터 업데이트
# 'lecture' 테이블에서 id가 1인 행의 데이터를 업데이트
cursor.execute(update_query)
db.commit() # 데이터 수정 후 변경사항을 커밋하여 저장
# 업데이트 잘 됐는지 확인
# 업데이트된 데이터를 확인하기 위해 다시 전체 데이터 조회
cursor.execute("SELECT * FROM lecture")
cursor.fetchall() # 업데이트된 결과를 모두 가져옴
- DELETE ROW : 테이블에서 id=1인 행을 삭제
# DELETE ROW
# 'lecture' 테이블에서 id가 1인 행을 삭제
cursor.execute(delete_query)
db.commit() # 데이터 삭제 후 변경사항을 커밋하여 저장
# 삭제 잘 됐는지 확인
# 삭제된 결과를 확인하기 위해 다시 전체 데이터 조회
cursor.execute("SELECT * FROM lecture")
cursor.fetchall() # 삭제 후 결과를 모두 가져옴 (이 행이 비어 있어야 정상 삭제)
- DROP TABLE : 테이블 삭제
# 'lecture' 테이블을 삭제
cursor.execute(drop_query)
db.commit() # 테이블 삭제 후 변경사항을 커밋하여 저장
# 드랍 잘 됐는지 확인; 해당 테이블이 없어서 에러남
# cursor.execute("SELECT * FROM lecture")
# cursor.fetchall() # 이 행을 실행하면 테이블이 없어서 에러 발생
③ Connection 종료
cursor.close() # 이제 쿼리문 돌려도 에러뜸
2. SQLAlchemy 라이브러리를 통한 Connection
1) SQLAlchemy ORM 방식
- ORM(Object-Relational Mapping, 객체 관계 매핑): 객체 지향 프로그래밍 언어를 사용하여 데이터베이스의 데이터를 관리할 수 있게 해주는 기술. 즉, 관계형 데이터베이스의 테이블과 레코드를 파이썬 코드 내의 클래스와 객체로 매핑하여 SQL 쿼리 없이도 데이터베이스 조작이 가능하게 함
- ORM의 장점
- 코드의 간결성: SQL 쿼리문을 작성하지 않고도 객체를 사용해 데이터베이스를 조작할 수 있어 코드가 간결해짐
- 데이터베이스 독립성: ORM을 사용하면 다양한 데이터베이스 시스템(MySQL, PostgreSQL, SQLite 등)에서 동일한 코드를 사용할 수 있음
- 보안성과 유지보수성: SQL 쿼리를 직접 사용하지 않으므로 SQL 인젝션과 같은 보안 문제를 줄일 수 있고, 유지보수도 용이해짐
- ORM의 장점
- SQLAlchemy 라이브러리: 파이썬의 대표적인 ORM 라이브러리
- SQLAlchemy의 주요 기능
- ORM 기능: 데이터베이스의 테이블을 파이썬 클래스와 연결하고, 테이블의 행을 객체로 다룰 수 있음
- SQL 표현 언어: SQLAlchemy는 복잡한 SQL 쿼리를 작성하기 위한 SQL 표현 언어를 제공
- 데이터베이스 연결 관리: 다양한 데이터베이스 시스템과 연결할 수 있으며, 트랜잭션 관리와 연결 풀링 기능을 제공
- 세션(Session): SQLAlchemy의 세션은 데이터베이스와의 상호작용을 관리하는 객체로, 데이터를 조회, 추가, 수정, 삭제하는 작업을 처리함. 세션은 트랜잭션을 통해 작업 단위로 데이터 변경 사항을 일괄 반영하며, 데이터베이스의 일관성을 유지하는 데 도움을 줌. 각 작업은 세션을 통해 수행되며, commit() 호출 시 데이터베이스에 최종 반영됨
- SQLAlchemy의 주요 기능
① 데이터베이스 Connection 및 Session 생성
psycopg2 cursor | SQLAlchemy session | |
사용 목적 | SQL 쿼리를 직접 작성∙실행하여 데이터베이스를 조작하는 저수준 객체 | ORM으로 객체와 데이터베이스를 매핑하여 상호작용하는 고수준 객체 |
트랜잭션 제어 | db.commit() 호출 필요 | session.commit() 호출 필요 |
상태 관리 | 없음 | 파이썬 객체의 상태 관리 (변경 추적) |
예시 사용 방식 | cursor.execute("SELECT ...") | session.add(object), session.query() |
from sqlalchemy import create_engine, text # SQLAlchemy 라이브러리에서 데이터베이스 연결 생성과 SQL 쿼리 실행을 위한 모듈 임포트
from sqlalchemy.orm import sessionmaker # 세션 생성에 필요한 sessionmaker 함수 임포트
# 데이터베이스 연결 정보 설정
engine_name = 'postgresql' # 사용할 데이터베이스 엔진 설정 (PostgreSQL 사용)
user_id = 'postgres' # 데이터베이스 로그인에 사용할 사용자 ID
user_pw = '1234' # 데이터베이스 로그인에 사용할 사용자 비밀번호
host = '127.0.0.1' # 데이터베이스 서버의 주소 (127.0.0.1은 로컬호스트를 의미)
port = '5432' # 데이터베이스 서버의 포트 번호 (PostgreSQL의 기본 포트)
database = 'postgres' # 연결할 데이터베이스 이름
# 데이터베이스 연결 엔진 생성
db = create_engine(f'{engine_name}://{user_id}:{user_pw}@{host}:{port}/{database}')
# 위 코드에서 create_engine() 함수는 주어진 연결 정보를 통해 데이터베이스 엔진 객체(db)를 생성함
# 이 엔진 객체(db)는 데이터베이스와의 상호작용에 사용됨
# 데이터베이스 연결 생성
cursor = db.connect() # 데이터베이스에 연결하고, SQL 쿼리를 직접 실행할 수 있는 cursor 객체 생성
# 세션 생성기 준비
Session = sessionmaker(bind=db) # sessionmaker 함수를 사용하여 세션을 생성하는 클래스를 만듦. 여기서 bind=db로 엔진을 바인딩함
session = Session() # session 객체 생성, 이 세션을 통해 ORM 기능을 이용하여 데이터베이스와 상호작용을 수행할 수 있음
② 테이블을 클래스 형태로 정의 → 데이터베이스에 테이블 생성
[tip] lecture(Base)의 의미
- declarative_base(): SQLAlchemy에서 ORM 클래스의 기본 클래스를 생성하는 함수
- Base 클래스 : declarative_base() 를 호출해서 생성된 기본 클래스. SQLAlchemy에서 ORM 클래스를 만들 때, 이 Base 상속하여 데이터베이스 테이블과 매핑할 클래스를 정의함
- lecture 클래스: Base 클래스를 상속받아 생성된 ORM 클래스. 모든 ORM 클래스는 Base 클래스를 상속받아 정의해야 함(데이터베이스와 연결되는 SQLAlchemy ORM 클래스가 됨)
from sqlalchemy import Column, INTEGER, VARCHAR # SQLAlchemy에서 열(Column)과 데이터 타입(INTEGER, VARCHAR)을 임포트
from sqlalchemy.orm import declarative_base # SQLAlchemy ORM에서 기본 클래스를 정의하기 위한 declarative_base 임포트
# 기본 클래스 생성
Base = declarative_base()
# lecture 테이블을 나타내는 ORM 클래스 정의
class lecture(Base):
__tablename__ = 'lecture' # 데이터베이스 테이블 이름을 'lecture'로 설정
# 각 컬럼 정의
id = Column('id', INTEGER, primary_key=True, autoincrement=True) # 좌변의 id는 클래스의 속성명, 우변의 'id'는 데이터베이스 테이블의 컬럼명 지정(두 이름은 통일하지 않아도 됨)
# 'id' 열을 INTEGER 타입으로 설정하고, 기본 키(primary_key)로 지정하여 고유하게 만듦. autoincrement로 자동 증가 설정
name = Column('name', VARCHAR(10), nullable=False)
# 'name' 열을 VARCHAR(10) 타입으로 설정. 길이 제한을 10으로 지정하며, NULL 값을 허용하지 않음 (nullable=False)
year = Column('year', INTEGER, nullable=False)
# 'year' 열을 INTEGER 타입으로 설정하고, NULL 값을 허용하지 않음
gender = Column('gender', VARCHAR(10), nullable=False)
# 'gender' 열을 VARCHAR(10) 타입으로 설정하고, NULL 값을 허용하지 않음
count = Column('count', INTEGER, nullable=False)
# 'count' 열을 INTEGER 타입으로 설정하고, NULL 값을 허용하지 않음
Base.metadata.create_all(db)
# Base.metadata: Base를 상속받는 모든 ORM 클래스(lecture, ...)의 테이블 정보(테이블 이름, 컬럼, 제약 조건 등)을 포함
# create_all(db): 현재 메타데이터에 정의된 테이블들을 데이터베이스에 실제로 생성. SQLAlchemy 엔진 객체인 db을 통해 지정된 데이터베이스에 테이블을 생성
# 테이블이 이미 존재하지 않는 경우에만 생성되며, 존재하면 생성되지 않음
[tip] 클래스 변수? 인스턴스 변수? 메서드? 클래스 속성?
- 클래스 변수: 클래스 자체에 속한 변수로, 모든 인스턴스가 공유함
- 인스턴스 변수: 개별 인스턴스에 속한 변수로, 각 인스턴스마다 별도로 존재
- 메서드: 클래스 안에 정의된 함수로, 클래스 또는 인스턴스와 관련된 작업을 수행
- 클래스 속성: 클래스 안에 정의된 모든 변수와 메서드를 포함하는 개념
class Car: wheels = 4 # 클래스 변수 def __init__(self, color): self.color = color # 인스턴스 변수 def describe(self): # 메서드 return f"This car is {self.color} and has {self.wheels} wheels." # 클래스 속성: 위의 모든 변수, 메서드를 통칭
③ CRUD 실행
- INSERT: 데이터 추가
datal = lecture(id=3, name='chunjae', year=1990, gender='M', count=1234)
# lecture 테이블에 삽입할 데이터를 가진 lecture 인스턴스를 생성. id, name, year, gender, count 값을 설정
session.add(datal) # 세션을 통해 새로운 데이터를 추가
session.commit() # 변경사항을 커밋하여 데이터베이스에 반영
- UPDATE: 특정 조건의 데이터 수정
session.query(lecture).filter(lecture.name == 'chunjae').update({'gender': 'F', 'count': 1500})
# lecture 테이블에서 name이 'chunjae'인 행을 찾아 gender를 'F', count를 1500으로 업데이트
session.commit()
- SELECT ONE: 테이블에서 첫 번째 행 조회
res = session.query(lecture).first()
# session.query(lecture): SQLAlchemy의 session 객체를 통해 lecture 테이블에서 데이터를 조회하는 쿼리 객체(res)를 생성
# 여기서 lecture는 Base를 상속받아 정의된 ORM 클래스. 데이터베이스의 lecture 테이블과 매핑되어 있음
# 추후, res.id, res.name 처럼 res 객체의 속성을 통해 데이터에 접근할 수 있음
# .first(): lecture 테이블에 존재하는 데이터 중 첫 번째 행을 가져옴. 만약 테이블에 데이터가 없으면 None을 반환
# 조회된 결과 출력
print(res.id, res.name, res.year, res.gender, res.count) # 만약 테이블에 데이터가 없으면 오류 발생
- SELECT ALL: 모든 데이터 조회
all_data = session.query(lecture).all() # lecture 테이블의 모든 데이터를 조회
for data in all_data:
print(data.id, data.name, data.year, data.gender, data.count)
# 모든 데이터를 출력. 각 열의 값을 반복문으로 순서대로 출력
- DELETE: 특정 조건의 데이터 삭제
session.query(lecture).filter(lecture.id == 3).delete() # lecture 테이블에서 id가 3인 행을 삭제
session.commit()
2) SQLAlchemy Core 방식
- SQLAlchemy Core 방식: SQLAlchemy의 저수준 접근 방식으로, SQL 쿼리를 직접 작성하고 실행할 수 있는 기능을 제공. ORM처럼 클래스를 사용해 테이블을 정의하는 대신, Table 객체와 SQLAlchemy Core의 메타데이터 객체를 사용하여 테이블을 정의하고 데이터베이스와 상호작용함
- Core 방식의 장점
- 세밀한 SQL 제어: SQL을 직접 작성할 수 있어 데이터베이스에 대한 세부적 제어가 가능함
- 효율성: ORM 방식에 비해 더 가볍고, 성능 측면에서 효율적일 수 있음
- 유연한 데이터베이스 조작: ORM 매핑 없이도 테이블 생성, 삭제, 데이터 삽입, 조회, 수정, 삭제 등의 작업을 수행할 수 있음
- Core 방식의 장점
- SQLAlchemy Core의 주요 기능
- SQL 표현 언어: SQLAlchemy Core는 SQL 표현 언어(SQL Expression Language)를 통해 Python 코드로 SQL 쿼리를 작성할 수 있게 함. select(), insert(), update(), delete() 메서드를 사용해 다양한 SQL 쿼리 실행 가능
- Table 객체와 MetaData 객체: Core 방식에서는 Table 객체를 사용하여 테이블과 열을 정의하고, MetaData 객체는 테이블 메타데이터를 관리함. 이를 통해 데이터베이스와의 매핑을 수행함
- 직접적인 커넥션 관리: Core 방식에서는 connect() 메서드를 통해 커넥션 객체를 생성하여 SQL 쿼리를 직접 실행 가능. ORM과 달리 커넥션을 명시적으로 생성하고, 필요 시 연결을 해제하는 방식으로 데이터베이스와의 상호작용을 제어함
[tip] Core 방식 vs ORM 방식
- 코드 스타일: Core는 SQL에 가깝게 작성하고 직접 제어할 수 있으며, ORM은 클래스와 객체를 사용하여 SQL 쿼리 없이도 데이터 조작 가능
- 유연성: Core 방식은 SQL 쿼리를 직접 다루기 때문에 더 높은 수준의 유연성을 제공하지만, 객체 지향적 접근에서는 ORM 방식이 더 적합
"Core 방식은 SQLAlchemy의 SQL 표현 언어와 Table, MetaData 객체를 사용하여 SQL을 직접 다루고, SQLAlchemy의 고성능 기능을 활용할 수 있는 유연한 방식"
① 데이터베이스 Connection
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData # SQLAlchemy에서 데이터베이스 연결과 테이블, 컬럼, 데이터 타입을 정의하는 모듈 임포트
from sqlalchemy.orm import sessionmaker # 세션 생성을 위한 sessionmaker 함수 임포트
# 데이터베이스 연결 설정
db = create_engine(f'{engine_name}://{user_id}:{user_pw}@{host}:{port}/{database}')
# 데이터베이스 연결 엔진 생성. 여기서 설정된 엔진(db)은 지정된 데이터베이스에 연결할 수 있도록 함
# 세션 생성기 준비
Session = sessionmaker(db) # sessionmaker 함수를 사용하여 세션을 생성하는 클래스 만듦
session = Session() # 세션 인스턴스를 생성하여 데이터베이스와의 상호작용에 사용
# 메타데이터 객체 생성
meta = MetaData() # 테이블과 열의 정보를 포함하여 데이터베이스 구조를 관리하는 메타데이터 객체 생성
② 테이블 생성
[tip] Table 객체
- SQLAlchemy의 Core API를 사용하여 테이블을 정의할 때 사용
- 이 Table 객체를 임의로 지정한 변수 core_table에 할당하여, 이후 SQL 쿼리 등을 작성할 때 core_table 을 통해 이 테이블을 참조할 수 있음
# 테이블 정의
core_table = Table( # core_table 변수에 Table 객체를 할당; 데이터베이스의 특정 테이블을 파이썬 코드에서 참조할 수 있게 함
'first', meta, # 'first'라는 테이블을 meta 객체와 함께 정의
Column('id', Integer, primary_key=True, autoincrement=True), # 'id' 열 정의. Integer 타입, 기본 키이며 자동 증가 설정
Column('name', String), # 'name' 열 정의. String 타입
Column('year', Integer), # 'year' 열 정의. Integer 타입
Column('gender', String), # 'gender' 열 정의. String 타입
Column('count', Integer) # 'count' 열 정의. Integer 타입
)
# 테이블 생성
meta.create_all(db) # 데이터베이스에 'first' 테이블 생성. 테이블이 이미 존재하지 않는 경우에만 생성됨
③ CRUD 실행
- SELECT ALL: 모든 데이터 조회
from sqlalchemy import insert, update, delete # SQLAlchemy에서 insert, update, delete 구문을 사용할 수 있도록 임포트
res = core_table.select() # core_table이라는 SQLAlchemy 테이블 객체를 SELECT 쿼리 형태로 선택 # 결과적으로 res에는 core_table 테이블의 모든 데이터를 가져오는 SQL 쿼리가 담김 # 이 쿼리는 아직 실행되지 않은 상태로, 실제 실행은 세션이나 연결 객체(execute())를 통해 이루어짐
result = session.execute(res) # 세션을 통해 생성한 쿼리를 실행하여 결과를 반환
# 조회 결과 출력
for row in result:
print(row) # 각 행의 데이터를 출력
- INSERT ONE: 단일 데이터 삽입
[tip] 연결 객체 conn 의 역할
- 데이터베이스와 하나의 연결 세션을 유지함. 이를 통해 execute() 와 같은 메서드를 호출하여 쿼리를 데이터베이스에 전송하거나, commit() 을 호출하여 변경 사항을 확정하는 등의 작업을 할 수 있음
- 예를 들어, conn.execute(stmt) 는 conn 객체를 통해 데이터베이스에 stmt 쿼리를 전달하고 실행함
stmt = insert(core_table).values(name="hahaha", year=2023, gender="M", count=555)
# stmt 변수에 INSERT 쿼리가 담김 # 이 stmt를 실제로 실행하려면 SQLAlchemy 엔진의 execute() 메서드를 사용해야 함
# .values() 메서드: INSERT 쿼리에 삽입할 데이터 값들을 지정
# with문을 사용하여 연결을 관리; 작업이 완료되면 자동으로 연결이 닫히게 됨
with db.connect() as conn: # db.connect(): 데이터베이스와의 연결을 생성 # 데이터베이스에 연결할 때, db.connect()를 호출하면 conn이라는 연결 객체를 반환; 이 연결 객체는 데이터베이스와 상호작용할 수 있는 통로 역할. 이를 통해 SQL 쿼리를 실행하고 결과를 가져오는 작업을 수행
result = conn.execute(stmt) # 미리 정의한 INSERT 쿼리를 실행하여 데이터를 테이블에 삽입 # result에 실행 결과 객체가 저장됨
conn.commit() # 실행한 INSERT 작업을 데이터베이스에 영구적으로 반영. 커밋을 호출하지 않으면 트랜잭션이 완료되지 않아, 작업이 확정되지 않고 취소될 수 있음
- INSERT MULTI: 여러 데이터 삽입
stmt = insert(core_table) # core_table에 데이터를 삽입할 insert 구문을 생성
data_list = [
{"name": "hwang", "year": 1994, "gender": "M", "count": 12},
{"name": "jeong", "year": 1996, "gender": "F", "count": 32}
]
# 여러 개의 딕셔너리를 포함한 리스트를 생성하여 삽입할 데이터를 준비
with db.connect() as conn:
result = conn.execute(stmt, data_list) # 여러 데이터를 한 번에 삽입
conn.commit() # 삽입된 내용을 커밋하여 데이터베이스에 반영
- UPDATE: 데이터 수정
stmt = update(core_table).where(core_table.c.name == "jeong").values(year=1000) # core_table.c.name: core_table의 모든 컬럼(.c) 중 name 이라는 컬럼(.name)을 참조
# core_table에서 name이 "jeong"인 행의 year 값을 1000으로 업데이트하는 구문 생성
with db.connect() as conn:
result = conn.execute(stmt) # 업데이트 구문을 실행하여 해당 행의 데이터를 수정
conn.commit() # 변경 사항을 커밋하여 데이터베이스에 반영
- DELETE: 데이터 삭제
# Delete : 데이터 삭제
stmt = delete(core_table).where(core_table.c.name == "jeong")
# core_table에서 name이 "jeong"인 행을 삭제하는 delete 구문 생성
with db.connect() as conn:
result = conn.execute(stmt) # 삭제 구문을 실행하여 해당 행을 삭제
conn.commit() # 변경 사항을 커밋하여 데이터베이스에 반영
[파이썬 클래스, 객체를 통한 데이터베이스 관리]
1) 클래스(TempClass) 생성
① 데이터베이스 연결
import psycopg2
db = psycopg2.connect(
dbname=database,
user=user,
password=password,
port=port,
host=host
)
cursor=db.cursor()
② 클래스 정의
[tip] 타입 힌트(Type Hint)
- 파이썬에서는 타입 힌트를 사용하여, 클래스에서 인스턴스 변수의 데이터 타입을 미리 지정
- 아래 예시에서, name: str, age: int, count: int 와 같이 타입을 지정한 부분을 말함
class Temp_Class: def __init__(self, name: str, age: int, count: int): self.name = name self.age = age self.count = count
[tip] __init__() 메서드
- 객체가 생성될 때 자동으로 호출되어, name, age, count 속성을 설정하고, addage() 메서드를 실행
- 객체를 생성할 때 addage()가 자동으로 실행되므로, new_age 속성이 이미 설정된 상태
# 객체 생성 temp_instance = TempClass(name="Alice", age=30, count=5) # 객체 생성 시 `addage()`가 호출되어 `new_age` 속성이 생성됨 print(temp_instance.age) # 출력: 30 print(temp_instance.new_age) # 출력: 40
# TempClass라는 클래스 정의
class TempClass:
def __init__(self, name: str, age: int, count: int):
# 인스턴스 초기화: name, age, count 속성 설정
self.name = name
self.age = age
self.count = count
self.addage() # 객체 생성시 addage() 메서드 호출
def printall(self):
# 모든 속성을 출력하는 메서드
print(self.name)
print(self.age)
print(self.count)
def printself(self):
# self 객체를 반환하는 메서드 (자기 자신을 반환)
return self
def addage(self):
# 나이 속성에 10을 더해 new_age 속성 생성
self.new_age = self.age + 10
③ 인스턴스 생성
# TempClass 클래스 인스턴스 생성
a1 = TempClass(age=2, count=3, name="chunjae") # 인스턴스 생성 및 초기화
s1 = a1.printself() # a1 인스턴스 자기자신을 반환; s1과 a1은 같은 객체를 가리키는 두 개의 변수가 됨
# 인스턴스 a1과 s1 출력 (s1은 a1의 self를 가리킴)
print(a1)
print(s1)
④ 인스턴스(객체) 내부 속성 및 값 확인; 딕셔너리로 반환
[tip] __dict__ 속성
- 파이썬의 내장 속성으로, 객체가 가진 속성과 속성값의 매핑 정보를 딕셔너리 형태로 저장
s1.__dict__
"""
{'name': 'chunjae', 'age': 2, 'count': 3, 'new_age': 12}
"""
⑤ 조건을 만족하는 항목만 반환
[tip] dir(s1)
- s1 객체가 가진 모든 속성과 메서드의 이름을 리스트 형태로 반환
- 여기에는 더블 언더스코어(__)로 시작하는 매직 메서드와 속성도 포함됨
[tip] 리스트 컴프리헨션(List Comprehension)
- 리스트를 간결하고 효율적으로 생성하는 방법
- 형식: [표현식 for 요소 in 반복 가능한 객체 if 조건]
- 예시
[i for i in dir(s1) if '__' not in i] # dir(s1) 에서 반환된 이름 중 __ 가 포함되지 않은 것만 필터링하여 리스트로 만듦
# dir()을 사용하여 객체의 메서드와 속성 목록에서 __로 시작하지 않는 항목만 가져옴
[i for i in dir(s1) if '__' not in i]
"""
['addage', 'age', 'count', 'name', 'new_age', 'printall', 'printself']
"""
⑥ 클래스(EasyDict)로 부터 객체(self) 생성
[tip] EasyDict(dict()) vs EasyDict()
- EasyDict: 딕셔너리 형태로 속성에 접근할 수 있는 클래스. 보통 기존 딕셔너리를 넘겨 받아 객체를 생성
- EasyDict(dict()): 빈 딕셔너리 dict()를 EasyDict 객체의 초기값으로 지정. 이후 속성을 자유롭게 추가하 수 있게 함
- EasyDict(): 이렇게 클래스를 호출해도 빈 딕셔너리로 초기화된 객체를 생성함. 하지만 dict()를 추가해주는게 코드의 의도(객체가 빈 딕셔너리로 초기화됨)가 더 분명하게 보여서 가독성이 향상됨
# EasyDict 라이브러리에서 EasyDict 클래스 가져오기
from easydict import EasyDict
# EasyDict 객체 self 생성
# EasyDict(dict())는 비어 있는 딕셔너리로 초기화된 EasyDict 객체를 생성함
self = EasyDict(dict())
# EasyDict 객체 self에 속성 추가
self["name"] = "jeongseok" # name이라는 키에 "jeongseok" 값 설정
self["age"] = 3 # age라는 키에 3 값 설정
self["count"] = 1 # count라는 키에 1 값 설정
# s2 변수에 EasyDict 객체 self를 할당
# 여기서 s2와 self는 같은 EasyDict 객체를 참조 (같은 값을 공유함)
s2 = self
# s2를 출력하면 self와 같은 내용이 출력됨 (EasyDict 객체 s2의 현재 상태를 확인)
s2
# EasyDict 객체 self에 새로운 속성 hoho 추가
# 객체처럼 . 표기법을 통해 속성 추가 및 값 설정.
self.hoho = "ho" # hoho라는 새로운 속성을 추가하고 값으로 "ho" 설정
# self를 출력하면 hoho 속성이 추가된 상태로 출력됨
self
2) 클래스(DBConnector) 생성
① 클래스 정의
- Context Manager (__enter__, __exit__)
- Context Manager(컨텍스트 관리자): 자원 관리나 특정 작업의 시작과 끝을 자동으로 처리하는 구조
- 데이터베이스 연결 시 with문을 사용하여 안전하게 DB 연결을 종료할 수 있도록 함
- __enter__: with문에 진입할 때 실행됨
- __exit__: with문이 끝날 때 실행되어 데이터베이스 연결을 자동으로 닫음(close 메서드를 호출할 필요X)
import psycopg2
class DBConnector:
def __init__(self, host, database, user, password, port):
# 초기화 메서드: DB 연결 정보 초기화
print("Start! Class")
# self.conn_params: 데이터베이스 연결에 필요한 매개변수들을 딕셔너리 형태로 저장한 속성. 필요시 참조 가능
self.conn_params = dict(
host=host,
dbname=database,
user=user,
password=password,
port=port
)
# 연결 정보 저장
self.host = host
self.database = database
self.user = user
self.password = password
self.port = port
def postgres_connect(self):
# PostgreSQL 연결 메서드
print("<<< postgres_connect >>>")
# psycopg2.connect() : 데이터 베이스 연결을 생성하고, 데이터베이스 연결 객체인 self.conn이 정의됨
self.conn = psycopg2.connect(
host=self.host,
dbname=self.database,
user=self.user,
password=self.password,
port=self.port
)
return self # self를 반환하여 체이닝이 가능하도록 설정
def print_conn_params(self):
# conn_params를 출력하는 메서드
print("Connection Parameters:", self.conn_params)
def __enter__(self):
# with문 진입 시 호출되어, 객체 반환
print("Enter")
return self
# 데이터베이스 연결 종료; postgres_connect(self)에서 생성된 연결 객체 self.conn을 참조하여 데이터베이스 연결을 종료
def __exit__(self, exc_type, exc_value, traceback):
# with문 종료 시 호출되어, DB 연결을 닫음
self.conn.close()
print("Exit")
③ 인스턴스(db_object) 생성
# DB 연결 인스턴스 생성
db_object = DBConnector(
host="127.0.0.1",
database="postgres",
user="postgres",
password="1234",
port="5432"
)
④ 데이터베이스 연결 테스트
# 데이터베이스 연결
db_object.postgres_connect() # self.conn 생성
# with문을 사용하여 안전하게 연결 관리
with db_object as connected:
db_conn = connected.conn # 연결된 DB 객체 가져오기
cursor = db_conn.cursor() # 커서 객체 생성
# 데이터베이스 조회 테스트
cursor.execute("SELECT * FROM lecture LIMIT 10")
print(cursor.fetchall()) # 조회 결과 출력
⑤ Context Manager 예제
- ContextManager 클래스 생성: with 문을 사용한 리소스 관리 예시
- __enter__, __exit__ 메서드 구현: with 문 내부와 외부에서 리소스 관리가 자동으로 이루어짐
[tip] return self
- return self: 추후 with 문 안에서 __enter__ 메서드가 호출될 때 self가 반환되어, with 문 내부에서 해당 객체(self)에 접근할 수 있음
- return self가 없다면 cm 변수는 None이 되어 객체에 접근할 수 없음
class ContextManager: def __init__(self, age): print("Start! Class") self.age = age def __enter__(self): print("Enter") return self # `self`를 반환하여 `with` 문에서 사용할 수 있게 함 def __exit__(self, exc_type, exc_value, traceback): print("Exit") # with 문을 사용하여 ContextManager 객체 생성 및 사용 with ContextManager(age=25) as cm: print("Inside with statement, age:", cm.age) # with 문을 시작할 때 __enter__ 메서드가 호출되고, 이 메서드가 반환한 값이 as 구문에서 지정한 변수에 할당됨. 이렇게 할당된 변수는 with 문 블록 내에서 바로 사용할 수 있게 됨 # cm: __enter__ 메서드의 return self에 의해 반환된 ContextManager 객체 # with 문 안에서 cm을 통해 ContextManager 객체의 속성과 메서드를 바로 사용할 수 있음 # 즉, with 문에서 as 구문을 사용하면 객체를 바로 변수로 받아 with 블록 안에서 편리하게 접근할 수 있게 됨
class ContextManager:
def __init__(self, age):
print("Start! Class")
self.age = age
def __enter__(self):
# with문 진입 시 호출
print("Enter")
return self
def __exit__(self, exc_type, exc_value, traceback):
# with문 종료 시 호출
print("Exit")
def show(self):
# age를 출력하는 메서드
print(self.age)
return self.__dict__
# ContextManager 인스턴스 생성 및 with문 사용 예제
context = ContextManager(age=10)
with context:
print(context.show())
[데이터베이스에 데이터 Bulk Insert]
1) for문을 통해 Bulk Insert
# 최종 목적: for문을 사용하여 데이터프레임의 데이터를 bulk insert하는 코드
# psycopg2를 불러와 PostgreSQL과 연결하는 라이브러리
import psycopg2 as pgsql
# PostgreSQL 데이터베이스 연결 설정
db = pgsql.connect(
host='127.0.0.1', # 로컬호스트 IP
dbname='postgres', # 사용할 데이터베이스 이름
user='postgres', # 데이터베이스 사용자 이름
password='1234', # 데이터베이스 비밀번호
port=5432 # PostgreSQL 기본 포트
)
# 커서 생성
cursor = db.cursor()
# 기존 lecture 테이블 삭제
drop_query = "DROP TABLE lecture"
cursor.execute(drop_query)
db.commit() # 변경사항을 데이터베이스에 반영
# pandas를 불러와 CSV 파일을 데이터프레임으로 읽어오기
import pandas as pd
# CSV 파일을 읽어와 데이터프레임 생성
df = pd.read_csv('./dataset/data-01/names.csv')
df.head(10) # 데이터프레임의 첫 10개의 행 출력 (테스트 용도)
# 데이터 타입 확인
df.dtypes
# pandas는 SQL의 VARCHAR 타입을 인식하지 않으며, integer 타입을 구분하지 않음
# lecture 테이블의 모든 데이터 삭제 (데이터 삽입 전 초기화)
delete_query = "DELETE FROM lecture"
cursor.execute(delete_query)
db.commit() # 변경사항을 데이터베이스에 반영
# 데이터프레임의 각 행을 튜플로 변환하여 lecture 테이블에 데이터 삽입
for data in range(len(df)): # df의 행 수만큼 반복문을 실행; 각 행을 반복적으로 처리
datas = tuple(df.iloc[data]) # 데이터프레임의 행을 튜플로 변환; INSERT INTO 쿼리에 사용할 값을 튜플 형태로 만들기 위함
query = f"INSERT INTO lecture VALUES{datas}" # INSERT 쿼리 생성(f-string)
cursor.execute(query) # 쿼리 실행; 현재 행을 lecture 테이블에 삽입
db.commit() # 모든 데이터를 삽입 후 변경사항을 데이터베이스에 반영
# lecture 테이블에서 상위 10개 행 선택
cursor.execute("SELECT * FROM lecture LIMIT 10")
cursor.fetchall() # 상위 10개 행을 가져와 출력
2) to_sql() 을 통해 Bulk Insert
# SQLAlchemy 라이브러리에서 create_engine을 불러와 데이터베이스와 연결 설정
from sqlalchemy import create_engine
# 데이터베이스 연결 정보 선언
engine = 'postgresql' # 사용할 데이터베이스 엔진 (PostgreSQL)
user = 'postgres' # 데이터베이스 사용자 이름
password = '1234' # 데이터베이스 비밀번호
host = '127.0.0.1' # 로컬호스트 IP 주소
port = '5432' # PostgreSQL 기본 포트
database = 'postgres' # 데이터베이스 이름
# SQLAlchemy 엔진 생성, 연결 문자열 형식으로 데이터베이스에 연결
db = create_engine(f'{engine}://{user}:{password}@{host}:{port}/{database}')
# pandas 불러오기
import pandas as pd
# CSV 파일을 읽어와 데이터프레임 생성
df = pd.read_csv('./dataset/data-01/names.csv')
# 특정 행 선택 (테스트용)
df.iloc[1] # DataFrame에서 인덱스 1에 해당하는 행 선택
df.iloc[1:] # 인덱스 1 이후의 모든 행을 선택
# 데이터프레임을 PostgreSQL 테이블에 BULK INSERT
df.to_sql(name="lecture", con=db, if_exists="replace")
# 'lecture'라는 테이블 이름으로 데이터프레임을 데이터베이스에 저장
# 기존 테이블이 존재할 경우 대체 (replace)
3) execute_values() 를 통해 Bulk Insert
# psycopg2 라이브러리를 불러와 PostgreSQL에 연결
import psycopg2
# PostgreSQL 데이터베이스 연결 설정
db = psycopg2.connect(
host='127.0.0.1', # 로컬 IP 주소
dbname='postgres', # 데이터베이스 이름
user='postgres', # 데이터베이스 사용자 이름
password='1234', # 데이터베이스 비밀번호
port=5433 # PostgreSQL 포트 (기본 5432가 아닌 5433으로 설정)
)
# 커서 생성
cursor = db.cursor()
# lecture 테이블의 모든 데이터를 삭제하는 쿼리
delete_query = "DELETE FROM lecture;"
cursor.execute(delete_query) # 쿼리 실행
db.commit() # 변경사항을 데이터베이스에 반영
# lecture 테이블에서 모든 데이터를 선택하는 쿼리
cursor.execute("SELECT * FROM lecture")
cursor.fetchall() # 선택된 데이터를 가져옴 (테스트 용도)
# pandas 라이브러리를 불러와 CSV 파일을 데이터프레임으로 읽기
import pandas as pd
# CSV 파일 읽어오기
df = pd.read_csv('./dataset/data-01/names.csv')
# 데이터형 변환 (id, year, count 열을 float 타입으로 변환)
df['id'] = df['id'].astype(float)
df['year'] = df['year'].astype(float)
df['count'] = df['count'].astype(float)
# 데이터 삽입을 위한 리스트 생성
insert_list = list()
# DataFrame에서 인덱스 5부터 49까지의 행을 tuple로 변환하여 insert_list에 추가
for i in range(5, 50):
insert_list.append(tuple(df.iloc[i]))
# 데이터 타입 확인
df.dtypes # 각 열의 데이터 타입 출력
# numpy에서 psycopg2로 데이터 전송 시 호환성을 위해 np.int64 데이터 타입을 SQL 타입으로 변환
import numpy as np
from psycopg2.extensions import register_adapter, AsIs
register_adapter(np.int64, AsIs) # np.int64 타입을 SQL에서 처리 가능한 타입으로 등록
# psycopg2의 execute_values를 사용하여 효율적인 Bulk Insert 수행
from psycopg2.extras import execute_values
# INSERT 쿼리를 생성하고, insert_list에 있는 데이터를 일괄 삽입
sql = f"INSERT INTO lecture VALUES %s;"
execute_values(cursor, sql, insert_list)
# 변경사항을 데이터베이스에 반영
db.commit()
[파이썬에서 민감 정보 관리]
1) dotenv를 활용한 환경 변수 관리 및 보안 강화
- dotenv : 파이썬에서 민감한 정보를 코드에 하드코딩하지 않고, .env 파일을 통해 관리할 수 있도록 도와주는 라이브러리
- 데이터베이스 정보, API 키, 비밀번호 등의 민감 정보를 코드에 직접 작성하지X → .env 파일에서 읽어와 코드에서 사용할 수 있음
① .env 파일 생성
- 보안이 필요한 환경 변수들을 저장
# .env
# 변동이 없을 변수는 대문자로 작성하고, 변수의 용도를 명확히 하기 위해 prefix를 붙임
# 예: 전역 변수에는 GLOBAL_ 같은 prefix를, 특정 모듈이나 클래스에서만 사용되는 상수에는 MODULE_, CLASS_ 같은 prefix를 사용 가능
# 여기서는 PostgreSQL 관련 상수에 POSTGRES_라는 prefix를 붙임
POSTGRES_ENGINE = 'postgresql' # 데이터베이스 엔진 이름 설정 (여기서는 PostgreSQL 사용)
POSTGRES_USER = 'postgres' # 데이터베이스 사용자 이름
POSTGRES_PASSWORD = '1234' # 데이터베이스 비밀번호
POSTGRES_HOST = '127.0.0.1' # 데이터베이스 호스트 주소 (로컬 호스트 IP) # 'localhost' 대신 직접 IP 주소를 적는 이유: 'localhost'를 해석하는 과정에서 문제가 발생할 수 있으므로 명확한 IP 주소를 사용하여 에러 발생 가능성을 줄임
POSTGRES_PORT = '5432' # PostgreSQL 기본 포트 번호
POSTGRES_DB = 'postgres' # 데이터베이스 이름
② find_dotenv(): .env 파일 경로 찾기
# dotenv 라이브러리를 불러오기
import dotenv
# .env 파일의 경로 찾기
env_path = dotenv.find_dotenv() # 현재 디렉토리나 상위 디렉토리에서 .env 파일을 찾음
print(env_path) # 찾은 경로를 출력하여 확인
③ load_dotenv(): 환경 변수 로드
- .env 파일에서 읽은 값을 시스템 환경 변수로 설정함; 추후 코드에서 os.getenv() 로 값을 사용할 수 있게 됨
# .env 파일에 저장된 환경 변수를 불러옴
dotenv.load_dotenv(env_path)
④ dotenv_values(): 환경 변수 값 확인
- .env 파일의 내용을 딕셔너리 형태로 반환하여 내용 확인
# .env 파일의 내용을 딕셔너리 형태로 불러오기
dotenv.dotenv_values(env_path) # .env 파일의 내용을 딕셔너리로 반환하여 확인
⑤ set_key(): 환경 변수 값 변경
- .env 파일에서 특정 키의 값을 업데이트
# .env 파일의 환경 변수 값 변경하기
dotenv.set_key(env_path, 'HOST', '876543') # .env 파일에서 'HOST'의 값을 '876543'으로 설정
⑥ os.getenv(): 환경 변수에서 값 불러오기
import os
from dotenv import load_dotenv
load_dotenv()
# 환경 변수에서 불러온 값을 변수에 저장
engine = os.getenv("POSTGRES_ENGINE")
user = os.getenv("POSTGRES_USER")
password = os.getenv("POSTGRES_PASSWORD")
host = os.getenv("POSTGRES_HOST")
port = os.getenv("POSTGRES_PORT")
database = os.getenv("POSTGRES_DB")
# 각 환경 변수 값 출력 확인
(engine, user, password, host, port, database)
⑦ DB 설정 딕셔너리 생성
# DB 설정 딕셔너리 생성
DB_SETTINGS = { # DB_SETTINGS: 데이터베이스 설정 정보를 저장하는 딕셔너리
"POSTGRES": { # POSTGRES: PostgreSQL 데이터베이스와 관련된 설정 정보를 저장하는 키
'host': os.getenv("POSTGRES_HOST"), # host, database, user,...: PostgreSQL에 연결할 때 필요한 환경 변수
'database': os.getenv("POSTGRES_DB"), # os.getenv(): .env 파일에서 로드된 환경 변수 값을 가져오는 함수. POSTGRES_HOST와 같은 키의 값을 불러옴
'user': os.getenv("POSTGRES_USER"),
'password': os.getenv("POSTGRES_PASSWORD"),
'port': os.getenv("POSTGRES_PORT")
}
}
# POSTGRES 설정 확인
DB_SETTINGS['POSTGRES']
⑧ DBConnector 인스턴스 생성
- DBConnector 클래스의 객체 db_object 생성
- DB_SETTINGS['POSTGRES']에 저장된 PostgreSQL 설정을 **를 사용해 인자로 전달
- 연결 정보가 포함된 딕셔너리를 가변 키워드 인자로 전달하여, PostgreSQL 데이터베이스에 연결하는 데 필요한 정보를 설정
# DBConnector 클래스에 POSTGRES 설정을 unpacking 하여 전달
db_object = DBConnector(**DB_SETTINGS['POSTGRES'])
⑨ lecture 테이블의 데이터 조회
# 데이터베이스에 안전하게 연결 및 쿼리 실행
with db_object as connected:
db_conn = connected.conn
cursor = db_conn.cursor()
cursor.execute("SELECT * FROM lecture LIMIT 5")
print(cursor.fetchall())
➉ 다중 데이터베이스 설정
- 다중 DB 설정을 DB_SETTINGS 딕셔너리로 관리; 여러 데이터베이스 전환이 가능
- DB_SETTINGS 딕셔너리에 두 개의 설정(POSTGRES, KDT9)을 정의
- 여러 데이터베이스를 쉽게 전환하거나, 연결할 수 있음
# 다중 DB 설정 추가
DB_SETTINGS = {
"POSTGRES": {
'host': os.getenv("POSTGRES_HOST"),
'database': os.getenv("POSTGRES_DB"),
'user': os.getenv("POSTGRES_USER"),
'password': os.getenv("POSTGRES_PASSWORD"),
'port': os.getenv("POSTGRES_PORT")
},
"KDT9": {
'host': os.getenv("POSTGRES_HOST"),
'database': os.getenv("POSTGRES_DB_2"),
'user': os.getenv("POSTGRES_USER"),
'password': os.getenv("POSTGRES_PASSWORD"),
'port': os.getenv("POSTGRES_PORT")
}
}
⑪ INFORMATION_SCHEMA.TABLES: 테이블 정보 조회
- with 문 사용; __exit__ 메서드가 자동 호출되어 연결이 안전하게 종료됨
# POSTGRES 설정을 사용하여 DBConnector 인스턴스 생성 및 연결
db_object = DBConnector(**DB_SETTINGS['POSTGRES'])
# with문을 사용해 데이터베이스 연결, 쿼리 실행 후 연결 자동 종료
with db_object as connected:
db_conn = connected.conn # 연결된 DB 객체 가져오기
cursor = db_conn.cursor() # 커서 생성
# 데이터베이스 내 테이블 정보 조회
cursor.execute("SELECT * FROM INFORMATION_SCHEMA.TABLES")
print(cursor.fetchall()) # 조회 결과 출력
[Asterisk(*)]
- 애스터리스크 : 리스트, 딕셔너리 요소를 함수의 인자로 유연하게 전달할 수 있음
1) *args: 위치 인자를 가변적으로 전달
[tip] 위치 인자: 리스트, 튜플, 문자열, 셋, 숫자형 데이터
def print_args(*args): for arg in args: print(arg) # 호출 예제 print_args([1, 2, 3], (4, 5, 6), "hello", {"apple", "banana"}, 42) """ [1, 2, 3] (4, 5, 6) hello {'apple', 'banana'} 42 """
def func(*args):
print(args)
func(1, 2, 3) # 출력: (1, 2, 3)
my_list = [1, 2, 3]
func(*my_list) # 리스트 요소들이 위치 인자로 전달되어 (1, 2, 3) 출력
2) **kwargs: 키워드 인자를 가변적으로 전달
[tip] 키워드 인자: 딕셔너리, 기본 자료형(숫자, 문자열, 불리언), 리스트, 튜플, 셋
def print_kwargs(**kwargs): for key, value in kwargs.items(): print(f"{key}: {value}") # 호출 예제 print_kwargs( name="Alice", age=25, hobbies=["reading", "swimming"], coordinates=(10.0, 20.0), favorite_fruits={"apple", "banana"}, address={"city": "Seoul", "country": "Korea"} ) """ name: Alice age: 25 hobbies: ['reading', 'swimming'] coordinates: (10.0, 20.0) favorite_fruits: {'apple', 'banana'} address: {'city': 'Seoul', 'country': 'Korea'} """
def func(**kwargs):
print(kwargs)
func(one=1, two=2) # 출력: {'one': 1, 'two': 2}
my_dict = {'host': 'localhost', 'port': 5432}
func(**my_dict) # 키워드 인자로 전달되어 {'host': 'localhost', 'port': 5432} 출력
3) 응용
# 함수에 일반 인자를 전달하는 예시
def func(one, two):
# 전달된 인자를 각각 출력
print("one = ", one)
print("two = ", two)
# 인자 1과 2를 전달하여 호출
func(1, 2)
# *args 예제 - 여러 개의 위치 인자를 가변적으로 처리
# *args는 여러 개의 위치 인자(순서가 있는 인자)를 전달할 때 사용
def func(*args):
# args는 튜플 형태로 인식
print("*args = ", args)
# 전달된 인자의 합을 계산하여 출력
print(sum(args))
# 위치 인자 1, 2, 3을 가변 인자로 전달
func(1, 2, 3)
# **kwargs 예제 - 여러 개의 키워드 인자를 가변적으로 처리
# **kwargs는 여러 개의 키워드 인자(이름이 있는 인자)를 딕셔너리 형태로 전달받을 때 사용
def func(**kwargs):
# kwargs는 딕셔너리 형태로 인식
print("**kwargs = ", kwargs)
# 키워드 인자 one=1, two=2를 전달
func(one=1, two=2)
# DBConnector 클래스 인스턴스 생성
# 인자를 직접 지정하여 인스턴스 생성
test = DBConnector(1, 2, 3, 4, 5)
# conn_params 확인 (인스턴스에 저장된 연결 정보 확인)
test.conn_params
# LIST 예제 - *args 사용
args = [1, 2, 3, 4, 5] # 위치 인자 목록을 리스트로 정의
# *args를 사용하여 리스트의 요소를 위치 인자로 전달
list_params = DBConnector(*args) # DBConnector(1, 2, 3, 4, 5)와 동일한 효과
# list_params의 conn_params 확인
list_params.conn_params
# DICT 예제 - **kwargs 사용
kwargs = dict(
host=1,
database=2,
user=3,
password=4,
port=5
)
# **kwargs를 사용하여 딕셔너리의 키-값 쌍을 키워드 인자로 전달
dict_params = DBConnector(**kwargs) # DBConnector(host=1, database=2, user=3, password=4, port=5)와 동일한 효과
# dict_params의 conn_params 확인
dict_params.conn_params
📙 내일 일정
- SQLAlchemy ORM 실습
'TIL _Today I Learned > 2024.10' 카테고리의 다른 글
[DAY 70] SQLAlchemy ORM (1) | 2024.10.29 |
---|---|
[DAY 69] SQLAlchemy ORM (1) | 2024.10.28 |
[DAY 67] AWS 아키텍처 그리기 (0) | 2024.10.24 |
[DAY 66] AWS 클라우드 환경 이해 및 실습 (0) | 2024.10.23 |
[DAY 65] AWS, Linux 란? (2) | 2024.10.22 |