2016/06/14

Day XXX: schema の使い方

{docopt} は python で作成したコマンドの引数・オプションのパースをするものだが、それらの値が想定した値かを調べることは出来ない。
これを行うのが {schema} である。
使い方は PyPI の schema のページに記載されている。
前回と同様に arith4 という sandbox パッケージ上で実装してみる。
arith4/arith4_command.py...(snip)...
from schema import Use, And, Or, Schema, SchemaError

arith4_command_schema = {
 '--head':
  Or(None,
     And(Use(int),
         lambda n: n > 0,
         error="--head <num>: 正の整数でなければなりません")),
 '--tail':
  Or(None,
     And(Use(int, error="--tail <num>: 整数でなければなりません"),
         lambda n: n > 0,
         error="--tail <num>: 正の値でなければなりません")),
 '<val>': [
  Use(int, error="values <val>: 整数でなければなりません")
 ],
 '<file>': [
  And(str, len,
      os.path.exists, os.path.isfile, lambda f: os.access(f, os.R_OK),
      error="files <file>: ファイルが読み込めません")
 ],
 str: object,
}

def arith4_command():
 try:
  args = docopt(__doc__, argv=None, help=True, version=arith4.__version__, options_first=False)
 except DocoptExit as e:
...(snip)...
 try:
  args = Schema(arith4_command_schema).validate(args)
 except SchemaError as e:
#  print(e.autos, file=sys.stderr)
  sys.exit(e.code)
...(snip)...
Schema はチェックすべき項目を辞書にしたもの(ここではスキーマ辞書と呼ぶことにする)を引数に持ち、Schema.validate() 関数に与えた辞書(チェック対象辞書)の値をチェックする。
{docopt} は引数をパースした結果を辞書の形で出力するので、docopt.docopt() の返り値をそのまま Schema.validate() に与えれば良く、非常に相性が良い。

上記の例におけるスキーマ辞書は arith4_command_schema である。
チェック対象である arith4_command の場合、変数を伴う引数・オプションは '--head', '--tail' ,'<val>', '<file>' の4つ(それぞれ docopt が出力する辞書の key に対応)であるため、その4つのみのスキーマを実装している。
(スキーマ辞書におけるエントリをここではスキーマと呼ぶことにする)

スキーマの書式は以下の通り。
  • int, str などの型:key もしくは value がその型であるかチェック
  • 文字列:key もしくは value がその文字列と一致するかチェック
  • タプル・リスト・セット:key もしくは value がその中に含まれているかチェック
  • Schema.Use(型):文字列が型にキャストできるかチェック
             Schema.Use(int) なら整数にキャストできるかチェックする
             Schema.validate() 後は実際にその型にキャストして出力される
  • 任意の(lambda)関数:関数の返り値が正の値、もしくは True であるかチェック
  • Schema.And(A, B):A かつ B
  • Schema.Or(A, B):A もしくは B
docopt.docopt() は key も value も文字列で出力されるため、整数(もしくは実数など)かどうかをチェックするためには Schema.Use(int) を用いる。
Schema.validate() の返り値は自動的にその型にキャストされるため、自分でキャストする必要がなく便利である。
例えば value にファイル名が格納されており、open() によってファイルが open できるかチェックしたい場合、Schema.validate() の返り値はファイル名ではなくファイルハンドルが格納される。
<val>... など value が配列であるものは、チェックする式をリストにする。リストが空の場合はチェックがスキップされる。
Schema.Use() など Schema の関数は error のオプションを与えて、エラー時のメッセージを設定できる。

チェックがエラーになった場合、SchemaError が送出される。
SchemaError.autos は Schema 側で自動的に作成されたエラーメッセージで、実際にエラーした変数が表示される。
error オプションにより自分で設定したエラーメッセージは print(SchemaError.errors) もしくは sys.exit(SchemaError.code) とすることで表示される。

チェックの必要がない変数については、Schema.validate() は各変数についてスキーマ辞書の最初のスキーマから順に評価をしていくため、str:object というスキーマを最後に追加することによって「keyが str型である」「value が object 型」であるという全ての変数に合致するスキーマをの評価を行うことになり、スキーマのチェックが実質的にスキップされる。
もちろん各変数について '--max':bool などのスキーマを設定しても良いし、str:Or(bool, None) も(docopt.docopt() の帰り値ならば)正しい実装だろう。
一般的な辞書について必ず通過するスキーマは object:object である。

docopt.docopt() の返り値ではない、一般的な辞書をスキーマ辞書で評価するために、{Schema} には以下の実装が含まれている。
  • Schema.Optional(A):A となる key が存在する場合にのみ評価する
               docopt の場合、全引数・オプションが返り値に含まれるため、不要
  • Schema.Optional(A, default=B):A となる key が存在しない場合、A:B というエントリを追加する
  • Schema(スキーマ辞書, ignore_extra_keys=True):スキーマ辞書に含まれない key がチェック対象辞書に存在した場合、それは返り値には含まれない
Schema.Optional() が存在することによって、任意のクラス・関数の引数に対して {Schema} が対応できるようになっていると考えられる。
(ただし、{docopt} は任意のクラス・関数に対応してない)

2016/06/12

Day XXX: docopt の使い方

{docopt} は docstring(__doc__)からヘルプ作成や引数のパースを自動的に行うパッケージである。
PyPIのdocoptページに使い方は記載されており、Try docoptでテストもできる。
通常は何らかのソースコードからパーサを作成しドキュメントを生成する流れで考えてしまいがちだが、逆にドキュメントからパーサを作成するというコロンブスの卵的な発想から生まれたパッケージである。
ドキュメントと実際の実装を確実に同期することができるという意味で、非常に強力なパッケージであると考える。
使い方は基本的に docstring を書くだけである。
前回と同様に arith4 という sandbox パッケージ上で実装してみる。
arith4/arith4_command.py#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
pythonパッケージの使い方を練習するための簡単な四則演算コマンド

Usage:
  arith4_command [--max|--min|--plus|--times] [--head <num>|--tail <num>] (values <val> [<val>...]|files <file> [<file>...])
  arith4_command -h|--help
  arith4_command -v|--version

Arguments:
  values <val> [<val>...]   値を引数として直接渡す場合
  files <file> [<file>...]  値を1行ひとつの値が書かれたファイルで渡す場合

Options:
  --max                     与えられた値の最大値を返す(デフォルト)
  --min                     与えられた値の最小値を返す
  --plus                    与えられた値を全て足した値を返す
  --times                   与えられた値を全て掛けた値を返す
  --head <num>              与えられた値の先頭<num>個を対象とする
  --tail <num>              与えられた値の最後<num>個を対象とする
  -h,--help                 ヘルプを表示
  -v,--version              バージョン番号を表示
"""

import os
import sys
from docopt import docopt, DocoptExit

...(snip)...

def arith4_command():
    try:
        args = docopt(__doc__, argv=None, help=True, version=arith4.__version__, options_first=False)
    except DocoptExit as e:
        print("Invalid arguments or options", file=sys.stderr)
        print(e, file=sys.stderr)
        sys.exit(1)
    except SystemExit:
        sys.exit(0)
here documentのに、ライプラリやコマンドの説明、ライブラリやコマンドの Usage、必須の引数である Arguments、任意のオプションである Options を書く。
これらの順番は任意のもので構わない。
ただし、Usage, Arguments, Options の先頭にはぞれぞれ「Usage:」「Arguments:」「Options:」の行を入れなければならない。
「Usage:」「Arguments:」「Options:」の大文字小文字は問わない。
「Usage:」「Arguments:」「Options:」は here document の先頭か、それらの前に1行開けないと、正しく判別されない。

Usageの書式は以下である。
  • 1文字のオプション名は '-'、複数文字のオプション名は '--' が文字列の前に来る
  • [] で囲まれたものは、必須ではないオプション
  • () で囲まれたものは、必須である引数をグループ化したもの
  • | は or の意味
  • <> で囲まれたものは、その位置(特定の引数・オプションの後)によって取り込まれる変数
  • ハイフンが前に無く、<>で囲まれてもいない文字列は、そのままの文字列が与えられることが必要とされている引数
  • <x>... と後にピリオドが3個付いているものは、その変数の0個以上の繰り返しを表す
上記の例の Usage は以下の意味である。
  • arith4_command は--max, --min, --plus, --times のどれかのオプションを受け取ることが出来る
    • これらのうちどれかひとつであり、これらをどれも指定しなくても良いが、複数を指定することはできない
  • arith4_command は --head もしくは --tail どれかのオプションを受け取ることが出来る
    • --head も --tail も、その後に変数をひとつ指定しなければならない
  • arith4_command は、values もしくは files という文字列の引数を指定することが必須であり、その後にひとつ以上の変数を指定しなければならない
    • values <val>... であると、... は変数の0個以上の繰り返しであるため、変数<val>を全く指定しなくても良いとなってしまう
    • <val> <val>... と指定することで、1個以上の繰り返しを指定できる
    • <val>... も <val> <val>... も、与えられた変数は同じ配列に格納される
Usage は複数行に書くことができ、複雑なパターンにも対応することができる。
上記の例ではヘルプとバージョンを別の行に記したが、() にまとめた values と files を分けて、以下の様に書くことも出来る。
arith4/arith4_command.py
"""
pythonパッケージの使い方を練習するための簡単な四則演算コマンド

Usage:
  arith4_command [--max|--min|--plus|--times] [--head <num>|--tail <num>] values <val> [<val>...]
  arith4_command [--max|--min|--plus|--times] [--head <num>|--tail <num>] files <file> [<file>...]
  arith4_command -h|--help
  arith4_command -v|--version

Arguments:
...(snip)...
"""
こうして作成した here document を元に、引数をパースする関数が docopt() である。
docopt()のAPIargs = docopt(__doc__, argv=None, help=True, version=arith4.__version__, options_first=False)
ひとつ目の引数がdocoptに対応するように書かれた文字列(この場合は here document)である。以降はオプション。
  • docopt はデフォルトで sys.argv[1:] に相当するものをパースするが、argv に指定したものをパースさせることもできる
    デフォルトは None(=sys.argv[1:]をパースする)
  • help はヘルプを表示させるか否か。
    デフォルトで True
  • versionは文字列を指定して、-v | --version オプションで表示させるもの
    デフォルトは None(=何も表示しない)
  • options_first は、True にすると、ハイフン無しの(文字列がそのまま指定されることが望まれている)オプション以降に、ハイフン付きのオプションを付けても、それは無視されるという POSIX 仕様に基づくもの
    デフォルトは False(=順番は関係ない)
    • 例えば arith4_command --head 2 values 1 2 3 4 5 --plus という呼び出しをして、options_first = True の場合、'--head 2' は有効であるが '--plus' は無視される
docopt() のエラーである DocoptExit はパースした結果が docopt のフォーマット(この場合 here document)と不一致の場合に送出され、Usage の内容が格納されている。
また、SystemExit は --help もしくは --version の場合に送出される。(なので、コマンドの場合は except で catch しなくても構わない)

docopt() はパースした結果を辞書の形で出力する。
例えば、arith4_command --plus --tail 2 values 1 2 4 5 6 という呼び出しをした場合、帰り値(args)は以下のようになる。
arith4_command --plus --tail 2 values 1 2 4 5 6 の場合の args{
  '--head': None,
  '--help': False,
  '--max': False,
  '--min': False,
  '--plus': True,
  '--tail': '2',
  '--times': False,
  '--version': False,
  '<file>': [],
  '<val>': ['1', '2', '4', '5', '6'],
  'files': False,
  'values': True
}
  • 引数および変数を伴わないオプションは、その名前を key として value は bool(True or False)となる
  • 変数を伴うオプションとその変数は、オプションの名前を key とし、value は変数の文字列となる
    • 指定されなかった場合は None になる
  • 引数に対する変数、もしくは引数を伴わず変数のみを指定されている場合は、<>付きの名前を key とする value に格納される
  • 複数指定されている変数は自動的に配列の形に格納される
  • 指定されていようがいまいが、全ての引数・オプション(と引数を伴わない変数)が辞書の key に登録されている

2016/06/09

Day XXX: setuptools の使い方

python パッケージの作成、および他のパッケージの依存関係などを管理するには、{setuptools} を使えば簡単に実現できる。
{setuptools} は Homebrew で python をインストールすると自動的にインストールされているパッケージである。
設定方法は、パッケージの root directory に setup.py を作成するだけである。
練習用に arith4 という sandbox package を作成した。
setup.py#!/usr/bin/env python
# -*- coding:utf-8 -*-

from setuptools import setup, find_packages
import arith4

def setup_package():
 metadata = dict()
 metadata['name'] = arith4.__package__
 metadata['version'] = arith4.__version__
 metadata['description'] = arith4.description_
 metadata['long_description'] = arith4.long_description_
 metadata['author'] =  arith4.author_
 metadata['url'] = arith4.url_
 metadata['license'] = arith4.license_
 metadata['entry_points'] = {
  'console_scripts': [
   'arith4_command = arith4.arith4_command:arith4_command',
  ],
  'gui_scripts': [
  ],
 }
 metadata['packages'] = find_packages(exclude=["tests"])
 metadata['include_package_data'] = True
 metadata['package_data'] = {
  '': ['*.rst'],
 }
 metadata['install_requires'] = [
  'setuptools &gt;= 22.0.5',
 ]
 setup(**metadata)

if __name__ == "__main__":
 setup_package()
基本的に {setuptools} の setup() を呼ぶだけである。
setup() に渡す引数(上記の metadata)として何があるかは、setuptools のドキュメントを見るのが一番良いが、使用したものについて簡単にまとめる。

  • name :
    パッケージの名前
  • version :
    バージョン
  • description, long_description :
    説明
  • author :
    作者
  • author_email :
    作者 e-mail アドレス
  • url :
    ホームページの URL
  • license :
    パッケージの利用ライセンス
  • entry_points :
    ここに 名前=関数 の形で登録しておくと、パスの通った場所に、その関数が実行されるスクリプトの形でインストールされる
    console_script と gui_script はその名の通り
  • packages :
    setuptools.find_package() を呼ぶと、root directory 以下の有効な(python ソースコードの入った)ディレクトリをパッケージと認識して、build, install を行う
    上記ではさらに tests フォルダを除外する設定にしてある
  • include_package_data :
    ソースコード以外のものもパッケージに含めるか
  • packages_data :
    パッケージ名と、パッケージに入れるファイルの形式を指定する
    上記ではすべてのパッケージにおいて .rst 拡張子のファイルを含める
  • install_requires :
    インストールの際に必要な他パッケージの依存関係
    パッケージ名 (評価記号) バージョン番号という形で登録する
    評価記号には == と &gt;= がある
主な設定項目は arith4/__init__.py に記述し、様々な箇所から参照できるようにした。

設定が終了したら、setup.py builld でビルドされ、 setup.py install でインストールされる。

2016/06/05

Day XXX: virtualenv で python3 環境を作成

Mac OSX を前提とし、これから作っていくものを python3 仮想環境上で動作させることを想定する。
pythonの仮想環境は pyenv (pyenv-virtualenvpyenv-virtualenvwrapper) と virtualenv (virtualenvwrapper) の2通りがあるが、私の趣味で virtualenv を使うことにする。
virtualenv の場合は、pythonを Homebrew 等で自前で用意する。
また、pythonパッケージ管理は anaconda と pip があるが、これも私の趣味で pip を使うことにする。
pythonの管理、パッケージ管理の両方でまとめて出来る conda もある。
pyenv, anaconda, conda もそのもののインストールは Homebrew で行うが、使い方の組み合わせは以下になることが多いと思われる。

python管理python仮想環境管理pythonパッケージ管理
Homebrewvirtualenv (virtualenvwrapper)pip
pyenvpyenv-virtualenv (pyenv-virtualenvwrapper)anaconda
condacondaconda

El Capitan の Rootless 問題対策は Qiita:【El Capitan】Mac OSX 10.11にHomebrewインストールに良くまとまっている。
Homebrew の git repository は変更される可能性があるので、Homebrew 本家を確認した方が良い。
Homebrew をインストールしたら、Homebrew で python3 をインストールする
command prompt$ brew install python3
上記のインストールでは、{setuptools}, {pip}, {wheel} の3つの python パッケージが自動でインストールされる。まずはこの3つを pip3 を使って upgrade する。
command prompt$ pip3 install --upgrade pip setuptools wheel
次にやはり pip3 で virtualenv および virtuelenvwrapper をインストールする
command prompt$ pip3 install virtualenv virtualenvwrapper
使用しているシェルの設定ファイル(bash なら ~/.bashrc、zsh なら ~/.zshrc)に以下の記述を追加して、virtualenv の設定を行う。
シェル設定ファイルexport WORKON_HOME=${HOME}/.virtualenvs
export PROJECT_HOME=${HOME}/Projects
export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3
export VIRTUALENVWRAPPER_VIRTUALENV=/usr/local/bin/virtualenv
export VIRTUALENVWRAPPER_VIRTUALENV_ARGS='--always-copy'
if [[ -f /usr/local/bin/virtualenvwrapper.sh ]]; then
    source /usr/local/bin/virtualenvwrapper.sh
fi
WORKON_HOME が各仮想環境で使用するシステムファイルが配置されるディレクトリで、PROJECT_HOME が各プロジェクトが作られるディレクトリである。 PROJECT_HOME は予め作っておく。
この設定後は source コマンドやターミナルを再立ち上げすることで、設定を読み込む。

python 仮想環境の作り方は2通りある。まずは汎用的な python 仮想環境を作る場合。
command prompt$ mkvirtualenv --python=/usr/local/bin/python3 --always-copy autograd-learn
上記コマンドを実行すると、WORKON_HOME 以下に autograd-learn が作成され、workon autograd-learn とコマンド入力すると python 仮想環境に移行する。
そして特定のプロジェクトに固有の python 仮想環境を作成する場合。
command prompt$ mkproject --python=/usr/local/bin/python3 --always-copy autograd-learn
上記コマンドを実行すると、WORKON_HOME 以下に autograd-learn が作成され、さらに PROJECT_HOME 以下に autograd-learn ディレクトリが作成される。workon autograd-learn とコマンド入力すると、自動的に ${PROJECT_HOME}/autograd-learn に移動する。また cdproject とコマンド入力すると、前記プロジェクトのホームディレクトリに移動する。
setvirtualenvproject コマンドで python 仮想環境とディレクトリ(プロジェクト)の関係を後から設定できるが、あくまで python 仮想環境とプロジェクトは1対1の関係である。
上記 mkvirtualenv と mkproject のコマンド例は --python と --always-copy(virtualenv へのオプション)を指定したが、省略するとそれぞれシェル設定ファイル中の VIRTUELENVWRAPPER_PYTHON, VIRTUALENVWRAPPER_VIRTUALENV_ARGS が使われる。

2016/06/04

Day XXX: 数式とソースコードの表示

ブログを始めるにあたって、数式とソースコードが綺麗に表示できるのが望ましい。
まずは数式。MathJax を使う。
ブログの設定画面から「テンプレート」「HTMLの編集」に移動し、head タグ内に以下の記述を追加する。
設定 → テンプレート → HTMLの編集 → <head>タグ内<script src="//cdn.mathjax.org/mathjax/latest/MathJax.js" type="text/javascript">
MathJax.Hub.Config({
  HTML: ["input/TeX","output/HTML-CSS"],
  TeX: { extensions: ["AMSmath.js","AMSsymbols.js"],
         equationNumbers: { autoNumber: "AMS" } },
  extensions: ["tex2jax.js"],
  jax: ["input/TeX","output/HTML-CSS"],
  displayAlign: "left",
  displayIndent: "2em",
  tex2jax: { inlineMath: [ ['$','$'], ["\\(","\\)"] ],
             displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
             processEscapes: true },
  "HTML-CSS": { availableFonts: ["TeX"],
                          linebreaks: { automatic: true } }
});
</script>
このような表示になる。
エントリ編集変数$x$と\(y\)があり、
$$x = 1$$
\[
y = 2
\]
という時に、式$\ref{sample}$が成立する。
\begin{equation}
2x = y\label{sample}
\end{equation}
変数$x$と\(y\)があり、 $$x = 1$$ \[ y = 2 \] という時に、式$\ref{sample}$が成立する。 \begin{equation} 2x = y\label{sample} \end{equation}

次にソースコードだが、以下の理由から Syntax Highlighter は使わなかった。
  • Syntax Highlighter の CDN が HTTPS に対応していない
  • 言語毎に js ファイルを読み込まなくてはならない
  • テーマにあまり良いものが無い
hilight.js を使うことにした。
  • HTTPS に対応している
  • ひとつの js を読み込むだけで言語を自動的に判断してハイライトする
  • テーマが豊富にある
同じく head 内に以下の記述を追加する。
設定 → テンプレート → HTMLの編集 → <head>タグ内<link href='//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.0.0/styles/tomorrow-night.min.css' rel='stylesheet'/>
<script src='//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.0.0/highlight.min.js'/>
<script>hljs.initHighlightingOnLoad();</script>
テーマの設定は highlight.jsのデモ を見て、好きなテーマに該当する css ファイルを指定すれば良い。
以下のような感じになる。
<pre>sample program<code>@requires_authorization
def somefunc(param1='', param2=0):
    r'''A docstring'''
    if param1 > param2: # interesting
        print 'Gre\'ater'
    return (param2 - param1 + 1 + 0b10l) or None

class SomeClass:
    pass

>>> message = '''interpreter
... prompt'''
</code></pre>
sample program@requires_authorization
def somefunc(param1='', param2=0):
    r'''A docstring'''
    if param1 > param2: # interesting
        print 'Gre\'ater'
    return (param2 - param1 + 1 + 0b10l) or None

class SomeClass:
    pass

>>> message = '''interpreter
... prompt'''
細かな設定は highlight.jsのusage や highlight.jsのreadthedocs を参照すれば良い。

MathJax も highlight.js も、テンプレートを「動的ビュー」のものにしてしまうと、jQueryの何らかとバッティングしてしまうせいか、なかなか動かない。
動的ビュー以外のテンプレートにすることが無難。

また、表などのスタイルのために bootstrap の CSS を読み込むようにする。
設定 → テンプレート → HTMLの編集 → <head>タグ内<link href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css' rel='stylesheet'/>
<link href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap-theme.min.css' rel='stylesheet'/>
bootstrap のCSS定義についてはbootstrap のサイトを参照する。