Blog>>Software development>>Best practices for Python code quality — linters

Best practices for Python code quality — linters

When developing and maintaining software, code quality is of paramount importance. Being confident that code is readable and therefore easier to maintain and more efficient not only makes it easier for developers to work together, but also significantly reduces the likelihood of errors. One effective way to maintain high quality and at the same time ease of use is to use Python linter tools. These tools are helpful in ensuring Python code is clean, consistent and error-free, resulting in a streamlined development process and a better final product.

This article is an overview of Python linters, outlining their advantages, popular options and best practices for incorporating them into your workflow. If you are looking for a way to improve the quality of your code or want to learn about the most popular Python linter tools this article is for you.

What is a Python linter?

Linters, also known as static code analyzers, are tools that analyze source code to detect potential issues such as syntax errors, style inconsistencies, and potential bugs. They play a crucial role in maintaining high-quality code as they automatically identify and report issues that may otherwise go unnoticed, leading to improved code readability and maintainability.

Services test automation

Python linters are tailored specifically to analyze Python code. They focus on enforcing best practices and identifying Python-specific issues, such as improper indentation or use of deprecated features. In addition to helping developers write better code, a Python linter also encourages the adoption of a consistent way of coding across a project, making it easier for team members to understand and collaborate on the codebase. Linters work by parsing the source code and analyzing its structure, evaluating it against a set of predefined rules or guidelines. These rules typically cover various aspects of code, including syntax, style, and potential functional issues. When a linter detects a violation, it generates a report with information about the issue and, in many cases, provides suggestions on how to fix it.

The impact of linters on Python code quality is significant. By automatically identifying and reporting issues, linters help developers address problems early in the development process, reducing the time and effort spent on debugging. Furthermore, they promote a consistent coding style, making the code more readable and maintainable. As a result, linters improve the software development process, collaboration between team members, and contribute to higher quality of the final product.

Fig.1: Fragment of code with highlighted linter issues
Fragment of code with highlighted linter issues

If you're at the stage of choosing a technology for your project, we recommend an article about what Python is used for.

Benefits of Using Python Linters

Before diving into the numerous benefits of linters, let's take a moment to acknowledge the key role they play in software development. Linters not only help maintain high code quality, but they also contribute to a more efficient and collaborative development process. With that said, let's uncover the key benefits of using Python linters and see how they contribute to the overall efficiency and collaboration within a development team.

Improved code readability and maintainability

Python linters enforce coding best practices and help ensure consistency in coding style, making the code easier to read and understand. This, in turn, improves maintainability, as developers can quickly comprehend the code structure and make necessary changes with minimal effort.

Consistent coding style across a project

Python linters can be configured to enforce a specific coding style across a project, ensuring that all team members adhere to the same guidelines. This consistency makes it easier for developers to collaborate on a codebase, as they can quickly understand and navigate the code written by their peers.

Early detection of syntax and style errors

Linters analyze code as it is being written or before it is executed, identifying potential syntax and style errors. Catching these issues early in the development process allows developers to fix them before they lead to more significant problems, such as runtime errors or hard-to-debug issues.

Faster code reviews

By automatically identifying and flagging issues, linters can streamline the process of code reviews. Reviewers can focus on more critical aspects of the code, such as logic, functionality, and code design, rather than spending time identifying syntax or style issues. This targeted approach ensures that reviewers concentrate on the elements that contribute most to the overall quality and maintainability of the codebase.

Enhanced collaboration among team members

As linters help enforce consistent coding practices, team members can more effectively collaborate on a project. When everyone on the team adheres to the same set of rules and guidelines, communication and understanding are improved, ultimately leading to a more efficient and productive development process.

Reduced debugging time and effort

By identifying issues early in the development process, linters help minimize the time and effort spent on debugging. Developers can address potential problems before they become more complex, reducing the overall time spent resolving issues and increasing the time available for feature development and improvement.

In this section, we will introduce popular linters for Python and discuss how they can help improve Python code quality. The following popular linters are organized by categories.

CategoryLinter
LinterPylint - A highly-configurable linter for comprehensive error checking and customizable coding guidelines enforcement.
GeneralFlake8 - A popular linter combining Pyflakes, pycodestyle, and McCabe complexity analysis for quick quality assurance.
TypingMyPy - A user-friendly type checker focusing on static type checking and gradual typing support.
TypingPyright - A fast, lightweight type checker and linter by Microsoft, offering real-time development feedback.
Imports orderingisort - A Python utility for sorting imports automatically, ensuring a clean and organized import section in your code.
Documentationpydocstyle - Docstring style checker.
SecuritySafety - checks Python dependencies for known security vulnerabilities and suggests the proper remediations for vulnerabilities detected.
SecurityBandit - a tool designed to find common security issues in Python code.
Code complexityXenon - a monitoring tool based on Radon. It monitors your code's complexity. Ideally, Xenon is run every time you commit code.
Code complexity Radon - a tool that computes various metrics from the Python source code.

These linters serve as invaluable tools for maintaining high-quality, consistent codebases. By incorporating them into your development process, you can ensure adherence to established standards and best practices, including PEP 8. As the official style guide for Python code, PEP 8 (Python Enhancement Proposal 8) offers recommendations on formatting and structuring Python code to maintain consistency and readability.

Python linter - example

Here we provide an example code that contains inconsistent indentation and is not aligned with PEP 8 guidelines. A Python linter can automatically detect and report these issues, guiding developers on how to fix them. In the following sections, we will examine this sample code with selected linters and take a closer look at their features and output.

# Linter issue: Missing whitespace after commas in function arguments

def bad_formatted_function( a,b, c):

# Linter issue: Inconsistent indentation

   if a > 0 and b > 0:

# Linter issue: 'sum' is a built-in function, consider using a different name

        sum = a + b + c

# Linter issue: Missing whitespace after the colon in the print statement

        print("Sum:",sum)

    else:

        print("a and b must be positive")



# Linter issue: Class name should be in CamelCase

class bad_formatted_class:

# Linter issue: Unused parameter 'z'

    def __init__(self, x, y, z):

        self.x = x

        self.y = y



    def calculate_sum(self):

        return self.x + self.y



# Linter issue: Variable name should be in snake_case (e.g., bad_instance)

BadInstance = bad_formatted_class(3,5)

result = BadInstance.calculate_sum()

# Linter issue: Missing whitespace after the colon in the print statement

print("Result:",result)



bad_formatted_function(1, 2, 3)

Pylint

Pylint is a widely-used and highly-configurable Python linter that checks for coding standard adherence, detects errors, and enforces customizable coding guidelines. It offers extensive checks for potential issues, such as an unused variable, undefined variable, and deprecated functions. Pylint also checks for adherence to PEP 8, Python's official style guide, and can generate reports on code quality.

Pros

  • Highly configurable and extensible

Users can customize Pylint's behavior by changing settings and adding plugins. Developers can use this flexibility to impose their chosen coding standards and practices.

  • Comprehensive error checking

Pylint detects a variety of potential problems in Python code, including unused variables, undefined variables, and deprecated functions. This assists developers in detecting issues early on, reducing the likelihood of bugs and improving the quality of the code.

  • Integrated support for PEP 8

Pylint enforces adherence to PEP 8, Python's main style guide, ensuring consistency throughout the project. By following these established rules, developers can maintain clean, readable code that is easier for others to understand.

  • Generates code quality reports

The reports generated by Pylint contain thorough information on the inspected code, suggesting opportunities for improvement. These reports can help teams track their progress and make educated judgments regarding refactoring and code cleanup.

Cons

  • Can be complex to set up and configure

Setting up and configuring Pylint may take some time, especially for those unfamiliar with its complexities. This complexity may be a barrier for some developers looking for a simpler linter. Moreover, the default settings provided by Pylint may not be sufficient for most projects, often requiring additional configuration. This makes it necessary for developers to invest time in understanding and customizing the linter rules to suit their specific needs.

  • May produce false positives or false negatives

Pylint may occasionally report issues that are not genuine issues (false positives) or may miss genuine issues (false negatives). These inaccuracies may cause confusion or the overlooking of legitimate issues, potentially jeopardizing the quality of your code.

How to set up and use Pylint

To set up and use Pylint, install it using pip:

pip install pylint



>>> Installing collected packages: pylint

>>> Successfully installed pylint-2.17.0

To lint a Python file, run:

pylint my_file.py



 

************* Module my_file

my_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)

my_file.py:2:0: C0116: Missing function or method docstring (missing-function-docstring)

my_file.py:2:28: C0103: Argument name "a" doesn't conform to snake_case naming style (invalid-name)

my_file.py:2:30: C0103: Argument name "b" doesn't conform to snake_case naming style (invalid-name)

my_file.py:2:33: C0103: Argument name "c" doesn't conform to snake_case naming style (invalid-name)

my_file.py:6:8: W0622: Redefining built-in 'sum' (redefined-builtin)

my_file.py:13:0: C0115: Missing class docstring (missing-class-docstring)

my_file.py:13:0: C0103: Class name "bad_formatted_class" doesn't conform to PascalCase naming style (invalid-name)

my_file.py:16:8: C0103: Attribute name "x" doesn't conform to snake_case naming style (invalid-name)

my_file.py:17:8: C0103: Attribute name "y" doesn't conform to snake_case naming style (invalid-name)

my_file.py:15:23: C0103: Argument name "x" doesn't conform to snake_case naming style (invalid-name)

my_file.py:15:26: C0103: Argument name "y" doesn't conform to snake_case naming style (invalid-name)

my_file.py:15:29: C0103: Argument name "z" doesn't conform to snake_case naming style (invalid-name)

my_file.py:15:29: W0613: Unused argument 'z' (unused-argument)

my_file.py:19:4: C0116: Missing function or method docstring (missing-function-docstring)

my_file.py:13:0: R0903: Too few public methods (1/2) (too-few-public-methods)

my_file.py:23:14: E1120: No value for argument 'z' in constructor call (no-value-for-parameter)



-----------------------------------

Your code has been rated at 0.00/10

Flake8

Flake8 is a popular Python linter that combines the power of Pyflakes, pycodestyle, and McCabe complexity analysis tools. It checks for syntax issues, stylistic errors, and code complexity, offering a quick and easy way to ensure quality. Flake8 is highly extensible and supports numerous plugins to enhance its functionality.

Pros

  • Fast and lightweight

Flake8 is known for its speed and minimal resource usage, making it an efficient choice for developers. Its lightweight nature ensures that it doesn't slow down the development process or hinder performance.

  • Combines multiple linting tools

Flake8 combines the capabilities of Pyflakes, pycodestyle and McCabe complexity analysis tools. This combination allows developers to take advantage of the strengths of each tool, providing a comprehensive approach to linting.

  • Supports a variety of plugins

Flake8 is highly extensible and supports numerous plug-ins to enhance its functionality. Developers can easily integrate additional tools by customizing the linter to meet their specific requirements.

  • Easy to set up and use

Configuring and using Flake8 is easy which makes it a tool for developers of all skill levels. Its simplicity is user-friendly and at the same time helps keep quality high in teams.

Cons

  • Less configurable than Pylint

Flake8 offers some configuration options, however it is not as flexible as Pylint. This limitation can be a drawback for developers who look for advanced configuration or need to enforce certain coding standards.

  • Limited error checking compared to Pylint

Flake8's error-checking capabilities are not as extensive as those provided with Pylint. As a result, developers may overlook some problems when they rely solely on Flake8.

How to set up and use Flake8

To set up and use Flake8, install it using pip:

pip install flake8



>>> Installing collected packages: pyflakes, pycodestyle, flake8

>>> Successfully installed flake8-6.0.0 pycodestyle-2.10.0 pyflakes-3.0.1

To lint a Python file, run:

flake8 my_file.py



my_file.py:2:28: E201 whitespace after '('

my_file.py:2:30: E231 missing whitespace after ','

my_file.py:5:80: E501 line too long (85 > 79 characters)

my_file.py:7:80: E501 line too long (81 > 79 characters)

my_file.py:8:21: E231 missing whitespace after ','

my_file.py:13:1: E302 expected 2 blank lines, found 1

my_file.py:23:1: E305 expected 2 blank lines after class or function definition, found 1

my_file.py:23:36: E231 missing whitespace after ','

my_file.py:26:16: E231 missing whitespace after ','

(venv) rafal@rafal-ThinkPad-E595:~/Dokumenty/demo$

MyPy

MyPy is a widely-adopted and user-friendly Python type checker that focuses on static type checking and enforces type annotations. It detects potential type-related issues, improving the quality and maintainability. MyPy also supports gradual typing, allowing developers to introduce type annotations incrementally throughout their projects.

Pros

  • Static type checking

MyPy checks for type-related issues in Python code statically, which means it analyzes the code without executing it. This helps developers catch potential errors before runtime, reducing the likelihood of bugs and enhancing quality. Static type checking also provides a more robust foundation for developing full-blown software, as it complements Python's duck typing, offering a more structured approach to type management.

  • Gradual typing support

MyPy allows developers to introduce type annotations incrementally, making it easy to adopt gradual typing in their projects. This flexibility enables teams to improve their quality without making drastic changes to their existing codebase.

  • User-friendly and straightforward

MyPy is designed to be easy to use and understand, making it accessible to developers of all skill levels. Its simplicity encourages adoption and helps maintain high-quality code across teams.

Cons

  • Limited error checking beyond types

MyPy primarily focuses on type checking and may not detect other potential issues in Python code. Developers should use complementary linting tools alongside MyPy to ensure a comprehensive code analysis.

  • May require additional effort to add type annotations

Using MyPy effectively requires adding type annotations to the codebase, which may be time- consuming for large or complex projects. However, the long-term benefits of improved code quality and maintainability often outweigh the initial effort.

How to set up and use MyPy

To set up and use MyPy, install it using pip:

pip install mypy



>>> Installing collected packages: mypy-extensions, mypy

>>> Successfully installed mypy-1.1.1 mypy-extensions-1.0.0

To type-check a Python file, run:

mypy my_file.py



my_file.py:23: error: Missing positional argument "z" in call to "bad_formatted_class"  [call-arg]

Found 1 error in 1 file (checked 1 source file)

Pyright

Pyright is a fast and lightweight Python type checker and linter that focuses on providing real-time feedback during development. Developed by Microsoft, it detects type-related issues, syntax errors, and offers code completion suggestions. Pyright is designed to be used alongside an integrated development environment (IDE) or as a standalone tool.

Pros

  • Fast and lightweight

Pyright is known for its speed and minimal resource usage. Its lightweight nature means that it does not slow down the development process or affect system performance.

  • Real-time feedback on type-related issues

Pyright provides real-time feedback on type issues, helping developers catch bugs early in the development process. This timely problem detection can save time and reduce the number of potential bugs in the finished solution.

  • Code completion suggestions

Pyright suggests code completion, making it easier for programmers to write code quickly and efficiently. The suggestions can also help programmers learn and use new features or libraries more effectively.

  • Can be used with IDEs or as a standalone tool

Pyright is designed to be used alongside an integrated development environment (IDE) or as a standalone tool, providing flexibility for developers in their preferred coding environment.

Cons

  • Less comprehensive error checking than Pylint

Pyright's error-checking capabilities are not as extensive as those provided by Pylint. Which makes it possible that some problems may be overlooked by programmers relying on Pyright.

  • Primarily focused on type checking

While Pyright excels at type checking, its primary focus on this area may leave other aspects of quality less scrutinized. Developers should consider using complementary linting tools alongside Pyright to ensure a comprehensive analysis of their code.

How to set up and use Pyright

To set up and use Pyright, install it using pip:

pip install pyright



>>> Requirement already satisfied: setuptools in ./venv/lib/python3.8/site-packages (from nodeenv>=1.6.0->pyright) (65.5.1)

>>> Installing collected packages: nodeenv, pyright

>>> Successfully installed nodeenv-1.7.0 pyright-1.1.300

To lint a Python file, run:

pyright my_file.py



WARNING: there is a new pyright version available (v1.1.300 -> v1.1.301).

Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`



No configuration file found.

No pyproject.toml file found.

stubPath /home/rafal/Dokumenty/demo/typings is not a valid directory.

Assuming Python platform Linux

Searching for source files

Found 1 source file

pyright 1.1.300

0 errors, 0 warnings, 0 informations 

Completed in 0.84sec

Integrating linters into your development workflow

Linter configuration and customization

To make the most of a Python linter, it's essential to configure it according to your project's specific requirements. Most linters provide configuration files, allowing you to define custom rules, adjust severity levels, and specify which files or directories to ignore. Be sure to review the documentation for your chosen linter to understand how to tailor its behavior to your needs.

Using linters with IDEs

Many IDEs, such as Visual Studio Code, PyCharm, and Sublime Text, support integrating many Python linters, providing real-time feedback and highlighting issues as you write code. This live integration allows developers to identify and fix problems immediately, improving overall quality and reducing debugging time. Refer to your IDE's documentation to learn how to set up your preferred linter.

Automating linting with pre-commit hooks

Pre-commit hooks are scripts that run automatically before each commit in a version control system, such as Git. By integrating a linter into your pre-commit hooks, you can ensure that all code is automatically checked for quality before being committed to the repository. This approach prevents problematic code from being introduced, streamlining the development process and improving overall quality. Various tools, like pre-commit      link-icon, can help you set up and manage pre-commit hooks.

Continuous integration and linters

Continuous integration (CI) is a development practice that involves automatically building, testing, and integrating code changes into a shared repository. By incorporating linters into your CI pipeline, you can ensure that all code is consistently checked for quality before being merged into the main branch. This process helps maintain high quality and prevents issues from slipping through the cracks. Popular CI services, such as GitHub Actions, GitLab CI/CD, and Jenkins, can easily integrate Python linters into their workflows.

If you’re interested in testing in Python, see our selection of Python testing frameworks for networks.

Linter Best Practices

Selecting the most suitable linter for your project depends on various factors, such as the size and complexity of the codebase, the team's preferred coding style, and the desired level of customization. Evaluate each linter's features, pros, and cons before making a decision. Striking a balance is crucial: strict checks may hinder productivity, while those too lenient may result in inconsistent quality. Adjust linter rules and severity levels to find the right balance for your project.

To maximize the benefits of using linters, ensure that all team members adopt and adhere to the linter rules. Enforce these rules through automation in both local development environments and a continuous integration (CI) pipeline. Implement pre-commit hooks that automatically run linters before each commit, ensuring quality before committing to the repository. Leverage tools like pre-commit      link-icon to manage these hooks. Incorporate linters into your continuous integration pipeline, which builds, tests, and integrates code changes into a shared repository. This practice maintains high quality and prevents issues from slipping through the cracks.

As your project evolves and best practices change, regularly update linter configurations and rules to reflect the latest standards. This approach helps maintain high quality and ensures that your team stays up to date with current best practices.

Conclusion

Python linters are indispensable tools for maintaining high quality. They automatically detect and report potential issues within the code, encourage adherence to consistent coding practices, and simplify the development process. By gaining a deeper understanding of the advantages, widely-used options, and effective techniques for incorporating linters into your programming routine, you can significantly enhance the readability, maintainability, and overall quality of your code.

Linters, such as Pylint, Flake8, MyPy, and Pyright, offer developers valuable insights and guidance, helping them identify areas for improvement and ensure their code aligns with established best practices. It's worth noting that you can use multiple linters simultaneously, as they can be complementary. Instead of choosing just one, consider using and configuring a set of tools that work together to cover various aspects of code.

This, in turn, leads to more streamlined project development and improved collaboration between team members. No matter your level of expertise in the programming world, integrating Python linting into your workflow can have a positive impact on your development process. Embracing these tools allows you to catch errors and inconsistencies early on, thereby saving time and effort while boosting the overall quality and performance of your projects.

Buczyński Rafał

Rafał Buczyński

Software Engineer

Rafał Buczyński is a software engineer and author on CodiLime's blog. Check out the author's articles on the blog.Read about author >

Read also

Get your project estimate

For businesses that need support in their software or network engineering projects, please fill in the form and we’ll get back to you within one business day.

For businesses that need support in their software or network engineering projects, please fill in the form and we’ll get back to you within one business day.

We guarantee 100% privacy.

Trusted by leaders:

Cisco Systems
Palo Alto Services
Equinix
Jupiter Networks
Nutanix