[Python] ๐ Pylint๋ก Python ์ฝ๋ ํ์ง ๊ด๋ฆฌํ๊ธฐ: ์ค์น๋ถํฐ CI/CD ์ฐ๋๊น์ง
Pylint๋ Python ์ฝ๋์ ์ค๋ฅ ๊ฒ์ถ, PEP 8 ์คํ์ผ ์๋ฐ, ์ฝ๋ ๋ณต์ก๋ ๋ถ์์ ์ํํ๋ ์ ์ ๋ถ์ ๋๊ตฌ์
๋๋ค. ๋จ์ํ ์คํ์ผ ๊ฒ์ฌ๋ฅผ ๋์ด ์ ์ฌ์ ๋ฒ๊ทธ๊น์ง ํ์งํ๋ฉฐ, 10์ ๋ง์ ํ์ง ์ ์๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด ๊ธ์์๋ Pylint์ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ๋ถํฐ .pylintrc ์ค์ , VSCode ์ฐ๋, pre-commit CI/CD ํตํฉ๊น์ง ์ค๋ฌด์์ ๋ฐ๋ก ํ์ฉํ ์ ์๋๋ก ์ ๋ฆฌํฉ๋๋ค.
๐ Pylint๋? #
Pylint๋ Python ์ฝ๋๋ฅผ ์คํํ์ง ์๊ณ ๋ ํ์ง ๋ฌธ์ ๋ฅผ ํ์งํ๋ ์ ์ ์ฝ๋ ๋ถ์(static analysis) ๋๊ตฌ์ ๋๋ค. ๋จ์ํ ์คํ์ผ ๊ฒ์ฌ๊ธฐ๋ฅผ ๋์ด, ๋ฐํ์ ์ด์ ์ ๋ฐ๊ฒฌํ๊ธฐ ์ด๋ ค์ด ๋ฒ๊ทธ์ ์ ์ฌ์ ์ค๋ฅ๊น์ง ์ก์๋ ๋๋ค.
์ฃผ์ ๊ฒ์ฌ ํญ๋ชฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๋ฌธ๋ฒ ์ค๋ฅ ๋ฐ ์ ์ฌ์ ๋ฒ๊ทธ โ ์ ์๋์ง ์์ ๋ณ์, self ๋๋ฝ, ์๋ชป๋ import ๋ฑ
- PEP 8 ์ฝ๋ฉ ํ์ค ์๋ฐ โ ๋ช ๋ช ๊ท์น(snake_case, PascalCase), ์ค ๊ธธ์ด, ๊ณต๋ฐฑ ๋ฑ
- ์ฝ๋ ๋ณต์ก๋ โ ์ค๋ณต ์ฝ๋, ๋๋ฌด ๊ธด ํจ์, ์ง๋์น๊ฒ ๋ง์ ์ธ์ ๋ฑ
- ๋ฌธ์ํ ๋ถ์ฌ โ docstring ๋ฏธ์์ฑ ๊ฒฝ๊ณ
- 10์ ๋ง์ ํ์ง ์ ์ โ ์ฝ๋ ์ ์ฒด์ ์ํ๋ฅผ ์์น๋ก ํ์ธ
Tip: Pylint์ ๊ฒฝ๊ณ ๊ฐ ์ ๋์ ์ธ ๊ธฐ์ค์ ์๋๋๋ค. ์๋๋ ์ฝ๋๊ฐ ๊ฒฝ๊ณ ๋ฅผ ๋ฐ์ ์ ์์ผ๋ฏ๋ก, ํ๋ก์ ํธ ์ํฉ์ ๋ง๊ฒ ๊ท์น์ ์กฐ์ ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
โ๏ธ Linter vs Formatter ์ฐจ์ด #
์ฝ๋ ํ์ง ๋๊ตฌ๋ฅผ ์ฒ์ ์ ํ ๋ ๊ฐ์ฅ ํผ๋์ค๋ฌ์ด ๋ถ๋ถ์ด Linter์ Formatter์ ์ฐจ์ด์ ๋๋ค.
| ๊ตฌ๋ถ | Linter | Formatter |
|---|---|---|
| ์ญํ | ์ฝ๋์ ๋ ผ๋ฆฌ ์ค๋ฅยท์ ์ฌ์ ๋ฒ๊ทธ ํ์ง | ์ฝ๋ ์คํ์ผยท๋ ์ด์์ ์๋ ์ ๋ฆฌ |
| ๋์ | ๋ฌธ์ ๋ฅผ ๋ณด๊ณ ํ๊ณ ์์ ์ ๊ฐ๋ฐ์๊ฐ ์ง์ | ์ฝ๋๋ฅผ ์๋์ผ๋ก ์์ |
| ์์ | ๋ฏธ์ฌ์ฉ ๋ณ์, import ์ค๋ฅ, ๋ช ๋ช ๊ท์น ์๋ฐ | ๋ค์ฌ์ฐ๊ธฐ ์ ๋ ฌ, ๋ฐ์ดํ ํต์ผ, ์ค ๋ฐ๊ฟ |
| ๋ํ ๋๊ตฌ | Pylint, Flake8, Ruff | Black, YAPF, Ruff(format) |
๐ Python ์ฝ๋ ํ์ง ๋๊ตฌ ๋น๊ต #
์ค๋ฌด์์ ์์ฃผ ํจ๊ป ์ธ๊ธ๋๋ 4๊ฐ์ง ๋๊ตฌ๋ฅผ ๋น๊ตํฉ๋๋ค.
| ๋๊ตฌ | ๋ถ๋ฅ | ํน์ง | ์๋ |
|---|---|---|---|
| Pylint | Linter | ๊ฐ์ฅ ์๊ฒฉ, ํ์ง ์ ์ ์ ๊ณต, ์ฌ์ธต ๋ถ์ | ๋๋ฆผ |
| Flake8 | Linter | ์์ ์ , PEP 8 ์ค์ฌ, ์ค์ ๊ฐ๋จ | ๋น ๋ฆ |
| Black | Formatter | ์ค์ ์์ด ์ผ๊ด๋ ์คํ์ผ ๊ฐ์ , ํ ํ์ ์ต์ | ๋น ๋ฆ |
| Ruff | Linter + Formatter | Rust ๊ธฐ๋ฐ, ๋งค์ฐ ๋น ๋ฆ, Flake8+isort+Black ํตํฉ | ๋งค์ฐ ๋น ๋ฆ |
Flake8 โ ์คํ์ผ ๊ฒ์ฌ โ ์ค๋ฅ ๋ณด๊ณ
Pylint โ ์ฌ์ธต ๋ถ์ โ ์ค๋ฅ ๋ณด๊ณ + ๊ฐ์ ์ ์ + ํ์ง ์ ์
Black โ ์๋ ํฌ๋งทํ
โ ์ฝ๋ ์ง์ ์์
Ruff โ lint + format โ ์ ์ธ ๋๊ตฌ๋ฅผ ๋๋ถ๋ถ ๋์ฒดTip: 2026๋ ํ์ฌ๋ Ruff ๋จ๋ ์ฌ์ฉ์ผ๋ก lintยทformatยทimport ์ ๋ฆฌ๊น์ง ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด ์ฆ๊ฐ ์ค์ ๋๋ค. ํ์ง๋ง Pylint๋ Ruff๊ฐ ํ์งํ์ง ๋ชปํ๋ ์ฌ์ธต ๋ถ์(์ฝ๋ ๋์, ๋ณต์ก๋, ํ์ง ์ ์)์์ ์ฌ์ ํ ๊ฐ์ ์ด ์์ต๋๋ค.
๐ ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ #
์ค์น #
1pip install pylint๊ฐ์ ํ๊ฒฝ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ๊ฒฝ ํ์ฑํ ํ ์ค์นํฉ๋๋ค.
1python -m venv .venv
2source .venv/bin/activate # Windows: .venv\Scripts\activate
3pip install pylintํ์ผ ๋ถ์ #
1# ๋จ์ผ ํ์ผ ๋ถ์
2pylint my_script.py
3
4# ๋ชจ๋ ์ ์ฒด ๋ถ์
5pylint my_package/
6
7# ์ฌ๋ฌ ํ์ผ ๋์ ๋ถ์
8pylint src/main.py src/utils.py์ถ๋ ฅ ๊ฒฐ๊ณผ ์์ #
my_script.py:5:0: C0304: Final newline missing (missing-final-newline)
my_script.py:10:4: W0611: Unused import os (unused-import)
my_script.py:15:0: E1101: Module 'os' has no 'pathh' member (no-member)
-----------------------------------
Your code has been rated at 6.50/10์ถ๋ ฅ ํ์์ ํ์ผ๋ช
:์ค๋ฒํธ:์ปฌ๋ผ:๋ฉ์์ง์ฝ๋: ์ค๋ช
(์ฝ๋๋ช
) ์
๋๋ค.
๐ ๋ฉ์์ง ์ ํ๊ณผ ์ฝ๋ ์ดํดํ๊ธฐ #
Pylint ๋ฉ์์ง๋ ์ํ๋ฒณ ์ ๋์ฌ๋ก ์ฌ๊ฐ๋๋ฅผ ๊ตฌ๋ถํฉ๋๋ค.
| ์ ๋์ฌ | ์ ํ | ์๋ฏธ |
|---|---|---|
| C | Convention | PEP 8 ๋ฑ ์ฝ๋ฉ ๊ท์น ์๋ฐ |
| W | Warning | ์ ์ฌ์ ๋ฌธ์ , ๊ฐ์ ๊ถ์ฅ |
| E | Error | ๋ฐํ์ ์ค๋ฅ๋ก ์ด์ด์ง ๊ฐ๋ฅ์ฑ์ด ๋์ ๋ฒ๊ทธ |
| R | Refactor | ๋ฆฌํฉํ ๋ง์ด ํ์ํ ์ฝ๋ ๊ตฌ์กฐ |
| F | Fatal | ๋ถ์ ์์ฒด๋ฅผ ๋ง๋ ์น๋ช ์ ์ค๋ฅ |
์์ฃผ ๋ง์ฃผ์น๋ ๋ฉ์์ง ์ฝ๋ #
| ์ฝ๋ | ์ด๋ฆ | ์ค๋ช |
|---|---|---|
C0301 | line-too-long | ์ค ๊ธธ์ด ์ด๊ณผ (๊ธฐ๋ณธ 100์) |
C0114 | missing-module-docstring | ๋ชจ๋ docstring ์์ |
C0116 | missing-function-docstring | ํจ์ docstring ์์ |
W0611 | unused-import | ์ฌ์ฉํ์ง ์๋ import |
W0612 | unused-variable | ์ฌ์ฉํ์ง ์๋ ๋ณ์ |
W1203 | logging-fstring | ๋ก๊น ์ f-string ์ฌ์ฉ (lazy loading ๋ถ๊ฐ) |
E0401 | import-error | ๋ชจ๋์ ์ฐพ์ ์ ์์ |
E1101 | no-member | ์กด์ฌํ์ง ์๋ ์์ฑ/๋ฉ์๋ ์ ๊ทผ |
R0913 | too-many-arguments | ํจ์ ์ธ์ ์ ์ด๊ณผ (๊ธฐ๋ณธ 5๊ฐ) |
R1705 | no-else-return | return ํ ๋ถํ์ํ else ๋ธ๋ก |
โ๏ธ .pylintrc ์ค์ ํ์ผ #
ํ๋ก์ ํธ ๋ฃจํธ์ .pylintrc ํ์ผ์ ๋๋ฉด ํ ์ ์ฒด์ ์ผ๊ด๋ ๊ท์น์ ์ ์ฉํ ์ ์์ต๋๋ค.
๊ธฐ๋ณธ ํ ํ๋ฆฟ ์์ฑ #
1pylint --generate-rcfile > .pylintrc์ฃผ์ ์ค์ ํญ๋ชฉ #
1[MASTER]
2# ๊ฐ์ํ๊ฒฝ, ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฒฝ๋ก ์ถ๊ฐ
3init-hook='import sys; sys.path.insert(0, "src")'
4
5# ๋ณ๋ ฌ ์ฒ๋ฆฌ (0 = CPU ์์ ๋ง๊ฒ ์๋)
6jobs=0
7
8[MESSAGES CONTROL]
9# ๋นํ์ฑํํ ๋ฉ์์ง ์ฝ๋ (์ผํ๋ก ๊ตฌ๋ถ)
10disable=
11 C0114, # missing-module-docstring
12 C0115, # missing-class-docstring
13 C0116, # missing-function-docstring
14 W1203 # logging-fstring
15
16[FORMAT]
17# ํ ์ค ์ต๋ ๊ธธ์ด (PEP 8 ๊ธฐ๋ณธ๊ฐ 79, ์ค๋ฌด 100~120 ๊ถ์ฅ)
18max-line-length=120
19
20# ๋ค์ฌ์ฐ๊ธฐ ๋จ์
21indent-string=' '
22
23[DESIGN]
24# ํจ์ ์ต๋ ์ธ์ ์
25max-args=7
26
27# ํจ์ ์ต๋ ์ค ์
28max-statements=50
29
30[TYPECHECK]
31# ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์
์ฒดํฌ ์ค๋ฅ ๋ฌด์
32ignored-modules=numpy,pandas,pydantic๐ ๊ฒฝ๊ณ ๋นํ์ฑํ ๋ฐฉ๋ฒ #
ํ๋ก์ ํธ ์ ์ฒด๊ฐ ์๋ ํน์ ์ฝ๋์๋ง ๊ท์น์ ๋นํ์ฑํํ ๋ ์ธ๋ผ์ธ ์ฃผ์์ ์ฌ์ฉํฉ๋๋ค.
1์ค ๋นํ์ฑํ #
1import os # pylint: disable=unused-import๋ธ๋ก ๋จ์ ๋นํ์ฑํ #
1# pylint: disable=too-many-arguments
2def complex_function(a, b, c, d, e, f):
3 pass
4# pylint: enable=too-many-argumentsํ์ผ ์ ์ฒด ๋นํ์ฑํ #
ํ์ผ ์ต์๋จ์ ์ถ๊ฐํฉ๋๋ค.
1# pylint: disable=missing-module-docstring,missing-function-docstringโ ๏ธ ์ธ๋ผ์ธ ๋นํ์ฑํ๋ฅผ ๋จ์ฉํ๋ฉด Pylint์ ํจ๊ณผ๊ฐ ๋จ์ด์ง๋๋ค. ์ค์ ๋ก ๋ถํ์ํ ๊ฒฝ๊ณ ๋ง ์ ํ์ ์ผ๋ก ๋๊ณ , ๊ฐ๋ฅํ๋ฉด
.pylintrc์์ ํ๋ก์ ํธ ์ ์ฒด ๊ธฐ์ค์ ์ค์ ํ๋ ๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค.
๐ฅ๏ธ VSCode ์ฐ๋ #
settings.json ์ค์ #
.vscode/settings.json์ ๋ค์์ ์ถ๊ฐํฉ๋๋ค.
1{
2 "python.linting.enabled": true,
3 "python.linting.pylintEnabled": true,
4 "python.linting.pylintArgs": [
5 "--disable", "C0301",
6 "--max-line-length", "120"
7 ]
8}์ ์ฅ ์ ์๋ ๊ฒ์ฌ #
1{
2 "editor.formatOnSave": true,
3 "python.linting.lintOnSave": true
4}Tip: VSCode์ Python ํ์ฅ(ms-python.python)์ ์ค์นํ๋ฉด ํธ์ง ์ค ์ค์๊ฐ์ผ๋ก Pylint ์ค๋ฅ๊ฐ ํ์๋ฉ๋๋ค.
๐ pre-commit์ผ๋ก CI/CD ํตํฉ #
์ปค๋ฐ ์์ ์ Pylint๋ฅผ ์๋ ์คํํ๋ฉด, ์ค๋ฅ๊ฐ ์๋ ์ฝ๋๋ ์ปค๋ฐ ์์ฒด๊ฐ ์ฐจ๋จ๋ฉ๋๋ค.
pre-commit ์ค์น #
1pip install pre-commit.pre-commit-config.yaml ์์ฑ #
1repos:
2 - repo: https://github.com/pycqa/pylint
3 rev: v3.3.0
4 hooks:
5 - id: pylint
6 args:
7 - "--max-line-length=120"
8 - "--disable=C0114,C0115,C0116"
9 additional_dependencies:
10 - pydantic
11 - pyyaml
12 - requestsํ ์ค์น ๋ฐ ์คํ #
1# ํ
์ค์น (์ต์ด 1ํ)
2pre-commit install
3
4# ์ ์ฒด ํ์ผ ์๋ ์คํ
5pre-commit run --all-files์ดํ git commit ์ ์๋์ผ๋ก Pylint๊ฐ ์คํ๋๋ฉฐ, ์ค๋ฅ๊ฐ ์์ผ๋ฉด ์ปค๋ฐ์ด ์ฐจ๋จ๋ฉ๋๋ค.
GitHub Actions ํตํฉ #
1name: Lint
2
3on: [push, pull_request]
4
5jobs:
6 pylint:
7 runs-on: ubuntu-latest
8 steps:
9 - uses: actions/checkout@v4
10 - uses: actions/setup-python@v5
11 with:
12 python-version: "3.12"
13 - run: pip install pylint
14 - run: pylint src/ --fail-under=8.0--fail-under=8.0 ์ต์
์ผ๋ก ํ์ง ์ ์๊ฐ 8์ ๋ฏธ๋ง์ด๋ฉด CI๊ฐ ์คํจํฉ๋๋ค.
๐ก ์ค๋ฌด ์ ์ฉ ํ #
์ ์ง์ ๋์ ์ ๋ต #
๊ธฐ์กด ํ๋ก์ ํธ์ Pylint๋ฅผ ๋์ ํ ๋๋ ์ฒ์๋ถํฐ ๋ชจ๋ ๊ท์น์ ๊ฐ์ ํ๋ฉด ์๋ฐฑ ๊ฐ์ ์ค๋ฅ๊ฐ ๋์ฌ ์ ์์ต๋๋ค. ์๋ ์์๋ก ๋จ๊ณ์ ์ผ๋ก ์ ๊ทผํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
- E(Error) ๋จผ์ โ ๋ฐํ์ ์ค๋ฅ๋ก ์ด์ด์ง๋ ์น๋ช ์ ๋ฌธ์ ์ฐ์ ํด๊ฒฐ
- W(Warning) ๋ค์ โ ์ ์ฌ์ ๋ฒ๊ทธ์ ๊ฐ์ ๊ถ์ฅ ์ฌํญ ์ฒ๋ฆฌ
- C/R ๋ง์ง๋ง โ ์ฝ๋ฉ ๊ท์น๊ณผ ๋ฆฌํฉํ ๋ง์ ์ฌ์ ๋ฅผ ๋๊ณ ๊ฐ์
์์ฃผ ๋ฐ์ํ๋ ์ค๋ฅ ํด๊ฒฐ #
| ์ค๋ฅ | ์์ธ | ํด๊ฒฐ์ฑ |
|---|---|---|
E0401 import-error | ๋ชจ๋์ ์ฐพ์ ์ ์์ | .pylintrc์ init-hook์ผ๋ก ๊ฒฝ๋ก ์ถ๊ฐ |
W1203 logging-fstring | ๋ก๊น ์ f-string ์ฌ์ฉ | logger.info("๊ฐ: %s", value) ํํ๋ก ๋ณ๊ฒฝ |
R1705 no-else-return | return ํ else ๋ธ๋ก | else ์ ๊ฑฐ ํ ๋ค์ฌ์ฐ๊ธฐ ๊ฐ์ |
C0301 line-too-long | ์ค ๊ธธ์ด ์ด๊ณผ | max-line-length ์กฐ์ ๋๋ ์ค ๋ฐ๊ฟ |
Pyreverse๋ก UML ๋ค์ด์ด๊ทธ๋จ ์์ฑ #
Pylint ํจํค์ง์ ํฌํจ๋ pyreverse๋ก ํด๋์ค ๊ตฌ์กฐ๋ฅผ ์๊ฐํํ ์ ์์ต๋๋ค. Graphviz ์ค์น ํ ์คํํฉ๋๋ค.
1pip install pylint
2brew install graphviz # macOS
3
4pyreverse -o png -p MyProject my_package/classes_MyProject.png์ packages_MyProject.png ํ์ผ์ด ์์ฑ๋ฉ๋๋ค.
โ ์์ฃผ ๋ฌป๋ ์ง๋ฌธ #
Q. Pylint์ Flake8 ์ค ๋ฌด์์ ์จ์ผ ํ๋์? #
๊ฐ๋จํ ์คํ์ผ ๊ฒ์ฌ๊ฐ ๋ชฉ์ ์ด๋ผ๋ฉด Flake8์ด ๋น ๋ฅด๊ณ ์ค์ ์ด ์ฝ์ต๋๋ค. ์ ์ฌ์ ๋ฒ๊ทธ ํ์ง, ์ฝ๋ ๋ณต์ก๋ ๋ถ์, ํ์ง ์ ์๊น์ง ํ์ํ๋ค๋ฉด Pylint๊ฐ ์ ํฉํฉ๋๋ค. ์ค๋ฌด์์๋ ๋ ๋๊ตฌ๋ฅผ ํจ๊ป ์ฌ์ฉํ๊ฑฐ๋, ์ต๊ทผ์๋ Ruff๋ก ํตํฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
Q. Pylint ์ ์ 10์ ์ ๋ชฉํ๋ก ํด์ผ ํ๋์? #
์๋๋๋ค. 10์ ์ด ํญ์ ์ข์ ์ฝ๋๋ฅผ ์๋ฏธํ์ง๋ ์์ต๋๋ค. ํ์ ๊ธฐ์ค(์: 8.0 ์ด์)์ ์ค์ ํ๊ณ , ์ ์๋ณด๋ค๋ EยทW ์ค๋ฅ 0๊ฑด์ ๋ชฉํ๋ก ์ผ๋ ๊ฒ์ด ๋ ์ค์ฉ์ ์ ๋๋ค.
Q. ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(numpy, pandas) ์ฌ์ฉ ์ E0401 ์ค๋ฅ๊ฐ ๊ณ์ ๋์ต๋๋ค. #
.pylintrc์ [TYPECHECK] ์น์
์ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
1[TYPECHECK]
2ignored-modules=numpy,pandas,torch,tensorflowQ. pre-commit ์ฌ์ฉ ์ ๊ฐ์ํ๊ฒฝ ํจํค์ง๋ฅผ ์ธ์ํ์ง ๋ชปํฉ๋๋ค. #
.pre-commit-config.yaml์ additional_dependencies์ ํ๋ก์ ํธ์์ ์ฌ์ฉํ๋ ํจํค์ง๋ฅผ ๋ช
์ํด์ผ ํฉ๋๋ค.
1additional_dependencies:
2 - pydantic
3 - sqlalchemy
4 - fastapi