そうだ! EuroPython 2011へ行こう

#4 アプリケーションに関する講演,LT,PSFメンバーミーティング,まとめ

この記事を読むのに必要な時間:およそ 8 分

5 Years Of Bad Ideas:過去5年間のバッドアイディア

日本でも人気のあるmitsuhikoことArmin Ronacher氏による講演です。氏はFlask,Jinja2,Sphinxといったフレームワークを開発しているPocooという開発チームのリーダーの1人でもあります。

本講演は,過去5年間,様々なフレームワークやライブラリを開発してきた氏の経験から「バッドアイディア」を紹介するものでした。こういったテクニックは,普通のアプリケーション開発では多用するものではないのでご注意ください。

なお,本講演は5 YEARS OF BAD IDEASから視聴できます。資料とサンプルコードも公開されているので,興味のある方はそちらも参照してください。ここでは,その中から一部の内容を紹介します。

Armin Ronacher氏

Armin Ronacher氏

Pythonとは何か?

Armin氏は,次の3つの要素をPythonの本質的な価値として挙げていました。

  • 美しいコード
  • 統合性のある整然とした構文
  • 関数を含め,すべてがファーストクラス

また,今回の講演の題材として次の要素が紹介されました。これらは,Pythonの深い部分の仕組みを知るきっかけとして筆者は興味深く聞いていました。

魔法について

@zedshaw氏による魔法についての見解を引用していました。少し調べてみましたが,この見解の意図することを筆者は掴めていません。どなたか分かる方がいましたら筆者に教えてください。

“And honestly, I only find this shit in web frameworks. WTF is up with Python people going ballistic when a web app uses magic? Bullshit.”

― Zed Shaw about Magic

また,本講演で言う魔法というのは,Webアプリケーションが正常に動かなかったときにデバッガを起動して対話的に調べるヘルパー機能の提供や,いざというときに動的にパッチを当てて対応するような用途においては,有効な場合もあると紹介されていました。

魔法の書

普通の魔法

普通の魔法

フレームオブジェクト

sys._getframe()は,コールスタックのフレームオブジェクトを返します。このフレームオブジェクトは,インタープリターからの情報源になります。例えば,実行中のソースの行番号は次のようにして取得できます。

>>> import sys
>>> sys._getframe().f_lineno
1

また標準ライブラリにあるオブジェクトの情報を調べるinspectモジュールも,内部的にsys._getframe()を使ってフレームオブジェクトを取得します。上述した行番号の取得は次のコードと同じです。

>>> import inspect
>>> inspect.currentframe().f_lineno
1
メタクラス

Pythonでメタクラスを使うには__metaclass__という特殊メソッドにtype()を継承したクラスを指定します。例えば,クラスの生成時にhelloというメソッドをもっているかをチェックするサンプルが次になります。

class MyMetaclass(type):
  def __new__(cls, name, base, d):
    rv = super(MyMetaclass, cls).__new__(cls, name, base, d)
    if not getattr(rv, "hello", None):
      raise NotImplementedError("Need to implement hello method")
    return rv

class MyClass(object):
  __metaclass__ = MyMetaclass

  def hello(self): pass

通常はこのようにメタクラスへtype()を継承したクラスを指定しますが,この方法だとクラスに対して1つのメタクラスしか持てません。Pythonのclass文はtype()のシンタックスシュガーなので,__metaclass__はクラスでなくても,型オブジェクトを返すファクトリ関数を指定することもできます。

def implements(*interfaces):
  cls_scope = sys._getframe(1).f_locals
  metacls = cls_scope.get('__metaclass__')
  metafactory = make_meta_factory(metacls, interfaces)
  cls_scope['__metaclass__'] = metafactory

class MyClass(object):
  implements(Interface1, Interface2)

implements関数内でクラスの__metaclass__にファクトリ関数を指定するサンプルが紹介されていました。この方法だと,implementsは複数のインターフェイスを受け取れます。このサンプルの完全なソースはinterfaces.pyにあるので,興味がある方はそちらを参照してください。

AST(抽象構文木)のハック

標準ライブラリにPythonのソースコードを抽象構文木というツリー状のデータに変換するastモジュールがあります。抽象構文木の演算子を書き換えるサンプルコードが紹介されていました。

>>> import ast
>>> x = ast.parse("1 + 2", mode="eval")
>>> x.body.op = ast.Sub()
>>> eval(compile(x, "<string>", "eval"))
-1
インタープリターとの魔大戦

インタープリターとの魔大戦

インタープリターとの魔大戦

変数名の取得

簡単にできそうで,いざやろうとすると,どうして良いのか悩むような課題です。 ガベージコレクタとのインターフェイスを提供するgcモジュールを使って変数名を取得しています。

import gc
import sys

def find_names(obj):
  frame = sys._getframe(1)
  while frame is not None:
    frame.f_locals
    frame = frame.f_back
  result = set()
  for referrer in gc.get_referrers(obj):
    if isinstance(referrer, dict):
      for k, v in referrer.iteritems():
        if v is obj:
          result.add(k)
  return tuple(result)
>>> a = []
>>> b = a
>>> find_names(a)
('a', 'b')
暗黙のself

Pythonの特徴の1つ,明示的なselfを隠してしまうハックです。

class User(ImplicitSelf):

  def __init__(username, password):
    self.username = username
    self.set_password(password)

  def set_password(password):
    self.hash = hashlib.sha1(password).hexdigest()

  def check_password(password):
    return hashlib.sha1(password).hexdigest() == self.hash

メタクラスを使ってメソッドのバイトコードを書き換えているようです。

class ImplicitSelfType(type):
  def __new__(cls, name, bases, d):
    for key, value in d.iteritems():
      if isinstance(value, FunctionType):
        implicit_self(value)
    return type.__new__(cls, name, bases, d)

def implicit_self(function):
  code = function.func_code
  bytecode, varnames, names = inject_self(code)
  function.func_code = CodeType(code.co_argcount + 1,
    code.co_nlocals + 1, code.co_stacksize, code.co_flags,
    bytecode, code.co_consts, names, varnames,
    code.co_filename, code.co_name, code.co_firstlineno,
    code.co_lnotab, code.co_freevars, code.co_cellvars)

このサンプルの完全なソースはimplicitself.pyにあるので,興味がある方はそちらを参照してください。

著者プロフィール

森本哲也(もりもとてつや)

一介のプログラマ。

自分で設計して,自分で開発して,自分で直せるような独立したプログラマを目指している。OSSコミュニティのゆるい人のつながりが性にあっていてPythonプログラミングが好き。共訳書に『エキスパート Pythonプログラミング』(アスキーメディアワークス)がある。

Twitter:@t2y

ブログ:http://d.hatena.ne.jp/t2y-1979/