[技術講座]
※ Netscape4.Xをご利用の方は、こちら からご覧ください。 なんとか第2回までたどりつきました。今回の「おれはJython」では、Jythonを使ったオブジェクト指向スクリプティングに挑戦します。
オブジェクト指向言語が持っているべき基本的な性質は、抽象化,継承,ポリモフィズム(多態性)です。オブジェクト指向スクリプティング言語を名乗るPython(Jython)でも、もちろんそれらをすべてサポートしています。またJythonでは、それに加えてJava言語で定義されたクラスを利用したり、Javaクラスを継承したPythonクラスを定義することができるようになってます。
オブジェクト指向スクリプト言語のメリットはどこにあるのでしょうか。
スクリプト言語のメリットは、高速試作、即実行できる手軽さです。スクリプト開発者にとっては、設計とコーディングとテストと実行は、ほぼ一連の作業になっていることでしょう。
オブジェクト指向のメリットは、「オブジェクト」という、人間にとってそこそこわかりやすい概念で、システムをとらえる点です。しかし、「オブジェクト」の概念が、最終的にJavaやC++で記述されたソースコードに落ちるまでは、まだまだ大きなギャップがあります。
オブジェクト指向スクリプト言語では、スクリプト言語の特徴を活かし「オブジェクト」のような抽象的な概念を、手軽に実装レベルに落とせるという利点があります。つまりオブジェクト指向スクリプト言語では、「オブジェクト指向分析・設計」と実装のギャップが少ないということです。
スクリプトを作成していると、昔作ったスクリプトを部品として再利用したいという欲求が必ず発生します。こんなときに、コピーアンドペーストするのは達人精神に反します。include文とかrequire文とか、言語レベルでこの手の再利用機構が用意されてないと困ります。
もちろんPython(Jythonも)では、import文をつかうことによって、他のファイルに定義されているスクリプトを取り込むことができます。さらにこのimportされたファイルはモジュールと呼ばれ、それ自身もオブジェクトとして取り扱うことができます。
import文の使い方は以下のとおり
import sys from java import lang #Javaのパッケージをimport
Python(CPython)ではimportすべきファイルを探すためのパスを指定するために、環境変数PYTHONPATHを設定します。
しかし、Jythonではこのパスは、環境変数に設定するのではなくregistryと呼ばれるファイルの中に記述することになります。Jythonがインストールされているディレクトリの中に、registryというファイルがあるはずです。このファイルを開いて、python.pathと記述されている行を編集しましょう。(バックアップは忘れずに)
# My Setting (kanno) python.path=.;c:/home/kanno/jython
例えば、以下のファイルをpython.pathで設定したディレクトリに保存しましょう。
def func_test(): print "test"
例えばJythonのインタプリタ上から利用したい場合は、以下のとおり。
>>> import Sample >>> Sample.func_test() test >>>
importされたファイルはモジュールと呼ばれます。Pythonスクリプト内では、モジュールは、まるでオブジェクトのように見えます。この例では、Sampleというオブジェクトのfunc_test()というメソッドを呼び出しているように見えますね。
モジュール内の関数を直接呼び出せるようにしたい場合は、このようになります。
>>> from Sample import func_test >>> func_test() test
どちらかというと、前者の方が好まれる傾向があるようです。名前の衝突を防ぎ、プログラムの可読性を上げるからでしょうか。
それでは本題のJythonでのオブジェクト指向スクリプティングの基本的な部分を説明していきましょう。
実際に例を見たほうが早いかもしれません。
class Person:
def setName(self,name):
self.name=name
def getName(self):
return self.name
def sayHello(self):
print "Hello!! MyName is" ,self.name
class クラス名:で始まるブロックがクラスの定義文です。もちろんインデントの終了がクラスの定義文の終了を表します。
メソッドの定義は、def メソッド名(self,・・・)で行います。メソッドの第一引数には、このオブジェクト自身selfが入ります。つまりJavaでいうthisに相当するものです。これに関しては、内部構造剥き出しという感じで、一瞬拒否反応が出そうになりますが、まぁ慣れれば平気です。。
また、さきほどのPersonクラスのメソッドsetName(self,name)の中身を見てみると、Pythonにおけるインスタンス変数の取り扱いがわかります。規則は簡単です。つまり「インスタンス変数にアクセスする際には、必ずself.をつける」ということです。self.がついていないものは、そのクラスのインスタンス変数ではないことが一目瞭然です。このメソッドの例では、インスタンス変数self.nameとメソッドの引数nameの違いがすぐにわかりますね。
(他の言語では)一部のコーディング規約に、この手のものがありますが、言語仕様に取り込んでいるのは、なんともPythonらしいところです。
インスタンス変数は、最初に値を代入する瞬間に定義されます。returnに関しては、getName()のあたりを参照してください。
作成されたクラス定義からオブジェクトを生成して利用する方法について説明しましょう。先ほど定義したPersonクラスを例として手順を説明します。
Jython 2.1 on java1.3.0_02 (JIT: null) Type "copyright", "credits" or "license" for more information.
>>> import sample >>>
インスタンスを作成します。importすることによって、sampleモジュールが使えるようになり、Personクラスに対しては、sample.Personでアクセスすることができるようになります。
>>> p = sample.Person() >>> p <sample.Person instance at 3753263>
この場合はモジュールが一種のファクトリのように見えて面白いですね。
>>> p.setName("Jython")
>>> p.getName()
'Jython'
>>> p.sayHello()
Hello!! MyName is Jython
>>>
Pythonでは、すでに名前が予約された特殊なメソッドがあります。コンストラクタや、演算子のオーバーロードを行う際は、これらのメソッドを定義します。
class Person:
def __init__(self):
self.name="Jython"
def setName(self,name):
self.name=name
def getName(self):
return self.name
def sayHello(self):
print "Hello!! MyName is" ,self.name
ここでは、__init__(self)がコンストラクタの定義になります。
このようにPythonでは、一部の特殊なメソッドは__メソッド名__()という名前になっていて、ユーザが必要に応じてオーバーライドすることができます。
Pythonでは、継承は以下のように表現します。Pythonでは、同名のメソッドを持っているクラスのオブジェクトに関しては、継承の階層関係は関係なく呼び出すことが可能です。したがってポリモフィズムに関しては継承の機構を利用しなくても実現できます。Pythonにおける継承の主目的は、実装の再利用と、概念の整理でしょう。
class Student(Person):
def getGrade(self):
return this.grade
def setGrade(self, grade):
self.grade=grade
Jythonならではの機能として、Javaのクラスを継承することができます。
Pythonでは多重継承を許しているので、JavaのクラスとPythonのクラスをミックスインして使うことができます。Javaのクラス同士を親として多重継承して組み合わせることはできないようです。
下記は、Personクラスとjavax.swing.JFrameクラスを混ぜてみた例です。この例のようなことは、あまりやらないでしょうが、、
from javax import swing
class PersonFrame(swing.JFrame,Person):
def __init__(self):
Person.__init__(self)
self.label=swing.JLabel()
self.getContentPane().add(self.label)
self.pack()
def sayHello(self):
self.label.text="Hello!! MyName is" + self.name
self.pack()
self.show()
>>> import sample
>>> p=sample.PersonFrame()
>>> p.setName("Jython")
>>> p.sayHello()
>>>
小さなウィンドウが現れて挨拶をしてくれるはずです。
実は、このプログラムの例では、Jythonならではの記述を見ることができるのですが、詳しい解説は今後にとっておきます。
それでは、もうすこし実践的な例で説明をしていきましょう。ある事情で、CSV(カンマ区切りテキスト)形式のファイルをHTML形式に直す必要があったとします。EXCELで読み込んでHTML形式でエクスポートするなり、いろいろ手段はあるでしょうが、Jythonのスクリプトをつかってツールを作ってみましょう。
1,2,3,4 1,2,3,4 TEST,TEST,TEST,TEST
| 1 | 2 | 3 | 4 |
| 1 | 2 | 3 | 4 |
| TEST | TEST | TEST | TEST |
<table >
<tr>
<td>TEST</td>
<td>TEST</td>
</tr>
<tr>
<td>TEST</td>
<td>TEST</td>
</tr>
</table>
それではざっくりと設計してみましょう。まずは、手を休めてイメージ。
もう少し、具体化してみましょう。必要そうなクラスは、CSVReaderクラス、テーブルの構造を表現する(表、行、セル)クラスといったところでしょうか。
以下が登場人物相関図もといクラス図です。
これらのクラスはそれぞれ、toHTMLメソッドを持ち自分自身が持つ情報をHTMLのテーブル形式にレンダリングします。
Tableの中にはRowが、Rowの中にはCell、という構造を持つことができます。
それでは、この構造をJythonで表現してみましょう。
from java import lang
from java import io
# テーブルクラス
class Table:
def __init__(self):
self.rowList = []
def toHTML(self):
lang.System.out.println("<table>")
for i in self.rowList:
i.toHTML()
lang.System.out.println("</table>")
def addRow(self,row):
self.rowList.append(row)
return self
# 行クラス
class Row:
def __init__(self):
self.cellList=[]
def addCell(self,cell):
self.cellList.append(cell)
return self
def toHTML(self):
lang.System.out.println(" <tr>")
for i in self.cellList:
i.toHTML()
lang.System.out.println(" </tr>")
# Cellクラス
class Cell:
def __init__(self,content):
self.content=content
def toHTML(self):
lang.System.out.println(" <td>"+self.content+"</td>")
Tableの構造を、スクリプト内で組み立てて、出力する部分までテストします。
CSVの事は考えずに、直接スクリプト内でテーブルを組み立て、出力をチェックします。
import Table
if __name__ == '__main__':
table=Table.Table()
row1=Table.Row()
row2=Table.Row()
cell_1_1=Table.Cell("TEST")
cell_1_2=Table.Cell("TEST")
cell_2_1=Table.Cell("TEST")
cell_2_2=Table.Cell("TEST")
table.addRow(row1)
row1.addCell(cell_1_1) \
.addCell(cell_1_2)
table.addRow(row2)
row2.addCell(cell_2_1) \
.addCell(cell_2_2)
table.toHTML()
>jython testTable.py
<table >
<tr>
<td>TEST</td>
<td>TEST</td>
</tr>
<tr>
<td>TEST</td>
<td>TEST</td>
</tr>
</table>
このツールの心臓ことCSVReaderは、CSVファイルを解析してTableの構造を作成します。java.io系の仕組みを利用するために、io.BufferedReaderのサブクラスとしました。(作法的には、Readerインタフェースを実装し、処理をBufferdReaderに委譲すべきだったかも知れません。)
from java import lang
from java import io
import Table
class CSVReader(io.BufferedReader):
def __init__(self,reader):
io.BufferedReader.__init__(self,reader) #スーパクラスのコンストラクタ
self.rows = []
def readRow(self): # 行を読み取り解析 Row(とCell)を作成する
rowString = io.BufferedReader.readLine(self) # javaでいうsuper.readLine()
if (rowString==None):
return None
rowData = Table.Row()
contentList = rowString.split(",")
for content in contentList:
rowData.addCell(Table.Cell(content))
return rowData
CSVReader.pyとTable.pyを利用して、CSV-HTML変換を行うスクリプトです。
from java import lang
from java import io
import Table
import CSVReader
if __name__ == '__main__':
sample = CSVReader.CSVReader(io.FileReader("test.csv"))
table = Table.Table()
row = sample.readRow()
while(row):
table.addRow(row)
row = sample.readRow()
table.toHTML()
HTMLを生成する部分がTable,Row,Cellクラスに直に記述してあるところなど、不満点は多いですが、最初に考えた仕様はとりあえず満たしたようです。これを叩き台に洗練させていくことも可能です。
今回は、Pythonの特徴ともいえるオブジェクト指向スクリプティングについて説明しました。後半の例では、オブジェクト指向スクリプト言語を使った高速プロトタイピングを行いました。このようにJythonなら、短時間でアイディア程度のスケッチから、実際に動くプログラムを、簡単に具現化できます。しかし、この程度のスクリプトなら、クラスなどは必要ない、もっとベタなものでよいのではないか、という意見が出そうですね。
まったくもってその通りです。Pythonでは、オブジェクト(というかクラス)指向のプログラミングスタイルは、強制されているわけではありません。クラスを使いたくない場合は、使わない。オブジェクト指向よりも関数型プログラミングが好きなら、そちらを使う。というように使い方を選ぶ自由があります。
スクリプト言語は、開発者の道具箱に入っていて、好きなように使うことができる道具です。自分が使う道具は、自分が一番慣れたスタイルで使えばよいでしょう。
開発者が、オブジェクト指向に慣れていれば、そちらを使えばよいということですね。(どうしても使いづらければ、他の道具=スクリプト言語を使えばよい)
| © 2002 OGIS-RI Co., Ltd. |
|