Python 문법을 고급스럽게 바꿔보자.

Python 문법을 고급스럽게 바꿔보자.

조금더 읽기 쉽고 있어보이게 Python 코드를 업그레이드 시켜보자.

Python 문법을 고급스럽게 바꿔보자.

사용하기 시작했고 같이 사용하면서 생각을 나누고 싶은 도구가 있다.

최근에 알게 된 Refurb는 코드의 효율과 가독성을 증가 시킬 수 있는 방법을 제안하는 open source tool 이다.

제작자의 제작 의도는 “무언가를 가져와서 더 나아지게, 더 빠르고 우아하게 만드는 것을 좋아합니다. 이미 많은 정적 분석 도구들이 존재하지만, 그 중에서도 코드를 더욱 우아하고 읽기 쉽고 현대적으로 만드는 데 초점을 맞춘 도구는 없는 것 같습니다. 그것이 Refurb의 존재 이유입니다.” 라고 한다.

Rust의 Clippy를 쓰면서 그 편리함을 Python에서도 느끼고 싶었던 것 같다.

Refurb는 Style 또는 Type를 확인해주는 checker가 아니다. 즉 linting이나 debuging을 하기 전 가볍게 문법을 확인하는 도구가 아닌 좋은 코드로 향상 시키는 도구다. 즉 코드 전체의 구조보단 함수 사용, 함수 조건문 선언 등 보다 detail한 부분에 대해 향상된 코딩을 제안해 주는 도구다.

Refurb의 설치와 몇 가지 예시를 보면서 Refurb가 하는 일을 알아보자.

Refurb 설치 방법

>> pip install refurb

설치에 주의해야 할 부분 : refurb는 Python 3.10 이상에서 만 작동한다. 즉 refurb를 ‘사용’ 하기 위해서 사용하는 Python이 3.10 이상 이어야 한다. 단, refurb를 사용해 체크한 python code는 3.10 아래도 사용 가능하다.

즉, refurb는 python 3.10 이상에서 설치해야 하지만, 기능 사용에 python버전은 크게 영향이 없다.

Refurb 사용 방법

Refurb의 사용 방법은 매우 간단하다. 작성한 py확장자 코드를 refurb로 읽으면 된다.

>> refurb my_code.py

Refurb가 하는 일

먼저 매우 간단한 예시를 하나 해보자.

filename = 'testFile.txt'
try:
	with open(filename) as f:
		contents = f.read()
	print(contents)
except FileNotFoundError:
	print(" File Not Found  !!")
	pass

-------------------------------
>> cat testFile.txt
1234567
abcdefg

위에 보여진 것 처럼 두 개의 file을 준비했다. 실행시킬 python code와 불러올 예제 text file.

try, except는 많이 사용하는 편이긴 하지만, text file을 불러올 때 개인적으론 with을 사용해 불러오진 않는다. read_text()를 사용하는 것이 더 효율적이지만 우선 refurb의 기능을 보기 위해 위와 같이 작성했다.

아래는 test file의 유무에 따른 결과다.

## File이 있을 경우
1234567
abcdefg

## File이 없을 경우
File Not Found !!

그럼 위 예제 python code를 refurb로 돌려보자.

>> refurb test_code.py
test_code.py:5:5 [FURB101]: Replace `with open(x) as f: y = f.read()` with `y = Path(x).read_text()`

Run `refurb --explain ERR` to further explain an error. Use `--quiet` to silence this message

결과에서 확인할 수 있듯, with을 사용해서 file을 읽는 것 보다 Path와 하위 함수인 read_text 사용을 권장하고 있다.

확실히 read_text를 사용하는 쪽이 코드 line수도 줄고 다른 개발자가 본다 하더라도 한눈에 이해가 가능해 편리할 것이다.

여기서 더 나아가, 결과를 기반으로 두 가지 기능을 사용할 수 있다.

첫 번째는 출력한 추천 결과의 이유를 확인할 수 있다. 출력 결과를 보면 ‘에러 코드’ 라고 부르는 각 추천 형식 ID가 있다. 위 예제의 경우 ‘FURB101’ 이고 이를 ‘explain’ parameter와 함께 입력하면 추천의 이유를 보여 준다.

> > refurb --explain FURB101
> > FURB101: use-pathlib-read-text-read-bytes [pathlib]

When you just want to save the contents of a file to a variable, using a
`with` block is a bit overkill. A simpler alternative is to use pathlib's
`read_text()` function:

Bad:

with open(filename) as f:
contents = f.read()

---

Good:

---

contents = Path(filename).read_text()

---

출력 결과의 구성으로, 기존 코드인 with을 사용하면 어떤 문제가 발생할 수 있는지를 간략하게 설명하고 안 좋은 코드와 좋은 코드를 비교할 수 있게 간단한 예제를 제공한다.

두 번째는 내 코딩 스타일을 지킬 수 있다. 간단한 예를 한번 더 들어보겠다.

print("Hello")
print("")
print("World")

Vim이 주 개발 도구이고 디버깅 환경이 terminal인 개발자의 경우, 코드의 간단한 디버깅을 위해 중간 중간 많은 print문을 넣는다. 어느 정도 줄 수 가 늘어나면 그 사이를 구별하기 위해 위처럼 빈 줄을 넣어서 구간 구분을 한다. 나는 주로 ‘print(””)’로 출력 부분 안에 “”을 넣는 것이 버릇이다. 코딩을 C++로 시작했다 보니 출력 사이사이에 “”를 넣어서 구별하는 것이 버릇이 되었다. Python을 주로 사용하는 지금도 버릇처럼 동일하게 사용하고 있다. 위 코드를 refurb로 확인해 보자.

>> refurb bbb.py
bbb.py:3:1 [FURB105]: Replace `print("")` with `print()`

Run `refurb --explain ERR` to further explain an error. Use `--quiet` to silence this message

결과처럼 print(””)보다 아무것도 넣지 않은 print() 라고 알려준다.

솔직히 print(””)와 print()는 아주 아주 미세한 출력 속도 차이와 메모리 사용 차이가 나지만 체감 불가능 이라 해도 무방하다. 만약 print(””)를 계속 사용하면, refurb역시 계속 ‘FURB105’ 추천을 할 것이다. 이럴 때 quiet기능을 사용하면 앞으로 모든 코드 확인 중에 위 추천은 하지 않는다.

>> refurb --quite FURB105

이 외에도, 내가 자주 하는 실수를 방지하기 위해 추천 종류를 customizing이 가능하다. 자주 틀리거나 헷갈리는 구문을 추가해서 활용 할 수 있다. 추가하는 command는 아래와 같다.

## gen은 fuzzy-finder (fzf)를 사용한다. 사용하기전에 fzf를 먼저 설치해야 한다.
>> refurb gen

사용하면서 느낀 점

코드를 고급스럽고 가독성 있게 제안하는게 refurb의 목적이라고 했다. refurb를 처음 설치하고 기존에 작성했던 코드를 포함 새로 작성할 때 마다 사용을 했는데, 크게 무언가를 추천해주거나 안 좋은 점을 찾아내는 경우가 거의 없었다.

내가 짠 코드가 심각하게 비효율적이거나 반복된 부분이 많아 가독성이 현저하게 떨어지지 않는 이상 아마 refurb는 코드에 크게 반응하지 않고 제안하지 않는 것 같다.

또 하나는 아직 실험해 보지 않았지만, python 의 기본 library외 다른 additional library는 (numpy, pandas, matplotlib, …) 는 인식하지 못하는 것 같다. 분명히 따로 하는 방법이 있거나 내가 못찾고 있는 것일 수도 있다.

비슷한 기능을 제공하는 github copliot은 더 강력하고 사용하기 편하지만 유료다. 무료인 Tool 속에서 꽤 유용한 코드 향상 Tool 중 하나 인 것은 맞다. 나처럼 refurb를 단독으로 사용하는 것이 아닌 하나의 조합을 만들어서 아래에 설명 할 다른 도구와 조합해서 사용한다면, 보다 효과적인 code pre-debuging 도구가 되지 않을까 싶다.

Refurb와 비슷하지만 다른 Python external library 와 의 차이점

Refurb는 이미 많은 Python 코드 Linting 및 분석 도구들이 있기 때문에 왜 Refurb가 존재하는지 궁금할 수 있다. 앞서 언급한 대로, Refurb는 보다 우아한 코드로 개선될 수 있는 부분을 확인하며, 다른 Linter들이 전문적으로 다루지 않는 내용에 특화되어 있다. 다음은 유사한 린터 및 분석 도구들과 Refurb의 차이점을 보여준다.

  • Black :: 코드의 formatting과 Styling (줄 길이, 후행 쉼표, 들여쓰기 등)에 더 초점을 둔 도구. Black을 사용하는 다른 프로젝트를 거의 동일하게 만드는 데에 아주 좋은 성능을 발휘한다. 그러나 타입 체크나 코드 특성 또는 패턴 검출과 같은 보다 복잡한 작업은 수행하지 않는다.

  • flake8 :: flake8도 Linter이며, 매우 확장 가능하며 “사용하지 않는 변수”, “Loop 외부에서의 break”와 같은 의미 분석 관련 검사를 수행. 또한 PEP8 준수 여부도 확인한다. Refurb는 이미 flake8를 사용하고 있기 때문에 flake8를 대체하기보다는 함께 사용하는 방식이다.

  • Pylint :: Pylint는 많은 영역을 다루는 여러 가지 검사들을 가지고 있지만, 일반적으로 나쁜 코드나 버그가 중점이며, 의도하지 않은 일을 수행한다. Refurb는 사용자가 이미 버그와 같은 문제점을 알고 있다고 가정하고, 작성되어있는 코드 자체를 최대한 정리해주려고 한다.

  • Mypy, Pyright, Pyre, Pytype :: 위 나열된 항목은 모두 타입 Checker 로서, 주로 타입을 강제하며, 인자 일치 여부, 함수 호출 시 타입 안전성 등을 보장한다. 그 외에도 다양한 작업을 수행하지만, 이것이 일반적인 아이디어다. Refurb는 실제로 Mypy 위에서 구축되어 있으며, 좋은 타입 정보를 얻기 위해 Mypy의 AST 파서를 사용한다.

  • pyupgrade :: Pyupgrade는 오래된 Python 코드를 최신 문법으로 업그레이드하는 데에 유용한 검사들이 많이 있다. Refurb와 다른 점은 Pyupgrade는 코드를 최신 버전으로 업그레이드하는 데에 초점을 두고 있지만, Refurb는 이미 있는 코드를 정리하고 단순화하는 데에 더 초점을 둔다.

결론적으로, Refurb는 기존 유용한 도구를 버리라는 의도가 아니다. 각각 코드의 다른 영역을 다루며 다른 목적을 가지고 있기 때문이다. Refurb는 위에서 언급한 도구들과 함께 사용하기 위한 것이다.