Read-onlyなclasspropertyデコレータの実装
class classproperty(property): def __init__(self, fget): super(classproperty, self).__init__(fget) self.__doc__ = fget.__doc__ def __get__(desc, self, cls): return desc.fget(cls) class A(object): @classproperty def __tablename__(cls): return cls.__name__.lower() class B(A): pass print(A.__tablename__) print(B.__tablename__)
ターミナルから起動するプログラムについて
catプログラムを起動する
例えば、ファイルの中身を見るcatというプログラムを起動してみよう。
ファイル名を引数にとって、ファイルの内容を出力する。
$ cat hello.txt hello, world!!
プログラムの場所
プログラムはどこかになくてはならない。
catはどこにあるのだろうか。
whichプログラムで確認できる。
$ which cat /bin/cat
/bin/catにあるらしい。
プログラムの種類
どういうファイルなのだろうか。
fileプログラムでファイルの概要はわかる。
$ file /bin/cat
/bin/cat: Mach-O 64-bit executable x86_64
コンパイルされたバイナリファイルのようだ。
内容は人間が読んで簡単にわかるものではない。
バイナリファイルというのはこのコンピュータのために作られた、
このコンピュータでしか動かないファイルだ。
だからこそ、このコンピュータは実行のしかたを知っている。
しかし、例えばこの/bin/catファイルをWindowsにコピーして実行してみよう。
Windowsは実行のしかたを知らないのでエラーが出る。
次いでpipプログラムをみてみよう。
$ which pip $ file /usr/local/bin/pip # file `which pip`でも同じ /usr/local/bin/pip: a /usr/local/opt/python/bin/pytho script text executable
script textと表示された。
テキストとバイナリ
pipの中身をみてみよう。
$ head `which pip` #!/usr/local/opt/python/bin/python2.7 # EASY-INSTALL-ENTRY-SCRIPT: 'pip==7.1.2','console_scripts','pip' __requires__ = 'pip==7.1.2' import sys from pkg_resources import load_entry_point if __name__ == '__main__': sys.exit( load_entry_point('pip==7.1.2', 'console_scripts', 'pip')() )
ただのテキストファイルだった。
さきほどのcatのように、このコンピュータ用に作られたバイナリファイルではない。
この内容は人間には読めるけれど、コンピュータにはわからないだろう。
ではpip
プログラムはなぜ動くのだろう?
答えは、テキストファイルの一行目で、
このテキストファイルを解釈できるプログラムを指定しているからだ。
#!/usr/local/opt/python/bin/python2.7
「このテキストはpython2.7というプログラムが解釈し実行するものだ」ということだ。
なのでpip
が実行されるとき、実際には以下のコマンドが実行されているのだ。
$ /usr/local/opt/python/bin/python2.7 pip
このpython2.7というファイルは何者だろうか。
$ file /usr/local/opt/python/bin/python2.7 /usr/local/opt/python/bin/python2.7: Mach-O universal binary with 2 architectures
2つのアーキテクチャに対応していると書かれているが、
基本はこのコンピュータために作られたバイナリファイルだ。
catとpythonの違いはなんだろうか。
それは、catが与えたファイルの内容をそのまま吐き出すのに対し、
pythonは与えたファイルの内容によって動作が全く異なるということだ。
サブプロセスを立ち上げたり、Webサーバーになったりもする。
これは驚くべきことだ。
このようなバイナリファイルをインタプリタ(解釈器・翻訳者)という。
pipを解釈できるのは何もpythonだけではない。
$ php /usr/local/bin/pip
結果は実行してみてのお楽しみ。
プログラムのパス
cat
の場所をわざわざwhich cat
で確認したけど、
どうして/bin/catだけでなくcatでも実行できるのだろうか?
実はcatというコマンドを実行したとき、
最初にcat
という名前に一致したプログラムが実行される。
/bin/というディレクトリも探すディレクトリリストの中に含まれているのだ。
":"で区切られて格納されている。
PATHの内容は以下のコマンドで確認できる。
echo $PATH
これでは見にくいので以下のコマンドでディレクトリが1行ずつ表示される。
$ echo $PATH | python -c 'for line in raw_input().split(":"):print(line)'
これで列挙されるディレクトリにcatがなければ見つけてはもらえない。
プログラムをコマンドとして登録する
/Users/chyka/my_programをmy_program
だけで起動できるようにしてみる。
2つ方法がある。
方法1:my_programがあるディレクトリをPATHに追加する
以下のコマンドで/Users/chykaディレクトリを検索対象にすることができる。
export PATH="${PATH}:/Users/chyka"
このコマンドの意味はスクリプト言語風に書くと、
PATH = PATH + ":/Users/chyka"
だ。
my_program
コマンドが使えるようになっているはずだ。
方法2:検索対象のディレクトリのどれかに追加する
$ echo $PATH | python -c 'for line in raw_input().split(":"):print(line)' ... /usr/local/bin ...
PATHの中にディレクトリならどこでもいいが、
/usr/local/binというディレクトリが含まれているようだ。
ここにmy_programをコピーするとmy_program
コマンドが使えるようになる。
ちなみにシンボリックリンクというものがあり、
わざわざファイルをコピーしなくてもそのディレクトリ以下に存在するかのようにリンクを貼ることができる。
Windowsでいうとショートカットみたいなものだ。
sudo ln -s /Users/chyka/my_program /usr/local/bin/my_program
OCaml getting started
Install
On ubuntu
Prerequisite on 15.04
sudo apt-get install curl build-essential m4 zlib1g-dev libssl-dev ocaml ocaml-native-compilers opam
Prerequisite on 14.04
sudo add-apt-repository ppa:avsm/ppa
sudo apt-get update
sudo apt-get install ocaml ocaml-native-compilers camlp4-extra opam
Setup OCaml
opam init eval `opam config env` opam switch 4.02.3 eval `opam config env` opam install core utop
Append the snippet to ~/.ocamllist
#use "topfind";; #thread;; #camlp4o;; #require "core.top";; #require "core.syntax";; open Core.Std
On Mac
brew install ocaml opam
Run
eval `opam config env` utop
Syntax
list and tuple
list
Elements of list are separated by ;
tuple
Elements of list are separated by ,
Thus, a list of tuples are represented as below
["tuple_1_el_1", "tuple_1_el_2"; "tuple_2_el_1, "tuple_2_el_2"]
SQLAlchemyでリレーション
以下のサンプルでは、
各クラスについてidやテーブル名を定義するのが面倒なのでベースクラスを用意する。
from sqlalchemy.ext.declarative import declarative_base, declared_attr Base = declarative_base() class BaseModel(Base): # 継承してね __abstract__ = True # idはすべてのクラスで使うので id = Column(Integer, primary_key=True) # 例えば、__tablename__ = "user" と同じ @declared_attr def __tablename__(cls): return cls.__name__.lower()
多対一
ユーザーが複数の日記をもっているリレーションの例。
class User(BaseModel): name = Column(String) class Dialy(BaseModel): # ForeignKeyはデータベースレベルでのみリレーションを定義する。 user_id = Column(Integer, ForeignKey("user.id"), nullable=False) title = Column(String) # オブジェクトレベルでのリレーションを張る # order_by はクラス名から書かないと組み込み関数idとかいわれる。 user = relationship(User, backref=backref("dialies"), order_by='User.id') def test_many2one(): engine = create_engine("sqlite:///:memory:", echo=True) BaseModel.metadata.create_all(engine) Session = sessionmaker(bind=engine) sess = Session() user = User(name="hoge") user2 = User(name="foo") sess.add(user) sess.add(user2) sess.commit() d = Dialy(user_id=user.id, title="first") sess.add(d) user.name = "fuga" sess.commit() for instance in sess.query(Dialy).all(): print(instance.title, instance.user.name)
一対一
ユーザーが専用のプロフィールをもっている例。
class Profile(BaseModel): user_id = Column(Integer, ForeignKey("user.id"), nullable=False) age = Column(Integer) # ProfileからもUserからもuselist=Falseを使うことがポイント user = relationship(User, uselist=False, backref=backref("profile", uselist=False)) def test_one2one(): engine = create_engine("sqlite:///:memory:", echo=True) BaseModel.metadata.create_all(engine) Session = sessionmaker(bind=engine) sess = Session() user = User(name="hoge") sess.add(user) sess.commit() ## コミットするとidプロパティが暗黙的に定義される p = Profile(age=50, user_id=user.id) sess.add(p) sess.commit() for u, p in sess.query(User, Profile).join(Profile, User.id == Profile.user_id).all(): print(p.user.name, p.user.profile.age)
多対多
ユーザーと行ったことがある国の関係。
user_country_table = Table("user_country", Base.metadata, Column("user_id", Integer, ForeignKey("user.id")), Column("country_id", Integer, ForeignKey("country.id")), ) class Country(BaseModel): name = Column(String) users = relationship("User", secondary=user_country_table, backref="countries") def test_many2many(): engine = create_engine("sqlite:///:memory:", echo=True) BaseModel.metadata.create_all(engine) Session = sessionmaker(bind=engine) sess = Session() user = User(name="hoge") user2 = User(name="foo") c1 = Country(name="Japan") c2 = Country(name="Russia") user.countries.extend([c1, c2]) user2.countries.extend([c1, c2]) sess.add(user) sess.add(user2) sess.commit() for u, c in sess.query(User, Country).join("countries").all(): print(u.name, c.name)
その他Tips
relation
は内部でrelationship
を呼んでいるので、
relationship
を直接呼ぶべし。
orm/init.py:112
def relation(*arg, **kw): """A synonym for :func:`relationship`.""" return relationship(*arg, **kw)
PySideでスクリーンサイズを取得する
QApplication
オブジェクトのdesktop
メソッドで、
QDesktopWidget
オブジェクトが取得できる。
このオブジェクトからデスクトップの情報を取得することができる。
以下のサンプルコードでは、
例としてデスクトップの左半分にウィンドウを表示している。
import sys from PySide import QtCore, QtGui app = QtGui.QApplication(sys.argv) desktop = app.desktop() height = desktop.height() width = desktop.width() window = QtGui.QMainWindow() window.setGeometry(0, 0, width/2, height) window.show() sys.exit(app.exec_())
SQLAlchemyでサブクエリ
問題
SQLAlchemyから集約関数sum
を使ったとき、
同じ列を重複してカウントしてしまった。
以下のようなSQLを実現したい。
sqlite> select l.lemma, lf.total, t.token, tf.total from lemma l join ( select lemma_id, sum(count) as total from lemma_in_file where file_id in (1,2) group by lemma_id ) lf on l.id == lf.lemma_id join token t on l.id == t.lemma_id join ( select token_id, sum(count) as total from token_in_file where file_id in (1,2) group by token_id ) tf on t.id == tf.token_id order by lf.count desc limit 10;
理解を助けるために、このSQLについて少しだけ補足する。
tokenというのは、テキストに出てくる語そのもののことで、
英語でいうとstudies
, study
など。
lemmaというのは、辞書に載っている語の形で、
英語でいうとstudy
。
tokenはlemmaの変種にすぎないので、1対多のリレーションが貼られている。
lemma token study -- study | |- studies
token, lemmaそれぞれのファイル内での出現数を記録しているテーブルが{token, lemma}_in_fileだ。
長いので、lf
, tf
とエイリアスをつけている。
取り出したい情報は、
l.lemma, lf.total, t.token, tf.total
totalというのはエイリアスで、
{token, lemma}.countが1ファイルの中の出現数であるのに対し、
複数ファイル中での出現数をsumで合計したものだ。
studyの例えでいうと、欲しい結果は以下のようなものだ。
l.lemma lf.total t.token tf.total study 50 study 25 study 50 studies 10 study 50 studied 10 study 50 studying 5
SQL文中で2回サブクエリを使っているのは、
sumの対象で、同じ列が複数回出現して重複してカウントするのを避けるだめだ。
あらかじめサブクエリ内で集約関数を使ったあとにjoin
している。
これをSQLAlchemyでやろうとしてちょっと手こずった。
解法
使うモジュールのインポート
まず今回使うものたちをimport。
from sqlalchemy.sql import func from sqlalchemy.sql.expression import desc from sqlalchemy.orm import aliased
サブクエリの構築
サブクエリから組み立てていく。
lf_subquery = session.query( LemmaInFile.lemma_id, func.sum(LemmaInFile.count).label("total") ).filter( LemmaInFile.file_id.in_([1,2]) ).group_by( LemmaInFile.lemma_id ).subquery("lf")
query
メソッドにこのサブクエリに期待するカラムを指定する。
lemmaのidと、それのファイル1, 2中の合計出現数total。
labelでSUM(count) AS total
のようにエイリアスをつけることができる。
サブクエリ内の値は外からアクセスしやすい必要があるのでこれは重要。
subquery
メソッドでクエリ全体をサブクエリとしてエイリアスをつけることができる。
(SELECT ... FROM ...) lf
token側も同様にサブクエリを構築する。
tf_subquery = session.query( TokenInFile.token_id, func.sum(TokenInFile.count).label("total") ).filter( TokenInFile.file_id.in_([1,2]) ).group_by( TokenInFile.token_id ).subquery("tf")
エイリアスを変数に束縛
lf = aliased(LemmaInFile, lf_subquery) tf = aliased(TokenInFile, tf_subquery)
これでlf
, tf
が使える。
クエリの構築
上で作ったサブクエリをjoin
していく。
query = session.query( Lemma.lemma, Lemma.pos, "lf.total", Token.token, "tf.total" ).join( lf, Lemma.id == lf.lemma_id ).join( Token, Token.lemma_id == Lemma.id ).join( tf, Token.id == tf.token_id ).order_by( desc("lf.total") )
SELECT
(query()
)には、Lemma, lf, Token, tf がでてくるので、
それらをjoin
する必要がある。
左から順番にjoin
している。
第一引数がテーブル(かサブクエリ)で、第二引数がjoinの条件だ。
そして最後にlemmaの出現数降順でソートしている。
まとめ
SQLが透けてみえるのがSQLAlchemyのよいところだ。
適宜生のSQL文を埋め込むことができる。
課題
lf.totalというアトリビュートが自動で作られるわけではないらしい。
CentOS6.5にPython3.5をインストール
依存しているライブラリを事前にインストールしておく。
sudo yum install tcl-devel tk-devel gdbm-devel openssl-devel sqlite-devel wget cd /usr/local/src wget https://www.python.org/ftp/python/3.5.0/Python-3.5.0.tar.xz tar Jxf Python-3.5.0.tar.xz cd Python-3.5.0 ./configure --prefix=/usr/local/python35 make sudo make install