Python: Flask: HTTP GET メソッドのクエリパラメータに真偽値を使いたい

うまい例をでっち上げられないので、実際のユースケースで説明する。

自宅のサーバで Flask を使った書籍管理の web アプリを運用してるんだけど(ソースコードは GitHub で公開してある)、その web アプリには JSON を返す API が定義してある。で、API のひとつ /api/books/ は書籍情報の一覧を返し、クエリパラメータとして offset と limit を取ることができる。

Flask では request.args.get でクエリパラメータの値を取得するついでに、値の型とデフォルト値を指定することができる。↓こんな感じ。

@app.route('/api/books/')
def api_books():
    offset = request.args.get('offset', default=0, type=int)
    limit = request.args.get('limit', default=100, type=int)
    ....

さて、この書籍管理アプリには「書籍を削除する」という機能がなくて、かわりに書籍を捨てた時には、データベース上の disposed カラムに True をセットして、web アプリ上では表示しないようになっている。だけど上記の API ではそこのところを考慮してなかったので、返ってくる JSON には捨てた書籍も含まれている、という状況だった。

ここからが本題。

API の返す JSON に、デフォルトでは捨てた書籍(つまり disposed=True)は含まず、クエリで include_disposed に真値をセットした時にだけ含むようにしたい。

最初の方法

上に示したコートでは、クエリパラメータの値を int に変換して取得しているんだから、同様に bool を指定してやればいいと思った。こういうふうに。

@app.route('/api/books/')
def api_books():
    offset = request.args.get('offset', default=0, type=int)
    limit = request.args.get('limit', default=100, type=int)
    include_disposed = request.args.get('include_disposed', default=False, type=bool)
    ....

開発用のサーバはエラーもなく立ち上がり、/aip/books/ にアクセスすると捨てた書籍が含まれていない JSON が、/api/books/?include_disposed=True にアクセスすると捨てた書籍も含まれた JSON が返ってきた。期待通りだ。

ところが、試しに /api/books/?include_disposed=False にアクセスしてみると、捨てた書籍も含まれた JSON が返ってきた。これは期待する動作と違う。

原因は追求していないけど、想像するに、クエリの値として渡ってきた文字列を bool(文字列) してるだけなんじゃなかろうか。だとすると、クエリで include_disposed の値がなんであるかにかかわらず、空文字列でない限りは、変数 include_disposed の値は True になるわけだ。

なんてこった。期待させやがって!

解決編

しかたがないので文字列を真偽値に変換する関数を書いた。一般に真を表すであろう文字列(true、 yes、 on、大文字小文字を問わない)の時だけ True を返し、ほかは False を返す。

def str_to_bool(s):
    p = re.compile(r'(true|yes|on)\Z', re.IGNORECASE)
    if p.match(s):
        return True
    else:
        return False

で、クエリの値を変数に代入するところはこうした。

    include_disposed = str_to_bool(request.args.get('include_disposed', default=''))

request.args.get の返す値を str_to_bool で変換している。default=” を指定しているのは、指定しないとクエリに include_disposed がなかった時に None が返ってくるため。

さて、これで期待通りの動作をするようになった。

Pythonで全文検索を実装してみた

JavaScript でやってるのを見かけたので。

 cf. JavaScriptで全文検索(N-gram)を実装してみる! – Simple is Beautiful.

アルゴリズムは N-gram っていう方法のうちでも簡単な uni-gram っていうみたい。詳しくはリンク先の記事を見て。

記事の説明を読んで、なんとなく理解したので Python で書いてみた。リンク先の JavaScript の実装はファイル名を参考にしたくらいで、ちゃんと読んでない。

できたのはこんな感じ。

  • create_index.py でインデックスを作って、search.py で検索
  • 検索対象のファイルはテキストファイルのみ。documents ディレクトリに入ってる
  • インデックスファイルは indexes ディレクトリ内につくる
  • document ID とファイル名の対応は docs.json ファイル

インデックスを作る create_index.py

from unigram import document
import json
import os


DOC_DIR = 'documents'
INDEX_DIR = 'indexes'
DOC_DATA = 'docs.json'


files = [os.path.join(DOC_DIR, f) for f in os.listdir(DOC_DIR)]
docs = {}
doc_id = 0
for file in files:
    with open(file, 'r') as f:
        text = f.read()
        tokens = document.tokenize(text)
        index = document.classify(tokens)
        document.save_index(index, doc_id, INDEX_DIR)
    docs[str(doc_id)] = {'name': os.path.basename(file), 'path': file}
    doc_id += 1

with open(DOC_DATA, 'w') as f:
    json.dump(docs, f, indent=2)

検索コマンド search.py

from unigram import document
import json
import os
import sys


INDEX_DIR = 'indexes'
DOC_DATA = 'docs.json'


string = sys.argv[1]

with open(DOC_DATA, 'r') as f:
    docs = json.load(f)
fcount = len(docs)

index_files = list(map(lambda x: os.path.join(INDEX_DIR, x), os.listdir(INDEX_DIR)))
index = {}
for file in index_files:
    c = chr(int(os.path.basename(file).replace('.index', '')))
    with open(file, 'r') as f:
        index[c] = document.parse_index(f.read())

m = list(map(lambda x: index[x], list(string)))

for i in range(fcount):
    doc_id = str(i)
    if not all(map(lambda x: doc_id in x.keys(), m)):
        continue
    n = list(map(lambda x: x[doc_id], m))
    s = set(n[0])
    for s1 in n[1:]:
        s = set(list(map(lambda x: x + 1, s)))
        s = s & set(s1)
    if len(s) > 0:
        pos = list(map(lambda x: x - len(string) + 1, s))
        pos.sort()
        print(docs[doc_id]['name'], pos)

両方から使うモジュール unigram/document.py

import os


def tokenize(text):
    return list(text)


def classify(token_list):
    tokens = {}
    pos = 0
    for t in token_list:
        if not t in tokens:
            tokens[t] = []
        tokens[t].append(pos)
        pos += 1
    return tokens


def save_index(index, doc_id, index_dir):
    for c, idx in index.items():
        l = str(doc_id) + ':' + ','.join(list(map(lambda x: str(x), idx))) + '\n'
        with open(os.path.join(index_dir, str(ord(c)) + '.index'), 'a') as f:
            f.write(l)


def parse_index(content):
    l = content.split('\n')
    l.pop()
    index = {}
    for l2 in l:
        a = l2.split(':')
        index[a[0]] = [int(x) for x in a[1].split(',')]
    return index

とにかくまずは動くものを、ってことで作ったので、コードが整理されてないのは目を瞑ってほしい(後で直す)。

検索対象ファイルのサンプルには、別のプロジェクトで書いた Ruby のソースファイル。

takatoh@apostrophe $ ls documents
Gemfile       Rakefile  boot.rb    config.yaml
Gemfile.lock  app.rb    config.ru  config.yaml.example

インデックスを作る。

takatoh@apostrophe $ python create_index.py

できたインデックスファイルがこれ。

takatoh@apostrophe $ ls indexes
10.index   111.index  123.index  44.index  58.index  71.index  84.index
100.index  112.index  124.index  45.index  60.index  72.index  85.index
101.index  113.index  125.index  46.index  61.index  73.index  87.index
102.index  114.index  126.index  47.index  62.index  74.index  89.index
103.index  115.index  32.index   48.index  63.index  75.index  91.index
104.index  116.index  34.index   49.index  64.index  76.index  93.index
105.index  117.index  35.index   50.index  65.index  77.index  95.index
106.index  118.index  36.index   51.index  66.index  78.index  97.index
107.index  119.index  39.index   52.index  67.index  79.index  98.index
108.index  120.index  40.index   53.index  68.index  80.index  99.index
109.index  121.index  41.index   55.index  69.index  82.index
110.index  122.index  43.index   56.index  70.index  83.index

「require」という文字列を検索してみる。ファイル名と出現位置(のリスト)が出力される。

takatoh@apostrophe $ python search.py require
boot.rb [0, 17]
Rakefile [0, 15, 32, 71]
config.ru [0]
app.rb [0, 23, 40, 56, 71]

大丈夫そうだ。

CentOS 8 をインストールしてみた

新しい PC を買ったんだ。これまで Dell か HP だったので、今回は Lenovo にしてみた。ThinkCentre M630e Tiny っていうモデル。てのひらにはちょっとあまるけど、超小型 PC と言っていい大きさだ。

で、せっかくなので最近リリースされたばかりの CentOS 8.0.1905 をインストールしてみた。プレインストールされていた Windows 10 は削除。

インストーラは CentOS 7 と特段変わらない印象。でもデフォルトのインストールタイプ(っていうんだっけ?)が、「サーバー(GUI使用)」になってた。もちろんこれでOK。ひととおりインストールが済んで再起動すると、ちゃんと CentOS が立ち上がった。いつかの Dell の PC みたいに起動しないなんてことはなかった。いい兆候だ。あと、ホスト名は rollo にした。

日本語入力

インストール時に日本語を選んでいるので、メニューとかは日本語になってるんだけど、そのままでは入力はできないようだ。↓このページが参考になった。

cf. デスクトップ環境 : GNOME デスクトップ インストール – Server World

まずは日本語入力プログラムをインストール。

[takatoh@rollo ~]$ sudo dnf -y install ibus-kkc

CentOS 7 までの yum コマンドじゃなくて dnf っていうコマンドでパッケージをインストールするらしい。

それから、背景画面の適当なところで右クリックして設定ウィンドウをひらいて、 Region & Language に移動する。入力ソースの欄で、日本語(かな漢字)を追加すれば準備は完了。画面右上のアイコンをクリックして日本語(かな漢字)を選べば OK だ。あとは半角/全角キーで入力モードを切り替えられる。今日のこのエントリもそうやって書いている。

Ruby とか Python とか

Python が入ってない、と思ったら python3 コマンドだった。

[takatoh@rollo ~]$ python3 -V
Python 3.6.8

Python はデフォルトが 3 系になったらしい。なら python コマンドでいいだろうになんで python3 なんだ。

Ruby は入ってない。のでインストールした。

[takatoh@rollo ~]$ ruby -v
bash: ruby: コマンドが見つかりませんでした…
コマンド ruby' を提供するためにパッケージ 'ruby' をインストールしますか? [N/y] y
キューで待機中… 
パッケージの一覧をロード中。… 
以下のパッケージはインストールされるべきものです:
ruby-2.5.3-104.module_el8.0.0+179+565e49e2.x86_64    An interpreter of object-oriented scripting language
ruby-irb-2.5.3-104.module_el8.0.0+179+565e49e2.noarch    The Interactive Ruby
ruby-libs-2.5.3-104.module_el8.0.0+179+565e49e2.x86_64    Libraries necessary to run Ruby
rubygem-bigdecimal-1.3.4-104.module_el8.0.0+179+565e49e2.x86_64    BigDecimal provides arbitrary-precision floating point decimal arithmetic
rubygem-did_you_mean-1.2.0-104.module_el8.0.0+179+565e49e2.noarch    "Did you mean?" experience in Ruby
rubygem-io-console-0.4.6-104.module_el8.0.0+179+565e49e2.x86_64    IO/Console is a simple console utilizing library
rubygem-json-2.1.0-104.module_el8.0.0+179+565e49e2.x86_64    This is a JSON implementation as a Ruby extension in C
rubygem-openssl-2.1.2-104.module_el8.0.0+179+565e49e2.x86_64    OpenSSL provides SSL, TLS and general purpose cryptography
rubygem-psych-3.0.2-104.module_el8.0.0+179+565e49e2.x86_64    A libyaml wrapper for Ruby
rubygem-rdoc-6.0.1-104.module_el8.0.0+179+565e49e2.noarch    A tool to generate HTML and command-line documentation for Ruby projects
rubygems-2.7.6-104.module_el8.0.0+179+565e49e2.noarch    The Ruby standard for packaging ruby libraries
変更したまま継続しますか? [N/y] y
キューで待機中… 
認証を待ち受け中… 
キューで待機中… 
パッケージをダウンロード中… 
データを要求中… 
変更をテスト中… 
パッケージのインストール中… 
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux] 
[takatoh@rollo ~]$ ruby -v
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]

2.5.3 がインストールされた。

というところで、今日はここまで。

3本のひもで作る四角形

3月も下旬になってしまった。

今回もたまたま見つけたネタ。前回と同じブログから。

 cf. 3本のひもで作る四角形 – utthi_fumiの日記

同じ長さの3本のひもを折り曲げて3つの四角形を作ります。 そのうち2本でそれぞれ長方形を作り、残りの1本は正方形を作ります。 このとき、作った2つの長方形の面積の和が、正方形の面積と同じになることがあります。 (ただし、いずれの長方形、正方形も辺の長さは整数)

例)紐の長さが20の時

1本目 縦1×横9の長方形→面積=9

2本目 縦2×横8の長方形→面積=16

3本目 縦5×横5の正方形→面積=25

さらに、ひもの長さを変えてできる長方形と正方形の組をカウントします。 ただし、同じ比で整数倍のものは1つとしてカウント

例)紐の長さが40の時

1本目 縦2×横18の長方形→面積=36

2本目 縦4×横16の長方形→面積=64

3本目 縦10×横10の正方形→面積=100

紐の長さが60の時

1本目 縦3×横27の長方形→面積=81

2本目 縦6×横24の長方形→面積=114

3本目 縦15×横15の正方形→面積=225

紐の長さを1から500まで変化させる時、2つの長方形の面積の和と 正方形の面積が同じなる組が何通りあるか?


Python でやってみる。

import math
from functools import reduce


def main():
    s = set()
    for l in range(1, 501):
        for c in height_of_rectangles(l):
            s.add(reduction(c))
    for c in s:
        print(format_rect(c))
    print(len(s))

def combi(l):
    if l % 4 == 0:
        h3 = int(l / 4)
        return [(h1, h2, h3) for h2 in range(1, h3) for h1 in range(1, h2)]
    else:
        return []

def area(h, l):
    w = int(l / 2) - h
    return h * w

def height_of_rectangles(l):
    hor = []
    for c in combi(l):
        (h1, h2, h3) = c
        if area(h1, l) + area(h2, l) == area(h3, l):
            hor.append(c)
    return hor

def format_rect(c):
    (h1, h2, h3) = c
    l = h3 * 4
    w1 = h3 * 2 - h1
    w2 = h3 * 2 - h2
    return "{l}: {h1} * {w1} + {h2} * {w2} = {h3} * {h3}".format(l=l, h1=h1, w1=w1, h2=h2, w2=w2, h3=h3)

def reduction(c):
    r = reduce(math.gcd, c)
    return tuple(map(lambda x: int(x / r), c))


if __name__ == '__main__':
    main()

実行結果:

116: 8 * 50 + 9 * 49 = 29 * 29
260: 9 * 121 + 32 * 98 = 65 * 65
100: 1 * 49 + 18 * 32 = 25 * 25
340: 8 * 162 + 49 * 121 = 85 * 85
388: 25 * 169 + 32 * 162 = 97 * 97
164: 1 * 81 + 32 * 50 = 41 * 41
404: 2 * 200 + 81 * 121 = 101 * 101
68: 2 * 32 + 9 * 25 = 17 * 17
52: 1 * 25 + 8 * 18 = 13 * 13
260: 2 * 128 + 49 * 81 = 65 * 65
212: 8 * 98 + 25 * 81 = 53 * 53
500: 8 * 242 + 81 * 169 = 125 * 125
436: 18 * 200 + 49 * 169 = 109 * 109
20: 1 * 9 + 2 * 8 = 5 * 5
340: 1 * 169 + 72 * 98 = 85 * 85
244: 1 * 121 + 50 * 72 = 61 * 61
148: 2 * 72 + 25 * 49 = 37 * 37
292: 18 * 128 + 25 * 121 = 73 * 73
356: 9 * 169 + 50 * 128 = 89 * 89
452: 1 * 225 + 98 * 128 = 113 * 113
20

いちばん最後に出力されている 20 が答え。20 通りあるってこと。

整数の割り算の結果が小数になることとか、map の結果が map object とかいうものになるとことかでちょってハマった。map object ってなにさ。

Pythonでwheelパッケージとその中のコマンドのバージョンを合わせる方法

タイトルが長いな。

コマンドを wheel パッケージにしたときに、そのコマンドとパッケージのバージョンを合わせたい、という当然の欲求を満足するための、(現在のところ)最良と思われる方法を見つけたのでメモしておく。

コマンドでバージョンを出力するには、コマンドのスクリプト中でバージョンがわからなければならないし、パッケージングするためには setup.py の中でバージョンがわからなければならない。当然のことながら、両方にバージョンを書くのは愚の骨頂。だからどこか1ヵ所でバージョンを定義して、それをコマンドと setup.py から参照することにする。

で、それはどこなのかというと、モジュールの __init__.py の中だ。この定義をコマンドと setup.py の両方から参照するんだけど、ちょっと工夫が必要だった。

ここでは、簡単な Hello world コマンドを例に説明する。ファイル構成はこんな感じ。

^o^ > tree /f .
フォルダー パスの一覧
ボリューム シリアル番号は 666A-93A9 です
C:\USERS\TAKATOH\DOCUMENTS\SANDBOX\HELLOPY
│ setup.py

└─hello
hello.py
__init__.py

hello ディレクトリがモジュールになっていて、コマンドの実体(関数)は hello/hello.py ファイルに書いてある。

バージョンの定義は、次の通り、hello/__init__.py の中。

__version__ = '0.3.2'

バージョン番号はいろいろ試行錯誤した結果を反映している。

これを setup.py から参照するのは簡単。モジュールをインポートして変数を参照するだけ。

import setuptools
import hello


setuptools.setup(
    name='hellopy',
    description='hello app.',
    version=hello.__version__,
    author='takatoh',
    author_email='[email protected]',
    packages=setuptools.find_packages(),
    classifiers=[
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
    ],
    entry_points={
        'console_scripts': [
            'hellopy=hello.hello:main',
        ],
    }
)

コマンドから参照するにはもうちょっと工夫が必要。こうなった。

import os
import argparse


here = os.path.abspath(os.path.dirname(__file__))
about = {}
with open(os.path.join(here, '__init__.py')) as f:
    exec(f.read(), about)
VERSION = 'v' + about['__version__']


def main():
    parser = argparse.ArgumentParser(description='Hello tool.')
    parser.add_argument('name', metavar='NAME', type=str, nargs='?', default='world')
    parser.add_argument('-v', '--version', action='version', version=VERSION)
    args = parser.parse_args()

    print('Hello, {name}!'.format(name=args.name))


if __name__ == '__main__':
    main()

hello/__init__.py を読み込んで exec で評価して変数への参照を得ている。直接 hello モジュールをインポートできれば楽なんだけど、このコマンド(スクリプト)自体が hello モジュールに属しているので、それはできないみたいだ。

というわけで、この方法が一番いいようだ。もっといい方法が見つかるまではこれで行こうと思う。

最後に、実際に wheel パッケージを作って、インストールして試してみよう。

^o^ > python setup.py bdist_wheel
^o^ > pip install dist/hellopy-0.3.2-py3-none-any.whl
^o^ > hellopy --version
v0.3.2

^o^ > hellopy
Hello, world!

^o^ > hellopy Andy
Hello, Andy!

うまくいってる。

CentOS 7上でbottleアプリをuWSGIで動かす(2)

一昨日の続き。今日は、アプリをデーモンとして動かす。
まず最初に、uWSGI の設定ファイルをちょっと書き換える。

<span class="token selector">[uwsgi]</span>
<span class="token constant">uid</span> <span class="token attr-value"><span class="token punctuation">=</span> lcstorage</span>
<span class="token constant">gid</span> <span class="token attr-value"><span class="token punctuation">=</span> lcstorage</span>
<span class="token constant">http</span> <span class="token attr-value"><span class="token punctuation">=</span> :8080</span>
<span class="token constant">venv</span> <span class="token attr-value"><span class="token punctuation">=</span> /home/lcstorage/lcstorage/env</span>
<span class="token constant">wsgi-file</span> <span class="token attr-value"><span class="token punctuation">=</span> /home/lcstorage/lcstorage/index.py</span>
<span class="token constant">master</span> <span class="token attr-value"><span class="token punctuation">=</span> true</span>
<span class="token constant">pidfile</span> <span class="token attr-value"><span class="token punctuation">=</span> /home/lcstorage/lcstorage/lcstorage.pid</span>
<span class="token constant">daemonize</span> <span class="token attr-value"><span class="token punctuation">=</span> /home/lcstorage/lcstorage/lcstorage.log</span>
Ini

最後の行を変更した。logger を daemonize に変えると何故かデーモンとしてバックグラウンドで動くようになるという、uWSGI のよくわからない仕様。

無事バックグラウンドで動くようになったら、systemd のサービスになるように設定ファイル /etc/systemd/system/lcstorage.service を書く。

[Unit]
Description=lcstorage service with uWSGI

[Service]
Type=forking
ExecStart=/usr/bin/uwsgi /home/lcstorage/lcstorage/lcstorage.ini
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -QUIT $MAINPID
PIDFile=/home/lcstorage/lcstorage/lcstorage.pid
WorkingDirectory=/home/lcstorage/lcstorage
User=lcstorage
Group=lcstorage

[Install]
WantedBy=multi-user.target
Ini

これでいいはず。試してみよう。

[lcstorage@bigswifty system]$ sudo systemctl start lcstorage
[sudo] lcstorage のパスワード:
[lcstorage@bigswifty system]$ ps ax | grep uwsgi
28937 ?        S      0:00 /usr/bin/uwsgi /home/lcstorage/lcstorage/lcstorage.ini
28946 ?        S      0:00 /usr/bin/uwsgi /home/lcstorage/lcstorage/lcstorage.ini
28947 ?        S      0:00 /usr/bin/uwsgi /home/lcstorage/lcstorage/lcstorage.ini
28949 pts/1    S+     0:00 grep --color=auto uwsgi

成功のようだ。他のマシンのブラウザからも確認できた。

最後に、マシンの起動にあわせて自動的に起動するようにする。

[lcstorage@bigswifty system]$ sudo systemctl enable lcstorage
Created symlink from /etc/systemd/system/multi-user.target.wants/lcstorage.service to /etc/systemd/system/lcstorage.service.

さあ、あしたは Nginx 経由でアクセスできるようにしよう。時間があったらだけど。

CentOS 7上でbottleアプリをuWSGIで動かす

専用ユーザーの作成

[takatoh@bigswifty ~]$ sudo useradd lcstorage
[sudo] takatoh のパスワード:
[takatoh@bigswifty ~]$ sudo passwd lcstorage
ユーザー lcstorage のパスワードを変更。
新しいパスワード:
よくないパスワード: このパスワードには一部に何らかの形でユーザー名が含まれています。
新しいパスワードを再入力してください:
passwd: すべての認証トークンが正しく更新できました。

よくないパスワードとか言われてるけど無視。sudo する権利をつける。

[takatoh@bigswifty ~]$ sudo usermod -G wheel lcstorage

ここからは新しいユーザーでの作業になるので、ログインし直し。

bottleアプリの配置

bitbucket.org から clone。

[lcstorage@bigswifty ~]$ git clone https://[email protected]/takatoh/lathercraft-storage-py.git lcstorage
Cloning into 'lcstorage'...
Password for 'https://[email protected]': 
remote: Counting objects: 127, done.
remote: Total 127 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (127/127), 13.63 KiB | 0 bytes/s, done.
Resolving deltas: 100% (55/55), done.

中に入って virtualenv を作成。

[lcstorage@bigswifty ~]$ cd lcstorage
[lcstorage@bigswifty lcstorage]$ virtualenv env
New python executable in /home/lcstorage/lcstorage/env/bin/python2
Also creating executable in /home/lcstorage/lcstorage/env/bin/python
Installing setuptools, pip, wheel...
done.
[lcstorage@bigswifty lcstorage]$ source env/bin/activate
(env) [lcstorage@bigswifty lcstorage]$

依存ライブラリのインストール。

(env) [lcstorage@bigswifty lcstorage]$ pip install -r requirements.txt
Collecting PyYAML==3.12 (from -r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/4a/85/db5a2df477072b2902b0eb892feb37d88ac635d36245a72a6a69b23b383a/PyYAML-3.12.tar.gz (253kB)
    100% |████████████████████████████████| 256kB 6.9MB/s 
Collecting bottle==0.12.10 (from -r requirements.txt (line 2))
  Downloading https://files.pythonhosted.org/packages/5c/a9/f157af11b37834db992b14e43ade37a1bdc141485657cfa36dc1d99420a6/bottle-0.12.10-py2-none-any.whl (88kB)
    100% |████████████████████████████████| 92kB 8.9MB/s 
Collecting requests==2.12.3 (from -r requirements.txt (line 3))
  Downloading https://files.pythonhosted.org/packages/84/68/f0acceafe80354aa9ff4ae49de0572d27929b6d262f0c55196424eb86b2f/requests-2.12.3-py2.py3-none-any.whl (575kB)
    100% |████████████████████████████████| 583kB 8.1MB/s 
Building wheels for collected packages: PyYAML
  Running setup.py bdist_wheel for PyYAML ... done
  Stored in directory: /home/lcstorage/.cache/pip/wheels/03/05/65/bdc14f2c6e09e82ae3e0f13d021e1b6b2481437ea2f207df3f
Successfully built PyYAML
Installing collected packages: PyYAML, bottle, requests
Successfully installed PyYAML-3.12 bottle-0.12.10 requests-2.12.3

アプリの設定ファイルを作って、起動確認。設定はとりあえずはデフォルトのままで。

(env) [lcstorage@bigswifty lcstorage]$ cp config.yaml.example config.yaml
(env) [lcstorage@bigswifty lcstorage]$ python index.py
Bottle v0.12.10 server starting up (using WSGIRefServer())...
Listening on http://0.0.0.0:8080/
Hit Ctrl-C to quit.

よし、大丈夫そうだ。じゃ、virtualenv をぬけて uWSGI に行くぞ。

uWSGIで起動

まずは uWSGI のインストール。

[lcstorage@bigswifty lcstorage]$ sudo pip install uwsgi
Collecting uwsgi
  Using cached https://files.pythonhosted.org/packages/a2/c9/a2d5737f63cd9df4317a4acc15d1ddf4952e28398601d8d7d706c16381e0/uwsgi-2.0.17.1.tar.gz
Installing collected packages: uwsgi
  Running setup.py install for uwsgi ... done
Successfully installed uwsgi-2.0.17.1
You are using pip version 8.1.2, however version 18.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

上では省略しちゃったけど、最初のインストールの時にエラーが出て、sudo yum install gcc python2-devel しなきゃならなかった。
ともかくインストールできたので、コマンドラインから起動してみる。

[lcstorage@bigswifty lcstorage]$ uwsgi --http :8080 --wsgi-file index.py -H env

他のマシンからアクセスしたら、ちゃんと動いていたのでOK。

設定ファイルを書く。

[uwsgi]
uid = lcstorage
gid = lcstorage
http = :8080
venv = /home/lcstorage/lcstorage/env
wsgi-file = /home/lcstorage/lcstorage/index.py
master = true
pidfile = /home/lcstorage/lcstorage/lcstorage.pid
logger = file:/home/lcstorage/lcstorage/lcstorage.log

起動確認。

[lcstorage@bigswifty lcstorage]$ uwsgi lcstorage.ini
[uWSGI] getting INI configuration from lcstorage.ini

OK。大丈夫そうだ。

とりあえずここまで。

リスト(配列)の中で隣り合う同じ値をグループ化する

リストでも配列でもいいけど、つまりこういうのを

[1, 1, 2, 2, 3, 1, 1]

こうしたい。

[[1, 1], [2, 2], [3], [1, 1]]

Ruby でやってみた。

def adjacent_group(ary)
  result = []
  current = nil
  ary.each do |x|
    if current.nil?
      current = x
      result << [x]
    elsif x == current
      result[-1] << x
    else
      current = x
      result << [x]
    end
  end
  result
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
^o^ >ruby adjacent_group.rb
[[1, 1], [2, 2], [3], [1, 1]]

おなじく Python で。

def adjacent_group(lis):
    result = []
    current = None
    for x in lis:
        if current is None:
            current = x
            result.append([x])
        elif x == current:
            result[-1].append(x)
        else:
            current = x
            result.append([x])
    return result

print(adjacent_group([1, 1, 2, 2, 3, 1, 1]))
^o^ >python adjacent_group.py
[[1, 1], [2, 2], [3], [1, 1]]

ちょっとベタだな。もう少しスマートにいかないものか、と考えて Ruby の Array#inject が使えそうだと思いついた。

def adjacent_group(ary)
  ary.inject([[]]) do |a, x|
    if a[-1].empty? || x == a[-1][-1]
      a[-1] << x
    else
      a << [x]
    end
    a
  end
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
^o^ >ruby adjacent_group2.rb
[[1, 1], [2, 2], [3], [1, 1]]

うまくいった。

さて、じゃ、Python ではどうか。reduce を使えば同じことができると考えたけど、Python の reduce は初期値がとれない。まあ、それはリストの頭に初期値をつけてやれば済む話ではあるけど、もうひとつ問題がある。Ruby の Array#inject はブロックをとれるけど、Python の reduce には関数を渡してやらなきゃいけないので、関数定義がひとつ増えてしまう。一行では書けないので lambda 式は使えない。
というわけで、上のようにベタに書いたほうがまだマシそうだ。何かいい書き方があったら、誰か教えてください。

[追記](9/25)

Ruby の2つ目の実装では、引数に空の配列を渡したときに期待通りに動作しない([[]] が返ってきてしまう)。そこでちょっと直したのがこれ。

def adjacent_group(ary)
  ary.inject([]) do |a, x|
    if !a.empty? && x == a[-1][-1]
      a[-1] << x
    else
      a << [x]
    end
    a
  end
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
p adjacent_group([])
^o^ >ruby adjacent_group.rb
[[1, 1], [2, 2], [3], [1, 1]]
[]

これでいいだろう。

Surface GoにAnaconda3をインストール

先日保留にしていた Python だけれど、結局 Jupyter Notebook を使ってみたくて Anaconda をインストールした。Anaconda は Python のディストリビューションのひとつで、科学計算関係のライブラリが多くバンドルされていて、そっち方面でよく使われているようだ。
まぁ、科学計算はともかく、Jupyter Notebook も使えるようになるというので Anaconda を選んだってわけ。
インストールは chocolatey を使った。

PS C:\Windows\system32> choco install anaconda3
Chocolatey v0.10.11
Installing the following packages:
anaconda3
By installing you accept licenses for the packages.
Progress: Downloading anaconda3 5.1.0... 100%

anaconda3 v5.1.0 [Approved]
anaconda3 package files install completed. Performing other installation steps.
The package anaconda3 wants to run 'chocolateyinstall.ps1'.
Note: If you don't run this script, the installation will fail.
Note: To confirm automatically next time, use '-y' or consider:
choco feature enable -n allowGlobalConfirmation
Do you want to run the script?([Y]es/[N]o/[P]rint): Y

WARNING: installing anaconda3, this can take a long time, because the installer will write tons of files on your disk
WARNING: Please sit back and relax
WARNING: This usually will take 10-15 mins on an SSD, and about 30 mins on HDD
WARNING:
WARNING: If you want to make sure the program is running, you can open Task Manager
WARNING: you will find the installer running in Background Process
WARNING: Url has SSL/TLS available, switching to HTTPS for download
Downloading anaconda3 64 bit
  from 'https://repo.continuum.io/archive/Anaconda3-5.1.0-Windows-x86_64.exe'
Progress: 100% - Completed download of C:\Users\takatoh\AppData\Local\Temp\chocolatey\anaconda3\5.1.0\Anaconda3-5.1.0-Windows-x86_64.exe (537.08 MB).
Download of Anaconda3-5.1.0-Windows-x86_64.exe (537.08 MB) completed.
Hashes match.
Installing anaconda3...
anaconda3 has been installed.
  anaconda3 can be automatically uninstalled.
 The install of anaconda3 was successful.
  Software installed to 'C:\tools\anaconda3'

Chocolatey installed 1/1 packages.
 See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).

コマンドひとつでインストールできるのはいいけど、500MB 以上あるじゃないか。えらい時間がかかった。

で、これで Python が使えるようになったかというと、だめだった。python は C:\tools\Anaconda3 にインストールされているけど、パスが通っていない。chocolatey がやってくれなかったみたいだ。仕方がないので、自分でパスを通した。

PS C:\Users\takatoh> python -V
Python 3.6.4 :: Anaconda, Inc.

これでOK。

[追記]

上記で Python は使えるようになったけど、pip や Anaconda のパッケージマネージャである conda コマンドは相変わらず使えない。使えるようにするには C:\tools\Anaconda\Scripts にパスを通す。通したうえで conda list コマンドを実行してみると、これだけのパッケージがインストールされているのがわかる。

PS C:\Users\takatoh> conda list
# packages in environment at C:\tools\Anaconda3:
#
# Name                    Version                   Build  Channel
_ipyw_jlab_nb_ext_conf    0.1.0            py36he6757f0_0
alabaster                 0.7.10           py36hcd07829_0
anaconda                  5.1.0                    py36_2
anaconda-client           1.6.9                    py36_0
anaconda-navigator        1.7.0                    py36_0
anaconda-project          0.8.2            py36hfad2e28_0
asn1crypto                0.24.0                   py36_0
astroid                   1.6.1                    py36_0
astropy                   2.0.3            py36hfa6e2cd_0
attrs                     17.4.0                   py36_0
babel                     2.5.3                    py36_0
backports                 1.0              py36h81696a8_1
backports.shutil_get_terminal_size 1.0.0            py36h79ab834_2
beautifulsoup4            4.6.0            py36hd4cc5e8_1
bitarray                  0.8.1            py36hfa6e2cd_1
bkcharts                  0.2              py36h7e685f7_0
blaze                     0.11.3           py36h8a29ca5_0
bleach                    2.1.2                    py36_0
bokeh                     0.12.13          py36h047fa9f_0
boto                      2.48.0           py36h1a776d2_1
bottleneck                1.2.1            py36hd119dfa_0
bzip2                     1.0.6                hbe05fcf_4
ca-certificates           2017.08.26           h94faf87_0
certifi                   2018.1.18                py36_0
cffi                      1.11.4           py36hfa6e2cd_0
chardet                   3.0.4            py36h420ce6e_1
click                     6.7              py36hec8c647_0
cloudpickle               0.5.2                    py36_1
clyent                    1.2.2            py36hb10d595_1
colorama                  0.3.9            py36h029ae33_0
comtypes                  1.1.4                    py36_0
conda                     4.4.10                   py36_0
conda-build               3.4.1                    py36_0
conda-env                 2.6.0                h36134e3_1
conda-verify              2.0.0            py36h065de53_0
console_shortcut          0.1.1                h6bb2dd7_3
contextlib2               0.5.5            py36he5d52c0_0
cryptography              2.1.4            py36he1d7878_0
curl                      7.58.0               h7602738_0
cycler                    0.10.0           py36h009560c_0
cython                    0.27.3           py36h22f4c84_0
cytoolz                   0.9.0            py36hfa6e2cd_0
dask                      0.16.1                   py36_0
dask-core                 0.16.1                   py36_0
datashape                 0.5.4            py36h5770b85_0
decorator                 4.2.1                    py36_0
distributed               1.20.2                   py36_0
docutils                  0.14             py36h6012d8f_0
entrypoints               0.2.3            py36hfd66bb0_2
et_xmlfile                1.0.1            py36h3d2d736_0
fastcache                 1.0.2            py36hfa6e2cd_2
filelock                  2.0.13           py36h20000bf_0
flask                     0.12.2           py36h98b5e8f_0
flask-cors                3.0.3            py36h8a3855d_0
freetype                  2.8                  h51f8f2c_1
get_terminal_size         1.0.0                h38e98db_0
gevent                    1.2.2            py36h342a76c_0
glob2                     0.6              py36hdf76b57_0
greenlet                  0.4.12           py36ha00ad21_0
h5py                      2.7.1            py36he54a1c3_0
hdf5                      1.10.1               h98b8871_1
heapdict                  1.0.0                    py36_2
html5lib                  1.0.1            py36h047fa9f_0
icc_rt                    2017.0.4             h97af966_0
icu                       58.2                 ha66f8fd_1
idna                      2.6              py36h148d497_1
imageio                   2.2.0            py36had6c2d2_0
imagesize                 0.7.1            py36he29f638_0
intel-openmp              2018.0.0             hd92c6cd_8
ipykernel                 4.8.0                    py36_0
ipython                   6.2.1            py36h9cf0123_1
ipython_genutils          0.2.0            py36h3c5d0ee_0
ipywidgets                7.1.1                    py36_0
isort                     4.2.15           py36h6198cc5_0
itsdangerous              0.24             py36hb6c5a24_1
jdcal                     1.3              py36h64a5255_0
jedi                      0.11.1                   py36_0
jinja2                    2.10             py36h292fed1_0
jpeg                      9b                   hb83a4c4_2
jsonschema                2.6.0            py36h7636477_0
jupyter                   1.0.0                    py36_4
jupyter_client            5.2.2                    py36_0
jupyter_console           5.2.0            py36h6d89b47_1
jupyter_core              4.4.0            py36h56e9d50_0
jupyterlab                0.31.4                   py36_0
jupyterlab_launcher       0.10.2                   py36_0
lazy-object-proxy         1.3.1            py36hd1c21d2_0
libcurl                   7.58.0               h7602738_0
libiconv                  1.15                 h1df5818_7
libpng                    1.6.34               h79bbb47_0
libssh2                   1.8.0                hd619d38_4
libtiff                   4.0.9                h0f13578_0
libxml2                   2.9.7                h79bbb47_0
libxslt                   1.1.32               hf6f1972_0
llvmlite                  0.21.0           py36he0b0552_0
locket                    0.2.0            py36hfed976d_1
lxml                      4.1.1            py36hef2cd61_1
lzo                       2.10                 h6df0209_2
markupsafe                1.0              py36h0e26971_1
matplotlib                2.1.2            py36h016c42a_0
mccabe                    0.6.1            py36hb41005a_1
menuinst                  1.4.11           py36hfa6e2cd_0
mistune                   0.8.3                    py36_0
mkl                       2018.0.1             h2108138_4
mkl-service               1.1.2            py36h57e144c_4
mpmath                    1.0.0            py36hacc8adf_2
msgpack-python            0.5.1            py36he980bc4_0
multipledispatch          0.4.9            py36he44c36e_0
navigator-updater         0.1.0            py36h8a7b86b_0
nbconvert                 5.3.1            py36h8dc0fde_0
nbformat                  4.4.0            py36h3a5bc1b_0
networkx                  2.1                      py36_0
nltk                      3.2.5            py36h76d52bb_0
nose                      1.3.7            py36h1c3779e_2
notebook                  5.4.0                    py36_0
numba                     0.36.2          np114py36h12cb543_0
numexpr                   2.6.4            py36h30784b8_0
numpy                     1.14.0           py36h4a99626_1
numpydoc                  0.7.0            py36ha25429e_0
odo                       0.5.1            py36h7560279_0
olefile                   0.45.1                   py36_0
openpyxl                  2.4.10                   py36_0
openssl                   1.0.2n               h74b6da3_0
packaging                 16.8             py36ha0986f6_1
pandas                    0.22.0           py36h6538335_0
pandoc                    1.19.2.1             hb2460c7_1
pandocfilters             1.4.2            py36h3ef6317_1
parso                     0.1.1            py36hae3edee_0
partd                     0.3.8            py36hc8e763b_0
path.py                   10.5             py36h2b94a8f_0
pathlib2                  2.3.0            py36h7bfb78b_0
patsy                     0.5.0                    py36_0
pep8                      1.7.1                    py36_0
pickleshare               0.7.4            py36h9de030f_0
pillow                    5.0.0            py36h0738816_0
pip                       9.0.1            py36h226ae91_4
pkginfo                   1.4.1            py36hb0f9cfa_1
pluggy                    0.6.0            py36hc7daf1e_0
ply                       3.10             py36h1211beb_0
prompt_toolkit            1.0.15           py36h60b8f86_0
psutil                    5.4.3            py36hfa6e2cd_0
py                        1.5.2            py36hbcfbabc_0
pycodestyle               2.3.1            py36h7cc55cd_0
pycosat                   0.6.3            py36h413d8a4_0
pycparser                 2.18             py36hd053e01_1
pycrypto                  2.6.1            py36hfa6e2cd_7
pycurl                    7.43.0.1         py36h74b6da3_0
pyflakes                  1.6.0            py36h0b975d6_0
pygments                  2.2.0            py36hb010967_0
pylint                    1.8.2                    py36_0
pyodbc                    4.0.22           py36h6538335_0
pyopenssl                 17.5.0           py36h5b7d817_0
pyparsing                 2.2.0            py36h785a196_1
pyqt                      5.6.0            py36hb5ed885_5
pysocks                   1.6.7            py36h698d350_1
pytables                  3.4.2            py36h71138e3_2
pytest                    3.3.2                    py36_0
python                    3.6.4                h6538335_1
python-dateutil           2.6.1            py36h509ddcb_1
pytz                      2017.3           py36h1d3fa6b_0
pywavelets                0.5.2            py36hc649158_0
pywin32                   222              py36hfa6e2cd_0
pywinpty                  0.5              py36h6538335_1
pyyaml                    3.12             py36h1d1928f_1
pyzmq                     16.0.3           py36he714bf5_0
qt                        5.6.2           vc14h6f8c307_12  [vc14]
qtawesome                 0.4.4            py36h5aa48f6_0
qtconsole                 4.3.1            py36h99a29a9_0
qtpy                      1.3.1            py36hb8717c5_0
requests                  2.18.4           py36h4371aae_1
rope                      0.10.7           py36had63a69_0
ruamel_yaml               0.15.35          py36hfa6e2cd_1
scikit-image              0.13.1           py36hfa6e2cd_1
scikit-learn              0.19.1           py36h53aea1b_0
scipy                     1.0.0            py36h1260518_0
seaborn                   0.8.1            py36h9b69545_0
send2trash                1.4.2                    py36_0
setuptools                38.4.0                   py36_0
simplegeneric             0.8.1                    py36_2
singledispatch            3.4.0.3          py36h17d0c80_0
sip                       4.18.1           py36h9c25514_2
six                       1.11.0           py36h4db2310_1
snowballstemmer           1.2.1            py36h763602f_0
sortedcollections         0.5.3            py36hbefa0ab_0
sortedcontainers          1.5.9                    py36_0
sphinx                    1.6.6                    py36_0
sphinxcontrib             1.0              py36hbbac3d2_1
sphinxcontrib-websupport  1.0.1            py36hb5e5916_1
spyder                    3.2.6                    py36_0
sqlalchemy                1.2.1            py36hfa6e2cd_0
sqlite                    3.22.0               h9d3ae62_0
statsmodels               0.8.0            py36h6189b4c_0
sympy                     1.1.1            py36h96708e0_0
tblib                     1.3.2            py36h30f5020_0
terminado                 0.8.1                    py36_1
testpath                  0.3.1            py36h2698cfe_0
tk                        8.6.7                hcb92d03_3
toolz                     0.9.0                    py36_0
tornado                   4.5.3                    py36_0
traitlets                 4.3.2            py36h096827d_0
typing                    3.6.2            py36hb035bda_0
unicodecsv                0.14.1           py36h6450c06_0
urllib3                   1.22             py36h276f60a_0
vc                        14                   h0510ff6_3
vs2015_runtime            14.0.25123                    3
wcwidth                   0.1.7            py36h3d5aa90_0
webencodings              0.5.1            py36h67c50ae_1
werkzeug                  0.14.1                   py36_0
wheel                     0.30.0           py36h6c3ec14_1
widgetsnbextension        3.1.0                    py36_0
win_inet_pton             1.0.1            py36he67d7fd_1
win_unicode_console       0.5              py36hcdbd4b5_0
wincertstore              0.2              py36h7fe50ca_0
winpty                    0.4.3                         4
wrapt                     1.10.11          py36he5f5981_0
xlrd                      1.1.0            py36h1cb58dc_1
xlsxwriter                1.0.2            py36hf723b7d_0
xlwings                   0.11.5                   py36_0
xlwt                      1.3.0            py36h1a4751e_0
yaml                      0.1.7                hc54c509_2
zict                      0.1.3            py36h2d8e73e_0
zlib                      1.2.11               h8395fce_2

CentOS 7にpyenvでPythonをインストール

CentOS 7 には標準で Python がインストールされている。ただちょっと古い。それに pip もない。

[takatoh@aybesea ~]$ python -V
Python 2.7.5
[takatoh@aybesea ~]$ pip list
bash: pip: コマンドが見つかりませんでした...

なので、pyenv を使って Python をインストールすることにした。これはひと月程前にも Ubuntu でやってるから大丈夫だろう。

環境

  • CentOS 7.4.1708 (Core)
  • システムの Python: 2.7.5

参考ページ

依存ライブラリのインストール

これ、なにが必要なんだかいつもよく分からないんだけど、とりあえず参考ページの通りにしてみる。

[takatoh@aybesea ~]$ sudo yum -y install zlib-devel bzip2 bzip2-devel readline readline-devel sqlite sqlite-devel openssl openssl-devel

pyenvとpyenv-virtualenvのインストール

[takatoh@aybesea ~]$ git clone https://github.com/yyuu/pyenv.git .pyenv
[takatoh@aybesea ~]$ git clone https://github.com/yyuu/pyenv-virtualenv.git .pyenv/plugins/pyenv-virtualenv

.bash_profile に追記。

export PYENV_ROOT=$HOME/.pyenv
export PATH=$PYENV_ROOT/bin:$PATH
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

.bash_profile を読み直し。

[takatoh@aybesea ~]$ source .bash_profile

Pythonのインストール

2.7.14 と 3.6.4 をインストール。

[takatoh@aybesea ~]$ pyenv install 2.7.14
Downloading Python-2.7.14.tar.xz...
-> https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tar.xz
Installing Python-2.7.14...
Installed Python-2.7.14 to /home/takatoh/.pyenv/versions/2.7.14

[takatoh@aybesea ~]$ pyenv install 3.6.4
Downloading Python-3.6.4.tar.xz...
-> https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tar.xz
Installing Python-3.6.4...
Installed Python-3.6.4 to /home/takatoh/.pyenv/versions/3.6.4

バージョンの切り替え

[takatoh@aybesea ~]$ pyenv versions
* system (set by /home/takatoh/.pyenv/version)
  2.7.14
  3.6.4

とりあえず、常用には 2.7.14。

[takatoh@aybesea ~]$ pyenv global 2.7.14
[takatoh@aybesea ~]$ pyenv versions
  system
* 2.7.14 (set by /home/takatoh/.pyenv/version)
  3.6.4

OK。当面これで運用してみる。