![]()
CPython: bytes.hex(bytes_per_sep) 허용 범위가 커졌다 — sys.maxsize도 이제 OK(gh-147944)
meta_description: Python 3.15에서 bytes.hex/bytearray.hex/memoryview.hex 및 binascii.b2a_hex의 bytes_per_sep 인자 허용 범위가 확대되어 sys.maxsize와 -sys.maxsize가 유효해졌다(gh-147944). 겉보기엔 테스트 한 줄 바뀐 수준이지만, 실제로는 Argument Clinic 변환(int→Py_ssize_t)과 정수 변환 경로가 정리되면서 “경계값에서의 Overflow/TypeError”가 더 일관되게 동작한다. 이 글은 무엇이 바뀌었는지, 실무에서 어디에 도움이 되는지(로그/헥스 덤프/프로토콜 디버깅), 그리고 안전한 사용 패턴을 정리한다. meta_keywords: python,bytes,bytearray,memoryview,hex,bytes_per_sep,binascii,b2a_hex,Py_ssize_t,sys.maxsize,OverflowError,Argument Clinic,debugging,hexdump,logging,protocol,binary,호환성,경계값,테스트 meta_robots: index,follow
바이너리 로그를 남기거나(패킷/토큰/압축 데이터), 디버깅용으로 bytes.hex()를 쓰다 보면 이런 걸 해본 적이 있을 거다.
- “너무 길어서 보기 힘드니, 구분자(sep)를 넣어서 그룹으로 끊자”
예:
payload.hex(':', 2) # b9:01ef 처럼 2바이트마다 구분
payload.hex(' ', -2) # 반대 방향(왼쪽부터)으로 2바이트마다
이때 bytes_per_sep에 “엄청 큰 값”을 넣으면, 사실상 구분자를 넣지 않는 효과가 난다.
bytes_per_sep가 길이보다 크면 → 구분자 없음
그런데 CPython 내부 구현/클리닉 파서가 int 기준이던 시절에는, 플랫폼에 따라 “큰 값”이 경계에서 애매하게 취급될 여지가 있었다.
이번 PR(gh-147944)은 이 허용 범위를 명확히 넓힌다.
- 이제
sys.maxsize와-sys.maxsize가 유효한 값으로 인정된다 - 대상 API:
bytes.hexbytearray.hexmemoryview.hexbinascii.b2a_hex/binascii.hexlify
![]()
1) 뭐가 바뀌었나: bytes_per_sep가 int가 아니라 Py_ssize_t로 들어온다
PR diff를 보면 핵심은 이거다.
- bytes_per_sep 파라미터 타입이
int→Py_ssize_t로 바뀐다 - Argument Clinic이
PyLong_AsInt()대신 “인덱스 정수”로 받아서PyLong_AsSsize_t()로 변환한다
그 결과로 문서/테스트도 이렇게 바뀐다.
- 기존 경계값 테스트:
2**31-1같은 32비트 int 범위 중심 - 변경:
sys.maxsize중심(플랫폼 의존 경계를 파이썬 레벨로 끌어올림)
NEWS에도 명시된다:
sys.maxsize와-sys.maxsize가 이제 유효
2) 왜 이게 실무에서 도움이 되나: ‘경계값’이 안정되면 디버깅 코드가 덜 깨진다
이 패치는 성능 패치가 아니다.
하지만 실무에서는 이런 류의 안정성/일관성 패치가 은근히 체감된다.
(A) 플랫폼/빌드에 따른 애매함을 줄인다
현실의 파이썬은
- 64-bit 리눅스
- 64-bit 윈도우
- 때로는 특수 임베딩 환경
처럼 다양한 환경에서 돌아간다.
int 기반 API는 이런 곳에서 “큰 값”의 취급이 경계에서 흔들릴 수 있다.
이번 변경은 bytes_per_sep를 아예 “사이즈(길이) 단위”로 받도록 맞추면서, sys.maxsize 같은 파이썬 레벨의 기준으로 통일한다.
(B) hexdump/logging 유틸에서 ‘off’ 스위치를 더 안전하게 만들 수 있다
실전에서는 이런 옵션이 자주 있다.
group_bytes: 그룹 끊기 단위sep: 구분자
그리고 “grouping을 끄고 싶다”는 요구도 많다.
나쁜 방법은 None 같은 특별값을 끼워 넣는 건데, API가 그걸 안 받으면 결국 조건문이 늘어난다.
이제는 아예 “극단적으로 큰 bytes_per_sep”를 하나의 스위치로 쓸 수 있다.
import sys
def hex_dump(buf: bytes, group: int | None = 2) -> str:
if group is None:
return buf.hex(':', sys.maxsize) # 사실상 sep 없음
return buf.hex(':', group)
(물론 sep=None을 쓰면 더 직접적으로 구분자 없이 갈 수도 있다. 하지만 유틸 설계에 따라선 “항상 sep는 있고 group만 바꾼다”가 편할 때가 있다.)
3) bytes.hex / binascii.hexlify에서 bytes_per_sep를 쓸 때 헷갈리는 포인트
포인트 1) 양수/음수의 의미
bytes_per_sep > 0: 오른쪽부터 그룹을 센다bytes_per_sep < 0: 왼쪽부터 그룹을 센다
이건 길이가 고정되지 않은 바이너리(가변 길이 필드)에서 꽤 유용하다.
- 오른쪽 정렬(예: 체크섬/하위 바이트 의미가 큰 경우)
- 왼쪽 정렬(예: 헤더가 앞에 몰려있는 경우)
포인트 2) sep는 “한 글자/한 바이트” 제약이 있다
이건 그대로다.
sep=':'또는sep=b':'같은 한 글자
포인트 3) 너무 큰 값은 “그룹 없음”이 된다
이게 이번 패치의 실사용 포인트다.
- bytes_per_sep가 버퍼 길이보다 크면 구분자가 삽입되지 않는다
4) 실전 예시 3개: 로그/프로토콜/테스트에서 bytes_per_sep를 이렇게 쓴다
예시 A) 로그를 ‘눈으로 읽게’ 만들기(2바이트 그룹)
16진수 덤프는 그냥 찍으면 너무 길다.
payload = b"\xb9\x01\xef\x00\x10\x20\x30\x40"
print(payload.hex(':', 2))
# b901:ef00:1020:3040
대부분의 프로토콜이 2바이트/4바이트 단위로 의미가 생기기 때문에, 2바이트 그룹이 생각보다 자주 먹힌다.
예시 B) 헤더(앞부분)만 보기 좋게(왼쪽 기준)
헤더가 앞에 몰려 있고 뒤는 페이로드라서 의미가 약할 때는, 왼쪽 기준 그룹이 더 편하다.
print(payload.hex(' ', -2))
# b901 ef00 1020 3040
예시 C) 테스트에서 “grouping을 끄는 스위치”로 쓰기
테스트에서 출력 포맷을 비교할 때, 옵션 조합이 많아지면 코드가 지저분해진다.
3.15+ 환경이라면 이렇게 ‘off’를 값으로 표현할 수 있다.
import sys
def hexfmt(buf: bytes, sep: str = ':', group: int | None = 2) -> str:
if group is None:
# 사실상 grouping off
return buf.hex(sep, sys.maxsize)
return buf.hex(sep, group)
5) 호환성 전략: 라이브러리는 보수적으로, 내부 서비스는 적극적으로
이 PR은 3.15 whatsnew에 들어간 변경이다.
- 즉, 3.14 이하에서는
sys.maxsize가 반드시 허용된다고 장담하기 어렵다(플랫폼/구현에 따라)
그래서 추천 전략은 단순하다.
- 오픈소스 라이브러리/외부 배포 코드:
bytes_per_sep에 굳이 sys.maxsize를 넣지 말고, 조건문으로 분기 - 내부 서비스/환경이 3.15+로 고정되면:
sys.maxsize를 “off 스위치”로 써도 된다
6) 결론: ‘보기 좋은 헥스’는 디버깅 시간을 줄인다
바이너리 문제는 대개 이런 식으로 시작한다.
- “뭔가 한 바이트가 다르다”
- “구분자를 어떻게 끊어 보냐에 따라 갑자기 보인다”
그래서 hex 출력의 옵션이 조금만 더 유연해져도, 디버깅 시간이 줄어든다.
이번 gh-147944는 그런 종류의 작은 개선이다.
- 경계값이 명확해지고
- 플랫폼 의존이 줄고
- API 사용이 조금 더 일관돼진다
결국 이런 변화가 쌓여서 표준 라이브러리가 “운영 친화적”이 되는 거라고 본다.
마지막으로, 이런 류의 변경은 단순히 sys.maxsize를 허용하는 걸 넘어서서, 실패 모드도 더 예측 가능하게 만든다.
sys.maxsize는 OKsys.maxsize + 1같은 값은 상황에 따라 OverflowError/예외로 떨어질 수 있고- 말도 안 되는 큰 값(예: 2**1000)은 확실히 예외가 난다
즉, 경계가 “대충”이 아니라 “명시적으로” 정리된다.
운영/디버깅 코드에서 이런 차이가 꽤 크다. 어설프게 동작하다가 모서리에서 깨지는 것보다, 빨리 예외로 떨어져서 원인을 드러내는 편이 낫다.
한 줄 요약:
- “hex 출력 포맷은 취향이 아니라 디버깅 비용이다.”
Keywords
python,bytes,bytearray,memoryview,hex,bytes_per_sep,binascii,b2a_hex,Py_ssize_t,sys.maxsize,OverflowError,Argument Clinic,debugging,hexdump,logging,protocol,binary,compatibility
이미지 크레딧/라이선스
- Mobile developer at work (Unsplash).jpg — Parker Byrd / CC0
- https://commons.wikimedia.org/wiki/File:Mobile_developer_at_work_(Unsplash).jpg
- http://creativecommons.org/publicdomain/zero/1.0/deed.en
댓글
댓글 쓰기