[Python] ๐ Pylint๋ก Python ์ฝ๋ ํ์ง ๊ด๋ฆฌํ๊ธฐ: ์ค์น๋ถํฐ CI/CD ์ฐ๋๊น์ง
Pylint๋ก Python ์ฝ๋์ ๋ฒ๊ทธยท์คํ์ผยท๋ณต์ก๋๋ฅผ ์๋ ๊ฒ์ฌํ๋ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํ์ต๋๋ค. .pylintrc ์ค์ , VSCode ์ฐ๋, pre-commit 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 ํตํฉ | ๋งค์ฐ ๋น ๋ฆ |
1
2
3
4
Flake8 โ ์คํ์ผ ๊ฒ์ฌ โ ์ค๋ฅ ๋ณด๊ณ
Pylint โ ์ฌ์ธต ๋ถ์ โ ์ค๋ฅ ๋ณด๊ณ + ๊ฐ์ ์ ์ + ํ์ง ์ ์
Black โ ์๋ ํฌ๋งทํ
โ ์ฝ๋ ์ง์ ์์
Ruff โ lint + format โ ์ ์ธ ๋๊ตฌ๋ฅผ ๋๋ถ๋ถ ๋์ฒด
Tip: 2026๋ ํ์ฌ๋ Ruff ๋จ๋ ์ฌ์ฉ์ผ๋ก lintยทformatยทimport ์ ๋ฆฌ๊น์ง ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด ์ฆ๊ฐ ์ค์ ๋๋ค. ํ์ง๋ง Pylint๋ Ruff๊ฐ ํ์งํ์ง ๋ชปํ๋ ์ฌ์ธต ๋ถ์(์ฝ๋ ๋์, ๋ณต์ก๋, ํ์ง ์ ์)์์ ์ฌ์ ํ ๊ฐ์ ์ด ์์ต๋๋ค.
๐ ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
์ค์น
1
pip install pylint
๊ฐ์ ํ๊ฒฝ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ๊ฒฝ ํ์ฑํ ํ ์ค์นํฉ๋๋ค.
1
2
3
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install pylint
ํ์ผ ๋ถ์
1
2
3
4
5
6
7
8
# ๋จ์ผ ํ์ผ ๋ถ์
pylint my_script.py
# ๋ชจ๋ ์ ์ฒด ๋ถ์
pylint my_package/
# ์ฌ๋ฌ ํ์ผ ๋์ ๋ถ์
pylint src/main.py src/utils.py
์ถ๋ ฅ ๊ฒฐ๊ณผ ์์
1
2
3
4
5
6
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 ํ์ผ์ ๋๋ฉด ํ ์ ์ฒด์ ์ผ๊ด๋ ๊ท์น์ ์ ์ฉํ ์ ์์ต๋๋ค.
๊ธฐ๋ณธ ํ ํ๋ฆฟ ์์ฑ
1
pylint --generate-rcfile > .pylintrc
์ฃผ์ ์ค์ ํญ๋ชฉ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[MASTER]
# ๊ฐ์ํ๊ฒฝ, ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฒฝ๋ก ์ถ๊ฐ
init-hook='import sys; sys.path.insert(0, "src")'
# ๋ณ๋ ฌ ์ฒ๋ฆฌ (0 = CPU ์์ ๋ง๊ฒ ์๋)
jobs=0
[MESSAGES CONTROL]
# ๋นํ์ฑํํ ๋ฉ์์ง ์ฝ๋ (์ผํ๋ก ๊ตฌ๋ถ)
disable=
C0114, # missing-module-docstring
C0115, # missing-class-docstring
C0116, # missing-function-docstring
W1203 # logging-fstring
[FORMAT]
# ํ ์ค ์ต๋ ๊ธธ์ด (PEP 8 ๊ธฐ๋ณธ๊ฐ 79, ์ค๋ฌด 100~120 ๊ถ์ฅ)
max-line-length=120
# ๋ค์ฌ์ฐ๊ธฐ ๋จ์
indent-string=' '
[DESIGN]
# ํจ์ ์ต๋ ์ธ์ ์
max-args=7
# ํจ์ ์ต๋ ์ค ์
max-statements=50
[TYPECHECK]
# ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์
์ฒดํฌ ์ค๋ฅ ๋ฌด์
ignored-modules=numpy,pandas,pydantic
๐ ๊ฒฝ๊ณ ๋นํ์ฑํ ๋ฐฉ๋ฒ
ํ๋ก์ ํธ ์ ์ฒด๊ฐ ์๋ ํน์ ์ฝ๋์๋ง ๊ท์น์ ๋นํ์ฑํํ ๋ ์ธ๋ผ์ธ ์ฃผ์์ ์ฌ์ฉํฉ๋๋ค.
1์ค ๋นํ์ฑํ
1
import os # pylint: disable=unused-import
๋ธ๋ก ๋จ์ ๋นํ์ฑํ
1
2
3
4
# pylint: disable=too-many-arguments
def complex_function(a, b, c, d, e, f):
pass
# pylint: enable=too-many-arguments
ํ์ผ ์ ์ฒด ๋นํ์ฑํ
ํ์ผ ์ต์๋จ์ ์ถ๊ฐํฉ๋๋ค.
1
# pylint: disable=missing-module-docstring,missing-function-docstring
โ ๏ธ ์ธ๋ผ์ธ ๋นํ์ฑํ๋ฅผ ๋จ์ฉํ๋ฉด Pylint์ ํจ๊ณผ๊ฐ ๋จ์ด์ง๋๋ค. ์ค์ ๋ก ๋ถํ์ํ ๊ฒฝ๊ณ ๋ง ์ ํ์ ์ผ๋ก ๋๊ณ , ๊ฐ๋ฅํ๋ฉด
.pylintrc์์ ํ๋ก์ ํธ ์ ์ฒด ๊ธฐ์ค์ ์ค์ ํ๋ ๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค.
๐ฅ๏ธ VSCode ์ฐ๋
settings.json ์ค์
.vscode/settings.json์ ๋ค์์ ์ถ๊ฐํฉ๋๋ค.
1
2
3
4
5
6
7
8
{
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.linting.pylintArgs": [
"--disable", "C0301",
"--max-line-length", "120"
]
}
์ ์ฅ ์ ์๋ ๊ฒ์ฌ
1
2
3
4
{
"editor.formatOnSave": true,
"python.linting.lintOnSave": true
}
Tip: VSCode์ Python ํ์ฅ(ms-python.python)์ ์ค์นํ๋ฉด ํธ์ง ์ค ์ค์๊ฐ์ผ๋ก Pylint ์ค๋ฅ๊ฐ ํ์๋ฉ๋๋ค.
๐ pre-commit์ผ๋ก CI/CD ํตํฉ
์ปค๋ฐ ์์ ์ Pylint๋ฅผ ์๋ ์คํํ๋ฉด, ์ค๋ฅ๊ฐ ์๋ ์ฝ๋๋ ์ปค๋ฐ ์์ฒด๊ฐ ์ฐจ๋จ๋ฉ๋๋ค.
pre-commit ์ค์น
1
pip install pre-commit
.pre-commit-config.yaml ์์ฑ
1
2
3
4
5
6
7
8
9
10
11
12
repos:
- repo: https://github.com/pycqa/pylint
rev: v3.3.0
hooks:
- id: pylint
args:
- "--max-line-length=120"
- "--disable=C0114,C0115,C0116"
additional_dependencies:
- pydantic
- pyyaml
- requests
ํ ์ค์น ๋ฐ ์คํ
1
2
3
4
5
# ํ
์ค์น (์ต์ด 1ํ)
pre-commit install
# ์ ์ฒด ํ์ผ ์๋ ์คํ
pre-commit run --all-files
์ดํ git commit ์ ์๋์ผ๋ก Pylint๊ฐ ์คํ๋๋ฉฐ, ์ค๋ฅ๊ฐ ์์ผ๋ฉด ์ปค๋ฐ์ด ์ฐจ๋จ๋ฉ๋๋ค.
GitHub Actions ํตํฉ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: Lint
on: [push, pull_request]
jobs:
pylint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pylint
- 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 ์ค์น ํ ์คํํฉ๋๋ค.
1
2
3
4
pip install pylint
brew install graphviz # macOS
pyreverse -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
2
[TYPECHECK]
ignored-modules=numpy,pandas,torch,tensorflow
Q. pre-commit ์ฌ์ฉ ์ ๊ฐ์ํ๊ฒฝ ํจํค์ง๋ฅผ ์ธ์ํ์ง ๋ชปํฉ๋๋ค.
.pre-commit-config.yaml์ additional_dependencies์ ํ๋ก์ ํธ์์ ์ฌ์ฉํ๋ ํจํค์ง๋ฅผ ๋ช
์ํด์ผ ํฉ๋๋ค.
1
2
3
4
additional_dependencies:
- pydantic
- sqlalchemy
- fastapi