Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 12905] ファイルの概要

このコミットは、codereview ツールにおけるヘルプメッセージの表示に関するバグ修正です。具体的には、hgcommand デコレータによってラップされた関数が、元の関数のドキュメント文字列(docstring)を正しく継承していなかった問題を解決します。これにより、コマンドラインインターフェースでヘルプメッセージが表示されないという不具合が解消されます。

コミット

commit d3889ff322ab82ccf0231ab1e04accb557c26e38
Author: Anthony Martin <ality@pbrane.org>
Date:   Tue Apr 17 15:51:05 2012 -0700

    codereview: restore help messages
    
    Docstrings were not being set for the wrapper
    functions returned by the hgcommand decorator.
    
    R=golang-dev, minux.ma, rsc
    CC=golang-dev
    https://golang.org/cl/6059043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d3889ff322ab82ccf0231ab1e04accb557c26e38

元コミット内容

codereview: restore help messages

Docstrings were not being set for the wrapper
functions returned by the hgcommand decorator.

変更の背景

この変更の背景には、Go言語のコードレビューシステムで使用される codereview ツールが関係しています。このツールは、Mercurial (hg) コマンドと連携して動作し、特定の操作をラップする際にPythonのデコレータを使用しています。

問題は、hgcommand というデコレータが関数をラップする際に、元の関数のドキュメント文字列(docstring)を新しいラッパー関数に引き継いでいなかった点にありました。Pythonでは、関数の __doc__ 属性にドキュメント文字列が格納されており、これは通常、help() 関数やインタラクティブシェルで関数の説明を表示するために利用されます。

docstringが正しく設定されていないと、codereview ツールが提供するコマンドのヘルプメッセージが表示されず、ユーザーがコマンドの機能や使い方を理解する上で支障をきたしていました。このコミットは、このユーザビリティの問題を解決し、ヘルプメッセージが期待通りに表示されるようにすることを目的としています。

前提知識の解説

PythonのDocstring (ドキュメント文字列)

Pythonにおいて、docstringは関数、クラス、メソッド、モジュールなどの定義の直後に記述される文字列リテラルです。これは、そのコードブロックの目的、引数、戻り値、例外などを説明するために使用されます。docstringは実行時に __doc__ 属性としてアクセス可能であり、help() 関数やIDEのツールチップなどで利用されます。

例:

def my_function(arg1, arg2):
    """
    この関数は2つの引数を受け取り、それらを合計して返します。

    Args:
        arg1 (int): 最初の数値。
        arg2 (int): 2番目の数値。

    Returns:
        int: arg1とarg2の合計。
    """
    return arg1 + arg2

print(my_function.__doc__)
# または help(my_function)

Pythonのデコレータ (Decorators)

デコレータは、既存の関数やクラスを変更せずに機能を追加するためのPythonの構文です。デコレータは関数を受け取り、新しい関数(ラッパー関数)を返します。このラッパー関数は、元の関数を呼び出す前後に何らかの処理を追加することができます。

デコレータの基本的な構造:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        # 前処理
        print("関数が呼び出される前です。")
        result = func(*args, **kwargs)
        # 後処理
        print("関数が呼び出された後です。")
        return result
    return wrapper

@my_decorator
def say_hello():
    """Helloを言います。"""
    print("Hello!")

say_hello()
# 出力:
# 関数が呼び出される前です。
# Hello!
# 関数が呼び出された後です。

この例では、say_hello 関数が my_decorator によってラップされています。しかし、wrapper.__doc__say_hello.__doc__ ではなく、wrapper 関数のdocstring(もしあれば)または None になります。これが今回のコミットで修正された問題の根源です。

Mercurial (hg) と hgcommand デコレータ

Mercurial (hg) は分散型バージョン管理システムです。Go言語のプロジェクトでは、初期の頃にMercurialが広く使われていました。codereview ツールは、Mercurialのリポジトリと連携してコードレビューのワークフローを管理するために開発されたと考えられます。

hgcommand デコレータは、Mercurialのコマンドとして登録されるPython関数をラップするために使用されていたと推測されます。これにより、MercurialのコマンドラインインターフェースからPython関数を直接呼び出せるようにし、エラーハンドリングなどの共通処理をデコレータ内で一元的に行うことが可能になります。

hg_util.Abort

hg_util.Abort は、Mercurialのユーティリティライブラリの一部であり、Mercurialコマンドの実行中に発生したエラーを示すために使用される例外クラスであると推測されます。この例外が送出されると、Mercurialはコマンドの実行を中止し、適切なエラーメッセージをユーザーに表示します。

技術的詳細

このコミットの技術的な核心は、Pythonのデコレータがラッパー関数を返す際に、元の関数のメタデータ(特に __doc__ 属性)を自動的に引き継がないという特性にあります。

デコレータ hgcommand(f) は、元の関数 f を引数として受け取り、新しい関数 wrapped を返します。この wrapped 関数が実際にMercurialコマンドとして実行される関数となります。Pythonのデフォルトの挙動では、wrapped 関数の __doc__ 属性は、wrapped 関数自体のdocstring(もし定義されていれば)か、そうでなければ None になります。元の関数 f のdocstringは、wrapped 関数には自動的にコピーされません。

このため、hgcommand デコレータを使用している場合、元の関数 f にどんなに詳細なdocstringが書かれていても、Mercurialのコマンドラインツールが wrapped 関数のヘルプメッセージを表示しようとすると、そのdocstringにアクセスできず、結果としてヘルプメッセージが表示されないという問題が発生していました。

この問題を解決するために、Pythonでは functools.wraps デコレータを使用するのが一般的です。functools.wraps は、ラッパー関数の __name__, __module__, __doc__, __dict__ などの重要な属性を元の関数からコピーしてくれます。

しかし、このコミットでは functools.wraps を使用する代わりに、wrapped.__doc__ = f.__doc__ という一行を追加することで、明示的に __doc__ 属性をコピーしています。これは、この特定のケースでは __doc__ 属性のみをコピーすれば十分であり、他のメタデータは不要であったか、あるいは functools.wraps が利用できない環境(古いPythonバージョンなど)であった可能性が考えられます。

この修正により、hgcommand デコレータによってラップされた関数が呼び出された際に、その __doc__ 属性が元の関数のdocstringを指すようになり、Mercurialのコマンドラインツールが正しくヘルプメッセージを表示できるようになりました。

コアとなるコードの変更箇所

--- a/lib/codereview/codereview.py
+++ b/lib/codereview/codereview.py
@@ -1263,6 +1263,7 @@ def hgcommand(f):
 		if not err:
 			return 0
 		raise hg_util.Abort(err)
+		wrapped.__doc__ = f.__doc__
 	return wrapped
 
 #######################################################################

コアとなるコードの解説

変更は lib/codereview/codereview.py ファイルの hgcommand デコレータ内にあります。

追加された行は以下の通りです。

wrapped.__doc__ = f.__doc__

この一行は、hgcommand デコレータが返すラッパー関数 wrapped__doc__ 属性に、デコレータに渡された元の関数 f__doc__ 属性の値を代入しています。

これにより、wrapped 関数が実行される際に、そのドキュメント文字列が元の関数 f のドキュメント文字列と同じになり、Mercurialのコマンドラインツールが wrapped 関数のヘルプメッセージを表示しようとしたときに、正しい情報にアクセスできるようになります。

この修正は非常にシンプルですが、Pythonのデコレータの動作原理と、それが関数のメタデータに与える影響を正確に理解していることを示しています。

関連リンク

参考にした情報源リンク