昨日見ておもしろかったので(auto-vivification)
直接元のgistを見ないで再現してみた。再現できていたので理解は合ってた。良かった。
code
## https://en.wikipedia.org/wiki/Autovivification from collections import defaultdict def tree(): return defaultdict(tree) def tree_as_dict(t): if hasattr(t, "__iter__"): return {k:tree_as_dict(t[k]) for k in t} else: return t t = tree() t["x"]["y"]["z"] = 1 t["x"]["y"]["a"] = 2 t["x"]["a"]["b"] = 2 t["x"]["b"]["a"] = 2 print tree_as_dict(t) # {'x': {'y': {'a': 2, 'z': 1}, 'a': {'b': 2}, 'b': {'a': 2}}}
see also
one-line tree in python ― Gist https://gist.github.com/2012250
dictをparseするコード書いた。
install
$ easy_install tinyschema
demo
from tinyschema.schema import Schema from tinyschema import fields class Person(object): def __init__(self, name=None, age=None): self.name = name self.age = age class PersonSchema(Schema): name = fields.string() age = fields.integer() class SuperPersonSchema(PersonSchema): skill = fields.noduplicated() class ParentsSchema(Schema): mother = PersonSchema(after_parse= lambda kwargs : Person(**kwargs)) father = PersonSchema(after_parse= lambda kwargs : Person(**kwargs)) print PersonSchema.parse({"name": "foo", "age": "10"}) print SuperPersonSchema.parse({"name": "foo", "age": "10", "skill": ["foo", "bar", "baz", "foo", "bar"]}) params = {"mother": {"name": "foo", "age": "30"}, "father": {"name": "bar", "age": "30"}, } print ParentsSchema.parse(params) # {'age': 10, 'name': 'foo'} # {'skill': set(['baz', 'foo', 'bar']), 'age': 10, 'name': 'foo'} # {'father': <__main__.Person object at 0x7fd3e0693ad0>, 'mother': <__main__.Person object at 0x7fd3e0693b10>}
追記
## object schema from tinyschema.schema import ObjectSchema class PersonSchema(ObjectSchema): name = fields.string() age = fields.integer() print PersonSchema.parse({"name": "foo", "age": "10"}) # <class '__main__.PersonSchema'>: ['age', 'name'] print PersonSchema.parse({"name": "foo", "age": "10"}).name # foo print PersonSchema.parse({"name": "foo", "age": "10"}).age # 10 from tinyschema import schema ParentsSchema = schema.schema_factory("ParentsSchema", ObjectSchema, mother=PersonSchema(), father=PersonSchema()) print ParentsSchema.parse(params) # <class 'tinyschema.schema.ParentsSchema'>: ['father', 'mother']
ubuntuでsolr動かしてみる。
install
# $ sudo apt-get install openjdk-6-jdk
$ sudo apt-get install solr-common solr-jetty
jettyの設定変更
jettyを/etc/init.dから起動できるようにする。ついでにadmin画面にアクセスしてsolrがjetty上で動いているか確認
$ sudoedit /etc/default/jetty # NO_START=0に変える ## jettyを立ち上げる $ sudo /etc/init.d/jetty start ## browserで見てみる(defaultは以下のurl) $ x-www-browser http://localhost:8080/solr
schema.xmlを変更
schema.xmlについて
solrでどのようなフィールドを格納するかの設定を記述するファイル
変更手順
ubuntuのaptでインストールした場合、schema.xmlは/etc/solr/conf/schema.xmlに置かれる。
(これは,/var/lib/dpkg/info/solr*.listの中を見ればわかる。)
schema.xmlは以下のように変更した.
<?xml version="1.0" encoding="UTF-8" ?> <schema name="example" version="1.4"> <types> <!-- The StrField type is not analyzed, but indexed/stored verbatim. --> <fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/> <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/> <fieldType name="text_ja" class="solr.TextField" positionIncrementGap="100"> <analyzer> <tokenizer class="solr.CJKTokenizerFactory"/> </analyzer> </fieldType> </types> <fields> <field name="id" type="string" indexed="true" stored="true" required="true" /> <field name="title" type="text_ja" indexed="true" stored="true" /> <field name="body" type="text_ja" indexed="true" stored="true" /> </fields> <uniqueKey>id</uniqueKey> <defaultSearchField>body</defaultSearchField> <solrQueryParser defaultOperator="AND"/> </schema>
上記の設定は以下のようなフィールドを登録可能にしている。
field name | type | - | |
id | int | id | |
title | text_ja | 日本語の文章タイトル(text_jaはn-gram) | |
body | text_ja | 日本語の文章タイトル(text_jaはn-gram) |
$ sudo /etc/init.d/jetty restart # $ sudo less /var/log/jetty/*.stderrout.logなどを見てエラーが起きていないことを確認する
pythonからsolrの機能を利用。
solrpyを使った。
## install $ easy_install solrpy ## try it $ python >>> import solr >>> s = solr.SolrConnection("http://localhost:8080/solr") >>> s.add(id=1, title=u"ja", body=u"日本語通る?") '<?xml version="1.0" encoding="UTF-8"?>\n<response>\n<lst name="responseHeader"><int name="status">0</int><int name="QTime">5</int></lst>\n</response>\n' >>> s.commit() '<?xml version="1.0" encoding="UTF-8"?>\n<response>\n<lst name="responseHeader"><int name="status">0</int><int name="QTime">41</int></lst>\n</response>\n' >>> s.query(u"body:日本語").results [{u'body': u'\u65e5\u672c\u8a9e\u901a\u308b\uff1f', u'score': 1.8472979, u'id': u'1', u'title': u'ja'}] >>> s.query(u"title:fo").results [] >>> s.query(u"title:ja").results
distributeのextra_commandの作り方
pythonのsetup.pyを使って以下のように実行可能なコマンドを作ってみる
$ python setup.py <command name> <args> ...
setup.py
pythonのパッケージはsetup.pyを持っている。
このsetup.pyは提供されるパッケージに関するコマンドを持っている。
例えば、以下のような機能がある。
- packageをpypiにuploadしたり
- テストランナーを走らせたり
- 配布用のバイナリを作ったり
extra_command
pythonのsetup.pyが提供するコマンドを自分で作成することができる。
利用できるコマンドは以下のようにすると分かる。
# $ ls | grep setup.py # setup.py $ python setup.py --help-command # Standard commands: # ... # # Extra commands: # rotate delete older distributions, keeping N newest files # develop install package in 'development mode' # setopt set an option in setup.cfg or another config file # saveopts save supplied options to setup.cfg or other config file # egg_info create a distribution's .egg-info directory # nosetests Run unit tests using nosetests # alias define a shortcut to invoke one or more commands # easy_install Find/get/install Python packages # bdist_egg create an "egg" distribution # install_egg_info Install an .egg-info directory for the package # test run unit tests after in-place build # # usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] # ...
rotateとかdevelopとか定義されている。
自分でコマンドを追加してみよう
行う手順は以下のとおり
- entry_pointに追加する
- 呼ばれるコマンドを作成
entry_pointに追加する。
setup.pynのetnry_pointに追加する。
今回のディレクトリ構成は以下のとおり。foo/scripts.pyに追加する予定
. ├── foo │ ├── __init__.py │ └── scripts.py ## ここにfoo_commandを作成 │ └── setup.py
setup.pyにentry_pointsを追加.(scripts.pyのfoo_command)
setup( # ... entry_points = { "distutils.commands": [ "foo = foo.scripts:foo_command", ], }, )
commandを作成する
(試しに関数を書いてみる)
# scripts.py def foo_command(*args, **kwargs): pass
このパッケージをインストールしたときfooコマンドが使えるようになれば良い
$ python setup.py develop $ python setup.py --help-commands | grep foo # foo (no description available) ## あった。実行してみる $ python setup.py foo # .. # TypeError: issubclass() arg 1 must be a class
実行できたけど、エラーで落ちた。次は真面目に実装してみる
真面目にコマンドを実装
調べてみると、distributeのextra_commandsは以下の要件を満たしている必要があるらしい。
- setuptools.Commandのsubclass
- run,initialize_options,finalize_optionsを実装
- その他細かな事
- user_optionsでオプショナル引数の指定
- (末尾が=のものは引数をとる)
- command_consumes_arguments=Trueにすると引数を受け取れるようになる。
- user_optionsでオプショナル引数の指定
from setuptools import Command class foo_command(Command): description = "echo foo" ## user_option = [(long, short, help-message), ....] # e.g. # long :: "verbsoe" # short :: "v" # help :: "verbose output" # user_options = [("user=", "u", "echo user")] command_consumes_arguments = True def run(self): print self.args print "user: %s foo" % self.user def initialize_options(self): self.args = [] self.user = None def finalize_options(self): pass
実行可能になった。
$ python setup.py foo -u xxx 1 2 3 4 # running foo # ['1', '2', '3', '4'] # user: xxx foo $ python setup.py foo 1 2 3 4 # running foo # ['1', '2', '3', '4'] # user: None foo
appendix コマンドにaliasを付ける。(setup.cfgでデフォルトの設定を付加する)
ついでに、setup.cfgを書いてdefaultの設定を付加してみることにする。
ここでは、上で作ったfooコマンドの-uの値を"User"に設定してみる。
setup.pyで結びつけたコマンド名をセクションにして設定を記述する。
#setup.cfg [foo] user = "User"
こうすると、python setup.py fooを実行したとき、デフォルトで-u "user"が渡された状態になる。
$ python setup.py foo 1 running foo ['1'] user: "User" foo
MixinについてAdapterについて
pyramidでmixinを使ってコードを書いていた。これは適切なコードの書き方では無かったそう。
もう少し具体的に言うと、component architectureの上では、mixinを使わない方が望ましいらしい。
代わりにadapter*1という機能を使うそう。
adapterのことをちょっとだけ調べてみたらおもしろかったので日記にまとめてみる。
実は。。
adapterの機能を詳しく知らなかった。動きは大まかにはあくしていたのだけれど、何に使えるのか分かっていなかったような感じ。
ここでは、adapterという言葉をZCA(zope component architecture)の中でのadapterに意味を限って使うことにする。
調べる前のadapterの認識は以下の程度の認識だった。
「何か、渡したオブジェクトをメンバーとして持つオブジェクト返してくれる機能」。。
良く見かける例はだいたいこんな感じで終わってる。
- Groupに所属するMemberがいるような感じ
- それぞれIGroup,IMemberを実装している。
- GroupはMemberをオブジェクトのメンバーとして持つ。
adapterは、オブジェクトを渡してあげると、それをオブジェクトのメンバーとしてもつオブジェクトを返してくれるもの。という感じの認識だった。
from zope.interface import implements from zope.interface import Interface from zope.interface import Attribute class IGroup(Interface): pass class Group(object): implements(IGroup) def __init__(self, member): self.member = member class IMember(Interface): name = Attribute("member name") class Member(object): implements(IMember) def __init__(self, name): self.name = name foo = Member("foo") ## from pyramid.registry import global_registry as registry registry.registerAdapter(Group, (IMember, ), IGroup,) print registry.queryAdapter(foo, IGroup) # <__main__.Group object at 0x1240490>
Memberをメンバに持つGroupをAdapterを通して生成できた。
GroupはIGroupの実装でIMemberが必要。
動きはおえるのだけど、使い道が分からない。。
mixinについて
一方で、mixinはわかりやすい。複数の機能を持ったオブジェクトを作成するときに、機能ごとにブロックで区切って実装すること。
ただし、コンストラクタを作成しない。
だいたいこんな感じ。
class MeasureSkillMixin(object): def has_empty_area(self, target): return True class SaveSkillMixin(object): def save(self, target): return "ok" class DownLoader(MeasureSkillMixin, SaveSkillMixin): def download(self, target): if self.has_empty_area(target): return self.save(target) dlr = DownLoader() print dlr.download("anything") # => ok
余裕があるか調べる機能と保存する機能を分けて定義。Downloaderでmixinしてる。
mixinが解決してくれること
mixinに分けることで、細かく機能を追加できるようになった。
そして各mixinが提供する機能が直交してるなら、それぞれを個別につなぎあわせて便利なオブジェクトを作ることができる。
また、ポリシー毎にmixinを用意して、そのポリシーを利用するオブジェクトには、mixinで機能を付加させることで、
綺麗な形で複数のオブジェクト間の関係を保つことができる。
mixinが解決してくれないもの
- 異なるポリシーを持つオブジェクト間の機能の共有。
どういうことか少しだけ説明。
今自分たちが作っているプロジェクトでは、容量に余裕が計測が必要クラスについて、
実際に作業(saveなど)を行う前に、has_empty_areaを実行して、作業可能かどうか判断する
というのポリシーとして持っているとする。
そのために
- MeasureableみたいなInterfaceを用意している
- MeasureSkillMixinみたいなその機能を提供してくれるMixinを用意している。
- 提供されているMixinを利用して各開発者は機能を実装していく。
ここで、このポリシーが守られている場合は、一定の秩序を保つことができる。
ここで、別のプロジェクトで使われていた巨大なライブラリ/システムを組込むことになった。
別のプロジェクトでは、上述したポリシーに基づいて設計されていることは期待できない。
例えば、これら複数のポリシーを持ついろいろなオブジェクトに対して、作業の実施コストを尋ねるような処理を書きたいとする。
こういう状況下におかれてるとする、さてどうしよう?
mixinの場合は、結局、実装をどちらかに寄せるということになると思う。
現在のプロジェクトに参加する全てのオブジェクトは、統一的なインターフェイスになるように新たにラッパーがたくさん作られることになると思う。
各オブジェクトに統一的な振る舞いを要求する感じなので、トップダウンの父権的な感じ。
具体例
もう少し具体的に書いてみる。
例えば、大きさ(size)を計算する必要があるものについて、2つのポリシーを持つもの同士を混ぜ合わせて利用したいとき。
- 一方は大きさをsizeとして扱っている
- 一方は大きさをlengthとして扱っている。
Mixinの例
例えば、全てをsizeとして扱うようにして、obj.lengthでアクセスするオブジェクトをラップするようなクラスを作って使う。
でも、どこかで明示的にラッパークラスで包んだりしないといけない。
#Aproject from zope.interface import Interface from zope.interface import Attribute class IAProjectObject(Interface): size = Attribute("size of object") class IBProjectObject(Interface): length = Attribute("length of object") class HasSizeDecorator(object): """ a object that obj.length object treats as obj.size object. """ implements(IAProjectObject) def __init__(self, has_length): self.obj = has_length @property def size(self): return self.obj.length @property def __getattr__(self, k): return getattr(self, k) ## とかUtilityをつくる.(条件が複雑になると死ねる?かも?) def size(o): return o.size if hasattr(o, "size") else o.length
結局、どちらがわに実装を寄せるとか、実際大きさを調べたい時適宜使い分けるなどする。
Adapterを使った場合はどうするか。
サイズが欲しいので、sizeという概念で区切る。ここではISizeということにする。
そういうインターフェイスを実装するクラスを作ってあげる。
作ったクラスを登録してあげる。元のクラスにはまったく手を加えない。
実際に利用する際には、オブジェクトがAprojectのものなのか。Bprojectのものなのか考える必要はなくなる。
利用する際に考えるのは「サイズが欲しい」だけなので。
from zope.interface.registry import Components component = Components("my") class ASize(object): implements(ISize) def __init__(self, a): self.size = a.size self.obj = a class BSize(object): implements(ISize) def __init__(self, b): self.size = b.length self.obj = b component.registerAdapter(BSize, (IBProjectObject, ), ISize) component.registerAdapter(ASize, (IAProjectObject, ), ISize) print component.queryAdapter(B(10), ISize).size #10 print component.queryAdapter(A(10), ISize).size #10
おもしろい。
機能を合成したり一ヶ所にまとめる瞬間が存在しなくて、緩やかな関係が裏側に存在するような感じ。
*1:ZCAの中の話。adapterパターンとかと関連があるかは良く分かんない。ごめんなさい。
virtualenv上のpythonをquickrunで呼ぶ
quickrun
現在のバッファの内容をファイル名に結びついた言語のコマンドで実行してくれる(e.g. foo.pyならpython)
https://github.com/syohex/emacs-quickrun
motivate
pythonを書くときには、virtualenvでプロジェクトごとに環境を分けている。
なので以下のようなことがquickrunでもできたら良いなと思った。
- ~/venvs/poject1/foo/bar.pyでは~/venvs/project1/bin/pythonを利用して欲しい。
- ~/venvs/poject2/boo/boo.pyでは~/venvs/project2/bin/pythonを利用して欲しい。
一方で、quickrunのpythonの設定を見ると、:command "python"とpython決め打ちだったのであまり良くない。
how to solve it
実際にコードを読んでみたら、`quickrun/eval-paramater'が期待どおりの実装になっていた。
どういうことかというと、:commandに関数を渡せる。(渡した場合渡された関数を評価した結果が使われる)
こういうことができる。*1
("python" . ((:command . (lambda () (print "hey") "python")) (:compile-only . "pyflakes %s") (:description . "Run Python script")))
e.g.
以下のような場合でもM-x quickrunが動作するようになった。
以下のような場合
普通のpythonだとモジュールが足りなくて実行できないような例。virtualenv上のpythonだと実行できる。
$ pwd /home/podhmo/.virtualenvs/my/sandbox $ python joinmodel.py Traceback (most recent call last): File "joinmodel.py", line 1, in <module> import sqlahelper ImportError: No module named sqlahelper $ ~/.virtualenvs/my/bin/python joinmodel.py name Person.name foo age Person.age 20 skill Person.skill fire id Person.id None
*1:もちろん表示されるheyには何の意味もない
テーブルをjoinした結果をモデルにmappingできる。
__table__にsqlalchemy.orm.joinした結果を代入すると、複数のテーブルをjoinした結果をmappingしたモデルが作れる。
(idのconflictを避けるためにorm.column_propertyを使うことが多い)
import sqlahelper import sqlalchemy as sa import sqlalchemy.orm as orm Base = sqlahelper.get_base() DBSession = sqlahelper.get_session() person_skillset = sa.Table( "person_skill",Base.metadata, sa.Column("id",sa.Integer,primary_key=True), sa.Column("skill",sa.String(255)) #in real, this is foreignkey ) person_information = sa.Table( "person_info",Base.metadata, sa.Column("id",sa.Integer,primary_key=True), sa.Column("name",sa.String(255)), sa.Column("age",sa.Integer)) class Person(Base): __table__= orm.join(person_skillset,person_information, person_skillset.c.id==person_information.c.id) id = orm.column_property(person_skillset.c.id, person_information.c.id)
利用例
engine = sa.create_engine("sqlite://") sqlahelper.add_engine(engine) Base.metadata.create_all() def _column_iter(model, obj): from sqlalchemy.sql.operators import ColumnOperators for k, v in model.__dict__.items(): if isinstance(v, ColumnOperators): yield k, v, getattr(obj, k, None) for k,f,v in _column_iter(Person,Person(name="foo",age=20,skill="fire")): print k,f,v # name Person.name foo # age Person.age 20 # skill Person.skill fire # id Person.id None