ASOP(Alternative Script Organizer for Python)を使ってみる

LibreOfficeのマクロをPythonで作るときに使えるユーティリティーにASOPというLibreOffice用の拡張機能がある。

ASOPの詳しい説明はここにある。

【準備】

早速最新版のapso.oxtファイルをダウンロードする。

LibreOfficeへの登録は

  • libreOfficeのアプリケーションの一つを開く。プルダウンメニューの「ツール」->「拡張機能マネジャー」
  • 登録済みの機能が表示される。
  • 追加ボタンを押してダウンロードしたaop.oxtファイルを指名する。
  • 登録が終わるとASOPが登録済みとなる。この窓の左下のあるオプションボタンを選択して、コードを記述するためのエディターを登録する(terapadを登録してみた)。

【実行】

早速ASOPを使ってみる。

Calcの「ツール」->「マクロ」->「Pythonスクリプトの管理」を選択する。ASOPの管理窓がでるので実行したいマクロを選択する。管理窓の「メニュー」から「デバッグ」を選ぶと以下のようなデバッガの画面がでる:

デバッグ画面

この画面で「端末」ボタンを押すとPythonコンソールの別画面が出る。「次へ」のボタンでプログラムの一行毎の実行と変数の値等が表示される。apso.oxtのインストールと同様にMRI.oxtをインストールしておくとこのデバッガの画面で変数の名前空間の性質も調べられる。「編集」ボタンで登録したエディタでマクロの編集ができる。「再スタート」ボタンを押すと編集の結果が反映される。「終了」ボタンを押すとデバッガは終了する(このデバッガはかなり有効に使える感じである)。またマクロにprint文があるとASOP管理下ではコンソールその出力がでる(これはprint文の多用したデバッグには便利)。

Pythonでマクロ(0):LibreOfficeのマクロを書いてみる

久ぶりのPythonの話題である。

LibreOfficeはそのマクロをPythonで書くことができるが、pythonが元々機能として持っていたprint文などがそのままでは使えない。このままではユーザインタフェースやデバッグ機能に欠けた状況でPythonマクロはあまり魅力的ではない。

調べてみるとprint文などに対応するユーザフェース機能をマクロが使うモジュールとして用意することできることがわかった。少し面白くなった。まずマクロのサンプルを示す:


import uno
import screen_io as ui


def my_first_macro_calc():
    doc = XSCRIPTCONTEXT.getDocument()
    for num, column in enumerate(['A1', 'A2', 'A3']):
        cell = doc.Sheets[0][column]
        cell.Value = num 
        ui.Print('OK?')
    return

このマクロの保存場所は(windowsの場合)

C:\Users\~~\AppData\Roaming\LibreOffice\4\user\python

である。

これはLibreOfficeの表計算ソフトCalcのシート番号0のシートの’A1’欄に数値0、’A2’欄に数値1、’A3’欄の数値2を入れる簡単なマクロである。

ui.Print(‘OK’)は数値を書き込む毎に警告窓を出す。ここではデバッグ機能として使っている。この関数Printはscreen_ioモジュールで定義されていて、import screen_io as uiとしてインポートされてされている。import unoがシステムモジュールのインポートであるのに対してimport screen_ioはユーザモジュールのインポートである。このモジュールのの実体はここにある。このモジュールの保存場所は

C:\Users\~~\AppData\Roaming\LibreOffice\4\user\python\pythonpath

である。

このモジュールはbasic言語で作ったモジュールのラッパーらしい。このbasic言語のモジュールの実体もここにある。このモジュールも保存する。保存する場所はLibreOfficeのプルダウン・メニュのマクロで

マイマクロ->standard->module1と進み、編集ボタンで編集画面になるので実体をペーストし、保存する(モジュール名をuiScriptsとすることを忘れないで)。

以上でPrint関数を含むマクロを実行できる環境ができあがった。

最後にPrint関数の実行結果の画面を示す:

【蛇足】pythonでマクロを書くときの注意:

  • indentはTabキーではダメで半角スペースを使う。
  • Pythonでよく使うprint文は使えない
  • 登録しようとしているマクロに構文エラーがあるとマクロの登録すらできない。しかもその構文エラーを知る手掛りがない

 

 

ディジタル化:センサー・マニピュレータ・小規模ネットワーク・ビッグデータ

盛んに「ディジタル化」が叫ばれている。

この「ディジタル化」は国民の生活の向上と生産活動の効率化に資するものでなければならない。

それに必要なものがセンサー・マニピュレータ・小規模ネットワーク・ビッグデータ処理技術である。

例えば農業で温室の管理を「ディジタル化」しようとする。

センサー:温室管理には温室内に設置された様々なセンサーからの情報が必要となる。温度・湿度・雨量・日照・酸素濃度など温室内の多くの場所に設置したセンサーが時系列にデータ吐き出す。

マニピュレータ:センサーからの情報に従って換気、水遣り装置を操作する多数のマニピュレータが必要となる。

小規模ネットワーク:センサー類及びマニピュレータは小規模のネットワークとして組織化される必要がある。

ビッグデータ処理技術:大量の(時系列)データが得られるがこの膨大なデータから必要な情報を取り出す技術も不可欠である。

これらの技術の集積で温室の自動管理が可能となる。このように国民生活・生産活動の多くの分野でディジタル技術を適用することが「ディジタル化」である。

Python備忘録:ジェネレータの実際

ジェネレータはnext関数を使って呼び出す。この関数の呼び出し毎にジェネレータは一定の長さのデータを吐き出す。この機構を使って動画ファイルをフレーム毎に読みこむことにする。このような例をimageioモジュールで見てみる。このモジュールは様々な画像ファイル(動画ファイルを含む)を処理することができる。今回の例は動画ファイルを読み込みヴィデオ表示するPythonプログラムである。

プログラム例


#coding: utf-8
import tkinter as tk, threading
import imageio
from PIL import Image, ImageTk

video_name = "test.mp4" #ここにヴィデオ・ファイル名を書く
video = imageio.get_reader(video_name)
gen = video.iter_data()
def stream(label):
    def showlabel():
        global count
        try:
            image=next(gen)
        except: 
            return
        frame_image = ImageTk.PhotoImage(Image.fromarray(image))
        label.config(image=frame_image)
        label.image = frame_image
        label.after(14, showlabel) #フレーム・レイトの調整
    showlabel()

if __name__ == "__main__":

    root = tk.Tk()
    root.title('TKinterでヴィデオを見る')
    my_label = tk.Label(root)
    my_label.pack()
    thread = threading.Thread(target=stream, args=(my_label,))
    thread.daemon = 1
    thread.start()
    root.mainloop()

赤い文字の部分がジェネレータに関連する部分で、video = imageio.get_reader(video_name)はファイルを読み込む機構の設定で、video.iter_data()でその機構にジェネレータの性格を付与している。

iter_data()
Iterate over all images in the series. (Note: you can also iterate over the reader object.)

と説明がある。
image=next(gen)でフレーム毎にデータを読み込みそれをimageに代入している。フレーム毎にデータを読みこみデータが尽きるとエラーになるのでエラー処理をしている。

Python備忘録:関数でジェネレータをつくる

関数でジェネレータをつくることもできる。


def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

この関数ではreturn文の代わりにyield文を使う。yield文がfor文の内部にあることに注意。yield文に達するとこの関数は一時停止し値を返すが、この関数のローカル変数などは保持されたままである。この一時停止の解除はこの関数を呼んだプログラムの要請による(同期している)。
上の関数の実行例を示す。


>>> s = reverse('golf')
>>> s
<generator object reverse at 0x0000000002ED07C8>
>>> next(s)
'f'
>>> next(s)
'l'
>>> next(s)
'o'
>>> next(s)
'g'
>>> next(s)
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in 
    next(s)
StopIteration
>>> 

 

Python備忘録:クラス定義によるジェネレータ

ジェネレータの性格を持ったクラスを定義することができる。例を示す:


class Reverse:
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

このクラス定義で__next__メソッドがあることが重要である。このメソッドがnext関数の定義をなしている(これはクラスの__str__メソッドがそのクラスの固有のprint関数の定義になっていることと同じである)。__iter__メソッドも同様にiter関数を定義するものであるが今は何もしない。
結果は


>>> s = Reverse('flog')
>>> next(s)
'g'
>>> next(s)
'o'
>>> next(s)
'l'
>>> next(s)
'f'
>>> next(s)
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in 
    next(s)
  File "<pyshell#0>", line 11, in __next__
    raise StopIteration
StopIteration
>>> 

となる。

Python備忘録:イテレータとジェネレータ

  • イテレータとは、プログラミング言語において配列やそれに類似する集合的データ構造(コレクションあるいはコンテナ)の各要素に対する繰り返し処理の抽象化である。 … 実際のプログラミング言語では、オブジェクトまたは文法などとして現れる。 JISでは反復子(はんぷくし)と翻訳されている。

Pythonに即してみると:


>>> a='abcd'

>>> for a in a:
	print(a)
a
b
c
d
>>> 

より基本的なイテレータはiter関数を使うものである:


>>> d=dict([(1,'one'),(2,'two')])
>>> d
{1: 'one', 2: 'two'}
>>> it = iter(d.items())
>>> next(it)
(1, 'one')
>>> next(it)
(2, 'two')
>>> next(it)
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in 
    next(it)
StopIteration
>>> 
  • ジェネレータは、プログラムにおいて、数列の各要素の値などを次々と生成(ジェネレート)し他の手続きに渡す、という機能を持っている手続きである。つまりイテレータを生成する手続きである。大事なことはジェネレータは要請がある毎に一つ一つ要素を生成することである。

Pythonチュートリアル」(Guido van Rossum著)で示した例:


def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

for char in reverse('golf'):
    print(char)

ジェネレータを一行で書くこともできる。


>>> g = (x**2 for x in range(5))
>>> g
<generator object <genexpr> at 0x0000000002ED07C8>
>>> next(g)
0
>>> next(g)
1
>>> 

ジェネレータは大きなファイルを要請のある毎に読むなどのとき便利な機能である。をみよ。

 

Pythonの話題:辞書の結合(付録)

二つの辞書の結合を議論して三つの方法があることがわかった。

今回はたまたま二つの辞書でキーが同じものがあったときの振る舞いを調べる:
例題


>>> d1=dict()
>>> d1['one'] = 'イチ'
>>> d1['two'] = 'ニ'
>>> d2=dict()
>>> d2['one'] = 'ヒトツ'
>>> d2['three'] = 'サン'
>>> d=dict(**d1, **d2)
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in 
    d=dict(**d1, **d2)
TypeError: type object got multiple values for keyword argument 'one'
>>> d=dict(list(d1.items()) + list(d2.items()))
>>> print(d)
{'one': 'ヒトツ', 'two': 'ニ', 'three': 'サン'}
>>> d1.update(d2)
>>> print(d1)
{'one': 'ヒトツ', 'two': 'ニ', 'three': 'サン'}
>>> 
  • 形式d=dict(**d1, **d2)ではエラーになる。
  • 形式d=dict(list(d1.items()) + list(d2.items()))では上書きされる。
  • 形式d1.update(d2)では予想したしたように上書きされる。

Pythonの話題:辞書の結合(再論)

辞書の結合を議論した。「ばらす」演算子(**)を使った以下のような方法を紹介した。


d = dict(**d1, **d2)

これはスマートに見える方法であるが、万能ではない。

例を示す:


>>> d1 = dict()
>>> d1[1]='one'
>>> d1[2]='two'
>>> d2=dict()
>>> d2[3]='three'
>>> d2[4]='four'
>>> d=dict(**d1, **d2)
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in 
    d=dict(**d1, **d2)
TypeError: keywords must be strings
>>

つまりキーが文字列以外のときは上記の方法は使えないことになる。


d1.update(d2)

とする方法もあるが、d1が書き換えられてしまうので面白くない。

以下のような関数はどうだろうか


def dict_merge(d1, d2):
    """二つの辞書d1,d2を結合して新たな辞書d
を返す関数"""
    d = dict()
    for key, item in d1.items():
        d[key] = item
    for key, item in d2.items():
        d[key] = item

    return d

d1=dict()
d1[1] = 'one'
d1[2] = 'two'
print(d1)
d2=dict()
d2[3] = 'three'
d2[4] = 'four'
print(d2)

d = dict_merge(d1, d2)
print(d)

追記:
コマンドで書ける:


d=dict(list(d1.items()) + list(d2.items()))

 

Pythonの話題:「ばらす」演算子(**)と(*)

Pythonでは可変長の引数を持つ関数を作ることができる。


def func( *arg, **kwarg):
    print(type(arg), arg)
    print(type(kwarg), kwarg)

func(1, 2, 3, k1=10, k2=20, k3=30)

結果の出力:


<class 'tuple'> (1, 2, 3)
<class 'dict'> {'k1': 10, 'k2': 20, 'k3': 30}
>>>

このように「ばらす」演算子(*)、(**)を使う。*argでは位置が固定されている実引数をタプルの形で引き取ることができ、**kwargでは、キーつきの引数を辞書の形で引き取ることができる。キー引数は文字列になっていることに注意。