PyInstallerでPythonスクリプトをexe化

PythonスクリプトをWindowsのexeにする方法 (調査中) - minus9d's diary にて、Python 3.5のスクリプトWindowsのexe化するにはPyInstallerが良さそうだという記事を書きました。この記事ではPyInstallerを使ってexe化する方法について調査した結果を記します。

スクリプトの用意

数式を微分するスクリプト differentiator.pyを用意します。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from sympy import symbols, diff
from sympy.parsing.sympy_parser import parse_expr

while True:
    x = symbols('x')
    math_expr = input('please input f(x): ')
    print("f'(x) =", diff(parse_expr(math_expr), x))

このスクリプトをコンソールで実行すると

please input f(x):

と聞かれるので、

a * x * sin(x)

などと入力してください。f(x)をxで微分した結果が以下のように得られるはずです。

f'(x) = a*x*cos(x) + a*sin(x)

Ctrl + Cで終了です。このスクリプトをexe化することを目標とします。

環境の用意

Windows 10 + Anaconda Python 64bit 4.1.6 + Python 3.5.1にて

> pip install pyinstaller

としてpyinstallerをインストールしました。現時点で3.2がインストールされました。

exeは生成できるが実行できない

exeを生成するには、コマンドプロンプトにて

> pyinstaller --onefile differentiator.py

とすればOKなはずです。実行すると、大量のWARNINGとErrorが出るものの、dist以下にdifferentiator.exeが生成されました。

しかし、生成されたexeをダブルクリックすると

---------------------------
differentiator.exe - 正しくないイメージ
---------------------------
C:\(省略)\VCRUNTIME140.dll は Windows 上では実行できないか、エラーを含んでいます。元のインストール メディアを使用して再インストールするか、システム管理者またはソフトウェアの製造元に問い合わせてください。エラー状態 0xc000007b。 
---------------------------
OK   
---------------------------

というエラーダイアログが出て、実行に失敗しました。

setuptoolsをダウングレードしてexeの実行に成功

'six' is required; worked fine with previous version of pyinstaller · Issue #1773 · pyinstaller/pyinstaller · GitHubに、setuptoolsのバージョンを19.2に下げると解決したという報告があります。私の環境でも、setuptoolsのバージョンを23.0.0から19.2に下げると、確かにexeが実行できるようになりました。ただし、exeの生成時に大量のWARNINGとErrorが出るのは相変わらずなので、まだ問題が潜んでいる可能性はあります。

以下は、Anacondaで仮想環境を作って試したときのコマンドです。仮想環境の詳細については"conda create"でググってください。

> conda create -n pyinstaller_test python setuptools=19.2 sympy pywin32
> activate pyinstaller_test
> pip install pyinstaller 

PythonスクリプトをWindowsのexeにする方法 (調査中)

表題の方法について調べてみました。Python 3.5で動かすことが目標なのですが、ハマっていてまだ動いていません。以下、調査した内容のメモです。

py2exeを使う方法

py2exeは、Python 2系とPython 3系とで配布場所が異なります。

Python 2系の場合、py2exe download | SourceForge.netからインストーラをダウンロードしてインストールするようです。インストーラの日付が古いですが、2005年時点でpy2exeでPythonのスクリプトを実行ファイル(exe)にする【py2exe】 - ぴよぴよ.pyで動作報告があるので、おそらく動くはずです。

Python 3系の場合、py2exe 0.9.2.0 : Python Package Indexにあるように、pip install py2exeでインストールするようです。ただし、サイトにはPython 3.3 and later are supportedとあるにもかかわらず、Python 3.5では動きませんでした。他にも同様の報告があります(Is there a py2exe version that's compatible with python 3.5? - Stack Overflow)。

cx_Freezeを使う方法

cx_Freezeの現時点での公式リリースは4.3.4です。pip install cx_Freezeでインストールを試みましたが、ビルドでこけました。深追いしていません。

Is there a py2exe version that's compatible with python 3.5? - Stack Overflowの回答者の一人が、cx_Freezeの開発版(多分これ)がPython 3.5に対応していることに気付き、私家製版のビルドをGitHub - sekrause/cx_Freeze-Wheels: cx_Freeze 5.0 for Python 3.5 on Windowsで公開してくれています。これをインストールすれば、Python 3.5でcx_Freezeが使えるようになると思われますが、まだ試せていません。

PyInstallerを使う方法

Welcome to PyInstaller official websiteによると、Python 2.7 and 3.3—3.5に対応しているそうです。pip install pyinstallerでpyinstaller-3.2がインストールできるところまで確認しました。

(追記) PyInstallerでPythonスクリプトをexe化 - minus9d's diaryに、exe化の方法をまとめました。現時点で多少のワークアラウンドが必要です。

Python3では変数名に日本語が使える

Fluent Python - O'Reilly Mediaを読んでてびっくりしたのでメモ。Python 3では変数名にアスキー文字以外も使えます。例えば

# -*- coding: utf-8 -*-

def 階乗(整数):
    if 整数 <= 0:
        return 1
    else:
        return 整数 * 階乗(整数-1)
        
print(階乗(5))

は問題なく実行できて

32

と表示されます。

Fluent Pythonはどのページにも新たな発見があってとてもよい本なのですが、日本語圏ではあまり話題になっていなくて残念です。そのうちレビューしたいと思います。

matplotlibで軸の値が小数になったりオフセット表現になったりするのを止める方法

問題

pythonのmatplotlibで、年ごとの値の変化を表す折れ線グラフを書こうとして以下のコードを書きました。

import numpy as np
import matplotlib.pyplot as plt

x = np.array([2006, 2007, 2008])
y = np.array([35.2, 27.4, 41.2])

plt.plot(x, y)
plt.show()

すると、以下のような期待に反したグラフが表示されてしまいます。 f:id:minus9d:20160421214605p:plain

期待に反しているのは以下の2点です。

  • 横軸の値に小数が出現している
  • 横軸の値からそれぞれオフセット分が引かれている
    • +2.006e3, つまり2006がオフセットとなっています

これを期待通りにするには以下の2種類の方法があるようです。

解決法1 - set_useOffset()とMaxNLocator()を使う方法

Matlabスタイルで書いているとき

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

# X軸の数字をオフセットを使わずに表現する
plt.gca().get_xaxis().get_major_formatter().set_useOffset(False)

# X軸の数字が必ず整数になるようにする
plt.gca().get_xaxis().set_major_locator(ticker.MaxNLocator(integer=True))

x = np.array([2006, 2007, 2008])
y = np.array([35.2, 27.4, 41.2])

plt.plot(x, y)
plt.show()

オブジェクト指向で書いているとき

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

x = np.array([2006, 2007, 2008])
y = np.array([35.2, 27.4, 41.2])

fig, ax = plt.subplots()
ax.get_xaxis().get_major_formatter().set_useOffset(False)
ax.get_xaxis().set_major_locator(ticker.MaxNLocator(integer=True))
ax.plot(x, y)

plt.plot()
plt.show()

結果

期待通りのグラフが描画できました。 f:id:minus9d:20160421215819p:plain

解決法2 - xticks()を使う方法

コメント欄で id:kochory さんに教えていただいた方法です。データの量によってはこの方法で済むこともあります。

Matlabスタイルで書いているとき

plt.xticks()という関数を使うと、X軸のラベルに任意の文字列を表示させることができます。例えば

x = np.array([2006, 2007, 2008])
y = np.array([35.2, 27.4, 41.2])

plt.plot(x, y)
plt.xticks(x, ["year1", "year2", "year3"])
plt.show()

とすると f:id:minus9d:20160422201916p:plain のようにX軸のラベルを変更できます。この関数を利用して

plt.plot(x, y)
plt.xticks(x, x)
plt.show()

と書くと f:id:minus9d:20160422202454p:plain と、意図通りの表示になりました。ここではxticks()の第二引数に数字のリストを与えていますが、このリストの各要素が文字列として評価されてラベルとなっているように見えます。

オブジェクト指向で書いているとき

set_xticks()とset_xticklabels()に分けて呼ぶ必要があるそうです。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

x = np.array([2006, 2007, 2008])
y = np.array([35.2, 27.4, 41.2])

fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xticks(x)
ax.set_xticklabels(x)

plt.plot()
plt.show()

結果は同じなので省略します。

解決法2の補足

解決法2の場合、以下のようにデータ数が多いと

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

x = np.arange(10000,10016,1)
y = np.arange(0,16,1)
plt.plot(x, y)
plt.show()

下図のようにX軸のラベルが混雑して破綻します。 f:id:minus9d:20160423210539p:plain

コメント欄での指摘通り、ラベルを手調整するのが一案です。

x = np.arange(10000,10016,1)
y = np.arange(0,16,1)
plt.plot(x, y)
xlabels = [10000,10005,10010,10015]
plt.xticks(xlabels, xlabels)
plt.show()

とラベルの場所とその値を明示的に指定することで f:id:minus9d:20160423210758p:plain となります。

参考URLs

matplotlibをオブジェクト指向スタイルで使う その2

前にmatplotlibをオブジェクト指向スタイルで使う - minus9d's diaryという記事を書きました。しかし、matplotlib によるデータ可視化の方法 (1) - Qiita および Why do many examples use "fig, ax = plt.subplots()" in Matplotlib/pyplot/python - Stack Overflow によると、plt.subplots()という関数を使うと、より簡潔に書けるようです。この記事ではplt.subplots()の例を紹介します。

なお、以下のコード片では、全て冒頭に

import matplotlib.pyplot as plt
import numpy as np

と書かれているものとします。

一つの図に一つのグラフの場合

まず、一つの図と一つのグラフ領域を持つ絵を、私が前の記事で紹介した方法で描くと以下のようになります。

x = np.arange(0, 4, 0.1)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, x**2)

今回紹介するplt.subplots()を使うと、以下のように一行減り簡潔に書けます。

x = np.arange(0, 4, 0.1)
fig, ax = plt.subplots()
ax.plot(x, x**2)

参考までに、plt.show()して表示されるグラフを示します。 f:id:minus9d:20160421213310p:plain

一つの図に複数のグラフの場合

subplots()に引数を入れると、複数のグラフ領域を持つ絵もかけます。例えば

x = np.arange(0, 4, 0.1)
fig, axes = plt.subplots(2, 2)
for j in range(2):
    for i in range(2):
        ax = axes[j, i]
        ax.plot(x, x**(j*2+i))

とすると、以下のような絵がかけます。

f:id:minus9d:20160420230454p:plain

同じことを行う以下のコードと比較してみてください。

x = np.arange(0, 4, 0.1)

fig1 = plt.figure(1)
ax1 = fig1.add_subplot(221)
ax1.plot(x, x**0)

ax2 = fig1.add_subplot(222)
ax2.plot(x, x**1)

ax3 = fig1.add_subplot(223)
ax3.plot(x, x**2)

ax4 = fig1.add_subplot(224)
ax4.plot(x, x**3)

(2016/4/21追記)ブコメでもっとよい書き方を教えていただきました、ありがとうございます。add_subplot()に3つの引数を与えることでループ化が可能だそうです。

x = np.arange(0, 4, 0.1)
fig1 = plt.figure(1)
for i in range(4):
    ax = fig1.add_subplot(2,2,i+1)
    ax.plot(x, x**i)

もっともこのようにループ化した場合であっても、figureの作成処理とaxの作成処理の2回が必要であり、subplots()を使う場合より繁雑なところは変わりません。

pythonでcsvを読む方法 - 標準ライブラリ, pandas, numpy

pythoncsvを読み込む方法についてまとめました。ライブラリによって微妙に読み込み方が異なるので大変です。

この記事では、以下のdata.csvを読み込む場合を考えます。最初の行がヘッダ行で、それ以降の行がデータ行です。

a,b,c
2,5.6,1
1,7.0,0
3,6.2,1
3,7.9,1

方法1: 標準ライブラリのcsvを使う方法

csv.readerオブジェクトを使って一行ずつ読んでいく方法です。ヘッダ行の部分を特別扱いする必要があります。

import csv
def open_with_python_csv(filename):
    data = []
    with open(filename, 'r') as filename:
        reader = csv.reader(filename)
        # ヘッダ行は特別扱い
        header = next(reader)
        # 中身
        for row in reader:
            data.append(row)
    return header, data

header, data = open_with_python_csv(csvfile)

header, dataをprintすると以下のようになります。

header: ['a', 'b', 'c']
data: [['2', '5.6', '1'], ['1', '7.0', '0'], ['3', '6.2', '1'], ['3', '7.9', '1']]

文字列ではなく数字が必要な場合は別途手動で変換が必要です。

高機能とはいえませんが、標準ライブラリのためどの環境でも使えるのが強みと言えます。

方法2: pandasを使う方法

データ分析ツールのpandasを使うこともできます。

import pandas

def open_with_pandas(filename):
    df = pandas.read_csv(filename)
    header = df.columns.values.tolist()
    data = df.values
    return df, header, data

df, header, data = open_with_pandas(csvfile)

header, dataをprintすると以下のようになります。

header: ['a', 'b', 'c']
data: [[ 2.   5.6  1. ]
 [ 1.   7.   0. ]
 [ 3.   6.2  1. ]
 [ 3.   7.9  1. ]]

header, dataの型はそれぞれlist, numpy.ndarrayです。 数値が自動的に適当な型で保持されています。数値の型を指定することもできますが本記事の内容を超えます。 pandasを使ってデータの処理を行いたいときにはよい選択です。

方法3: numpyを使う方法

numpyにも、csvの読み込みに使える関数がloadtxt()とgenfromtxt()の2つ存在します。

numpy.loadtxt()を使う場合

もしヘッダ行が不要な場合は、以下のようにskiprowsを設定すればOKです。デリミタを明示的に指定する必要があるのに注意が必要です。

import numpy as np
def open_with_numpy_loadtxt(filename):
    data = np.loadtxt(filename, delimiter=',', skiprows=1)
    return data

data = open_with_numpy_loadtxt(csvfile)

dataの型はnumpy.ndarrayです。

ヘッダ行も読み込みたい場合は、以下の2通りの方法があるようです。まず一つ目。

import numpy as np
def open_with_numpy_loadtxt_2(filename):
    with open(filename, 'r') as file:
        line = file.readline()
        header = line.strip().split(',')
        data = np.loadtxt(file, delimiter=',')
    return header, data

header, data = open_with_numpy_loadtxt_2(csvfile)

そして二つ目。

import numpy as np
def open_with_numpy_loadtxt_3(filename):
    with open(filename, 'r') as file:
        lines = list(file)
    header = lines[0].strip().split(',')
    data = np.loadtxt(lines[1:], delimiter=',')
    return header, data

header, data = open_with_numpy_loadtxt_3(csvfile)

numpy.genfromtxt()を使う場合

genfromtxt()はloadtxt()に比べて、欠損値を埋める機能などがあり高機能だそうです。

まずはヘッダ行を読み飛ばす方法です。デリミタの指定が必要なのはloadtxt()と同じです。

def open_with_numpy_genfromtxt(filename):
    data = np.genfromtxt(filename, delimiter=',', skip_header=1)
    return data```

data = open_with_numpy_genfromtxt(csvfile)

ヘッダ行を解析する方法がまだ分かっていません。genfromtxt()にnames=Trueという引数を加えると、コラムに名前が付いたnumpy.ndarrayが得られるのですが、二次元配列ではなくなってしまいます。

import numpy as np
def open_with_numpy_genfromtxt_2(filename):
    data = np.genfromtxt(filename, delimiter=',', names=True)
    header = data.dtype.names
    return header, data

header, data = open_with_numpy_genfromtxt_2(csvfile)
# data[0]['a']などとアクセス可能

全コード

最後に、この記事を書くのに使ったスクリプトを貼っておきます。

read a csv file using python

Emacsに入ってる人工無脳ELIZAと対話する

AlphaGo | Google DeepMindと人類の戦いが最高潮なのにかこつけて、人工知能ネタを紹介します。

EmacsELIZA(イライザ)と呼ばれる、簡素な対話プログラムが入っているのはご存知でしょうか? ELIZAはJoseph Weizenbaumによって1964から1966年に書かれたプログラムで、人間の入力した言葉に対して、サイコセラピストであるかのような返答を返すというプログラムです。Emacs版の実装を試すには、M-x doctorとします。すると

I am the psychotherapist.  Please, describe your problems.  Each time
you are finished talking, type RET twice.

と、問診が始まります。「頭痛がする」と返してみましょう。

(私) I have a headache.

(ELIZA) Maybe you should consult a medical doctor; I am a psychotherapist. .

医者にかかれと怒られてしまいました。では、今度は「気持ちが落ち込んでいる」と返してみます。

(私) I am depressed.

(ELIZA) Are you depressed often?

会話を続けてみます。

(私) Yes.

(ELIZA) I see...  Well, why do you say this is so?

(私) I have a lot of work.

(ELIZA) What makes you believe you have a lot of work?

この辺で早くも限界を感じますが、続けてみます。

(私) I have to overwork every day.

(ELIZA) Earlier you said you felt depressed?

(私) Yes.

(ELIZA) Yes, hmmm... so, are you sure that this is so?

(私) Yes.

(ELIZA) I understand.  So do you really think this is so?

(私) Yes.

(ELIZA) Oh. ...And what makes you think this is so?

堂々巡りになり埒が明きません。目先を変えて、「死にたい」と伝えてみます。

(私) I want to die

(ELIZA) This is not a healthy way of thinking.

さらに直球で自殺をほのめかすと

(私) I wanna commit suicide.

(ELIZA) If you are really suicidal, you might want to contact the Samaritans
via E-mail: jo@samaritans.org or, at your option, anonymous E-mail:
samaritans@anon.twwells.com .  Or find a Befrienders crisis center at
http://www.befrienders.org/ .  I would appreciate it if you would
continue.

こころの相談室的なところを紹介されてしまいました。急にマジレスされて驚きました。

ソースコードらしきものがEmacs Doctorにあります。これを見るとELIZAが反応しそうな単語が分かります。

最後に、さよならの挨拶をしてみましょう。

(私) Bye!

(ELIZA) My secretary will send you a bill.