読者です 読者をやめる 読者になる 読者になる

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というインタプリタバイナリでも解釈できる。

$ php /usr/local/bin/pip

結果は実行してみてのお楽しみ。

プログラムのパス

catの場所をわざわざwhich catで確認したけど、

どうして/bin/catだけでなくcatでも実行できるのだろうか?

実はcatというコマンドを実行したとき、

あるディレクトリリストを順番に探して、

最初にcatという名前に一致したプログラムが実行される。

/bin/というディレクトリも探すディレクトリリストの中に含まれているのだ。

探すディレクトリはPATHという環境変数に、

":"で区切られて格納されている。

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のようにエイリアスをつけることができる。

サブクエリ内の値は外からアクセスしやすい必要があるのでこれは重要。

Column.label

subqueryメソッドでクエリ全体をサブクエリとしてエイリアスをつけることができる。

(SELECT ... FROM ...) lf

Query.subquery

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が使える。

sqlalchemy.orm.aliased

クエリの構築

上で作ったサブクエリを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の条件だ。

Query.join

そして最後にlemmaの出現数降順でソートしている。

まとめ

SQLが透けてみえるのがSQLAlchemyのよいところだ。

SQLを知っていれば柔軟にメソッドチェーンを使えるし、

適宜生のSQL文を埋め込むことができる。

課題

lf.totalというアトリビュートが自動で作られるわけではないらしい。

queryメソッド内、desc関数内で、"lf.total"などとSQLをじかに書いている。

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