Skip to main content

Pythonのlintでコード品質を向上させる

著者

Keshav Malik

feature python linting

2022年10月12日

0 分で読めます

Pythonは成長を続ける言語です。進化し、用途が広がるにつれて、Pythonを使った開発に役立つツールや手法も増えています。近年、特に広く使われるようになった手法の1つがlintです。lintでは、コードに潜在的な問題がないかをチェックします。コード内のエラーにフラグを立てることで、問題の原因になり得る一般的でないプログラミング手法を修正できます。

lintは、ソースコードの作成中、コンパイル前に実行します。つまり、lintはビルド前のチェックで、「静的コード解析」とも呼ばれます。定期的にlintを実行すれば、コードやコードベース全体の一貫性を保てます。これにより、小さなエラーがコード実行後に複雑な問題へと発展する可能性を抑えられます。

lintを使わない開発者も少なくありません。lintを実行してもバグを防げないため、導入する価値がないと考えるからです。しかし、この見方では、コード品質の向上におけるlintの価値が十分に評価されていません。

この記事では、人気の高いlintツールの1つであるPylintを使って、Pythonのコードをすばやく簡単にチェックする方法を実践形式で紹介します。また、コードのlintによってPEP8コードスタイルガイドに沿ったコードを書きやすくなることも見ていきます。

前提条件

始める前に、以下を準備してください。

  • Pythonとpipがマシンにインストールされていること

  • コマンドラインインターフェース(CLI)の基本的な知識

  • 関数やクラスなど、Pythonの概念に関する知識

ここで紹介するコマンドはLinuxおよびmacOSベースのシステムに対応していますが、Windowsで使う場合は注意が必要です。

Pythonコードのlint

Pythonでのlinterの使い方を見ていく前に、ディレクトリと仮想環境を作成して準備しましょう。

環境をセットアップする

まず、プロジェクト用のディレクトリを作成します。このチュートリアルでは、ディレクトリ名をpylint-demoとします。

$ mkdir pylint-demo

$ cd pylint-demo

次に、仮想環境を作成します。仮想環境を使うと、プロジェクトの依存関係を分離し、他のプロジェクトとの競合を防げます。

$ pip install pipenv

$ pipenv shell

プロンプトは、(pylint-demo) $のように表示されます。これは仮想環境が有効になっていることを示します。

仮想環境が有効な状態で、次のコマンドを使ってlinterをインストールします。

$ pipenv install pylint

これでpylintコマンドを使ってlinterを実行できます。Pylintが正しくインストールされたことを確認するため、次のコマンドを実行します。

$ pylint –help

lintを始める

簡単なPythonプログラムを書き、Pylintを実行して動作を確認しましょう。main.pyファイルを作成し、次のコードを貼り付けます。

def is_number_even(num):
    return "Even" if num % 2 == 0 else "Odd"

num = 5
print(f"The number {num} is {is_number_even(num)}")

上記のコードでは、数値が偶数か奇数かを判定する関数を追加しました。このコードのエラーをPylintでチェックするには、次のコマンドを使います。

$ pylint <<file_name>>
$ pylint main.py

Pylintの出力は次のとおりです。

************* Module main
main.py:12:0: C0304: Final newline missing (missing-final-newline)
main.py:1:0: C0114: Missing module docstring (missing-module-docstring)
main.py:8:0: C0116: Missing function or method docstring (missing-function-docstring)
main.py:8:19: W0621: Redefining name 'num' from outer scope (line 11) (redefined-outer-name)
main.py:11:0: C0103: Constant name "num" doesn't conform to UPPER_CASE naming style (invalid-name)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

コードには、C0304のような文字コードで示された、内容の分かりやすい問題がいくつかあります。Pylintでは、エラーの重要度と種類を区別するために、すべてのエラーに文字コードを付けます。エラーは次の5つのカテゴリに分類されます。

  • C:規約違反(コード規約に違反している場合)

  • R:リファクタリング(コードの臭いやリファクタリングに関する問題)

  • W:警告(エラーではない、プログラミング上の問題)

  • E:エラー(プログラミング上のエラー)

  • F:致命的エラー(Pylintの実行を停止させる重大な問題)

Pylintは、検出されたエラーの数に基づいて、コードを10点満点で評価します。

この例では、エラーコードのうち1つが警告で、残りはすべて規約違反です。これらの問題を解決するため、コードをいくつか修正してからPylintを再実行し、スコアを確認しましょう。

""" File contains various function to under Pylint """

def is_number_even(num):
    """Function to check if number is even or odd"""
    return "Even" if num % 2 == 0 else "Odd"

NUM = 5
print(f"The number {NUM} is {is_number_even(NUM)}")

このコードでは、モジュールと関数のdocstringを追加し、末尾に改行を入れ、上記のコード内の変数名を変更しました。Pylintを再実行すると、問題はなく、10点満点を獲得できます。

単一ファイルに対してPylintを実行する

Pylintの動作に慣れてきたところで、別の例を見てみましょう。次のコードを入力します。

""" File contains various function to under Pylint """

class animal:
  def __init__(self, name):
    self.name = name

obj1 = animal("Horse", 21)
print(obj1.name)

このコードには、animalという名前のシンプルなクラスと、obj1という名前のクラスオブジェクトがあります。このコードにPylintを実行してみましょう。

************* Module main
main.py:4:0: W0311: Bad indentation. Found 2 spaces, expected 4 (bad-indentation)
main.py:5:0: W0311: Bad indentation. Found 4 spaces, expected 8 (bad-indentation)
main.py:3:0: C0115: Missing class docstring (missing-class-docstring)
main.py:3:0: C0103: Class name "animal" doesn't conform to PascalCase naming style (invalid-name)
main.py:3:0: R0903: Too few public methods (0/2) (too-few-public-methods)
main.py:7:7: E1121: Too many positional arguments for constructor call (too-many-function-args)

今回はコード品質に関する問題はありませんが、代わりに、より重大なエラーが見つかりました。フラグが立った問題を、以下のコードを使って修正してみましょう。

""" File contains various function to under Pylint """

class Animal:
    "Animal Class"
    def __init__(self, name):
        self.name = name

obj1 = Animal("John")
print(obj1.name)

続けてPylintを再実行します。クラス名をanimalからAnimalに変更し、クラスにdocstringを追加し、関数呼び出しから不要な引数を削除して、適切なインデントを設定すると、コードのエラーをほぼ解消できます。ただし、まだ1つ残っています。

************* Module main
main.py:3:0: R0903: Too few public methods (0/2) (too-few-public-methods)

この残ったエラーの修正方法を見てみましょう。Pylintによると、パブリックメソッドが2つ以上ありません。しかし、コードにパブリックメソッドが2つ以上ない可能性が高そうです。では、どうすれば修正できるでしょうか。

このような場合は、Pythonのコメントを使って問題を抑制できます。抑制するための構文は次のとおりです。

# pylint: disable=<<issue_name>>

コードは次のようになります。

""" File contains various function to under Pylint """

# pylint: disable=too-few-public-methods
class Animal:
    "Animal Class"
    def __init__(self, name):
        self.name = name

obj1 = Animal("John")
print(obj1.name)

Pylintの出力を確認すると、問題が解消されていることが分かります。

ディレクトリに対してPylintを実行する

単一ファイルに対するPylintの実行方法を見てきました。しかし、プロジェクトで作業する場合は、チェック対象が1つのファイルだけとは限りません。ディレクトリ全体をlintする必要があります。

ディレクトリ全体に対してPylintを実行するには、次のコマンドを実行します。

$ pylint <<name_of_directory>>

ディレクトリのlintがどのように機能するか確認するため、ファイルを2つ追加してコードを記述しましょう。

$ mkdir src; cd src
$ touch helpers.py config.py __init__.py

main.pyファイルをsrcディレクトリに移動し、それぞれのファイルに次のコードを貼り付けます。

<<main.py>>
""" File contains various function to under Pylint """

from helpers import connect_db
from config import DB_USER, DB_PASS

is_connected = connect_db(DB_USER, DB_PASS)

if is_connected:
    print("Connected to DB")
else:
    print("Failed to connect to DB")

<<helpers.py>>
def connect_db(user, password):
    """Dummy function to connect to DB"""
    if user is None or password is None:
        return False
    return True

<<config.py>>
DB_USER = "root"
DB_PASS = "toor"

srcディレクトリには、main.py、helpers.py、config.pyの3つのファイルがあります。main.pyには、DBに接続されているかどうかを出力するダミー関数があります。helpers.pyにはDB接続用のダミーヘルパー関数があり、config.pyにはDBのユーザー名とパスワードが含まれています。

次に、ルートディレクトリから以下のコマンドを実行し、ディレクトリ全体に対してPylintを実行します。

$ pylint src

コマンドの出力は次のとおりです。

************* Module src.config
src/config.py:2:0: C0304: Final newline missing (missing-final-newline)
src/config.py:1:0: C0114: Missing module docstring (missing-module-docstring)
************* Module src.main
src/main.py:11:0: C0304: Final newline missing (missing-final-newline)
src/main.py:3:0: E0401: Unable to import 'helpers' (import-error)
src/main.py:4:0: E0401: Unable to import 'config' (import-error)
************* Module src.helpers
src/helpers.py:6:0: C0304: Final newline missing (missing-final-newline)
src/helpers.py:1:0: C0114: Missing module docstring (missing-module-docstring)

ご覧のとおり、Pylintはファイルごとの出力を、***とモジュール名で区切って表示します。問題を修正するには、次の変更が必要です。

  • 各ファイルの末尾に改行を追加する。

  • 各ファイルと関数にdocstringを追加する。

  • import文をhelpers import connect_dbから.helpers import connect_dbに変更する。

これらの問題を修正すると、別の問題が見つかります。is_connected変数を大文字で始まる名前にする必要があります。変数名を変更するか、警告を抑制してこのエラーに対処できます。

警告を抑制する

Pythonコードをlintする際に、複数の警告をカスタマイズしたり抑制したりする必要が出てくることはよくあります。そのたびにコメントを追加するのは現実的ではありません。警告を1つずつ抑制する代わりに、.rcファイルを作成してPylintの動作をカスタマイズすれば、.rcファイルからプロジェクト全体の警告を直接抑制できます。

次のコマンドで作成できます。

$ pylint --generate-rcfile > pylint.rc

lintで、より安全で質の高いコードを

Pythonのlintでは、ソースコードの作成中にチェックを行い、コードを実行する前にエラーを検出します。Pylintをエディターに組み込めば、リアルタイムでlintの結果を確認することもできます。

lintでバグが自動的に修正されるわけではありませんが、継続的に使うことでコード品質を高く保てます。lintは時間の無駄だと考える開発者もいますが、小さな問題が大きな問題へと発展する前に検出するうえで、非常に効果的です。

この記事では、lintを実行し、Pylintの推奨事項を取り入れることで、サンプルコードがどのように改善されるかを見てきました。また、このプロセスを通じて、PEP8スタイルガイドにも自然に沿うことができます。プロジェクトにlintを導入できるようになったら、さまざまなlintツールを試し、Python開発のアプローチに最適なものを見つけて、さらに活用していきましょう。

Capture the Flagを始めよう

オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。

続きを読む

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

illustration hero ai
Blog

AIハリケーンが到来

AIはソフトウェア開発とサイバー攻撃の双方を加速させています。リーダーは、エージェントとコードを開発の初期段階から保護し、実行時に制御を徹底するとともに、防御策を独立して検証しなければなりません。

feature insights context
Blog

予防は、本質的に解決済みの問題なのでしょうか?

エージェントが生成するコードの予防策はアーキテクチャ上解決されていますが、開発を遅らせることなくセキュリティを守る制御を選ぶことが、依然として課題です。