1P by GN⁺ | ★ favorite | 댓글 1개
  • Rust로 작성된 Python 린터·포매터 Ruff v0.16.0은 기본 활성화 규칙을 59개에서 413개로 늘려, 별도 설정 없이 구문 오류와 즉시 발생하는 런타임 오류를 더 폭넓게 탐지함
  • Markdown에서 python, py, pyi, pycon 등으로 표시된 Python 코드 블록 포매팅을 지원하며 Quarto 노트북에도 적용 가능함
  • ruff: ignoreruff: file-ignore 가 추가돼 논리적 코드 행이나 파일 전체의 진단을 억제할 수 있고, --add-ignore로 주석을 자동 삽입할 수 있음
  • checkformat --check는 수정 내용을 기본 진단 아래 diff로 표시하며, 포매터 검사도 JSON과 GitHub·GitLab CI 주석용 출력 형식을 지원함
  • 대부분은 큰 변경 없이 업그레이드할 수 있지만, 늘어난 기본 규칙과 일부 값이 null이 될 수 있는 JSON 출력 변경이 기존 설정과 자동화 도구에 미치는 영향을 확인해야 함

기본 규칙 413개로 확대

  • Ruff v0.16.0은 Rust로 작성된 고속 Python 린터·포매터로, PyPI 또는 uv tool install ruff@latest로 설치할 수 있음
  • Ruff의 전체 규칙은 v0.1.0 당시 708개에서 968개로 늘었지만, 기본 활성화 규칙은 그동안 59개로 유지돼 왔음
  • v0.16은 기본 규칙을 413개로 확대해 구문 오류와 즉시 발생하는 런타임 오류를 포함한 심각한 문제를 별도 설정 없이 탐지함
    • flake8-bugbear의 B, pyupgrade의 UP, Ruff 자체 RUF 범주 규칙 등이 포함됨
    • 전체 목록은 Default Rules 문서에서 확인할 수 있음
  • selectextend-select를 이미 사용하는 프로젝트도 새 기본 규칙을 통해 이전에 알지 못했던 유용한 규칙을 확인할 수 있음
  • 이전 기본 규칙으로 되돌리려면 다음과 같이 설정함
[lint]
select = ["E4", "E7", "E9", "F"]
  • 이번 변경은 장기 과제인 규칙 재분류와 연결되며, 관련 작업도 계속 이어질 예정임

Markdown 코드 블록 포매팅

  • ruff format이 Markdown 파일에 포함된 Python 펜스 코드 블록을 포매팅함
  • 지원하는 정보 문자열은 python, py, python3, py3, pyi, pycon
    • pyi는 스텁 파일 형식으로 처리함
    • pycon은 REPL 세션 형식으로 처리함
    • 나머지는 일반 Python 파일처럼 포매팅함
  • {python}처럼 언어 이름이 중괄호로 둘러싸여 있어도 인식하므로 Quarto 노트북에도 사용할 수 있음
    • .qmd 확장자를 쓴다면 extension 매핑 설정이 필요할 수 있음
  • 코드 블록 내부에서는 fmt: offfmt: on으로 일부 포매팅을 억제할 수 있음
  • Markdown 문서 영역 전체는 <!-- fmt: off --><!-- fmt: on --> HTML 주석으로 제외할 수 있음
  • 모든 Markdown 파일을 제외하려면 extend-exclude*.md 같은 glob을 지정함
  • 세부 동작은 Markdown 코드 포매팅 문서에서 확인할 수 있음

새로운 진단 억제 주석

  • v0.15의 ruff: disable·ruff: enable 범위 억제에 이어, v0.16은 ruff: ignoreruff: file-ignore 를 추가함
  • ruff: ignorenoqa처럼 같은 행의 진단을 억제하거나, 독립된 주석으로 작성해 다음 논리적 행 전체에 적용할 수 있음
    • 여러 줄로 작성된 함수 헤더에서는 def부터 콜론까지가 하나의 논리적 행으로 취급됨
  • ruff: file-ignoreruff: noqa처럼 파일 전체에서 지정된 진단을 억제함
  • 각 억제 주석에는 규칙 코드 뒤에 적용 이유를 작성할 수 있음
  • --add-ignore CLI 옵션은 필요한 ruff: ignore 주석을 자동으로 추가함
  • 미리보기 모드에서는 F401 같은 코드 대신 unused-import 같은 규칙 이름도 사용할 수 있음
  • 전체 주석 명세는 Ruff linter 문서에 정리돼 있음

수정 diff와 출력 형식

  • checkformat은 기존에도 --diff를 지원했지만 일반 진단과 별도로 동작해, 수정 이유를 보여주는 진단과 함께 표시되지 않았음
  • v0.16의 기본 full 출력은 가능한 린터·포매터 수정 사항을 진단 아래 diff로 표시
  • format --check도 린터가 지원하는 전체 출력 형식을 사용할 수 있음
    • 기계 판독용 JSON을 생성할 수 있음
    • GitHub와 GitLab이 CI에서 주석으로 렌더링하는 형식을 출력할 수 있음
  • 지원 형식은 CLI 도움말과 출력 형식 문서에서 확인할 수 있음

호환성과 안정화

  • v0.16의 파괴적 변경은 소수여서 대부분은 코드나 설정을 크게 바꾸지 않고 업데이트할 수 있음
  • JSON 출력의 filename, location, end_location, fix.edits[].location, fix.edits[].end_location은 빈 문자열이나 1행 1열을 기본값으로 쓰는 대신 null이 될 수 있음
    • 현재 영향을 받는 진단은 매우 적지만 향후 규칙에서는 더 흔해질 수 있음
  • 12개 규칙이 미리보기에서 안정 상태로 전환됨
    • Airflow 3 함수 시그니처 호환성 AIR303, 저작권 고지 CPY001, float 변환 FURB164, 정렬된 min/max FURB192
    • 컬렉션 리터럴 문자열 결합 ISC004, 예외 처리기 밖의 예외 로깅 LOG004, 잘못된 bool 반환형 PLE0304
    • 과도한 위치 인자 PLR0917, StopIteration 반환 PLR1708, Union 내 None 위치 RUF036
    • 클래스 딕셔너리의 annotation 접근 RUF063, __all__ 중복 항목 RUF068
  • 일부 기존 규칙의 안정화된 동작도 기본 적용됨
    • BLE001critical, error, exception 이외의 logging 메서드로 예외를 기록해도 억제됨
    • FA102collections.abc 등 추가 PEP 585 호환 API를 검사함
    • INT001·INT002·INT003gettextbuiltins._에 할당하는 등 일반적인 사용 방식도 검사함
    • S310은 로컬 문자열 리터럴 바인딩을 해석해 오탐을 줄임
    • S508·S509는 최신 PySNMP의 권장 API를 지원함
    • UP019typing.Text뿐 아니라 typing_extensions.Text도 인식함
  • 전체 변경 사항은 GitHub 릴리스에서 확인할 수 있음

댓글과 토론

Hacker News 의견들
  • 3천 줄 규모의 Python 프로젝트를 v0.15.x에서 새 버전으로 올렸는데 오래 걸리지 않았고, 이전 버전이 놓치던 문제를 다수 찾아내 코드 품질도 좋아졌음
    제안에 따른 수동 수정: https://github.com/nickjj/plutus/commit/9af66d31f98bef841588...
    줄 길이 규칙 재활성화: https://github.com/nickjj/plutus/commit/21789f89bbcee37913c1...
    미사용 변수에 _ 접두사 강제: https://github.com/nickjj/plutus/commit/a272c77b932e1c78558a...
    Ruff 자동 수정: https://github.com/nickjj/plutus/commit/6fe69cf88385ebdf9b8c...

  • Astral이 OpenAI에 인수된 뒤에도 Ruff, ty, uv가 활발히 개발돼서 반가움

    • ty를 기대했지만 basedpyright보다 크게 뒤처져 결국 사용을 중단했음. 검사 항목 부족보다 오탐이 치명적이었고, 대규모 코드베이스에 매우 유용한 기준선(baselining) 기능도 없었음
      uv와 Ruff는 훌륭하며 ty도 언젠가는 그 수준에 도달하길 바람
  • 서로 자의적인 규칙을 구현하고 무엇이 좋은 Python 코드인지조차 합의하지 못하는 문법 경찰 도구에 이토록 열광하는 모습이 놀라움
    여러 줄 딕셔너리를 한 줄로 합치면서 주석의 의도를 망치고, 공백 두 개와 큰따옴표 같은 사소한 형식만 바로잡는 식임. 실제 문제는 줄 끝 공백이나 import 정렬이 아니라 해석하기 어려운 10줄짜리 리스트 컴프리헨션인데 이런 도구들은 잡지 못함
    직장에서는 pylint, flake8, black, Ruff를 사용하면서 변경마다 수백 개의 커밋이 생겼고, 그 에너지를 다른 곳에 쓰는 편이 나았을 것임

    • 이런 도구의 목적은 실제 문제에 집중하게 하는 것임. 린트 결정을 자동화하면 PR에서 형식을 두고 논쟁하느라 사고력을 낭비하지 않아도 됨
      자동 실행 결과를 그대로 받아들이면 린트 논의에서 빠질 수 있지만, 이런 도구가 없는 조직에서는 형식을 고민하고 토론하는 데 실제 시간을 써야 했음
    • 린터가 시간을 낭비한다는 반감이 합리적인 수준을 넘어선 듯함. 현장에서는 오히려 시간을 절약한다는 근거가 충분하며, 결국 핵심은 린터가 마음에 들지 않는 변경도 가끔 한다는 정도임
      팀 개발에서는 개인적 선호를 강하게 밀어붙이기보다 주변 의견을 듣고, 협업과 장인정신의 우선순위를 다시 살펴볼 필요가 있음
    • Ruff는 줄 끝 주석을 인식해 각 항목을 별도 줄에 유지하고 후행 쉼표와 공백만 추가함. 후행 쉼표가 있으면 줄을 합치지도 않으며, 제시된 결과는 Black에서 나온 듯함
      줄 주석이 있는데 줄을 합치는 동작은 부적절해 보임. 큰따옴표는 Python에서 단순한 스타일 선택이며, 작은따옴표 안에 큰따옴표가 있으면 Ruff도 그대로 둠
    • 이런 도구는 오히려 팀의 에너지를 절약해 줌. 도구가 없으면 개발자마다 형식, 코드 품질, 가독성에 대한 기준이 달라 끝없이 토론하게 되므로 Ruff에 맡기는 편이 나음
    • 마지막 항목 뒤에 쉼표를 빠뜨렸기 때문에 한 줄로 합쳐진 것임. 적어도 Black에서는 후행 쉼표를 유지하면 항목들이 압축되지 않지만, 의도한 형식을 자주 깨뜨려 더는 내 코드에 연결하지 않고 있음
  • Go에도 Ruff 같은 도구가 있으면 좋겠음. 여러 언어에서 훌륭한 도구가 나오지만 Go 생태계는 분산돼 있고, Ruff, Oxc, Biome, Mago만큼 완성도 높게 느껴지는 도구가 없음

    • Go에는 더 나은 Go Analysis Framework가 있음: https://pkg.go.dev/golang.org/x/tools/go/analysis
      비교적 새로워 덜 알려졌지만 go fix와 go vet의 기반이며, Go 팀은 모듈 작성자가 go fix 실행 시 자동으로 작동할 사용자 정의 분석 패스를 쉽게 정의하도록 작업 중인 것으로 보임
      analysis.Analyzer 구조체로 AST, 타입, SSA 정보에 접근하고 분석기 간 정보를 조합할 수 있으며, 바이너리로 컴파일해 go fix에 전달하면 도구 체인이 복잡한 캐싱까지 처리함. Go 팀이 직접 만들고 도구 체인에 포함했으므로 golangci-lint 같은 도구도 장기적으로 이 프레임워크로 통합될 가능성이 큼
      AI 에이전트에게 Go Analysis 분석기를 작성하고 go fix로 구동하라고 시켜볼 수 있으며, 내 프로젝트에서도 부정확한 Markdown 지침 대신 여러 규칙을 결정론적으로 자동 강제하는 데 활용 중임
    • 얼마 전까지만 해도 분위기는 정반대여서 Python 커뮤니티가 도구 부족으로 고생하며 모두 Python용 gofmt 를 원했음. Ruff는 포매터가 아닌 린터지만 최근 Python 생태계의 발전 방향은 반가움
    • Go 생태계가 분산됐다는 말은 잘 이해되지 않음. Go에는 공식 포매팅과 린팅 도구가 있고, 초보자가 작성해도 일정한 형태가 되도록 언어 자체가 의도적으로 제한돼 있음
      공식 도구가 특정 스타일을 강제하지 않는 Python이나 TypeScript와 달리 Go에서는 Ruff나 Biome을 처음 쓸 때와 같은 극적인 효과가 나오기 어려움
    • Go는 거대한 IDE 없이도 사용할 수 있는 최고의 언어 도구 생태계 중 하나이며 golangci-lint도 상당히 포괄적임. Go 배포판 자체가 이미 많은 부분을 해결해 줌
    • golangci-lint는 오래전부터 존재했고 널리 사용되고 있음
  • 기본으로 413개 규칙을 활성화하면 대부분의 프로젝트가 설정을 건드리지 않고도 유용한 린트를 받을 수 있어 좋은 변화임

    • 기존 프로젝트에서 갑자기 413개 잠재적 경고가 쏟아지는 것이 유용한지는 의문이며, 신규 프로젝트에 더 적합해 보임
      요즘은 에이전트에게 모든 린트 경고를 정해진 기준에 따라 수정하라고 맡기고 몇 시간 두면 해결할 수 있겠지만, Ruff가 제공하는 세밀한 진단 자체는 환영함
  • Ruff에도 적용할 기본값 집합을 결정하는 Nix의 stateVersion 같은 기능이 필요함. 여러 저장소에서 Ruff를 업데이트하면 새 기본 규칙이 추가될 때마다 즉시 끄거나 위반 사항을 수정해야 해 결과를 예측하기 어려움
    활성화할 규칙을 모두 허용 목록에 적을 수도 있지만, 설정은 단순하게 유지하고 모두가 몇 시간 투자할 수 있을 때 상태 버전만 올리는 편이 나음

    • 프로젝트마다 pyproject.toml에 원하는 Ruff 버전을 고정하는 방식이 더 적절함. 각 프로젝트가 준비됐을 때 버전을 올리면 되므로 여러 프로젝트를 동시에 조율할 필요가 없고, 하나가 뒤처져도 나머지가 막히지 않음
    • 원문에 따르면 Ruff의 기본 규칙 집합은 2년이 넘도록 바뀌지 않았으며, 마지막 변경은 v0.1.0이었음
      https://github.com/astral-sh/ruff/releases/tag/v0.1.0
  • 에이전트 코딩 시대에는 강력한 린팅이 어느 때보다 중요하며, 더 많은 언어에서 forbidigo 같은 도구를 보고 싶음

    • 프로젝트들을 업그레이드하고 있지만 복잡한 마음도 듦. 직접 코드를 작성할 때는 직관에 따라 규칙을 건너뛰거나 무시할 시점을 판단했지만, pylint 규칙을 대부분 활성화한 프로젝트에서는 오히려 pylint를 만족시키기 위한 편법 때문에 코드가 읽기 어려워졌음
      코딩 에이전트도 사소한 문제를 고치는 데 많은 토큰을 쓰거나, 테스트가 실패하면 아예 비활성화하기도 함. AI 결과의 전반적인 정확성은 신뢰하게 됐지만 코드 품질에 대한 판단력은 여전히 믿기 어려움
  • 규칙이 413개나 되는데도 새 코드베이스에 합류할 때마다 import 정렬을 놓고 똑같은 세 가지 논쟁을 반복하게 됨

  • 이제 무설정 사용이 권장되는 듯해 반가움. 새 .ruff.toml에는 line-length = 300만 두면 됨