廃止予定の実装であることをスマートにお知らせする - debtcollerctor

廃止予定(deprecated)なクラスやメソッドをモジュールのユーザに通知するために python では warnings があります。

以下をコードを実行すると

    # -*- coding: utf-8 -*-
    import warnings
    
    
    def deprecated_method():
        warn_msg = "`deprecated_method` is deprecated and will be removed in v0.5"
        warnings.warn(warn_msg, UserWarning)
        print "I'm old"
    
    
    if __name__ == '__main__':
        deprecated_method()

以下のようにwarningsが出ます。

    $ python warning_sample.py 
    warning_sample.py:7: UserWarning: `deprecated_method` is deprecated and will be removed in v0.5
      warnings.warn(warn_msg, UserWarning)
    I'm old

今回は、この deprecated メッセージを作りやすくしてくれる[deptcollector](https://pypi.python.org/pypi/debtcollector)の紹介です。


# debtcollectorの使い方

ほぼ、[Examples](http://docs.openstack.org/developer/debtcollector/examples.html#removing-a-class-classmethod-method-function)を見ればいいのですが、自分でもやってみます。

# class, calssmethod, method, functionのdeprecated

クラス内のメソッドが廃止予定の場合は以下のように@removals.removeのデコレータを使います。

    # -*- coding: utf-8 -*-
    from debtcollector import removals
    import warnings
    
    warnings.simplefilter('always')
    
    
    class Car(object):
        @removals.remove
        def start(self):
            pass
    
    
    if __name__ == '__main__':
        Car().start()

実行結果は以下。

    $ python removals_sample.py 
    removals_sample.py:15: DeprecationWarning: Using function/method 'Car.start()' is deprecated
      Car().start()

コードの中にある、warnings.simplefilter('always')はなぜ必要なのでしょう。

@removals.removeはデフォルトだと、DeprecationWarningがwarings.warnに渡されて実行されてしますので、表示されません。
warningsの[警告カテゴリ](http://docs.python.jp/2/library/warnings.html#warning-categories)に詳しくのってます。

コード内に書かずに、コマンドラインでも-Wオプションを指定することにより警告を表示できます。
さきほどのソースコードから、warnings.simplefilter('always')を削除し、以下のように実行します。

    $ python -Wd removals_sample.py
    ... 省略
      file, filename, etc = imp.find_module(subname, path)
    removals_sample.py:15: DeprecationWarning: Using function/method 'Car.start()' is deprecated
      Car().start()

Car().start()のエラーに混じって他の警告も表示されました。
自分でライブラリなどを作ったら一回やってみるといいかもしれないですね。

クラスへの警告は以下のように書きます。

    @removals.remove
    class Pinto(object):
        pass

クラスメソッドの場合は、@classmethodデコレータの上につけます。

     class OldAndBusted(object):
        @removals.remove
        @classmethod
        def fix_things(cls):
            pass

# method, property, class, keywordなどが、他の名前に変わった時

function名が変わった時はmoves.moved_functionを使います。

    # -*- coding: utf-8 -*-
    from debtcollector import moves
    import warnings
    warnings.simplefilter('always')
    
    
    def new_thing():
        return "new thing"
    
    old_thing = moves.moved_function(new_thing, 'old_thing', __name__)
    
    
    if __name__ == '__main__':
        print new_thing()
        print old_thing()

実行すると、old_thing()を呼んでも新しいメソッドが呼ばれている事がわかります。

    $ python moving_sample.py 
    new thing
    moving_sample.py:15: DeprecationWarning: Function '__main__.old_thing()' has moved to '__main__.new_thing()'
      print old_thing()
    new thing

methodの場合は、デコレータでできます。

    class Cat(object):
        @moves.moved_method('meow')
        def mewow(self):
            return self.meow()
        def meow(self):
            return 'kitty'

その他の例は、最初に紹介したリンクを見てください。

# 将来的に置き換えられるバージョンを指定する

現在のバージョンと、置き換えれるバージョンを指定するには、以下のようにversionとremoval_versionのキーワードを指定します。

    # -*- coding: utf-8 -*-
    from debtcollector import moves
    import warnings
    warnings.simplefilter('always')
    
    
    def new_thing():
        return "new thing"
    
    old_thing = moves.moved_function(new_thing, 'old_thing', __name__,
                                     version="0.5", removal_version="0.7")
    
    
    if __name__ == '__main__':
        print new_thing()
        print old_thing()

実行結果は以下です。

    $ python moving_version_sample.py 
    new thing
    moving_version_sample.py:16: DeprecationWarning: Function '__main__.old_thing()' has moved to '__main__.new_thing()' in version '0.5' and will be removed in version '0.7'
      print old_thing()
    new thing

# その他の方法
    
以下のようにしてメソッド内でメッセージを出す事もできます。
    
    import debtcollector
    debtcollector.deprecate("This is no longer supported", version="1.0")


# まとめ

**deptcollector**を使うと、デコレータを使うだけでスマートに廃止予定のメソッドをユーザに通知できそうです。