ObjectSquare [2002 年 10 月号]

[技術講座]

Powered by SmartDoc

おれはJython第2回

Thu Sep 26 16:11:20 JST 2002
YojiKanno
http://www.ogis-ri.co.jp/otc/hiroba/index.html

目次

※ Netscape4.Xをご利用の方は、こちら からご覧ください。 なんとか第2回までたどりつきました。今回の「おれはJython」では、Jythonを使ったオブジェクト指向スクリプティングに挑戦します。

Jythonでオブジェクト指向スクリプティング

オブジェクト指向 on Jython

オブジェクト指向言語が持っているべき基本的な性質は、抽象化,継承,ポリモフィズム(多態性)です。オブジェクト指向スクリプティング言語を名乗るPython(Jython)でも、もちろんそれらをすべてサポートしています。またJythonでは、それに加えてJava言語で定義されたクラスを利用したり、Javaクラスを継承したPythonクラスを定義することができるようになってます。

スクリプト言語とオブジェクト指向

オブジェクト指向スクリプト言語のメリットはどこにあるのでしょうか。

スクリプト言語のメリットは、高速試作、即実行できる手軽さです。スクリプト開発者にとっては、設計とコーディングとテストと実行は、ほぼ一連の作業になっていることでしょう。

オブジェクト指向のメリットは、「オブジェクト」という、人間にとってそこそこわかりやすい概念で、システムをとらえる点です。しかし、「オブジェクト」の概念が、最終的にJavaやC++で記述されたソースコードに落ちるまでは、まだまだ大きなギャップがあります。

オブジェクト指向スクリプト言語では、スクリプト言語の特徴を活かし「オブジェクト」のような抽象的な概念を、手軽に実装レベルに落とせるという利点があります。つまりオブジェクト指向スクリプト言語では、「オブジェクト指向分析・設計」と実装のギャップが少ないということです。

importと環境設定

importとは

スクリプトを作成していると、昔作ったスクリプトを部品として再利用したいという欲求が必ず発生します。こんなときに、コピーアンドペーストするのは達人精神に反します。include文とかrequire文とか、言語レベルでこの手の再利用機構が用意されてないと困ります。

もちろんPython(Jythonも)では、import文をつかうことによって、他のファイルに定義されているスクリプトを取り込むことができます。さらにこのimportされたファイルはモジュールと呼ばれ、それ自身もオブジェクトとして取り扱うことができます。

import文の使い方は以下のとおり

importの例
import sys
from java import lang  #Javaのパッケージをimport

python.pathの設定

Python(CPython)ではimportすべきファイルを探すためのパスを指定するために、環境変数PYTHONPATHを設定します。

しかし、Jythonではこのパスは、環境変数に設定するのではなくregistryと呼ばれるファイルの中に記述することになります。Jythonがインストールされているディレクトリの中に、registryというファイルがあるはずです。このファイルを開いて、python.pathと記述されている行を編集しましょう。(バックアップは忘れずに)

設定例 パスは好きなところに設定してください
# My Setting (kanno)
python.path=.;c:/home/kanno/jython

利用例

例えば、以下のファイルをpython.pathで設定したディレクトリに保存しましょう。

Sample.py
def func_test():
  print "test"

例えばJythonのインタプリタ上から利用したい場合は、以下のとおり。

利用例
>>> import Sample
>>> Sample.func_test()
test
>>>

importされたファイルはモジュールと呼ばれます。Pythonスクリプト内では、モジュールは、まるでオブジェクトのように見えます。この例では、Sampleというオブジェクトのfunc_test()というメソッドを呼び出しているように見えますね。

モジュール内の関数を直接呼び出せるようにしたい場合は、このようになります。

from importの使い方
>>> from Sample import func_test
>>> func_test()
test

どちらかというと、前者の方が好まれる傾向があるようです。名前の衝突を防ぎ、プログラムの可読性を上げるからでしょうか。

(1)

  1. 前回、javaのライブラリを利用するためにfrom java.util import *としていましたが、from java import utilとして、util.ArrayList()といった形で利用するほうが一般的なようです。

Jythonによるオブジェクト指向の基本

それでは本題の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クラスを例として手順を説明します。

  1. Personクラスを、sample.pyというファイル名でpython.pathで指定したディレクトリに保存します。
  2. jythonインタプリンタを立ち上げます
    Jython 2.1 on java1.3.0_02 (JIT: null)
    Type "copyright", "credits" or "license" for more information.
    
  3. 以下のようにimport文を入力します
    >>> import sample
    >>>
    
  4. インスタンスを作成します。importすることによって、sampleモジュールが使えるようになり、Personクラスに対しては、sample.Personでアクセスすることができるようになります。

    >>> p = sample.Person()
    >>> p
    <sample.Person instance at 3753263>
    

    この場合はモジュールが一種のファクトリのように見えて面白いですね。

  5. メソッドの呼び出しを行います。メソッドの定義時には、selfを意識していましたが、呼び出し時には指定しないことを確認してください。
    >>> 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

Javaとの接点

Jythonならではの機能として、Javaのクラスを継承することができます。

Pythonでは多重継承を許しているので、JavaのクラスとPythonのクラスをミックスインして使うことができます。Javaのクラス同士を親として多重継承して組み合わせることはできないようです。

下記は、Personクラスとjavax.swing.JFrameクラスを混ぜてみた例です。この例のようなことは、あまりやらないでしょうが、、

JavaのクラスとPythonクラスのミックスイン
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ならではの記述を見ることができるのですが、詳しい解説は今後にとっておきます。

実践例

CSV2HTMLの作成

それでは、もうすこし実践的な例で説明をしていきましょう。ある事情で、CSV(カンマ区切りテキスト)形式のファイルをHTML形式に直す必要があったとします。EXCELで読み込んでHTML形式でエクスポートするなり、いろいろ手段はあるでしょうが、Jythonのスクリプトをつかってツールを作ってみましょう。

CSV2HTMLの仕様

CSVの例
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>

CSV2HTMLの設計

それではざっくりと設計してみましょう。まずは、手を休めてイメージ。

もう少し、具体化してみましょう。必要そうなクラスは、CSVReaderクラス、テーブルの構造を表現する(表、行、セル)クラスといったところでしょうか。

Tableクラス
表全体の情報をもち、HTML形式での出力可能。Rowを持つ。
Rowクラス
行の情報をもち、HTML形式での出力(TRタグに相当)。Cellを持つ。
Cellクラス
Cellの情報をもち、HTML形式での出力(TDタグに相当)。肝心の内容を持つ。
CSVReaderクラス
パーサ役。コンパイラが構文木を組み立てるかの如く、CSVを読み取ってテーブルの構造を組み立てる。処理の主役。

以下が登場人物相関図もといクラス図です。 

クラス図

Tableクラス、Rowクラス、Cellクラスの試作

これらのクラスはそれぞれ、toHTMLメソッドを持ち自分自身が持つ情報をHTMLのテーブル形式にレンダリングします。

Tableの中にはRowが、Rowの中にはCell、という構造を持つことができます。

それでは、この構造をJythonで表現してみましょう。

Table.py
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>

CSVパーサの設計

このツールの心臓ことCSVReaderは、CSVファイルを解析してTableの構造を作成します。java.io系の仕組みを利用するために、io.BufferedReaderのサブクラスとしました。(作法的には、Readerインタフェースを実装し、処理をBufferdReaderに委譲すべきだったかも知れません。)

Table.py
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変換を行うスクリプトです。

Table.py
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では、オブジェクト(というかクラス)指向のプログラミングスタイルは、強制されているわけではありません。クラスを使いたくない場合は、使わない。オブジェクト指向よりも関数型プログラミングが好きなら、そちらを使う。というように使い方を選ぶ自由があります。

スクリプト言語は、開発者の道具箱に入っていて、好きなように使うことができる道具です。自分が使う道具は、自分が一番慣れたスタイルで使えばよいでしょう。

開発者が、オブジェクト指向に慣れていれば、そちらを使えばよいということですね。(どうしても使いづらければ、他の道具=スクリプト言語を使えばよい)

リソース




記事の内容を5点満点で評価してください。
1点 2点 3点 4点 5点
記事に関するコメントがあれば併せてご記入ください。
  

© 2002 OGIS-RI Co., Ltd.
Next Index
Prev. Index