実行時エラー ‘1004’: アプリケーション定義またはオブジェクト定義のエラーです。

今日も見に来てくださって、ありがとうございます。
お仕事で、ちょっとつまずいたので、まとめます。誰かのお役にたつとうれしいです。

 あ、お急ぎの方に、結論から言いますと、Microsoft Excel VBAマクロでセルに正しくない計算式をセットするとこのエラーを発生させてしまいます。
 例:

Range("A1").Value = "=SUM([3:[10)"

 業務でマクロ、普通に作られていますね。ある日、「実行時エラー ‘1004’:」なんて発生して動かなくなると、ショックですよねぇ。このエラー、Google先生に聞いてみたのですけど、どうして発生するのかという条件がありすぎるせいか、原因を特定するのが難しいエラーのようです。通常のエラーだと、マクロが発生個所を教えてくれるのですが、関数内で「On Error GoTo 」が記述されてあったので、このエラー、どこで発生したのか分かりませんでした。なので1行ずつ自分でステップ実行していって、発生個所を特定する必要がありました。
 今回エラーが発生したマクロのイメージは以下のような感じです。商品ごとに毎月の売り上げがあって、1年間の合計欄を作成するようなケースです。

Sub 年間合計欄をセット()
    ' 開始行、開始列から下にある12か月分の合計セル(=SUM())を商品店数分セットします。
    Dim i As Integer
    Dim 商品点数 As Integer
    Dim 開始行 As Long
    Dim 開始列 As String
    Dim 出力列 As String
    Dim 計算式 As String

    商品点数 = 5
    開始行 = 3
    開始列 = "C"
    For i = 1 To 商品点数
        出力列 = Chr(Asc(開始列) + i - 1)
        計算式 = "=SUM(" & 出力列 & Format(開始行) & ":" & 出力列 & Format(開始行 + 12 - 1) & ")"
        Range(出力列 + Format(開始行 + 12)).Value = 計算式
    Next
 End Sub

 ここでのポイントは、「出力列」の算出方法です。「開始列」”C”は、Asc関数で67になります。これは”C”をASCII文字コードに置き換えています。これに順次数字を加えていって、商品点数5の場合、”C”(67)、”D”(68)、”E”(69)、”F”(70)、”G”(71)が出力列として算出されます。できあがる一つ目の「計算式」は、この場合「=SUM(C3:C14)」になります。一見問題なさそうに見えますね。
 問題点は、列が”Z”(90)までは見つかりません。問題は列がその隣にうつった時、”AA”≠(91)のため発生するのです。今回これを回避するためにエクセルの機能を利用して以下のように関数を作成しました。

'*****************************************************************
'*関数名 :Number2Letter
'*機能概要:入力されたカラム位置の数値からカラム文字の英数字に変換します
'*引数   :カラム位置(1~16384)
'*戻り値 :カラム文字
'*****************************************************************
Function Number2Letter(iCol As Integer) As String
    Number2Letter = Split(Columns(iCol).Address(True, False), ":")(0)
End Function

 あるいは、数値をA~Zの26文字で表現する26進数に変換する、と考えると以下のような関数をつくってもよいかも知れませんね。

Function Number2Letter(iCol As Integer) As String
    If iCol < 1 Then Number2Letter = "": Exit Function
    Number2Letter = Number2Letter(Int((iCol - 1) / 26)) & Chr(Asc("A") + ((iCol - 1) Mod 26))
End Function

 これで、スッキリ解決です。

 ちなみに、今回の問題点をわかりやすくするために、単純化して書いてありますけど、実際にはちょっとした落とし穴があって、エラーは以下のような行で発生していました。簡単にしてますけど、”A:A”,”AA:AA”は複雑な変数で記載されていましたよ。

Range("A:A","AA:AA").Value = Range("A:A","AA:AA").Value

 同じ範囲のレンジを代入しているだけなので、エラーが起きる意味が分かりませんよね。今回、対象商品が増えてエラーが発生したので、すぐに「AA」が怪しいな、とあたりを付けて、「Range(“A:A”,”Z:Z”).Value」と「Range(“AA:AA”).Value」に分けて実行したところ、前者は成功して、後者が失敗したのです。それで、エクセルは「AA」セルのコピーを失敗する不具合があるのかな、と、一つ目の落とし穴に落ちたのでした。
 このValueへのセット、実は、文字列の式を値に変換するために代入しているのでした。そのため、文字列の式に誤りがあると、今回のエラーが発生するわけです。同様のエラーを発生させるステップは以下の通りです。文章だと意味がわかりにくいと思いますので実際に実行してみてくださいね。

Sub test()
    Range("A1").NumberFormat = "@" ' 文字列書式に変更
    Range("A1").Value = "=SUM(]3:]14)" ' 正しくない式を代入
    Range("A1").NumberFormat = "General" ' 書式を戻す
    Range("A1").Value = Range("A1").Value ' エラーが発生します
End Sub

 と、いうことで、処理データが増えたときにカラム方向へセルを拡張させるときには、気を付けましょう、というお話でした。

追記

 このページ、ぼくのブログの中では一番アクセスが多いので、きっとこの問題で悩んでいる人は多いのでしょうね。ということで、またしても発生しましたので原因を追記いたします。

 再現するには、イミディエイトウィンドウで、以下のように実行します。

? Sheet1.Cells(0,1).Value
実行時エラー’1004′

 セルの行列の数値を正しくセットしていない場合にも発生します。オフィス2013のエクセルの場合、行の値の範囲は、1~1048577、列の値の範囲は1~16384の制限があるようです。なので、VBAで変数を使って行列をセットしている場合は注意が必要です。

 不覚にもこのエラーがまた出てしまいましたが、原因は、if文の条件ミスでした。変数に値をセットできておらず、0で実行してしまったのでした。みなさんも、気をつけましょう。

さらに追記

 ここまで読んでくださって、ありがとうございます。さて、ここまで読まれたあなたは、問題が解決したでしょうか?いやいや、まだ解決していない、ということでもしよけらばコメントいただけますでしょうか。Excelの記事は数えるほどしかないホームページ ですが、前述しましたとおり、このページは中でもアクセス数が多いページです。訪れられた方の問題が少しでも多く解決するといいな、という気持ちでやっておりますので、上記以外の問題でも解決できるようにしたいと思います。新たな問題、お待ちしております。

PythonでGUI

 今日も見に来てくださって、ありがとうございます。

 Pythonを使ったGUIプログラミング、何を使ったらいいのでしょうね。ネットを調べてみると、いろいろとあって悩みます。標準搭載のGUIはTkinterなのですが、ビジネスでちょっと使ってみるGUIだと、これで充分ですね。ということで、helpに記載のあった以下の例題を実行してみます。

import tkinter
from tkinter.constants import *
tk = tkinter.Tk()
frame = tkinter.Frame(tk, relief=RIDGE, borderwidth=2)
frame.pack(fill=BOTH,expand=1)
label = tkinter.Label(frame, text="Hello, World")
label.pack(fill=X, expand=1)
button = tkinter.Button(frame,text="Exit",command=tk.destroy)
button.pack(side=BOTTOM)
tk.mainloop()

でました!
定番の、Hello, Worldです♪

 何から始めるのがいいか、ちょっと迷うところではありますが、チュートリアルとかはもう概ね経験済みなので、tkinterのソースを見てみようと思います。ぼくの環境だとインストールされているフォルダは「C:\ProgramData\Anaconda3\lib\tkinter」でした。このフォルダがモジュール名なので、最初に読み込まれる「__init__.py」をざっと見てみました。基本的には、何をやるにも「self.tk.call(…)」と、呼び出しているようです。self.tkの正体は、というと、最初に「_tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)」のように呼び出されています。「_tkinter」は何者かわかりませんが、最初に「import _tkinter」と読み込まれていました。今使っているのがAnaconda3の環境なので、このフォルダ以下を「_tkinter」で検索してみました。すると、 libsフォルダ、DLLsフォルダ から「_tkinter.lib」「_tkinter.pyd」が検索されました。これらのライブラリを使ってます、ということでしょうね。利用者側としてはひとまずこれくらいの理解でよいでしょうか。

 ライブラリを使ってできることは、tkのライブラリがどうなっているのか知る必要がある、ということですね。ライブラリを参照しに行く前に、とりあえず、定義されているclassについて見ていくことにしましょう。っと、一行ずつコピペしましたが、多いなぁ。内部的なクラス(Internal class)、と、書いてあったのは、線を引いていきましょうか。む~、まだよくわかりませんねぇ。親子関係になっているものは、インデントを付けてみましょうか。

class EventType(str, enum.Enum):
class Event:
class Variable:
  class StringVar(Variable):
  class IntVar(Variable):
  class DoubleVar(Variable):
  class BooleanVar(Variable):
class Misc:
class CallWrapper:
class XView:
class YView:
class Wm:
  class Tk(Misc, Wm):
class Pack:
class Place:
class Grid:
  class BaseWidget(Misc):
    class Widget(BaseWidget, Pack, Place, Grid):
    class Toplevel(BaseWidget, Wm):
      class Button(Widget):
      class Canvas(Widget, XView, YView):
      class Checkbutton(Widget):
      class Entry(Widget, XView):
      class Frame(Widget):
      class Label(Widget):
      class Listbox(Widget, XView, YView):
      class Menu(Widget):
      class Menubutton(Widget):
      class Message(Widget):
      class Radiobutton(Widget):
      class Scale(Widget):
      class Scrollbar(Widget):
      class Text(Widget, XView, YView):
class _setit:
        class OptionMenu(Menubutton):
class Image:
  class PhotoImage(Image):
  class BitmapImage(Image):
      class Spinbox(Widget, XView):
      class LabelFrame(Widget):
      class PanedWindow(Widget):

 インデントを付けてみて、なんとなくわかってきました。画面の要素はWidgetを継承していて、→BaseWidget→Miscとなっているので、Miscが一番の先祖になっているのですね。他に、イベントを管理するためのEventクラスがあって、Variableを継承している何らかの値を保持するクラスがあって、Widegetの中でも見え方に特徴のあるものがXViewやYViewを多重継承して見せ方に特徴を持たせてそう。他のWmはウィンドウマネージャーかなぁ、何となくGUIの全体を管理しそうな、、、そして、Pack、Place、Gridは位置決めに使うクラス、あとは、メニュー用とイメージ用、と、ざっとこんな感じでしょうか。
 それ以外にtkinterのフォルダを見てみました。

colorchooser.py ネイティブのカラーダイアログ(Chooserクラス)
commondialog.py 共通のダイアログ関連(Dialogクラス)
constants.py コンスタント値関連
dialog.py ダイアログ関連(Dialogクラス)
dnd.py ドラッグアンドドロップ関連
filedialog.py ファイル関連のダイアログ関連
font.py フォント関連
messagebox.py メッセージボックス関連
scrolledtext.py スクロールするテキスト関連
simpledialog.py 簡単なダイアログ関連
test テスト用かな
tix.py Tkの拡張ウィジェット(3.6から非推奨。使いません。ttkを使いましょう。)
ttk.py Tk8.5から新しく追加された、テーマ付きウィジェットに関するもの
__init.py
__main.py
__pycache

 全体感は、これで何となくわかりました。あとは、個別の利用方法ですかね。

 と、いうことで、最近gitLaboというサービスを触り始めたので、そちらの勉強もかねてもろもろが分かりやすくなるように簡単なサンプル集でもつくりますかねぇ。動くものがないと、理解できないですよね、きっと。
サンプルをつくっていく中で、いろいろと書きたいことが増えてくると思います。

2021年1月10日 追記

 こちらに、クラス図を追記いたしました。概要を把握するのにどうぞ。

Python Programming 動的にclassを作成

今日も見にきてくださって、ありがとうございます。

 勉強会でPythonの講師をさせていただいているので、ここのところPythonについて思いを馳せるタイミングが増えてきています。最近Pythonを使っていて、おお!と思ったことをちょっと書こうと思います。なんと、クラス作成後に属性を追加することができるのです!すべてオブジェクトにすると、こんなこともできてしまうのですね。ちょっといろいろと確認してみます。

 まずは、何もないクラスを作成。そして、インスタンスを作成します。

>>> class Box:
...     pass
... 
>>> box1 = Box()

なんと、このあと動的にアトリビュートを追加することができるのです。

>>> box1.x = 100
>>> box1.y = 120         
>>> print(box1.x, box1.y)
100 120
>>> 

当然、メソッドも追加できますよね?
ということで、やってみます。まず関数を定義して、代入して、実行してみます。

>>> def getpos(self):
...     print(self.x, self.y)
...
>>> box1.getpos = getpos
>>> box1.getpos()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getpos() missing 1 required positional argument: 'self'
>>> </module></stdin>

おっと、うまくいきませんね。ちなみに、この「self」というのは、クラスのメソッドがインスタンスになった時、関数実行時に一つ目のパラメータとしてインスタンスが渡されるために必要になる引数です。ホントはどんな名前でもいいのですけど慣例として「self」とすることになっています。
あ、ここまで書いて気づきました。インスタンスに関数を定義したのがいけないのかも知れません。Boxクラスの方へセットしてみます。

>>> Box.getpos = getpos
>>> box2 = Box()
>>> box2.x = 300
>>> box2.y = 320
>>> box2.getpos()
300 320
>>>              

できました!
このように、クラス作成後に動的にクラスを変更できてしまうので、プログラムを使ってコーディングすることができますね。ちなみに、アトリビュートは文字列を使ってセットできることを確認しました。dir()関数を使って、定義したクラスの中身を調べてみました。

>>> dir(Box)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getpos']
>>> 

名前からアトリビュートっぽいのを探ります。以前いろいろと探して「__dir__」に含まれているのを知っていたので、中を見てみましょう。

>>> dir(Box.__dict__)
['__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'copy', 'get', 'items', 'keys', 'values']

ええと、ディクショナリークラスの中身のようですね。見方が違いました。きっと、こうですね。

>>> Box.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Box' objects>, '__weakref__': <attribute '__weakref__' of 'Box' objects>, '__doc__': None, 'getpos': <function getpos at 0x0000029DA5021E18>})

先程追加した関数getposはありましたが、xとyがありませんね。あ、、、そういえばインスタンスの方にセットしただけでした。では、インスタンスの方を確認しましょう。

>>> box1.__dict__
{'x': 100, 'y': 120, 'getpos': <function getpos at 0x0000029DA5021E18>}

そうそう、こんな感じです。
では、文字列の’x’と’y’を使って、クラスの方へアトリビュートと初期値をセットしてみます。

>>> setattr(Box,'x',10)
>>> setattr(Box,'y',20)
>>> dir(Box)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getpos', 'x', 'y']
>>> box3 = Box()
>>> box3.getpos()
10 20
>>>

はい、見事、xとyがクラスに追加されて、メソッドが最初から利用できるようになりましたね。オブジェクトの場合は「__dict__」を普通のdictと同じようにしてアトリビュートを追加することができたのですけど、クラスの場合の「__dict__」はmappingproxyになっていて読み込みのみでしたので、setattrを使う必要がありました。ちなみに、中を見てみるとどうなっているかというと、

>>> Box.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Box' objects>, '__weakref__': <attribute '__weakref__' of 'Box' objects>, '__doc__': None, 'getpos': <function getpos at 0x0000029DA5021E18>, 'x': 10, 'y': 20})
>>>

こんな感じです。オブジェクトの方にアトリビュートを追加するもう一つの方法は以下の通りです。

>>> box3.__dict__['z'] = -100
>>> box3.__dict__
 {'z': -100}
>>>

おっと、少し想定と違いました。xとyがいませんね。クラスで定義したアトリビュートはここには入らない、ということでしょうか。以下の通り、値は取り出せました。

>>> box3.x
10
>>> box3.y
20
>>> box3.z
-100
>>>

ちょっと、xに値を代入したらどうなるかやってみます。

>>> box3.x = -10
>>> box3.__dict__
{'z': -100, 'x': -10}
>>> 

なるほど、思った通りですね。クラスで定義した値が変更されていなければインスタンスごとにその値を持つ必要がない、ということですね。

そういえは、関数を文字列から作成して組み込むのはどうやるのだろうか、と、気になってまいりましたが、長くなってきたので今回はこれくらいで。

Google Colaboratoryを知る

HAWAII

 今日も見に来てくださって、ありがとうございます。

 Google先生、これまでもいろいろなサービスを提供してくれていますが、この機能、昨日(ダジャレ)初めて知りました。Google Colaboratoryです。何かというと、Googleのアカウントを持っていれば、Jupyter notebookの機能がWebブラウザだけで使える、というサービスです。GPUも使えるそうです。そして、無料です!いや~、Google先生、ありがとうございます。

 知らない人に簡単に説明すると、Jupyter notebookは、対話的にPythonスクリプトを実行することができ、その結果を記録するドキュメントの機能を融合したWeb技術を基盤としたツールです。実行する前の状態をドキュメントにしておいて、説明するときに順番に実行して見せる、という使い方もできるし、説明ドキュメントをメインにして、実行結果を付けて見せる、という使い方もできます。再利用可能という点においても優れており、研究成果や教科書的な媒体としての位置づけを獲得しているそうです。

 ここで使い方の詳細を紹介しようかとも思ったのですけど、サービス上にすでに紹介がありますので、そのリンクを張り付けておきます。

https://colab.research.google.com/notebooks/welcome.ipynb

これで、誰でもJupyter notebookが使えますね。すばらしい。

Pythonでプログラミング キー入力を受け付ける

 今日も読みに来てくださって、ありがとうございます。今回は、Pythonのお話です。

 Pythonでファイルの更新とキー入力待ちを同時にチェックしたいという要望がありました。ファイルの更新チェックは、新しいデータが記録されたのを抜き出したい、ということで、更新確認は、前回タイムスタンプと今回タイムスタンプが異なっていれば、何らかの更新があった、という判断で対応できました。その更新チェックをしながら、キー入力待ちを同時にやりたい、という要望です。

 通常Pythonでキー入力を受け付けるというと、「input()」が思いつきます。こんな感じですね。

>>> a = input('Hit any key:')
Hit any key:aaa
>>> print(a)
aaa

 ただこれだと、「input()」が実行されると先に進まなくなって、待ち状態になってしまいます。そうすると、ファイルのタイムスタンプが更新されたらデータを読み込むという処理をしながらキー入力待ちができないのです。そう、やりたいのは、かつてBASICでINKEY$というコマンドで実装していたように、その関数が実行された時に入力されたキーを知りたい、ということです。調べてみるといろんなやり方があるようでしたが、中でも簡単に実装できたやつをメモしておきます。blessedというモジュールに入っている、Terminalを利用します。「input()」は標準モジュールに含まれていますが、blessedは標準モジュールではないのでpipでインストールする必要があります。

pip install blessed

 このblessedは、cursesモジュールという文字セル表示を扱うためのライブラリをより簡単に使えるようラップしたものだそうです。cursesは、Unix端末で主に利用されているcursesライブラリのインターフェースということでした。もっと簡単に言うと、CUIのインターフェースをうまく扱えるようにするために使えるライブラリですね。
 インストールできたので、さっそく使ってみます。

from blessed import Terminal

t = Terminal()

with t.cbreak():
    while True:
        k = t.inkey(timeout=0.001)
        if not k :
            pass
        elif k.is_sequence:
            if k.name == 'KEY_ESCAPE':
                break
            print(f'"{k.name}"が押されました。終了するには「ESC」キーを押してください。')
        else:
            print(f'"{k}"が押されました。終了するには「ESC」キーを押してください。')

 これでESCキーが押されるまでループを繰り返すようになりました。合間にファイルのタイムスタンプ更新チェックを入れれば要望通りのことができます。
 ただ、このスクリプトの実行に、ぼくの環境ではCPUの使用率が35%ほどかかっていました。もしこれが問題で、使用率を下げたいなら、timeモジュールのsleepを上記のpassのところに記載するとよいでしょう。試しにぼくの環境で入れてみましたが、sleep(0.1)と入れるだけで、CPU使用率が2.5~3%ほどに下がりました。単純にループしてるだけなのに、sleepしないとCPU使用率って意外とかかるのですね。

Pythonでは「クラスもオブジェクト」です

WIKI WIKI SHUTTLE

 今日も見にきてくださって、ありがとうございます。がんばって更新していきます。

 ずっとデータベースの論理設計の勉強会に参加しているのですけど、そちらでしばらくPythonの勉強会をすることになりました。メンバーのなかでは適任者ということで講師に任命されまして、それで先日、第一回目が開催されました。その講習の中でぼくが「Pythonでは、すべてがオブジェクトとして管理されています。」と説明してから、ふと、class作成後、class変更したら、以前のclassとは異なるオブジェクトになっているのだろうか、ということが自分でも気になったので、確認してみました。でも、このタイトルだと、なんのこっちゃ、という感じですねぇ♪

 まず、オブジェクトとはこんな感じのモノです、というイメージがわかりやすいように、listを作成してみます。ご覧のとおり、typeはlistになり、オブジェクトとして管理するためにidが採番されています。

>>> alist = [1,2,3,4,5]
>>> type(alist)
<class 'list'>
>>> id(alist)
3005538914888
>>>
http://pythontutor.com/visualize.html#mode=display
にてイメージ化。

 ちなみに、この中で使われている「1」などの数値もオブジェクトとして管理されています。調べていて面白かったのは、-5~256の範囲の数値はintのオブジェクトとしてあらかじめキャッシュされて使いまわされているそうです。この点は処理系によって実装は異なるのでしょうけど、ぼくの環境では以下のように確かに使いまわされているのが確認できました。

>>> type(1)
<class 'int'>
>>> id(1)
140729051148544
>>> id(alist[0])
140729051148544
>>> id(2)
140729051148576
>>> id(1+1)
140729051148576
>>> id(alist[1])
140729051148576

 では、本題のclassを定義して確認してみます。以下のようなスクリプトを実行してみます。poiクラスを作成してidを確認してから、内容を少し変更したpoiクラスを再作成してもう一度idを確認してみます。

class poi:
    pass

print(type(poi))
print(id(poi))

class poi:
    a = 1

print(type(poi))
print(id(poi)) 
一つ目のクラス
二つ目のクラス

printの実行結果は、以下の通りでした。やっぱり違うモノ(オブジェクト)になってますよね。

出力結果

 ちょっと意外に感じたのは、class定義されたもののtypeが「type」だったことです。やっぱり、<class ‘class’>はちょっとヘンだよねぇ、ということにでもなったのでしょうか?このあたりの呼び名は宗教観(?)の違いで争われそうですね。
 idは予想通り異なっていたので、class定義することで、以前のクラスが別のオブジェクトで上書きされた、という風に考えられますね。あ、、、でも、上書きではありませんでしたね。自分で説明しておいて間違えました。変数名は、オブジェクトに張り付けた付箋のようなイメージです、と説明したのでした。実際にはpoiクラスというクラスのオブジェクトができて、再度、別のpoiクラスというクラスのオブジェクトができる、というのが正しい考え方ですね。ちょっと変数にセットしてみてやってみます。

class poi:
     pass

poiA = poi

class poi:
    a = 1

poiB = poi
クラスが作成されるごとに変数へ格納

 予想通り、「poi class」というオブジェクトが二つできました。先ほどの例ではpoiAに代入しなかったので、最初の「poi class」オブジェクトがガベージコレクトされて捨てられてしまった、ということですね。

xdwlibでDocuworks9の中のテキストを出力

 今日も見にきてくれて、ありがとう。久しぶりの更新です。そのうちまた更新しますので、また見にきてくださいね。

 おしごとの関連で富士ゼロックスさんのドキュワークスでつくられた文書のテキストを抜き出す必要があったので、Pythonスクリプト作成してみました。帳票出力のプログラムを修正したときに、修正前後の差分をとって、チェックするのに便利です。
ご利用は自己責任でどうぞ。

利用するまでに必要な作業は以下の通りです。

1.Pythonのインストール
  これは、本家のサイトをご覧になってがんばってやってください。
  ぼくの環境は、実行時最新の3.7.4をインストールしました。

2.xdwlibのインストール
  「pip install xdwlib」で完了。3.8.2.0がインストールされました。なぜかインストール時にエラーがでましたけど、正常に利用できました。林秀樹さん、ありがとうございます。

from sys import argv, exit
from os.path import basename, isfile, splitext
from xdwlib import xdwopen

def export_xdwfulltext(input_file):

    BASE_FILENAME , ext = splitext(input_file)
    if ext.lower() != ".xdw":
        print("指定されたファイルの拡張子が.xdwではありません。["+ext+"]")
        input("Hit Enter key.")
        return

    if not isfile(input_file):
        print("指定されたファイルが存在しません。["+input_file+"]")
        input("Hit Enter key.")
        return

    OUTPUT_PATHNAME = BASE_FILENAME + ".txt"

    if isfile(OUTPUT_PATHNAME):
        print("出力先のファイルが既に存在します。["+OUTPUT_PATHNAME+"]")
        answer = input("上書きしますか?(Y/N):")
        if answer.upper()[0] != "Y":
            print("処理を中断しました。")
            return

    with open(OUTPUT_PATHNAME,"w",encoding="utf-8") as f,\
         xdwopen(input_file) as doc:
        for p in doc:
            f.write(p.fulltext())
            f.write("\n")


if __name__ == '__main__':

    if len(argv) < 2:
        print(basename(argv[0]),"は、ドキュワークスで出力されたxdwファイルの中のテキストを出力するプログラムです。")
        print("拡張子が.xdwのファイルのみ対象とし、同ファイル名の.txtファイルとして出力します。")
        print("使い方1:",basename(argv[0]),"hoge.xdw ...")
        print("使い方2:",basename(argv[0]),"に、ファイルをドラッグ&ドロップ")
        input("Hit Enter key.")
        exit

    for f in argv[1:]:
        export_xdwfulltext(f)

エンティティの種類

 今日も見に来てくれて、ありがとう。地道に書いていきますので、また見に来てくださいね。先週、700%にユーザー数がいきなり増えていて、びっくりしました。なぜかカナダから、同時期に34ユーザの訪問があったのでした。ま、そのひとたちは、二度と来ないと思いますけど。そう34ユーザーでも700%です♪

 さて、先日エンティティの定義について書きました。エンティティが定義できたら、次はエンティティの種類について検討します。システム設計では、データをマスタ系とトランザクション系というふうに分けて管理することが多いのですが、TM(T字形ER手法)では、エンティティをリソースとイベントの二種類に分けて考えます。リソースはその名の通り、その組織の中で管理されている資源にあたるものになります。イベントは基本的にはその組織の中で発生した出来事(実施した仕事)を記録したものになります。また、イベントはリソースから生成することができるので、全てのリソースを把握することがとても重要なことだと思います。なので、リソースとイベントの数を比べたときに、リソースの数が多ければ、その組織はまだまだ改善の余地(新たに仕事を生み出す可能性が残っている)があり、イベントの数が多ければ、打つ手が少ないのでは、という可能性が考えられます。

 TMでは、エンティティの種類の定義は以下の通りです。このように、明確に定義されているのがTMの特徴です。他の手法でもリソースとイベントを分ける場合はあるようですが、その場合でも例示(社員はリソース、受注はイベント等)のみで、定義は示されていないということだそうです。

イベント:日付(タイムスタンプ)によって並べられる
リソース:イベント以外

 例えば、認知番号が「受注番号」だとエンティティは「受注」になり、「受注日」という属性があれば、そのエンティティはイベント、ということになります。そう、とっても簡単ですね。これで世の中のエンティティをすべてイベントとリソースに分けることができるようになりました!

 めでたし、めでたし、と、なればよかったのですけど、現実世界はそんなに甘くありません。ある日、最初の悩みが生まれました。それは、イベントのふりをしているけれど、イベントではないエンティティがあるんじゃないか、という悩みです。基本的な考え方として、イベントは、取引の実績など、事実を記録していくもの、という特性があり、リソースは、仕事をしていく上で必要な資源、という特性があります。上記のルールどおりに考えると、資源にあたるものだけれどもイベントに分類されてしまうものが出てくるのです。例えば、契約番号に契約日があればイベントです。でも、「契約」エンティティが組織にとって重要な資源として扱われそこからさまざまな仕事が生まれてくるような場合、リソースになるような気がするのですよね。仕事とともに忘れ去られてしまってよい情報ではありませんのでどうしてもリソースとして扱いたくなったのでした。で、よくよく考えてみたところ、日付が表していることがらに違う意味があったのです。

日付の違い:
1.記録のためのタイムスタンプの日付(監査証跡)
2.開始、終了などの、範囲を表す日付(未来日付)

 そう、この「1.」の方がイベントのよりどころになる日付で、「2.」の方は違う、ということを発見したのでした。ぼくが悩んだ今回の「契約日」は、契約を交わしたイベントを記録した日付ではなくて、もうちょっとていねいに表現すると、この日から契約が始まりますよ、という「契約開始日」だった、というわけです。このように日付の意味を考慮した場合には、契約がリソースになることがあるので注意が必要です。契約をリソースとして正しく管理できれば、譲渡や継承の問題も解決しやすくなりそうですね。

 このリソースとイベントの分類、なにがうれしいの?というひともいるかも知れませんので、ちょっとだけ補足します。例えば、海外旅行保険契約やレンタカーの貸渡契約などは、ルール通りイベントで問題ないと思います。ただ、その時に契約したひとの情報をイベントの中に記録しておくだけだと、新たな仕事も生まれません。でも、これら契約者の情報をリソースとして管理できるような仕組みにした場合、新たな可能性が考えられるようになります。例えば、契約者に対して、他の類似商品の販売や、サービス向上の可能性を考えられます。イベントとして埋もれてしまったままだと、その可能性に気づくのはなかなか難しいと思います。システム設計上はもちろんですが、ビジネスとして考えた場合にも、リソースを把握して見える化する、ということがとても重要ですね。

エンティティ

 今日も見に来てくれて、ありがとう。励みになります♪

 先日「エンティティ」という単語を何となく使ってしまいましたが、それほど一般的なことばではないよねぇ、ということが気になっていて、ちょっと補足したいと思います。

 ぼくが初めてエンティティということばを聞いたのはいつでしょうかねぇ、たぶん、システム開発の中で必要だと言われていた、「ER図(Entity-Relationship-Diagram)」というドキュメントからでしょうか。日本語では実体関連図、と翻訳されています。ぼくはこの図を見たとき、リレーショナルデータベースのテーブルとその関係を表す図になっていたので、単純にこう思っていました。

エンティティ    = テーブル
リレーションシップ = テーブルとテーブルの関係

 システム開発の製造工程、プログラムを作成するうえでは、この理解で充分でした。画面や帳票があって、どのテーブルのどの項目が画面や帳票とマッピングされるのか、どんな条件で取り出せばいいのか、ということさえわかればプログラミングできたからです。当時はほとんどのひとがそんな認識だったんじゃないかなぁ。どうしてそんな風になっていたかというと、当時、リレーショナルデータベース(RDB)は新しく登場した概念だったし、システム開発においては、単なるファイルに替わるものとして利用されていたからだと思います。画面のプログラムを作るためにはそこに表示するデータを格納するためのテーブルが必要で、これがなかなか決まらなくて、苦労していたのをよく覚えています。

 TM(T字形ER手法)を習ったときには、エンティティは以下の通り定義されていました。あまりにも簡単で明快!でも、それまでモヤモヤしていたのがスッキリしました!

エンティティ:管理したい対象で、認知番号があるもの

 当時「認知番号」は、個体指定子と言われていました。英語では、entity-setterです。その前は、identifierと言っていました。日本語だと「識別子」という意味になるのですけど、あまり適切な訳ではないということで、当時はidentifierを英語のまま使っていたということでした。このあたり、佐藤正美先生(TM(T字形ER手法)の考案者)が考えられることですので、またちょっとしたら呼び名は変わるかもしれません。いずれにせよ、簡単に言うと、認知番号は、番号、No、ID、コードがついた項目で、これらをもとにエンティティを作る、ということになります。例えば、以下のようになります。

認知番号:顧客番号 → エンティティ:顧客
認知番号:商品No → エンティティ:商品
認知番号:店舗番号 → エンティティ:店舗
認知番号:取引ID → エンティティ:取引

 「エンティティ=テーブル」と思い込んでいて、テーブルって、何を基準に作るんだろうねぇ、と思っていたぼくにはかなりの衝撃でした。それまでぼくが知っていたエンティティは実体と訳されていて「業務におけるひとまとまりのモノ」みたいに言われていたのです。「モノ」ってなんなんだよ、誰がどうやって決めるんだよ!というぼくの疑問のような不満なような問題は、簡単に決着がついたわけです。

 この衝撃の事実を知ったことで、うれしくなったぼくは、ずっといろんなひとに説明して回っていたのですけど、あるときなんと、「そんなの当たり前のことでしょ。どうして大発見みたいな感じなのか、よく分からないんですけど?」というひとが現れました!ぼくにとってのこの大発見は、プログラムもテーブルも作ったことがないひとからすると、別に声を大にして言うようなことでもなんでもなくて、自然なことだったようです。む~、確かに!余計な前提知識があったために、ずいぶんと遠回りしちゃったようです。もうちょっと、素直になろう♪

日頃のトレーニング(日々妄想)

MJ BOOK CAFE

 今日も読みに来てくれてありがとう。来てもらってうれしくなったのでまた記事を書こうと思います。
 どうしてこんなことを書いたかというと、Facebookでつながっている方がブログアクセス数500を超えた、と、大喜びをしていたのです。ぼくのブログは完全に自己満足なものなので、ほとんどのひとが読んでいないだろうなぁ、と、思っていたのです。それでもまあ勉強になるよね、と思ってその方のまねをしてGoogle Analyticsを設定してみたところ、なんと、意外にも毎日ひとりとかふたりとか、ちょくちょくアクセスがあることが発覚しました。(予想通りアクセスのない日もありましたよ。)
 来てもらったひとにあんまりがっかりさせては申し訳ないので、もうちょっと記事を書いていこうかと思った次第です。今後とも、よろしくお願いしますね。

 さて、タイトルの「日頃のトレーニング」について、です。かれこれ20年ほど前、佐藤正美先生の「ビジネスの実態がわかるデータベースの作り方」という早稲田大学エクステンションセンターのセミナーで、衝撃を受けました。野球選手なら、日頃のトレーニングとして色んな練習をしてから、試合に臨みます。それに比べて、練習なしで、いきなり試合に参加してくるエンジニアが多すぎる、というお話をしていただきました。確かに!ぼくはエンジニアとしてということを意識した練習、何にもしてませんでしたね!でも、エンジニアの日頃のトレーニングって何だろうねぇ、というのが、今日のお話です。

 池袋ジュンク堂書店では、書籍などを1万円以上お買い上げの方には、4階にあるMJブックカフェ池袋店のブレンドコーヒー引換券がもらえます。今日はこの引換券を使ってただでコーヒーをいただきました。ただ券でも、レシートをもらったら、必ずトレーニング開始します。

 セミナーで教わったことは、データベースの論理設計をしながらビジネスの分析をする、というもので、まずは「番号、No、IDなどの管理するための番号を認知番号として、エンティティを作る」ということです。この認知番号が取られているモノは、その企業などの組織の中で、管理したいモノであるということが現れているので、まずは、そこを起点にします。

  • 端末番号 → 端末
  • 取引ID → 取引
  • ID → Wi-fi
  • No. → ?

 という感じです。このレシートに情報を出力するためには、店舗も管理しないとねぇ、とか、取引IDとNoはどちらもこのレシートを特定するために使えるけど、その違いは何かなぁ、とか、Noの桁数が多すぎるので、桁の上の方は他の何かを管理している番号かもねぇ、とか、Wi-fiはホントに管理されているかなぁ、管理するとしたらどんな管理方法になるかなぁ、とか、【IK】って何かなぁ、とか、引換券は「割引100%」として入力されているのかぁ、とか、他の割引方法はないのかなぁ、とか、取引IDが連番なら、いつからの連番なのかなぁ、いまお客さんが10人入っていて、一時間ごとに入れ替わるとしたら、一日10時間営業だとおよそ100人、取引IDの138130を100で割ると1381、365日で割ると、およそ4年分くらいなのかなぁ、とか、端末番号には47AEと書いてあるけど、この店舗に1個しかないのに、変な番号だなぁ、どういう付け方をしたらこうなるんだろう、とか、とか、とか、ずっと妄想、いや、これがトレーニングですね。
 こうやって、日々、思考力(妄想力)を鍛えておけば、試合で考えることを要求されたときにも、パッと答えが出せる、ということになりますね。そう考えると、コンサルタントって、妄想力が強いひとに向いた職業なのかなぁ。