SHOEISHA iD

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

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

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

japan.internet.com翻訳記事

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

Python 3.0の主要な言語機能

PEP-3115:Python 3000のメタクラス

 メタクラスは、そのインスタンスもクラスになるクラスです。私はメタクラスをクラスカスタマイザと考えています。メタクラスはクラスデコレータと同じ役割を果たしますが、仕組みが異なります(より複雑です)。メタクラスとクラスデコレータには、次のような特徴の違いがあります。

  1. メタクラスは継承される。
  2. 各クラスは1つのメタクラスしか持てない。

 このような単純な特徴でさえ、既に問題をはらんでいます。例えば、異なるメタクラスを持つ2つのクラスを継承したらどうなるのでしょうか。答えは、「場合による」です。2つのメタクラスに関連性がない場合、継承を掛け合わせることはできません。

class M1(type): pass
class M2(type): pass

class A(metaclass=M1): pass
class B(metaclass=M2): pass

class C(A, B): pass

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: metaclass conflict: the metaclass of a derived class must be 
   a (non-strict) subclass of the metaclasses of all its bases

 これをうまく動かすには、一方のメタクラスをもう一方の(場合によっては間接的な)サブクラスにする必要があります。最派生メタクラスが、新しいクラスのメタクラスとして使われます。

class M1(type): pass
class M2(M1): pass

class A(metaclass=M1): pass
class B(metaclass=M2): pass

class C(A, B): pass

>>> C.__class__
<class 'article_1.M2'>

 Python 3.0のメタクラスを使うと、クラス属性の順序を__dict__からだけでは判断できないという、Python 2.xの厄介な問題が修正されます。多くの場合、外部システム(ORM、外国語ブリッジ、COMオブジェクトなど)と連携するときは順序が重要です。そのため、これらの用途にメタクラスを使用する場合は、すべての関連フィールドの順序付きリストを用意するなど、何らかの人為的な規則を使って順序を指定することを各クラスに義務付ける必要がありました。

>>> class A(object):
...   x = 3
...   y = 4
...   z = 5
...   order = ['x', 'y', 'z']
...
>>> A.__dict__.keys()
['__module__', '__doc__', '__dict__', 'y', 'x', 'z', '__weakref__', 'order']
>>> A.order
['x', 'y', 'z']

 これに対し、Python 3.0のメタクラスでは、標準のdict()の代わりにメンバを格納する働きをするオブジェクトを返す__prepare__()というメソッドを実装できます。__prepare__()が返すオブジェクトが、フィールドの順序を格納するか、イテレーションでは常に挿入された順序で要素を返すようにすることを利用します。

 例えば、メソッドを決まった順序で呼び出す必要があるリニアワークフロークラスを作成するとします。いくつものワークフローがあり、ステップの追加と削除を続けていると、記録を付けて、ユーザーがワークフローのメソッドを正しい順序で呼び出すようにするためにかなりの注意を要することがあります。同じ状況でメタクラスベースの解決策を用いると、クラスで宣言したとおりの順序でメソッドを呼び出さなければならないクラスを作成できます。次のコードは、そのクラスと、クラスを正しく使った場合と間違って使った場合の例を示しています。

 LinearWorkflowクラスは非常に単純で、startstep_1step_2step_3という4つのメソッドを持ち、メソッドはこの順序で宣言されています。また、このクラスはメタクラスを持ち(これについては後ほど詳しく説明します)、メソッドが正しい順序で呼び出されるようにしています。この例では、メソッドは単に自らの名前を出力します。クラスは次のようになっています。

class LinearWorkflow(metaclass=PedanticMetaclass):
  def __init__(self):
    pass

  def start(self):
    print('start')

  def step_1(self):
    print('step_1')

  def step_2(self):
    print('step_2')

  def step_3(self):
    print('step_3')

 次の対話型セッションでは、LinearWorkflowオブジェクトをインスタンス化し、メソッドの呼び出しを開始しています。start()step_1()を呼び出し、それぞれのメソッドが実行されます。次に、step_1を再度呼び出そうとしますが、呼び出しの順序に反しているという理由で例外が発生します。step_2()の呼び出しに成功した後、強情にもstart()をもう一度(やはり順序に反して)呼び出そうとして、再び例外が発生しています。

>>> x = LinearWorkflow()
>>> x.start()
start
>>> x.step_1()
step_1
>>> x.step_1()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "article_1.py", line 192, in decorated
    raise Exception('Method %s called out of order' % f.name)
Exception: Method step_1 called out of order

>>> x.step_2()
step_2
>>> x.start()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "article_1.py", line 192, in decorated
    raise Exception('Method %s called out of order' % f.name)
Exception: Method start called out of order

 この魔法にはどのような仕掛けがあるのでしょうか。PedanticMetaclassは、OrderedDict(挿入順にペアのリストとして要素を格納する辞書風クラス)を提供することによって、メソッドの順序の記録を付けています。PedanticMetaclass__new__()メソッドを呼び出すと、修飾対象のクラス(この例ではLinearWorkflow)の、アンダースコアで始まらない各メソッドがOrderedDictに追加されます。その後、修飾対象のクラスで呼び出される各メソッドが記録され、メソッドが順序に反して呼び出されると例外が発生します。次にOrderedDictクラスとPedanticMetaclassクラスを示します。

class OrderedDict(collections.Mapping):
  def __init__(self):
    self._items = []

  def __getitem__(self, key):
    for k, v in self._items:
      if k == key:
        return v
    raise KeyError(key)

  def __setitem__(self, key, value):
    for i, (k, v) in enumerate(self._items):
      if k == key:
        self._items[i] = (k, value)
        return
    self._items.append((key, value))

  def __iter__(self):
    return iter([k for k,v in self._items])

  def __len__(self):
    return len(self._items)

  def __repr__(self):
    s = '{'
    for k, v in self._items:
      s += '%s: %s, ' % (str(k), str(v))
    s += '}'
    return s
※著者注

 OrderedDictは、あくまで説明用の例にすぎません。非常に効率が悪いので、実稼働用のコードで使うことはお勧めしません。このクラスは要素にアクセスするたびにリニア検索を実行します。

 PedanticMetaclassはあまり単純ではありませんが、コードを追うことはできるでしょう。__prepare__()メソッドはclassmethodでなければなりません。このメソッドは、単に空のOrderedDictを返します。decorate()関数は、各メソッド呼び出しを_orderリストで包んで、順序チェック処理のロジックを実行します。デコレータが__new__()メソッドで手動で適用された後、修飾対象のメソッドがsetattrを使ってnew_classに割り当てられます(元のメソッドを置き換えます)。

class PedanticMetaclass(type):
  @classmethod
  def __prepare__(metacls, name, bases):
    return OrderedDict()

  def decorate(self, f):
    """This method is called everytime an attribute is accessed
    it ensures that the attributes are accessed in the order they were declared
    """
    def decorated(*args, **keywds):
      assert f.name in self._order
      if self._called + [f.name] != self._order[:len(self._called) + 1]:
        raise Exception('Method %s called out of order' % f.name)
      self._called.append(f.name)
      if len(self._called) == len(self._order):
        self._called = []
      return f(*args, **keywds)
    return decorated

  def __new__(metacls, name, bases, ordered_dict):
    # Must convert the OrderedDict back to a regular dict
    new_class = type.__new__(metacls, name, bases, dict(ordered_dict.items()))
    # Create the ordered list of public methods automatically
    # (ignore non-callables and methods that start with underscore)
    order = [k for k,v in ordered_dict.items() if hasattr(v, '__call__')
                                            and not k.startswith('_')]
    new_class._order = order
    # Keep the last accessed
    new_class._called = []

    # Decorate each method to record and check call order
    for x in order:
      m = getattr(new_class, x)
      m.name = x
      m = PedanticMetaclass.decorate(new_class, m)
      setattr(new_class, x, m)

    return new_class

次のページ
PEP-3109: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」など、さまざまなカンファレンスを企画・運営しています。

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

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

メールバックナンバー