python/hack 属性シンタックスによるによるミニDSL。

# -*- coding: utf-8 -*-
"""
近頃はスクリプト言語シンタックスを乱用、じゃなくてクリエイティブに使って違う言語のようにすると「DSL」と呼べるらしい。 ならテングもpythonでマイクロDSL
"""
def main():
"""

ベースクラス。ディフォルトの挙動(はつまらない)。


"""
hoge=AttrChain()
print hoge.foo.bar.baz
# >>> foo.bar.baz

"""

「.」によるパス表記


"""
class PathAttrChain(AttrChain):
def _end(self, path_func=lambda p: p):
import os.path
path=os.path.join(self.kw.get('base','/'), *self._tuple())
return path_func(path)
"""

属性チェーンからパスへ


"""
print PathAttrChain().usr.local.bin.emacs
# /usr/local/bin/emacs

"""

ルートを変更できる。


"""
print PathAttrChain(base='/alt').local.bin.emacs
# /alt/local/bin/emacs

"""

パスを引数とする関数を入れると答えが帰ってくる


"""
import os,sys
print "emacs size=", PathAttrChain().usr.local.bin.emacs(os.stat).st_size
# emacs size= 10938819

"""

ものぐさモジュールアクセス


"""
class ModuleAttrChain(AttrChain):
def _end(self):
return __import__(self._dot(), fromlist=['hoge'])

os.environ['DJANGO_SETTINGS_MODULE']='settings'
sys.path.insert(0, '/home/tengu/disco/server')

"""

ものぐさな人に: インポートしなくてもそのままモジュール内のものがアクセスできる表現。


"""
print ModuleAttrChain().django.contrib.auth.models().User
#

"""

ウェブサイトアクセス


"""
class WwwHostAttrChain(AttrChain):
def _end(self, path='/'):
import urllib2
url='http://%s%s' % (self._dot(),path)
return urllib2.urlopen(url)

"""

ホスト(パス) --> http response


"""
print WwwHostAttrChain().www.gnu.org('/robots.txt').read()
# robots.txt for http://www.gnu.org/
# User-agent: *
# ...

"""

属性チェーン表記の利点は…

特にない。ただできるからやってみただけ。 多分、最初は独自のHTMLテンプレートのデータアクセスの表記を pythonのパーサにやらせようとしたのが切っ掛けだったと思う。
"""

"""

実装


"""
class AttrChain(object):
""" 鎖のリンク
foo.bar.bazのような連鎖表現のそれぞれの節はこのクラスのインスタンス
"""
def __init__(self, prev=None, *args, **kw):
self._prev=prev
self._attr=None
self._path=None

self.args=args
self.kw=kw

def __repr__(self):
return '<%s "%s">' % (self.__class__.__name__, self._attr)

def __str__(self):
""" 文字列にするとチェーンが終了する。
str(foo.bar.baz) は foo.bar.baz._end() の簡略版。
"""
return self._end()

def __call__(self, *args, **kw):
""" foo.bar.baz(arg)
関数として呼出すとチェーンが終了する。
最終リンクだけ引数をうけることができる。
foo.bar().bazはできない。
"""
return self._end(*args, **kw)

def _tuple(self):
""" 属性チェーンを単語のリストに変換。
foo.bar.baz --> ('foo', 'bar', 'baz')
"""
cur=self
path=[]
while cur:
path.append(cur)
cur=cur._prev
path.reverse()
self._path=path # avoid traversing again..
return tuple([ n._attr for n in path if n._attr and n._attr!='_' ])

def _dot(self):
""" foo.bar.baz --> "foo.bar.baz"
「.」つなぎ表記。
"""
return '.'.join(self._tuple())

def _end(self):
""" ディフォルトの最終リンク。「.」つなぎ表記("foo.bar.baz")を返す。
サブクラスはこれをオーバライドすることにより、好みのオブジェクトを返す
ことができる。
"""
return self._dot()

def __getattr__(self, name):
""" 属性連鎖のミソ
"""
self._attr=name
# foo.bar.baz._ を foo.bar.baz._end() として扱う。
if name=='_':
return self._end()
# _で始まる属性は普通扱い。リンク属性は'_'で始まってはならない。
elif name.startswith('_'):
return object.__getattribute__(self, name)
# 普通の名前の属性はリンク扱い。自分と同じクラスのものを返す。
return self.__class__(prev=self, *self.args, **self.kw)
"""

"""
if __name__=='__main__':
main()

"""
ちなみに、このポストのソースはそのまま実行可能なpythonスクリプトだ。こういうのを「literate programming」って言うのかな。ブログを実行するとこうなる:
"""


python hack-attrchain-ja.py
foo.bar.baz
/usr/local/bin/emacs
/alt/local/bin/emacs
emacs size= 10938819

# robots.txt for http://www.gnu.org/

User-agent: *
Crawl-delay: 4
Disallow: /private/

User-agent: *
Crawl-delay: 4
Disallow: /savannah-checkouts/