MLpylint: ML Code Smell Analyzer
- MLpylint is a static analysis tool specifically designed to detect machine learning-specific code smells in Python codebases.
- It differentiates between precise code smells and advisory smells, addressing issues with libraries like Pandas, NumPy, and TensorFlow.
- Empirical validation on 160 open-source ML projects demonstrates its effectiveness in identifying maintainability, reproducibility, and technical debt challenges.
MLpylint is a stand-alone static code analysis tool for Python ML applications that automatically detects machine learning-specific code smells. It is intended to help developers identify maintainability, reproducibility, and technical-debt issues that arise specifically in ML code, especially when using libraries such as Pandas, NumPy, scikit-learn, PyTorch, and TensorFlow. The tool is positioned as a response to a gap in existing research and tooling: traditional code smell research is mature for conventional software, but ML systems exhibit distinct quality risks tied to third-party ML libraries, data semantics, numerical computation, model training behavior, randomness control, and API-specific misuse. MLpylint was evaluated on 160 open-source Python ML application repositories from GitHub and through a static validation with 15 ML professionals (Hamfelt et al., 4 Aug 2025).
1. Definition and scope
MLpylint targets machine learning-specific code smells rather than ordinary software smells. In the formulation used for the tool, the core problem is that ML software has distinct quality risks, including reproducibility problems, hidden technical debt, misuse of ML and data libraries, and mistakes in training, evaluation, and data processing pipelines. These issues often do not resemble classic object-oriented or general-purpose code smells. Instead, they are frequently tied to third-party ML libraries rather than core Python, to data semantics and numerical computation, to model training behavior, to randomness control and determinism, and to API-specific misuse in frameworks such as Pandas, NumPy, TensorFlow, and PyTorch (Hamfelt et al., 4 Aug 2025).
This scope distinguishes MLpylint from general Python linting. A large-scale study of 74 open-source ML projects found that Pylint was useful for surfacing maintainability issues in ML codebases, but that some of its most alarming findings around imports and member access had to be interpreted cautiously because ML code depends heavily on dynamic, C-backed libraries and often has fragile dependency setups. That study also concluded that Pylint cannot reliably check correct usage of imported dependencies, including prominent ML libraries such as PyTorch (Oort et al., 2021). MLpylint was designed specifically for this gap: it analyzes ML-library-aware patterns that are not well captured by plain Python linting (Hamfelt et al., 4 Aug 2025).
The tool is also narrower than project-level ML quality analyzers. mllint, for example, is introduced as a broader ML-project-oriented static analysis tool that explicitly goes beyond what a code linter such as pylint—or a hypothetical ML-focused code linter like MLpylint—would usually do, emphasizing project smells such as dependency management, version control, CI, and testing rather than ML-specific code semantics (Oort et al., 2022). MLpylint instead centers on source-level ML smells.
2. Research basis and smell selection criteria
The development of MLpylint followed Design Science Methodology, organized into the phases of problem identification, solution design, and evaluation. In the problem identification phase, a comprehensive literature review was conducted to identify ML-specific code smells. The most important source reported was Zhang et al., who cataloged 22 code smells distinct to Python-based ML applications. In solution design, a secondary literature review and consultations with experts from the Python Code Quality Authority were used to determine what makes an ML smell suitable for static analysis and what implementation strategy would be practical (Hamfelt et al., 4 Aug 2025).
From this process, the tool adopts five code smell criteria:
| Criterion | Description |
|---|---|
| CSC1 | Smell Descriptions: The code smell includes context, problem, and solution description. |
| CSC2 | Domain Relevance: The code smell is specifically relevant to machine learning within Python. |
| CSC3 | Static Detectability: The code smell can be detected through static analysis without code execution. |
| CSC4 | Distinct Pattern: The code smell pattern is identifiable in Python source code. |
| CSC5 | Precision of Pattern: The code smell exhibits a clear and unambiguous pattern. Determines code smell classification. |
These criteria were used to classify candidate smells into three categories: Code Smell (CS) for precise, unambiguous, directly detectable patterns; Code Smell Advice (CSA) for broader heuristics that may indicate a problem but require developer judgment; and Not Applicable (NA) for patterns that were insufficiently precise or not statically analyzable. Applying the criteria to the 22-smell catalog produced 20 relevant smells, of which 14 were classified as CS, 6 as CSA, and 2 as NA (Hamfelt et al., 4 Aug 2025).
This classification is central to MLpylint’s design. It encodes an explicit distinction between rule families that can be handled with high-confidence static detection and rule families that are better surfaced as advisory findings. That distinction also explains why the tool later reports separate precision figures for CS and CSA detections (Hamfelt et al., 4 Aug 2025).
3. Smell taxonomy
The implemented smell taxonomy is inherited from the literature review and organized by MLpylint into CS, CSA, and NA categories. The paper’s classification is as follows (Hamfelt et al., 4 Aug 2025).
| Classification | Smells |
|---|---|
| CSA | Unnecessary Iteration; Memory Not Freed; Broadcasting Feature Not Used; Training / Evaluation Mode Improper Toggling; Gradients Not Cleared before Backward Propagation; Data Leakage |
| CS | NaN Equivalence Comparison; Chain Indexing; Columns and Data Type Not Explicitly Set; Empty Column Misinitialization; Merge API Parameter Not Explicitly Set; In-Place APIs Misused; Dataframe Conversion API Misused; Matrix Multiplication API Misused; Hyperparameter Not Explicitly Set; Deterministic Algorithm Option Not Used; Randomness Uncontrolled; Missing the Mask of Invalid Value; TensorArray Not Used; Pytorch Call Method Misused |
| NA | No Scaling before Scaling-Sensitive Operation; Threshold-Dependent Validation |
Among the CS items, several are described as precise source-level patterns. “NaN Equivalence Comparison” is treated as a direct code pattern involving comparison to NaN. “Chain Indexing” is described as a chained indexing pattern, typically relevant in Pandas. “Merge API Parameter Not Explicitly Set” is given as a representative case where a function call can be detected and validated for required keywords. “Randomness Uncontrolled” is framed as failure to control randomness, such as not setting seeds consistently, and is later reported as the most frequently detected smell (Hamfelt et al., 4 Aug 2025).
The CSA category contains patterns that the tool surfaces as advisory rather than definitive. “Memory Not Freed” is explicitly described as a category in which a potential memory leakage caused by a specific ML model could be flagged, but where thorough investigation is required to confirm the issue. “Training / Evaluation Mode Improper Toggling,” “Gradients Not Cleared before Backward Propagation,” and “Data Leakage” are also treated as advisory because the paper considers them broader or more context-sensitive under static analysis (Hamfelt et al., 4 Aug 2025).
This taxonomy suggests a layered notion of ML code quality. Some ML-specific defects admit crisp AST-level detection; others are more plausibly approximated as warning templates that direct expert attention. The paper does not present per-smell formal logical rules or detection equations, so the taxonomy functions as the primary formalization exposed in the publication (Hamfelt et al., 4 Aug 2025).
4. Architecture and implementation
MLpylint is implemented as a static analyzer for Python and distributed via PyPI. The key implementation technology is Astroid, used for parsing Python into an abstract syntax tree and for some degree of object and value inference. The tool’s architecture consists of three modules plus Astroid: Analysis Runner, Code Smell Checkers, Results, and the third-party parsing library Astroid (Hamfelt et al., 4 Aug 2025).
The Analysis Runner starts the tool based on input parameters, walks the target project path recursively, extracts Python files, and sends them to Astroid for parsing. The Code Smell Checkers constitute the rule engine. Every specific smell checker derives from a common base class; the base class recursively iterates through every node in an AST module; and this traversal follows the visitor pattern. Individual smell checkers specify which node types they care about and encapsulate their unique detection logic in dedicated checker classes. The Results module stores findings from all checkers, and the Analysis Report compiles output for developers (Hamfelt et al., 4 Aug 2025).
A notable design decision is that the authors did not extend Pylint directly. Although Pylint was initially considered because it is mature and already uses Astroid, consultations with the PyCQA maintainers indicated that Pylint could not infer external modules well enough for the use case. Since many ML-specific smells depend on imported third-party libraries rather than built-in Python features, the maintainers recommended developing a new tool that leverages Astroid directly. That recommendation led to MLpylint as a stand-alone analyzer (Hamfelt et al., 4 Aug 2025).
This implementation choice aligns with broader empirical observations about general-purpose linting in ML code. Pylint-based studies reported systematic problems with imported ML libraries, especially C-backed APIs such as PyTorch (Oort et al., 2021). Context-aware work such as MLSmellHound took a different route by adapting Pylint output through subtraction, addition, reprioritisation, and remessage transformations, but that work remained focused on contextual reinterpretation of existing lint rules rather than a machine-learning-specific smell detector built from dedicated checker classes (Kannan et al., 2022). MLpylint instead treats ML-specific smells as first-class static-analysis targets.
The paper also describes intended workflow integration. MLpylint can be used manually from the command line, as a pre-commit hook in Git workflows, or in CI/CD pipeline integration. The architecture figure described in the paper places the tool both in local development and in automated analysis phases. At the same time, the authors note a practical limitation: relevant external ML libraries may need to be installed in the virtual environment, which is resource-intensive because most target smells are tied to libraries such as Pandas, NumPy, scikit-learn, PyTorch, and TensorFlow (Hamfelt et al., 4 Aug 2025).
5. Empirical evaluation
The performance evaluation analyzed 160 open-source Python ML application repositories from GitHub. The search strategy used five library-driven search strings plus one broad ML/AI/DS query, and selected repositories had to satisfy five inclusion criteria: explicitly centered on machine learning, written in Python, pushed after January 1, 2023, not merely tutorials or guides, and retrieved with GitHub sorting set to “Best match.” Every repository was forked to preserve the analyzed state (Hamfelt et al., 4 Aug 2025).
Across this corpus, MLpylint analyzed 15 GB of project data, 86,577 files, 16,806 folders, 33,360 Python files, and 10,015,028 lines of Python code. It reported 3,877 Python files with detected smells, while 807 Python files could not be parsed due to syntax errors. Total runtime for all 160 projects was 35m 54s, with an average runtime per project of 13.4 seconds. The paper reports a Rate of Syntax Errors of 2.4% and a Code Coverage of 37.6%, where the latter means that 37.6% of analyzed files were Python files (Hamfelt et al., 4 Aug 2025).
The tool detected 5,380 code smells in total. Average code smells per project were reported as 17.96 for CS, 15.66 for CSA, and 33.63 in total. Density per thousand lines of code was 0.2869 for CS, 0.2502 for CSA, and 0.5371 in total, summarized in the paper as 0.53 code smells per KLOC (Hamfelt et al., 4 Aug 2025).
Validation used proportional stratified random sampling and manual review. The resulting precision figures were:
| Detection class | Precision |
|---|---|
| Code Smell (CS) | 100% |
| Code Smell Advice (CSA) | 73.6% |
| Overall | 87.9% |
The paper emphasizes that sampled CS detections showed a consistent trend of true positives with no obvious false positives, whereas CSA findings were noisier. The most common CS smell was CS14 Randomness Uncontrolled, with 1796 occurrences. The most frequent advisory categories were CSA12 Memory Not Freed and CSA21 Data Leakage, both with over a thousand instances. Four CS smells—CS5 Empty Column Misinitialization, CS9 Matrix Multiplication API Misused, CS13 Deterministic Algorithm Option Not Used, and CS19 Pytorch Call Method Misused—were not observed in the selected corpus (Hamfelt et al., 4 Aug 2025).
The manual validation also documented specific error modes. CSA1 exhibited incorrect inference in some configurations and dataframe-column iteration scenarios. CSA12 was described as too broad, especially around memory management and loop structures involving TensorFlow or Torch objects. CSA20 showed incorrect inference and string-detection issues. CSA21 sometimes confused similarly named methods from different libraries, specifically a method used in a transformers pipeline versus a smell pattern intended for sklearn pipeline (Hamfelt et al., 4 Aug 2025).
6. Expert assessment, related tools, and outlook
To assess usefulness, 15 Ericsson practitioners used MLpylint for approximately one month after an initial demonstration of installation and features. The participant pool consisted of 14 data scientists and 1 ML architect; 3 had 1–5 years of ML development experience, 7 had 5–10 years, and 5 had more than 10 years. The survey asked about ease of use, ability to identify ML-specific code smells, reliability, effectiveness, reduction of time and effort, and relevance of the integrated smells (Hamfelt et al., 4 Aug 2025).
The reported responses were consistently positive. For ease of use and understandability, over 33% strongly agreed and close to 67% agreed. For aiding identification of ML-specific smells, 20% strongly agreed, about 67% agreed, and just over 13% were neutral. For reliability, about 7% strongly agreed, around 67% agreed, and nearly 27% were neutral. For effectiveness, over 13% strongly agreed, nearly 67% agreed, and 20% were neutral. For reducing time and effort, about 27% strongly agreed and 73% agreed. For relevance of integrated smells, 20% strongly agreed, approximately 73% agreed, and nearly 7% were neutral (Hamfelt et al., 4 Aug 2025).
Open-ended feedback described the tool as useful for “basic hygiene checks,” with satisfactory results and interest in continued use. At the same time, participants noted that expert review is still necessary, that integration into a familiar linting ecosystem would help, that broader ML library support is desirable, and that CI/CD integration and configurability would be valuable. The future-work section explicitly states that one ongoing initiative is to include MLpylint functionality as a plugin to extend Pylint’s capabilities for ML code, despite the original implementation decision not to build directly on Pylint (Hamfelt et al., 4 Aug 2025).
Within the broader research landscape, MLpylint occupies a middle position between general Python linting and highly specialized ML analyzers. Pynblint is a static analyzer for Python Jupyter notebooks and their surrounding repositories, grounded in 17 empirically validated best practices, but it focuses on notebook quality, reproducibility, and repository hygiene rather than ML-library-specific code smells (Quaranta et al., 2022). LeakageDetector is an ML-specific static-analysis assistant for data leakage in Python ML pipelines, but its scope is deliberately narrow, focusing only on Overlap leakage, Multi-test leakage, and Preprocessing leakage (AlOmar et al., 18 Mar 2025). More recent work such as scicode-lint broadens coverage to 66 methodology-bug patterns across AI training, AI inference, scientific-numerical, scientific-performance, and scientific-reproducibility categories, but it uses an LLM-generated pattern architecture rather than the rule-based AST-checker design of MLpylint (Samsonau, 18 Mar 2026).
A plausible implication is that MLpylint represents one concrete static-analysis answer to a broader problem that later tools address with different architectural commitments. Its distinguishing feature is not merely that it is ML-aware, but that it grounds ML-specific code quality in an explicit smell-selection methodology, separates precise smells from advisory ones, and demonstrates that several ML-library-centric smells can be detected with high precision at repository scale (Hamfelt et al., 4 Aug 2025).