본문 바로가기
Python/Data analysis

시퀀스 슬라이싱과 컴프리헨션, 문자열 형식 지정, 컨텍스트 관리

by curious week 2025. 10. 3.

데이터 분석을 위한 파이썬 1

1) 시퀀스 슬라이싱(Slicing)

핵심 개념

  • 슬라이싱: 시퀀스(리스트/튜플/문자열 등)에서 부분 시퀀스를 seq[start:stop:step]으로 선택/복사/수정.
  • 경계 규칙: start 포함, stop 미포함, step 기본 1, 음수 가능(역순).

주요 패턴

numbers = [10, 20, 30, 40, 50, 60, 70]

# 1) 기본 범위 선택
subset1 = numbers[0:3]           # [10, 20, 30]
subset2 = numbers[:3]            # 시작 생략 → 0부터
subset3 = numbers[-3:]           # 끝에서 3개 [50, 60, 70]

# 2) 간격/역순
evens   = numbers[::2]           # [10, 30, 50, 70]  # step=2
reversed_numbers = numbers[::-1] # 역순 복사

# 3) 슬라이스 대입(제자리 수정)
nums = [0, 1, 2, 3, 4]
nums[1:4] = [10, 11]             # 길이 달라도 치환 가능 → [0, 10, 11, 4]

실무 팁 & 주의점

  • 얕은 복사(shallow copy): new = old[:]는 얕은 복사. 중첩 리스트가 있으면 내부는 참조 공유됨.
  • 범위를 벗어나도 IndexError 없음(조용히 잘리는 점이 장점이자 함정).
  • 문자열도 슬라이싱 가능(불변이라 새 문자열 생성).

2) 컴프리헨션(Comprehension)

리스트 컴프리헨션

# [표현식 for 변수 in 반복가능객체 if 조건]
nums = [1, 2, 3, 4, 5]

squares = [n ** 2 for n in nums]                  # 원소 변환
evens   = [n for n in nums if n % 2 == 0]         # 조건 필터
pairs   = [(i, j) for i in range(3) for j in range(2)]  # 중첩 루프

딕셔너리 컴프리헨션

# {키:값 for ...}  /  {k:v for k,v in dict_.items()}
# 타입: dict[int,int]
squares_map = {i: i**2 for i in range(5)}         # {0:0, 1:1, 2:4, ...}

prices = {"apple": 1200, "banana": 800, "melon": 5000}
# 1000원 이상만 10% 인상
inc_prices = {k: int(v * 1.1) for k, v in prices.items() if v >= 1000}

세트/제너레이터 컴프리헨션

# set comprehension: 중복 제거한 제곱값
sq_set = {n**2 for n in [1,2,2,3]}    # {1,4,9}

# generator expression: 지연 평가(메모리 절약)
gen = (n**2 for n in range(10))

실무 팁

  • 한 줄에 조건/중첩이 과하면 가독성↓ → 일반 for로 분해 권장.
  • 딕셔너리/세트 컴프리헨션으로 중복 제거/매핑 변환을 깔끔하게 처리.

3) 문자열 형식 지정 — % / str.format() / f-문자열

선택 가이드

  • f-문자열(추천): 파이썬 3.6+ 권장. 가독성 최고, 표현식 직접 삽입.
  • str.format(): 이름/인덱스 기반 포맷이 필요할 때.
  • % 포맷: C 스타일 익숙할 때, 간단한 경우만.

예제 비교

name: str = "홍길동"
age: int = 30
salary: int = 3_600_000
tax_rate: float = 0.1234

# 1) C 스타일 (%)
print("이름: %s, 나이: %d, 월급: %d원" % (name, age, salary))

# 2) str.format()
print("이름: {}, 나이: {}, 월급: {:,}원".format(name, age, salary))
print("직원 {n}: 세후 {net:,}원".format(n=name, net=int(salary*(1-tax_rate))))

# 3) f-문자열
print(f"이름: {name}, 나이: {age}, 월급: {salary:,}원")
print(f"세율: {tax_rate:.1%}, 세후: {int(salary*(1-tax_rate)):,}원")

# 디버깅 편의: f"{변수=}"
print(f"{salary=:,}, {tax_rate=:.3%}")

포맷 미세 조정(자주 쓰는 규칙)

  • 천단위 구분자: :, → f"{salary:,}"
  • 소수점 자리: :.2f → f"{pi:.2f}"
  • 퍼센트: :.1% → 0.1234 → 12.3%
  • 정렬/폭: :>10, :<10, :^10 → 우/좌/가운데 정렬

4) 컨텍스트 관리(with) — 파일/네트워크/DB 자원 안전 관리

핵심 개념

  • 컨텍스트 관리자: 자원 획득(open)→사용(write)→해제(close)를 자동화.
  • with 블록을 벗어날 때 예외 발생 여부와 관계없이 정리 보장.

파일 입출력 예제

# 역할/타입:
# path: str - 파일 경로
# mode: str - 파일 모드("w", "r", "a", "wb" 등)
# encoding: str - 텍스트 인코딩(한국어 권장: "utf-8")
path: str = "output.txt"

# 쓰기
with open(path, "w", encoding="utf-8") as f:
    f.write("Hello, world!\n")

# 읽기
with open(path, "r", encoding="utf-8") as f:
    data: str = f.read()

직접 컨텍스트 만들기(심화)

class managed_file:
    # file: typing.Optional[io.TextIOWrapper]
    def __init__(self, path: str, mode: str = "w", encoding: str = "utf-8"):
        self.path = path
        self.mode = mode
        self.encoding = encoding
        self.file = None

    def __enter__(self):
        self.file = open(self.path, self.mode, encoding=self.encoding)
        return self.file  # with ... as file 에서 바인딩되는 객체

    def __exit__(self, exc_type, exc, tb):
        if self.file and not self.file.closed:
            self.file.close()
        # False 반환 → 예외를 상위로 전파(일반적 선택)
        return False

with managed_file("sample.txt", "w") as f:
    f.write("context manager by class\n")

보너스: contextlib.contextmanager 데코레이터로 제너레이터 기반 컨텍스트를 간단히 만들 수 있습니다.


5) 미니 실습(체크리스트와 연계)

A. 리스트/딕셔너리 조작(학습목표 1)

  1. data = list(range(1, 21))에서 짝수만 역순으로 뽑아 보세요.
    • 힌트: 슬라이싱 + 조건, 혹은 컴프리헨션
  2. scores = {"kim":88,"lee":92,"park":75,"choi":96}에서
    90점 이상만 5점 가산한 새 딕셔너리를 컴프리헨션으로 만드세요.

B. 문자열 형식 지정(학습목표 2)

  1. 사원정보(name, salary, tax_rate)를 f-문자열
    이름: OOO, 세전: 1,234,567원, 세후: 1,111,111원(세율 12.3%) 형태로 출력.
  2. 동일 정보를 %와 str.format()으로도 각각 출력.

C. 컨텍스트 관리(학습목표 3)

  1. with open(..., "w", encoding="utf-8")로 실습 A/B 결과를 한 줄씩 파일에 저장.
  2. 다시 with open(..., "r", encoding="utf-8")로 읽어와 화면에 출력.

6) 자주 하는 실수

  • 슬라이싱 stop 미포함을 잊고 한 칸 모자라거나 넘기는 경우 → 항상 인덱스 범위 확인.
  • 중첩 컴프리헨션이 길어지면 가독성↓ → 단계 분리.
  • 금액/퍼센트 표기는 f-문자열의 :,, :.1%를 습관화.
  • 파일 I/O는 반드시 with 사용(예외 발생 시에도 닫힘 보장).
  • 텍스트 파일은 encoding="utf-8"을 명시해 한글 깨짐 방지.

7) 60초 복습 요약

  • 슬라이싱: seq[start:stop:step], 역순은 [::-1], 대입으로 부분 수정 가능.
  • 컴프리헨션: 변환/필터/매핑을 간결하게(리스트·딕셔너리·세트·제너레이터).
  • 문자열 포맷팅: 기본은 f-문자열(표현식·가독성↑), 상황에 따라 format/%.
  • 컨텍스트 관리: with로 자원 자동 정리(파일/소켓/DB).

 

오픈소스 기반 데이터 분석 2강 — 데이터 분석을 위한 파이썬 1

 

Data-Analysis-with-Open-Source/오픈소스_데이터_분석_2강.ipynb at main · mors119/Data-Analysis-with-Open-Source

Data Analysis with Open Source. Contribute to mors119/Data-Analysis-with-Open-Source development by creating an account on GitHub.

github.com

 

2-1 리스트 슬라이싱

# 리스트 슬라이싱 기본 예시
numbers = [10, 20, 30, 40, 50, 60, 70]

# 1) 첫 4개 원소 [:4]  — 시작 인덱스 생략 시 0부터
first_four = numbers[:4]                   # => [10, 20, 30, 40]
print(first_four)

# 대안: itertools.islice (이터레이터 기반으로 메모리 절약)
# [import itertools]
# from itertools import islice
# first_four_iter = list(islice(numbers, 4))  # => [10, 20, 30, 40]

# 2) 뒤 3개 원소 [-3:] — 음수 인덱스는 뒤에서부터
last_three = numbers[-3:]                  # => [50, 60, 70]
print(last_three)

# 옵션: step(간격) 지정 [start:stop:step]
# 3) 홀수번째(0,2,4...) — step=2
every_other_list = numbers[::2]            # => [10, 30, 50, 70]
print(every_other_list)

# 4) 역순 — step=-1
reverse_list = numbers[::-1]               # => [70, 60, 50, 40, 30, 20, 10]
print(reverse_list)

# 2차원(중첩) 리스트 슬라이싱
matrix = [
    [1,  2,  3,  4 ],
    [5,  6,  7,  8 ],
    [9, 10, 11, 12],
    [13,14, 15, 16],
]

# 5) 첫 두 행 [:2]
first_two_rows = matrix[:2]                # => [[1,2,3,4],[5,6,7,8]]
print(first_two_rows)

# 6) 첫 두 열 — 행 단위로 열 슬라이싱
first_two_columns = [row[:2] for row in matrix]  # => [[1,2],[5,6],[9,10],[13,14]]
print(first_two_columns)

# 대안/옵션:
# - numpy 슬라이싱 (대규모 수치연산에서 권장)
#   [import numpy as np]
#   arr = np.array(matrix)
#   arr[:2, :]   # 행 슬라이싱
#   arr[:, :2]   # 열 슬라이싱

2-2 리스트/딕셔너리 컴프리헨션

# 1) 기본 리스트 컴프리헨션 — range로 0~4 생성
numbers = [i for i in range(5)]            # => [0,1,2,3,4]
print(numbers)

# 대안:
# - 전통적 for-append 패턴 (가독성 ↑, 길이 ↑)
#   out = []
#   for i in range(5):
#       out.append(i)

# 2) 변환(map) — 모든 항목 제곱
base = [1, 2, 3, 4, 5]
squares = [x**2 for x in base]             # => [1,4,9,16,25]
print(squares)

# 대안:
# - map 함수 + lambda
#   squares = list(map(lambda x: x**2, base))  # [import (built-in) — 별도 import 불필요]

# 3) 필터링(filter) — 짝수만 추출
nums = [1,2,3,4,5,6,7,8,9,10]
even_numbers = [x for x in nums if x % 2 == 0]  # => [2,4,6,8,10]
print(even_numbers)

# 대안:
# - filter 함수 + lambda
#   even_numbers = list(filter(lambda x: x % 2 == 0, nums))

# 4) 중첩 루프 — 모든 조합(카테시안 곱)
list1 = ['사과', '복숭아', '바나나']
list2 = ['주스', '잼', '통조림']
pairs = [(f, p) for f in list1 for p in list2]
print(pairs)
# 대안:
# - itertools.product
#   [import itertools]
#   pairs = list(itertools.product(list1, list2))

# 5) 딕셔너리 컴프리헨션 — 키:값 변환
squares_map = {i: i**2 for i in range(5)}  # => {0:0,1:1,2:4,3:9,4:16}
print(squares_map)

# 6) 조건부 딕셔너리 필터링
city_population = {
    '서울': 957, '부산': 339, '인천': 294, '대구': 242, '광주': 145, '대전': 147,
    '울산': 114, '세종': 36,  '수원': 115, '창원': 103, '고양': 105, '용인': 108, '성남': 94
}

# (예시) 인구 200 이상 도시만 — .items()로 (키,값) 튜플 순회
large_cities = {city: pop for city, pop in city_population.items() if pop >= 200}
print(large_cities)  # => {'서울': 957, '부산': 339, '인천': 294, '대구': 242}

# (예시) 인구 300 이상 + 이름에 '산' 포함
large_short_name_cities = {
    city: pop for city, pop in city_population.items()
    if pop >= 300 and '산' in city
}
print(large_short_name_cities)  # => {'부산': 339}

# 옵션/주의:
# - 컴프리헨션이 너무 복잡해지면(중첩+조건 다수) 가독성 저하 → 일반 for문 권장
# - set 컴프리헨션도 동일 문법 {expr for ...}  (중복 제거 필요 시)

2-3 문자열 형식화(포매팅)

name = '홍길동'
age = 30
salary = 3_500_000
tax_rate = 0.10

# 1) C 스타일(구식) — % 연산자
basic_format = '이름: %s, 나이: %d, 월급: %d원' % (name, age, salary)
print(basic_format)
# 옵션(형식 지정자):
# %s(문자열), %d(정수), %f(부동소수), %.2f(소수점 2자리), %10d(폭), %-10s(좌측정렬)

# 2) str.format — 위치/키워드 인자
index_format = '이름: {0}, 나이: {1}, 월급: {2}원'.format(name, age, salary)
print(index_format)
# 대안(키워드):
# index_format_kw = '이름: {n}, 나이: {a}, 월급: {s}원'.format(n=name, a=age, s=salary)

# 3) f-문자열 — 가장 가독성 높고 빠름(파이썬 3.6+)
keyword_format = f'이름: {name}, 나이: {age}, 월급: {salary}원'
print(keyword_format)

# 4) 계산 포함 + 서식 옵션(세후 월급, 세금, 실수령액)
tax = salary * tax_rate
net = salary - tax

# - 천 단위 구분자: :, 소수점 자리: .2f, 정렬/폭: >10, <10, ^10
line1 = f'세율: {tax_rate:.0%}, 세금: {tax:,.0f}원, 실수령액: {net:,.0f}원'
print(line1)

# 5) 동일 내용의 다른 방식들 (학습용 비교)
line1_percent = '세율: %.0f%%, 세금: %s원, 실수령액: %s원' % (
    tax_rate*100, format(tax, ',.0f'), format(net, ',.0f')
)
print(line1_percent)

line1_format = '세율: {rate:.0%}, 세금: {tax:,.0f}원, 실수령액: {net:,.0f}원'.format(
    rate=tax_rate, tax=tax, net=net
)
print(line1_format)

# 옵션/팁:
# - {:,} : 천 단위 구분
# - {:.2f} : 소수점 둘째 자리까지
# - {var:>10} : 폭 10, 우측정렬 / < 좌측 / ^ 중앙
# - 날짜/시간: datetime + f-string 또는 strftime 사용
#   [import datetime]
#   from datetime import datetime
#   now = datetime.now()
#   f'오늘은 {now:%Y-%m-%d %H:%M:%S}'

2-4 컨텍스트 관리 (Context Manager)

# 1) 파일 열기/닫기 — 수동 관리 (권장 X: 예외 시 close 누락 위험)
file = open('output.txt', 'w')
file.write('Hello, world!\n')
file.close()

# 2) with 문(권장) — 예외 발생해도 안전하게 close 보장
with open('output.txt', 'w', encoding='utf-8') as f:  # 옵션: mode='a' 추가 시 append
    f.write('Hello, world!\n')

# 옵션:
# - open 모드: 'r'(읽기), 'w'(새로쓰기), 'a'(덧붙이기), 'x'(신규 생성), 'b'(바이너리), '+’(읽기/쓰기)
#   예) 'rb' 바이너리 읽기, 'w+' 읽기/쓰기
# - encoding: 텍스트 파일은 'utf-8' 권장
# 3) URL 연결 관리 — urllib.request (표준 라이브러리)
#    with 문으로 HTTP 연결 객체도 안전하게 정리
# [import urllib.request]
import urllib.request

url = 'https://raw.githubusercontent.com/jaehwachung/Data-Analysis-with-Open-Source/refs/heads/main/Chapter%203/students.csv'

# 바이너리 스트림을 열고, UTF-8로 디코드
with urllib.request.urlopen(url) as response:  # 옵션: timeout=10 등
    data = response.read().decode('utf-8')     # 옵션: 인코딩 다르면 'cp949' 등
print(data[:200])  # (미리보기) 처음 200자만 출력

# 대안 1) requests (서드파티, 간단 API)
# [import requests]
# import requests
# resp = requests.get(url, timeout=10)
# resp.raise_for_status()         # HTTP 에러 시 예외
# data = resp.text                # 자동 인코딩 추정
# print(data[:200])

# 대안 2) pandas로 바로 읽기 (CSV 처리에 강력)
# [import pandas]
# import pandas as pd
# df = pd.read_csv(url)           # 옵션: sep, header, encoding, dtype, usecols 등
# print(df.head())

# 옵션/주의:
# - 네트워크 I/O는 예외 대비 try/except 권장
# - 대용량 파일은 스트리밍(iter_content) 또는 chunk 단위 처리 고려(requests)
# - 인증/헤더 필요 시: urllib.request.Request 사용, 혹은 requests의 headers 파라미터
# 4) 사용자 정의 컨텍스트 매니저 — contextlib.contextmanager
#    파일/락/세션 등 "열고-사용하고-정리" 패턴을 캡슐화할 때 유용
# [import contextlib]
from contextlib import contextmanager

@contextmanager
def opened(path, mode='r', encoding=None):
    f = open(path, mode, encoding=encoding)
    try:
        yield f              # with 블록 내부로 자원(f) 넘김
    finally:
        f.close()            # 블록 종료 시 정리 보장

with opened('sample.txt', 'w', encoding='utf-8') as fp:
    fp.write('컨텍스트 매니저 예시\n')

# 대안:
# - 클래스 기반 __enter__/__exit__ 구현
# - 파일 외에도 DB 커넥션, 스레드 락, 임시 디렉토리 등 관리 가능
# 5) 여러 파일/자원 동시 관리 — 여러 with를 한 줄로
#    (파이썬 3.10+에서는 괄호로 줄바꿈 가능)
with open('in.txt', 'r', encoding='utf-8') as fin, \
     open('out.txt', 'w', encoding='utf-8') as fout:
    for line in fin:
        fout.write(line.upper())

# 대안:
# - contextlib.ExitStack (가변 개수의 자원 동적 관리 시 유용)
# [import contextlib]
# from contextlib import ExitStack
# with ExitStack() as stack:
#     files = [stack.enter_context(open(p, 'r', encoding='utf-8')) for p in paths]
#     # files 리스트로 일괄 처리
# 6) 임시 파일/디렉터리 — 자동 정리되는 자원
# [import tempfile]
import tempfile
import os

# 임시 파일
with tempfile.NamedTemporaryFile(mode='w+', delete=True, encoding='utf-8') as tmp:
    tmp.write('임시 데이터\n')
    tmp.seek(0)
    print(tmp.read())

# 임시 디렉터리
with tempfile.TemporaryDirectory() as tmpdir:
    path = os.path.join(tmpdir, 'data.txt')
    with open(path, 'w', encoding='utf-8') as f:
        f.write('tempdir 예시')
    # 블록 종료 시 디렉터리/내용 모두 삭제
# 7) 표준 입출력 리다이렉션 — contextlib.redirect_stdout/redirect_stderr
# [import contextlib, io]
import io
from contextlib import redirect_stdout

buf = io.StringIO()
with redirect_stdout(buf):
    print("이 출력은 화면 대신 버퍼로 갑니다.")
captured = buf.getvalue()
print("캡쳐된 출력:", captured)

 

'Python > Data analysis' 카테고리의 다른 글

데이터 전처리 1  (0) 2025.10.10
데이터 저장  (0) 2025.10.07
데이터 수집  (0) 2025.10.07
언패킹, 예외처리, 함수형 프로그래밍  (0) 2025.10.03
데이터 분석과 오픈소스  (0) 2025.10.03