Skip to content
This page has been auto-translated and may contain errors.View in English

모듈과 표준 라이브러리

docs.scrimba.com

Python에는 이미 사용할 수 있는 수많은 도구가 있습니다. 난수 생성, 수학, 날짜, 파일 경로 등이 있습니다. 이러한 도구들은 모듈에 살아있으며, import를 사용하여 코드로 가져옵니다. 파일과 예외 장에서 이미 import json을 사용했습니다. 이 장에서는 import를 완전히 다루고 표준 라이브러리의 가장 유용한 부분을 소개합니다.

Python의 표준 라이브러리는 일반적인 문제에 대한 검증되고 문서화된 솔루션을 제공합니다. 모듈은 코드 구성의 단위입니다. 각 파일이 모듈이고, __init__.py가 있는 각 디렉터리는 패키지입니다. import 시스템은 모듈을 찾고, 필요하면 컴파일한 후, sys.modules에 캐시하므로 한 번만 로드됩니다.

import는 실제 작업을 수행합니다. Python은 모듈을 찾아 한 번 실행한 다음 sys.modules(이미 로드된 모든 모듈의 딕셔너리)에 결과를 캐시하므로 같은 이름의 두 번째 import는 빠른 조회이지 다시 실행하는 것이 아닙니다. import requests는 모듈 객체를 현재 네임스페이스(코드의 해당 지점에서 보이는 이름 테이블)의 이름 requests에 바인딩합니다. from requests import get은 같은 로드를 실행하지만 get만 바인딩합니다. 실제 프로젝트에서 효과적인 부분은 Python이 검색하는 위치(sys.path), 디렉터리가 어떻게 임포트 가능한 패키지가 되는지(__init__.py), 순환 import를 피하는 방법이며, 모두 아래에서 다룹니다.

모듈 임포트하기

가장 간단한 import는 전체 모듈을 가져오고 점 표기법으로 내용을 사용할 수 있게 합니다. 모듈의 특정 이름을 임포트하여 접두사 없이 직접 사용할 수도 있습니다. 별칭은 긴 이름을 단축합니다.

import module은 모듈 객체를 현재 범위의 이름 module에 바인딩합니다. from module import namename만 바인딩합니다. 별칭(import module as alias)은 타사 라이브러리에서 일반적입니다. from module import *는 피하세요. 네임스페이스를 오염시키고 이름이 어디서 왔는지 불명확하게 만듭니다.

from module import name은 작업을 건너뛰지 않습니다. Python은 여전히 전체 모듈을 로드하고 실행한 후 요청한 하나의 이름만 바인딩합니다. 알아야 할 함정은 순환 import입니다. 두 모듈이 로드 중에 서로를 임포트하려고 하면 하나가 반쯤 구축된 모듈을 보고 이름이 누락됩니다. 일반적인 해결책은 그 import를 그것을 필요로 하는 함수 내부로 이동하거나(양쪽 모듈이 로드 완료 후 실행되도록) 공유 코드를 양쪽이 임포트할 수 있는 세 번째 모듈로 분리하는 것입니다. 런타임에만 알 수 있는 이름의 모듈을 임포트해야 할 때, importlib.import_module("name")은 이름을 문자열로 가져옵니다.

python
import math

math.sqrt(16)     # 4.0
math.pi           # 3.141592653589793
math.floor(3.9)   # 3
math.ceil(3.1)    # 4

모듈의 특정 이름을 임포트하면 접두사 없이 직접 사용할 수 있습니다:

python
from math import sqrt, pi

sqrt(16)    # 4.0 ("math." 접두사가 필요 없음)
pi          # 3.141592653589793

모듈이나 이름에 별칭을 주어 단축합니다:

python
import math as m

m.sqrt(16)    # 4.0

from math import sqrt as square_root
square_root(25)    # 5.0

별칭은 인기 있는 타사 라이브러리에서 흔합니다(import numpy as np, import pandas as pd). 표준 라이브러리 모듈의 경우 전체 이름을 사용하는 것을 선호합니다. 코드가 더 읽기 쉬워집니다.

Juno모듈 임포트하기import math는 전체 모듈을 가져오고, 점으로 내부에 접근합니다: math.sqrt(16). from math import sqrt는 하나의 이름을 가져와서 접두사를 제거할 수 있습니다. as 키워드는 긴 import의 이름을 바꾸지만, 표준 라이브러리의 경우 전체 이름이 더 명확하게 읽힙니다.
Juno모듈 임포트하기import module은 모듈 객체를 바인딩하고, from module import name은 하나의 이름을 바인딩하고, import module as alias는 이름을 변경합니다. from module import *를 건너뛰세요. 모든 이름을 범위에 덤프하고 각 이름이 어디서 왔는지 숨깁니다.
Juno모듈 임포트하기from module import name은 여전히 전체 모듈을 로드하고 하나의 이름만 유지합니다. 결국 마주칠 문제는 순환 import입니다. 두 모듈이 로드 중에 서로를 임포트합니다. 그 `import`를 함수 내부로 이동하거나 공유 코드를 세 번째 모듈로 분리하세요. `importlib.import_module()`은 이름만 런타임에 알 때 사용합니다.

random

random 모듈은 난수를 생성하고 무작위 선택을 합니다. 게임, 시뮬레이션, 무작위 샘플링, 예측 불가능성이 필요한 모든 것에 사용합니다. 시드를 설정하면 결과를 재현할 수 있습니다. 같은 시드는 매번 같은 시퀀스를 생성합니다.

random은 Mersenne Twister 의사난수 생성기를 사용합니다. 시드는 전체 시퀀스를 결정합니다. 같은 시드는 항상 같은 출력을 생성합니다. .choice()는 하나의 항목을 선택하고, .choices()는 복원 추출로 선택하고, .sample()은 비복원 추출로 선택합니다. .shuffle()은 리스트를 제자리에서 수정하고 None을 반환합니다.

random의사난수를 생성합니다. 난수처럼 보이지만 시작 값(시드)에서 나온 고정 수식에서 나오므로, 같은 시드는 모든 시스템에서 같은 시퀀스를 제공하고, 이것이 테스트를 재현 가능하게 만드는 것입니다. 프로덕션에서 중요한 규칙: 사람이 악용할 수 있는 것(암호, 세션 토큰, 비밀번호 재설정 코드)에는 random을 사용하지 마세요. 설계상 예측 가능하므로 충분한 출력을 본 공격자는 나머지를 추측할 수 있습니다. 이 경우 대신 운영 체제의 안전한 무작위성에서 가져오는 secrets 모듈을 사용합니다. random.SystemRandom()은 API를 전환하지 않으려면 같은 안전한 소스를 친숙한 random 메서드 이름 뒤에 제공합니다.

python
import random

random.random()              # 0과 1 사이의 float (배타적)
random.randint(1, 10)        # 1부터 10까지의 정수 (둘 다 포함)
random.uniform(1.0, 10.0)    # 1.0과 10.0 사이의 float

colours = ["빨강", "녹색", "파랑"]
random.choice(colours)       # 하나의 항목을 선택합니다
random.choices(colours, k=3) # k개의 항목을 선택합니다 (복원 추출)
random.sample(colours, k=2)  # k개의 항목을 선택합니다 (비복원 추출)

numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers)      # 제자리에서 섞고 None을 반환합니다

재현 가능한 결과를 위해(테스트 및 데이터 과학에 유용), 생성 전에 시드를 설정합니다:

python
random.seed(42)
random.randint(1, 100)   # 시드 42에 대해 항상 같은 값입니다

같은 시드는 모든 시스템의 모든 시간에 같은 시퀀스를 생성합니다.

Junorandomrandom.choice()는 리스트에서 하나의 항목을 선택하고, random.randint(1, 10)은 범위에서 정수를 제공합니다(끝 포함). 매 실행마다 같은 결과를 원하나요, 테스트를 위해? 먼저 random.seed()를 호출하면 시퀀스가 잠깁니다.
Junorandom.choice()는 하나를 선택하고, .choices()는 복원 추출로 선택하고, .sample()은 비복원 추출로 선택합니다. .shuffle()은 리스트를 제자리에서 재정렬하고 None을 반환하므로 nums = random.shuffle(nums)를 절대 쓰지 마세요. 시드는 전체 시퀀스를 고정하므로 무작위 테스트를 재현 가능하게 합니다.
Junorandomrandom은 의사난수이고 설계상 예측 가능하므로 게임과 테스트에는 좋지만 공격자가 악용할 수 있는 것에는 틀렸습니다. 토큰, 암호, 재설정 코드에는 secrets를 사용하세요. 시퀀스를 반복하고 싶은 재현 가능한 경우를 위해 random.seed()를 유지하세요.

math

math 모듈은 숫자 장에서 만난 기본 산술 연산자를 넘어선 더 고급 수학 연산을 추가합니다. 제곱근, 거듭제곱, 로그, 삼각함수, 그리고 pi와 무한대 같은 특수한 값들이 모두 여기에 있습니다.

math는 표준 수학 함수의 C 수준 구현을 제공합니다. math.pow()는 항상 float를 반환하지만 Python의 ** 연산자는 정수 밑과 지수에 대해 int를 반환한다는 점에 주의하세요. math.log(x, base)는 임의의 밑에 대한 로그를 계산합니다. math.log(x)는 자연 로그를 계산합니다.

실제 코드에 물린 세부 사항은 nan("숫자 아님", 0/0 또는 실패한 파싱 같은 정의되지 않은 결과에서 나온 float)과 inf(무한대)입니다. nan은 자기 자신도 포함하여 어떤 것과도 같지 않습니다. 따라서 x == float("nan")은 항상 False입니다. 즉, ==로 테스트할 수 없습니다. math.isnan(x)를 사용하고, 무한대에는 math.isinf(x)를 사용하세요. 다른 유용한 습관: 두 float를 비교할 때 a == b보다 math.isclose(a, b)를 선호합니다. 부동소수점 반올림이 정확한 동일성을 신뢰할 수 없게 만들기 때문입니다. 숫자 하나 대신 전체 배열에 대한 수학의 경우 numpy가 표준 도구이지만 이 가이드 범위 밖입니다.

python
import math

math.sqrt(25)        # 5.0
math.pow(2, 10)      # 1024.0 (2 ** 10과 같지만 항상 float을 반환)
math.log(100, 10)    # 2.0 (log base 10)
math.log(math.e)     # 1.0 (자연 로그)

math.sin(math.pi / 2)   # 1.0
math.cos(0)             # 1.0

math.ceil(3.2)    # 4
math.floor(3.9)   # 3
math.trunc(3.9)   # 3 (양수의 경우 int()와 같음)

math.inf          # 무한대
math.isnan(float("nan"))   # True
math.isinf(math.inf)       # True
Junomathmath+ - * /를 넘어: 제곱근, 거듭제곱, 로그, 삼각함수, 그리고 math.pi 같은 상수를 포함합니다. 계산이 기본 연산자를 넘어설 순간 도달하면 이것을 사용하고 직접 수식을 쓰는 것을 건너뜁니다.
Junomathmath는 제곱근, 로그, 삼각함수를 가집니다. 주목할 한 가지: math.pow(2, 10)은 항상 `float`을 반환하지만 `2 ** 10`은 정수 입력에 대해 `int`로 유지됩니다. math.log(x, base)는 임의의 밑을 가지고, math.log(x)는 자연 로그입니다.
Junomath 함정은 `nan`과 `inf`입니다. `nan`은 자신과도 같지 않으므로 `==`이 아닌 `math.isnan()`로 테스트하세요. 그리고 float을 `==`이 아닌 `math.isclose()`로 비교하세요. 반올림이 정확한 동일성을 신뢰할 수 없게 만들기 때문입니다. 숫자 배열의 경우 `numpy` 영역입니다.

datetime

datetime 모듈은 날짜와 시간을 처리합니다. datetime.now()는 현재 날짜와 시간을 제공합니다. strftime()은 문자열로 형식화합니다. strptime()은 문자열을 datetime으로 파싱합니다. timedelta는 더하거나 뺄 수 있는 기간을 나타냅니다.

datetime, date, timedelta는 주요 클래스입니다. strftime()은 형식 코드를 사용하여 datetime을 문자열로 형식화합니다. strptime()은 형식 패턴이 주어졌을 때 문자열을 파싱합니다. timedelta는 산술을 지원합니다. 날짜에 기간을 더하거나 빼거나 <, >, -로 datetime을 비교할 수 있습니다.

프로덕션의 함정은 순진한 datetime.now()입니다. 순진한: 타임존이 붙지 않아서 실제로 세계의 어느 부분을 나타내는지 모릅니다. 다른 시스템에서 온 두 순진한 datetime은 비교 가능해 보이지만 조용히 그렇지 않고, 이 모호함이 시간 차이 버그를 일으킵니다. 해결책은 인식형 datetime(타임존을 가진 것)으로 작업하고 시스템 간에 저장되거나 전송되는 모든 것에 대해 UTC를 표준화하는 것입니다. datetime.now(timezone.utc). 사람에게 보여줄 때만 지역 시간으로 변환합니다. zoneinfo 모듈을 "Europe/Oslo" 같은 이름 있는 영역에 사용합니다. 일광절약시간 이동을 처리합니다. 한 가지 더 규칙: datetime.now() 차이로 경과 시간을 측정하지 마세요. 시스템 시계가 점프할 수 있기 때문입니다(동기화 또는 수동 변경). 항상 앞으로만 이동하는 카운터인 time.perf_counter()를 기간에 사용합니다.

python
from datetime import datetime, date, timedelta

now = datetime.now()             # 현재 날짜와 시간
today = date.today()             # 현재 날짜만

print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)

# 형식화
print(now.strftime("%Y-%m-%d"))           # "2024-01-15"
print(now.strftime("%d %B %Y, %H:%M"))   # "15 January 2024, 09:42"

# 파싱
deadline = datetime.strptime("2024-12-31", "%Y-%m-%d")

# 산술
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
diff = deadline - now
print(f"{diff.days} days until deadline")

일반 strftime 코드:

코드의미예제
%Y4자리 연도2024
%m월(0으로 채워짐)01
%d일(0으로 채워짐)15
%H시간(24시간)09
%M42
%B전체 월 이름January
Junodatetimedatetime.now()는 현재 날짜와 시간을 제공합니다. strftime()은 `%Y-%m-%d` 같은 코드로 문자열로 변환하고, strptime()은 반대 방향으로 문자열을 다시 파싱합니다. timedelta는 더하거나 뺄 수 있는 기간이므로 `today + timedelta(days=1)`은 내일입니다.
Junodatetimedatetime, date, timedelta는 핵심입니다. strftime()은 코드로 형식화하고, strptime()은 일치하는 패턴으로 파싱합니다. 날짜는 산술을 합니다. 둘을 빼면 `timedelta`를 얻고, `timedelta`를 더해 앞뒤로 이동합니다.
Junodatetime 순진한 `datetime.now()`은 타임존이 없어서 두 시스템이 관여하면 조용히 깨집니다. `datetime.now(timezone.utc)`로 UTC를 저장하고 전달하고, 표시를 위해서만 지역으로 변환하고, 이름 있는 영역에는 `zoneinfo`를 의존합니다. 그리고 `now()` 차이로 경과 시간을 측정하지 마세요. `time.perf_counter()`를 사용합니다. 벽시계는 점프할 수 있기 때문입니다.

os와 pathlib

pathlib는 파일 경로 작업의 현대적 방식입니다. Path 객체는 / 연산자를 사용하여 경로를 구축, 검사, 네비게이션할 수 있게 합니다. os는 환경 변수와 낮은 수준의 OS 작업에 접근할 수 있게 합니다. 새 코드에는 pathlib를 선호합니다.

pathlib.Path는 쿼리와 네비게이션을 위한 메서드가 있는 객체로 파일시스템 경로를 나타냅니다. / 연산자는 경로 구성 요소를 깔끔하게 조인하고 OS 특정 구분자를 자동으로 처리합니다. os.environ은 환경 변수를 위한 딕셔너리 같은 객체입니다. os.environ.get("KEY", "default")는 누락된 변수에 안전합니다.

Path는 운영 체제에 자동으로 적응하므로 같은 코드가 모든 시스템에서 올바른 구분자로 경로를 조인합니다. Path("data") / "file.csv"를 쓰고, Windows에서 깨지는 "data/" + name 문자열을 절대 손으로 만들지 마세요. 두 가지 실용적인 주의 사항입니다. 첫째, .glob(), .rglob(), .iterdir()생성기를 반환합니다(전체 리스트를 구축하는 대신 항목을 하나씩 생성합니다). 두 번 루프하거나 처음부터 개수를 알아야 한다면 list()로 래핑하세요. 거대한 디렉터리의 경우 게으르게 반복하는 것이 요점입니다. 둘째, 몇몇 오래된 라이브러리는 Path 객체 대신 순수 문자열을 기대합니다. 하나가 Path를 거부하면 str(path)를 전달하고 계속 진행하세요. 환경 변수를 읽을 때 항상 os.environ.get("KEY", "default")를 사용하세요. 인덱싱 형식(os.environ["KEY"])보다는 변수가 설정되지 않았을 때 KeyError를 발생시키고, 이것이 신규 배포에서 일반적인 경우입니다.

python
from pathlib import Path

p = Path("data/reports")

p.exists()           # 경로가 존재하면 True
p.is_dir()           # 디렉터리면 True
p.is_file()          # 파일이면 True

p.mkdir(parents=True, exist_ok=True)   # 디렉터리 생성

for f in p.glob("*.csv"):              # 디렉터리의 모든 CSV 파일
    print(f.name)                      # 파일명

report = p / "report_jan.csv"          # / 연산자가 경로를 조인합니다
report.stem       # "report_jan" (확장자 없는 이름)
report.suffix     # ".csv"
report.parent     # Path("data/reports")

content = report.read_text()           # 파일 내용을 직접 읽습니다
report.write_text("new content\n")    # 직접 씁니다

os 모듈의 경우:

python
import os

os.getcwd()                        # 현재 작업 디렉터리
os.listdir(".")                    # 디렉터리 내용을 나열합니다
os.path.exists("data.txt")        # 경로가 존재하면 True
os.path.join("data", "file.txt")  # "data/file.txt" (크로스 플랫폼)
os.environ.get("HOME")            # 환경 변수를 읽습니다

새 코드에는 pathlib를 선호합니다. 환경 변수가 필요하거나 문자열을 기대하는 오래된 API와 작업할 때 os를 사용합니다.

Junoos와 pathlibpathlib.Path는 파일 경로를 객체로 취급합니다. 당신이 찔러볼 수 있습니다: .exists(), .read_text(), .write_text(), .glob(). / 연산자가 조각을 조인하므로 `p / "report.csv"`는 실제 경로처럼 읽힙니다. 새 코드에서는 오래된 `os.path`보다 pathlib를 사용하세요.
Junoos와 pathlibPath/로 조인하고 OS 구분자를 처리하므로 경로가 이식 가능합니다. os.environ은 환경 변수를 위해 딕셔너리 같고, os.environ.get("KEY", "default")는 안전한 읽기이고 누락된 키에 폭발하지 않습니다.
Junoos와 pathlibPath/로 경로를 구축하고, Windows에서 깨지는 접착된 문자열은 절대 안 됩니다. .glob()과 친구들은 생성기를 반환하므로 두 번 루프해야 하면 list()합니다. 그리고 환경 변수를 `os.environ.get(key, default)`로 읽으세요. 브라켓 형식은 변수가 설정되지 않은 순간 `KeyError`를 발생시키고, 이것이 정확히 신규 배포 경우입니다.

timeit

timeit는 코드가 얼마나 오래 실행되는지 측정합니다. 두 가지 접근 방식을 비교하고 더 빠른 것을 선택하고 싶을 때 유용합니다. 안정적인 측정을 얻기 위해 여러 번 코드를 실행합니다.

timeit.timeit(stmt, setup, number)stmtnumber번 실행하고 총 경과 시간(초)을 반환함으로써 시간을 잽니다. setup 문자열은 타이밍된 루프 전에 한 번 실행됩니다. 호출당 시간을 얻으려면 결과를 number로 나누세요. 더 많은 반복은 시스템 스케줄링의 노이즈를 줄입니다.

timeit마이크로벤치마크: 한 작은 스니펫을 고립으로 시간 측정하고, 스케줄링 노이즈가 평균화되도록 여러 번 실행합니다. 노이즈를 조용하게 하지만 단일 실행은 여전히 신뢰할 수 없으므로 하나의 숫자보다 여러 반복의 최고를 가져가세요. 피해야 할 실수는 전체 프로그램의 속도 저하를 찾기 위해 사용하는 것입니다. 당신이 손으로 제공한 스니펫만 시간을 재므로 어느 함수가 실제로 느린지 알 수 없습니다. 이를 위해 cProfile를 사용하세요. 이것은 실제 프로그램을 실행하고 시간이 어디로 갔는지 보고합니다. 일반적인 규칙: timeit는 두 가지 구현을 비교하고, cProfile은 먼저 병목을 찾으세요.

python
import timeit

# 단일 문을 시간 측정합니다
timeit.timeit("sum(range(1000))", number=10000)

# 더 복잡한 블록을 시간 측정합니다
setup = "data = list(range(1000))"
code = "[x * 2 for x in data]"
time = timeit.timeit(code, setup=setup, number=10000)
print(f"{time:.4f} seconds for 10,000 runs")

number는 반복 횟수입니다. 더 많은 반복은 더 안정적인 측정을 제공합니다.

Junotimeittimeit.timeit()는 코드를 반복해서 실행하고 실행 횟수로 나누어 안정적인 호출당 숫자를 얻음으로써 코드의 시간을 재습니다. 추측하는 대신 "이 둘 중 어느 것이 더 빠른가"를 해결하는 방법입니다. 한 번의 실행은 신뢰하기에 너무 시끄러우므로 루프하게 합니다.
Junotimeittimeit.timeit(stmt, setup, number)는 `stmt`를 그 많은 번 실행하고 총 초를 반환합니다. `setup` 문자열은 루프 전에 한 번 실행됩니다. 호출당 시간을 위해 `number`로 나누고, 스케줄링 노이즈를 익사시키려면 반복을 올립니다.
Junotimeit `timeit`는 마이크로벤치마크를 위한 것입니다. 두 작은 스니펫을 비교하고, 하나의 시끄러운 실행보다 여러 개 중 최고를 가져갑니다. 실제 프로그램에서 어느 함수가 느린지 알 수 없으므로 느림을 찾을 수 없습니다. `cProfile`을 사용하여 병목을 찾고, `timeit`로 수정을 비교합니다.

string

string 모듈은 문자, 숫자, 구두점을 위한 미리 만들어진 문자열 상수를 제공합니다. 문자를 확인해야 할 때나 특정 알파벳에서 무작위 문자열을 생성할 때 유용합니다.

string 모듈 상수(ascii_letters, digits, punctuation)는 인덱싱, 반복 또는 in 사용할 수 있는 순수 문자열입니다. random.choices()와 결합하는 것이 무작위 토큰이나 암호를 생성하는 표준 방법입니다.

이 상수들은 순수 문자열이므로 char in string.ascii_letters 확인이 매번 전체 문자열을 스캔합니다. 이 스캔은 O(n): 작업이 문자열의 길이와 함께 증가합니다. 한 번에 좋고, 타이트한 루프에서는 낭비입니다. 멤버십을 반복해서 테스트 중이면 한 번 set을 구축합니다(letters = set(string.ascii_letters)) 그리고 대신 그것에 대해 확인합니다. set 조회는 얼마나 많은 항목을 가지고 있든 빠르게 유지됩니다. 여기서 다른 유용한 부분은 string.Template이고, 순수 $name 치환을 합니다. 템플릿이 사용자 입력 또는 구성 파일에서 나올 때, 신뢰할 수 없는 텍스트를 f-문자열이나 str.format()에 주는 것보다 안전합니다. 당신의 변수에 도달하거나 메서드를 호출할 수 없기 때문입니다.

python
import string

string.ascii_lowercase   # "abcdefghijklmnopqrstuvwxyz"
string.ascii_uppercase   # "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
string.ascii_letters     # 둘 다 결합
string.digits            # "0123456789"
string.punctuation       # 모든 구두점 문자

문자를 확인해야 할 때나 무작위 문자열을 생성할 때 유용합니다:

python
import string, random

chars = string.ascii_letters + string.digits
password = "".join(random.choices(chars, k=12))
Junostring `string` 모듈은 당신에게 준비 완료된 문자 집합을 제공합니다: `string.ascii_letters`, `string.digits`, `string.punctuation`. 그들은 순수 문자열이므로 루프하거나 샘플링할 수 있습니다. 알려진 알파벳을 구축하거나 확인해야 할 때마다 편리합니다.
Junostring `string.ascii_letters`, `string.digits`와 친구들은 순수 문자열이고 인덱싱, 반복 또는 `in`으로 테스트할 수 있습니다. `random.choices()`와 짝을 이루면 무작위 토큰이나 암호의 표준 레시피를 얻습니다. `random`은 실제 비밀에 안전하지 않다는 점에 주의하세요.
Junostring 상수는 순수 문자열이므로 `char in string.ascii_letters`는 모든 호출마다 전체를 스캔합니다. 루프에서 확인 중이면 한 번 `set`을 구축합니다. 그리고 템플릿이 사용자 입력에서 나올 때, `string.Template`과 `$name`은 f-문자열보다 안전합니다. 당신의 변수에 도달할 수 없기 때문입니다.

자신의 모듈 만들기

모든 Python 파일은 모듈입니다. 다른 파일에서 사용하려면 파일명으로 임포트하세요(.py 제외). 전체 모듈을 임포트하고 점 표기법으로 내용을 사용하거나 특정 이름을 직접 임포트할 수 있습니다.

Python이 모듈을 임포트할 때, 파일을 위에서 아래로 한 번 실행하고 sys.modules에서 결과를 캐시합니다. 같은 모듈의 후속 임포트는 파일을 다시 실행하지 않고 캐시된 객체를 반환합니다. 더 큰 프로젝트의 경우 모듈은 패키지로 조직됩니다. __init__.py 파일이 있는 디렉터리입니다.

import utils를 쓸 때, Python은 sys.path의 디렉터리를 순서대로 검색하고, 당신이 실행한 스크립트의 폴더로 시작합니다. 이것이 가장 일반적인 "내 컴퓨터에서는 작동하는" import 버그의 원인입니다. 다른 작업 디렉터리에서 파일을 실행하고 기대하는 폴더가 경로에 없을 수 있으므로 import가 실패합니다. 신뢰할 수 있는 수정은 느슨한 파일을 가리키는 대신 프로젝트를 패키지로 실행하는 것입니다(python -m mypackage.main). 패키지__init__.py 파일이 있는 모듈 디렉터리입니다(비어있을 수 있습니다. 그 존재가 Python에 폴더를 임포트 가능하게 알려줍니다). 패키지 내에서 상대 import 같은 from . import helpers는 "이 같은 패키지에서"를 의미하고, 패키지의 이름이 변경되거나 이동되어도 import가 작동하게 유지합니다. 한 가지 날카로운 모서리: importlib.reload()는 모듈을 다시 실행하지만 이미 임포트한 이름은 여전히 오래된 버전을 가리키므로 REPL의 빠른 실험을 넘어 리로딩은 신뢰할 수 없습니다.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

PI = 3.14159
python
# main.py
import utils

utils.clamp(150, 0, 100)   # 100
utils.PI                    # 3.14159

from utils import clamp
clamp(50, 0, 100)           # 50

Python은 임포트하는 파일과 같은 디렉터리(및 몇몇 다른 장소)를 찾으면 모듈을 찾습니다. 더 큰 프로젝트의 경우 모듈은 패키지로 조직됩니다. __init__.py 파일이 있는 디렉터리입니다.

Juno자신의 모듈 만들기 모든 `.py` 파일은 이미 모듈입니다. 파일명 없이 `.py`로 임포트하고, 함수와 변수는 당신의 것입니다. `import utils` 그 다음 `utils.clamp(...)` 또는 한 이름을 `from utils import clamp`로 가져옵니다. 그것은 당신이 성장하는 프로그램을 파일 전체에 분할하는 방법입니다.
Juno자신의 모듈 만들기 모듈을 임포트하면 위에서 아래로 한 번 실행되고, `sys.modules`에서 캐시되므로 나중에 임포트는 파일을 다시 실행하지 않고 같은 객체를 반환합니다. 그 때문에 최상위 코드를 가볍게 유지합니다. 무거운 모든 것은 import 시간에 실행됩니다. 관련 모듈을 패키지로 그룹화합니다. `__init__.py`를 가진 폴더입니다.
Juno자신의 모듈 만들기 Import는 `sys.path`를 통해 해석되고 스크립트 자신의 폴더로 시작되므로 잘못된 디렉터리에서 느슨한 파일을 실행하면 import가 깨집니다. 대신 패키지로 실행하세요: `python -m pkg.main`. 그리고 REPL 빠른 찌르기를 넘어 `importlib.reload()`를 신뢰하지 마세요. 이미 임포트한 이름은 오래된 버전을 가리킵니다.

__name__ == "__main__"

Python이 파일을 직접 실행할 때, __name__"__main__"으로 설정됩니다. 같은 파일을 모듈로 임포트할 때, __name__은 모듈 이름입니다. 이 패턴은 파일을 직접 실행할 때 실행되지만 다른 모듈에 의해 파일이 임포트될 때 건너뜬 코드를 작성할 수 있게 합니다.

if __name__ == "__main__":는 실행 가능한 모듈 코드를 위한 표준 보호입니다. 모듈을 임포트 가능하게(함수를 노출하고) 직접 실행 가능하게(테스트 또는 데모 코드 포함) 합니다. 이것 없으면 모듈 임포트는 모든 최상위 코드를 실행하고, 거의 바람직하지 않습니다.

Python은 실제로 실행한 파일에 __name__"__main__"으로 설정하고, 임포트된 모든 것에 모듈 자신의 이름으로 설정합니다. 보호는 import 시간 부작용(모듈이 로드될 때 뭔가 하는 코드, 인수 파싱, 연결 열기 또는 데모 실행 같은)이 다른 파일이 당신을 임포트하는 순간 발생하지 않기 위해 존재합니다. 유지하는 패턴: 실제 작업을 main() 함수에 놓고 보호 아래에서 호출합니다. 그래서 임포터는 함수를 얻고, 파일은 혼자서 깔끔하게 실행됩니다. 보호를 건너뛰는 것은 테스트 스위트가 모듈을 임포트하는 순간 신비롭게 누군가의 데모 코드를 실행하는 고전적인 이유입니다.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

if __name__ == "__main__":
    # 이 것만 실행됩니다: python utils.py
    # 이것이 아닌: import utils
    print(clamp(150, 0, 100))   # 100

이것은 모듈이자 독립 스크립트로 유용한 모든 모듈을 위한 표준 패턴입니다.

Juno__name__ == '__main__'if __name__ == "__main__":는 한 파일이 임포트 가능 모듈과 실행 가능 스크립트 둘 다 되게 합니다. 보호 아래의 코드는 `python utils.py`할 때 실행되고, 다른 파일이 `import utils`할 때 건너뜁니다. 모듈의 아래에 빠른 데모나 테스트를 집어넣기에 편합니다.
Juno__name__ == '__main__' `__name__`은 직접 실행한 파일에서만 `"__main__"`입니다. 보호는 모듈이 임포트될 때 데모나 테스트 코드가 조용히 실행되지 않게 유지합니다. 라이브러리와 스크립트 둘 다로 작동하는 파일을 만드는 표준 방법입니다.
Juno__name__ == '__main__' 보호는 import 시간 부작용, arg 파싱, 데모, 연결이 누군가가 당신을 임포트할 때 발생하지 않게 합니다. `main()`에 작업을 놓고 보호 아래에서 호출합니다. 임포터는 함수를 얻고, 파일은 혼자서 실행됩니다. 건너뛰면 테스트 스위트가 모듈을 임포트하는 순간 데모를 실행합니다.

표준 라이브러리 하이라이트

알아야 할 몇몇 모듈이 더 있습니다. 각각 자신이 직접 구현하려면 의미 있는 작업이 필요한 일반적인 문제를 해결합니다.

표준 라이브러리는 광범위합니다. 아래 하이라이트는 프로덕션 코드에서 가장 자주 만날 것들입니다. 완전한 참고는 docs.python.org/3/library가 권위 있는 소스입니다.

표준 라이브러리는 잘 테스트되고 문서화된 모듈의 정선된 집합입니다. 타사 패키지에 도달하기 전에 표준 라이브러리가 솔루션을 가지는지 확인하세요. functools, itertools, contextlib, dataclasses, typing, abc는 각각 타사 패키지가 자주 다시 만드는 도구를 제공합니다.

collections: 특화된 컨테이너 타입:

python
from collections import Counter, defaultdict, deque

Counter(["a", "b", "a", "c", "a"])   # Counter({'a': 3, 'b': 1, 'c': 1})
defaultdict(list)                      # 누락된 키를 자동 생성하는 dict
deque([1, 2, 3], maxlen=5)            # 양쪽 끝에서 빠른 append/pop

itertools: 반복 가능을 위한 도구:

python
import itertools

list(itertools.chain([1, 2], [3, 4]))          # [1, 2, 3, 4]
list(itertools.islice(range(100), 5))          # [0, 1, 2, 3, 4]
list(itertools.combinations([1, 2, 3], 2))     # [(1, 2), (1, 3), (2, 3)]
list(itertools.product([0, 1], repeat=2))      # [(0,0), (0,1), (1,0), (1,1)]

sys: Python 해석기에 접근:

python
import sys

sys.argv        # 명령행 인수의 리스트
sys.exit(1)     # 상태 코드로 종료
sys.version     # Python 버전 문자열

타사 패키지: 표준 라이브러리를 넘어, pip는 커뮤니티 패키지를 설치합니다:

bash
pip install requests    # HTTP 라이브러리
pip install pandas      # 데이터 조작
pip install numpy       # 숫자 계산

타사 패키지는 이 가이드 범위 밖이지만 패턴은 항상 같습니다. pip install, 그 다음 import.

실제로

random, string, datetime을 결합하여 타임스탬프가 있는 고유한 게임 ID를 생성합니다:

python
import random
import string
from datetime import datetime

def generate_game_id(length: int = 8) -> str:
    chars = string.ascii_uppercase + string.digits
    return "".join(random.choices(chars, k=length))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

game_id = generate_game_id()
print(f"[{timestamp()}] Starting game {game_id}")

scores = [random.randint(50, 100) for _ in range(5)]
print(f"Round scores: {scores}")
print(f"Best: {max(scores)}")

pathlibdatetime을 사용하여 디렉터리의 파일을 찾고 크기를 보고합니다:

python
from pathlib import Path
from datetime import datetime

def find_files(directory: str, pattern: str = "*.csv") -> list[Path]:
    return sorted(Path(directory).glob(pattern))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

files = find_files(".", "*.md")[:3]
print(f"[{timestamp()}] Found {len(files)} file(s)")
for f in files:
    size = f.stat().st_size if f.exists() else 0
    print(f"  {f.name} ({size} bytes)")

환경 변수에서 앱 구성을 타입된 기본값으로 읽고, 구조화된 접근 로그 항목을 newline-delimited JSON으로 씁니다:

python
import os
import json
from datetime import datetime
from pathlib import Path

def load_env_config() -> dict:
    return {
        "debug": os.environ.get("DEBUG", "false").lower() == "true",
        "port": int(os.environ.get("PORT", "8080")),
        "log_level": os.environ.get("LOG_LEVEL", "INFO"),
    }

def write_access_log(method: str, path: str, status: int) -> None:
    log_dir = Path("logs")
    log_dir.mkdir(exist_ok=True)
    entry = {
        "ts": datetime.now().isoformat(),
        "method": method,
        "path": path,
        "status": status,
    }
    with open(log_dir / "access.jsonl", "a") as f:
        f.write(json.dumps(entry) + "\n")

config = load_env_config()
print(f"Starting on port {config['port']}, debug={config['debug']}")
write_access_log("GET", "/users", 200)

Newline-delimited JSON(.jsonl)은 일반적인 로그 형식입니다. 각 라인은 유효한 JSON 객체이므로 전체 파일을 로드하지 않고 스트림, 추가, 라인 단위로 파싱하기가 간단합니다.