SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

Python 3.0開発者ガイド:コア言語の変更点

Python 3.0の主要な言語機能

クラスデコレータ

 クラスデコレータを使うと、クラスを最初に宣言するときに、メソッド、属性、プロパティ、基底クラスなどを追加/削除/変更する関数にクラスを渡して、そのクラスを変更できます。一連のクラス(必ずしもすべてが共通の基底クラスから派生しているとは限らない)に何らかの変更を適用する必要がある場合に、これが特に役立ちます。

 次の例では、dump()関数がクラスデコレータとなり、入力クラスを受け取って、dump_methods()というメソッドをクラスに追加します。dump_methods()メソッドは自身のクラスの辞書を走査し、呼び出し可能なすべてのメソッドをタイトル付きで出力します。このメソッドがcls入力クラスを返すことに注意してください。後でこのデコレータクラスを使って、オブジェクトをインスタンス化します。

def dump(cls):
  def dump_methods(self):
    cls = type(self)
    s = cls.__name__ + ' methods'
    print (s + '\n' + '-' * len(s))
    for k,v in cls.__dict__.items():
      # The callable() builtin has been removed in Python 3.0
      if hasattr(v, '__call__'):
        print (k)
    print ()

  # Attch the dump_methods nested function as a method to the input class
  cls.dump_methods = dump_methods
  return cls

 クラスデコレータを定義したところで、例として2つのクラスを修飾してみます。クラスAとクラスBはつまらない内容ですが、クラスデコレータの働きを示すには十分です。

@dump
class A:
  def foo():
    pass
  def bar():
    pass

@dump
class B:
  def baz():
    pass

 クラスAとクラスBはどちらも@dumpで修飾されているので、次のようにそれぞれのインスタンスのdump_methods()を呼び出すことができます。

A().dump_methods()
B().dump_methods()

A methods
---------
bar
dump_methods
foo

B methods
---------
dump_methods
baz

 ご覧のように、dump_methods()自体も出力に表示されています。デコレータで追加されたメソッドは、元のメソッドと区別が付かないからです。

 クラスデコレータは、関数デコレータとメソッドデコレータの自然な延長線上にあります。Python 2.4の設計プロセス時(関数デコレータとメソッドデコレータが導入されたとき)には、クラスデコレータは重複機能のように思われました。「メタクラス」が非常によく似た機能を提供していたからです。主な違い(構文以外)は、メタクラスは継承されるという点です。つまり、あるクラスにメタクラスを適用すると、そのクラスのすべてのサブクラスがメタクラスを継承します。これに対し、クラスデコレータはサブクラスには影響を与えません。話がややこしくなりますが、メタクラスを1つだけ持つクラスデコレータを作成できます(それぞれ独自のメタクラスを持つ複数の基底クラスから継承するとややこしいことになりますが、結局は1つのメタクラスだけが勝ち残ります)。私は、デコレータの仕組みの方がメタクラスよりはるかに理解しやすく、クラス、関数、およびメソッドデコレータに統一された構文とセマンティクスがあるのは良いことだと思います。

※著者注

 詳細については、「PEP-3129」を参照ください。

関数アノテーション

 関数アノテーションは(ABCと共に)私のお気に入りのPython 3000の新機能です。関数の引数や戻り値にPythonの任意の式を付けることができます。関数にどのようにアノテーションを付け、どのような意味を持たせるかは自分で決めます。型チェックは1つの代表例で、関数の各引数と戻り値に型のアノテーションを付けます。そうすることで、ユーザーはその関数を呼び出す方法を知ることができます。例えば、calc_circumference()関数が整数の半径を受け取り、浮動小数点数を返すとします。引数radiusと戻り値のアノテーション構文は次のようになります。

def calc_circumference(radius: int) -> float:
  return 2 * math.pi * radius

 これ自体はそれほど大したことではありません。docstringで記述すれば済むことだからです(とはいえ、アノテーションの方が読みやすくなります)。関数アノテーションは、__anotations__という関数属性にdictとして格納されます。

>>> calc_circumference.__annotations__
{'radius': <class 'int'>, 'return': <class 'float'>}

 もちろん、関数内のアノテーションにアクセスし、各引数の型がアノテーションと一致しているかどうかを確認することもできます。これは思ったより少し複雑です。Pythonでは、アノテーションが順不同のコレクションとしてdictに格納されるからです。次の簡単な関数は、3つのint引数を受け取り、それらを出力します。

def print_3(a: int, b: int, c: int)

 次の例は、関数アノテーションにアクセスし、半径が実際に整数かどうかを検証する1つの方法を示しています。

def calc_circumference(radius: int) -> float:
  assert isinstance(radius, calc_circumference.__annotations__['radius'])
  return 2 * math.pi * radius

 残念ながら、これはかなりできの悪いコードです。煩雑で間違いが起きやすく、たくさんの引数を確認する必要がある場合はとても読みにくくなります。しかも、このような関数固有の検証を記述する場合は、アノテーションは必要ありません。関数の作成者は「半径が整数でなければならないことを既に知っている」ので、次のように記述すれば済むからです。

assert isinstance(radius, int)

 本領が発揮されるのは、汎用デコレータを使って関数アノテーションにアクセスするときです。call_checkデコレータ(リスト2を参照)を使うと、引数に型アノテーションが付いた関数やメソッドを修飾できます。このデコレータはアノテーションを抽出し、各引数の型が型アノテーションと一致していることを呼び出しごとにアサートします。また、戻り値の型も確認します(returnアノテーションが付いている場合)。辞書内のアノテーションは順不同なので、関数のコードオブジェクトから非キーワード引数の名前を抽出するには少し凝ったコードが必要です。要素に対してイテレーションを行うだけでは、関数の引数と順序が異なる場合があります。これが関数アノテーションの大きな弱点で、Python 3.1では修正されることを願っています。

リスト2 call_checkデコレータ
def call_check(f):
  def decorated(*args, **keywds):
    # Get the code object for the function
    c = f.__code__
    # Names of arguments in order to lookup in the annotations dictionary
    arg_names = c.co_varnames[:c.co_argcount]
    annotations = f.__annotations__

    # Collect all arguments into a single dict (now that all arguments have names)
    d = {}
    d.update(keywds)
    d.update(zip(arg_names, args))

    # Validate every argument
    for name, value in d.items():
      t = annotations[name]
      if not isinstance(value, t):
        s = 'The type of ' + name + '=' + str(value) + ' should be ' + t.__name__
        raise Exception(s)

    # Call the original function
    result = f(*args, **keywds)

    # Check return value
    if 'return' in annotations:
      assert isinstance(result, annotations['return'])

    return result
  return decorated

 デコレータをテストするために、次のコードではcalc_circumferenceメソッドにデコレータを適用しています。

@call_check
def calc_circumference(radius: int) -> float:
  return 2 * math.pi * radius

 こうしておいて、有効な引数と無効な引数を使ってcalc_circumferenceを呼び出してみると、半径10を使った呼び出しでは正しい答えが返ることが分かります。無効な引数10.5(整数以外)を使った2番目の呼び出しでは例外が発生し、問題が発生した場所を示すメッセージと、値10.5を指定した引数radiusには整数を指定しなければならないことを知らせるメッセージが表示されます。

>>> calc_circumference(10)
62.8318530718

>>> calc_circumference(10.5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "article_1.py", line 117, in decorated
    raise Exception(s)
Exception: The type of radius=10.5 should be int

 次のステップ(この部分は読者にお任せします)は、クラスデコレータまたはメタクラスを使って、クラスのすべてのメソッドをcall_checkデコレータで自動的に修飾することです。

 関数アノテーションには、ヘルプ文字列、詳細な型情報(有効な値の範囲など)、直接検証関数、IDEを使いやすくするためのヒント、マッピング、アダプタ、対応するCの型など、ほかにもさまざまな用途があります。

 関数アノテーションに関する1つの懸念は、これを使いすぎると「言語内の言語」を作ってしまい、アノテーションを処理するコードを見ないと、関数を呼び出したときにどう処理されるがわからなくなってしまうことです。

次のページ
PEP-3115:Python 3000のメタクラス

この記事は参考になりましたか?

japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Gigi Sayfan(Gigi Sayfan)

 主に大規模分散システムを対象とした、C/C++/C#/Python/Javaによるクロスプラットフォームのオブジェクト指向プログラミングに精通。現在は、Numenta社で脳の働きを模した人工知能システムの開発に取り組んでいる。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/4051 2009/06/25 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー