Source

JythonBook_KO / JythonAndJavaIntegration.rst

10장. 자이썬과 자바 통합

자바와의 통합은 자이썬 애플리케이션 개발의 심장이라 할 수 있다. 대부분의 자이썬 개발자들은 파이썬 개발자 또는 자바 개발자로서, JVM이 제공하는 매우 다양한 도구들을 활용하는 방법을 모색하고 있거나, 혹은 완전히 다른 환경으로 옮겨가지 않으면서도 파이썬 언어의 장점을 취하고자 하는 사람들이다. 대부분의 자이썬 개발자들은 자바 세계의 방대한 라이브러리를 활용하는 이점 때문에, 그리고 애플리케이션에서 어느 정도 자바 통합의 필요가 있기 때문에 자이썬을 사용하고 있는 것이 사실이다. 당신의 응용 프로그램에서 기존 자바 라이브러리의 일부를 사용하려는 경우나, Java 응용 프로그램에 몇 가지 큰 파이썬 코드를 혼합하는데 관심이 있는 경우에는, 이 장이 큰 도움이 될 것이다.

이 장에서는 자바와 파이썬의 통합에 초점을 두는 한편, 그에 대한 다양한 각도의 접근을 모색할 것이다. 당신은 당신의 자바 애플리케이션 내에서 사용하는 자이썬 코드를 만들기 위해 몇 가지 기술을 배우게된다. 여러분은 자신의 코드를 좀 단순화하고 싶다고 느낄 때가 있을 것이다. 이 장에서는 어떻게 당신은 가능한 한 간단하게 코드를 만들 수 있도록 자바에서 자이썬과 다른 코드의 특정 부분을 작성하는 방법을 보여준다.

당신은 또한 파이썬다운 구문을 사용하는 동안 당신의 자이썬 애플리케이션 내에서 많은 자바 라이브러리의 사용을 만드는 방법을 배울 것이다. 이러한 프로그램을 자바에서 코딩하는 것은 잊어버려라. 자이썬을 사용하면 라이브러리의 자바 구현은 무대 뒤에서 이루어지도록 할 수 있지 않은가? 이 장에서는 어떻게 파이썬 코드를 작성하고, 그 코드로 직접 라이브러리를 사용하는 법을 보여줄것이다.

자이썬 애플리케이션 내에서 자바 사용하기

자이썬 애플리케이션에서 자바를 활용하는 것은, 자이썬 스크립트에서 자이썬 모듈을 사용하는 것처럼 원활하게 이루어진다. 8장에서 배운대로, 단순히 필요한 자바 클래스를 가져와서 직접 사용할 수 있다. 자바 클래스는 자이썬 클래스와 같은 방식으로 호출할 수 있으며, 마찬가지로 메소드도 호출할 수 있다. 당신은 파이썬에서 했던 방식으로 클래스 메소드를 호출하고 매개 변수를 전달한다.

강제 변형Type coercion은 두 언어를 완벽하게 통합하기위해 자바에서 자이썬을 사용할때 많이 발생한다. 다음 표에서, 당신은 파이썬 형식들로 자바 유형들을 강제 변형하고, 그것들을 어떻게 짝짓는지 볼 수 있다. 표 10-1은 자이썬 사용자 안내서에서 발췌한 것이다.

표 10-1. 파이썬과 자바의 유형

자바 유형 파이썬 유형
char String(길이가 1)
boolean Integer(true = not zero)
byte, short, int, long Integer
java.lang.String, byte[], char[] String
java.lang.Class JavaClass
Foo[] Array(Foo 클래스 또는 하위 클래스의 객체를 포함)
java.lang.Object String
orb.python.core.PyObject 변경되지 않음
Foo 자바 클래스 Foo를 나타내는 JavaInstance

자이썬 내에서 자바 활용에 관해 알아둘 또 다른 한가지는 몇 가지 이름짓기 충돌이 있을 수 있다는 것이다. 자바 개체가 파이썬 개체의 이름과 충돌한다면, 자바 개체에 대한 완전한 이름을 표기함으로써 충돌을 해결할 수 있다. 또 다른 방법으로는 8장에서 논의한 'as' 키워드를 사용하여 import된 코드에 대하여 이름을 바꿀 수도 있다.

다음에 나오는 몇가지 예제들에서, 자이썬내에서 자바 개체들을 불러오고, 사용하고 있는 것을 확인할 수 있다.

예제 10-1. 자이썬에서 자바 사용하기

>>> from java.lang import Math
>>> Math.max(4, 7)
7L
>>> Math.pow(10,5)
100000.0
>>> Math.round(8.75)
9L
>>> Math.abs(9.765)
9.765
>>> Math.abs(-9.765)
9.765
>>> from java.lang import System as javasystem
>>> javasystem.out.println("Hello")
Hello

자바 개체를 만든 것을 가지고, 자이썬 애플리케이션 내에서 사용해보도록 하자.

Beach.java

public class Beach {

    private String name;
    private String city;


    public Beach(String name, String city){
        this.name = name;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

}

자이썬에서 Beach.java 사용

>>> import Beach
>>> beach = Beach("Cocoa Beach","Cocoa Beach")
>>> beach.getName()
u'Cocoa Beach'
>>> print beach.getName()
Cocoa Beach

8장에서 배운 것과 같이, 여러분이 할 일은 사용하고자하는 자바 클래스를 CLASSPATH에 두는 것이다. 위의 예제에서는 Beach 클래스를 포함하는 JAR 파일을 만들고, CLASSPATH에 JAR를 넣어두었다.

또한, 자이썬의 클래스를 통해 자바 클래스들로 확장하거나 서브클래스화할 수 있다. 이러면 자이썬 개체들을 사용하고 있는 주어진 자바 클래스의 기능성을 확장할 수 있고, 때때로 매우 도움이 된다. 다음 예제는 몇 가지 계산 기능을 포함하는 자바 클래스를 확장한 자이썬 클래스를 보여준다. 자이썬 클래스는 다른 계산 메소드를 추가하고, 자바 클래스와 자이썬 클래스의 계산 메소드들을 사용한다.

예제 10-2. 자바 클래스 확장

Calculator.java

/**
* 두 가지 간단한 메소드를 포함하는 자바 계산기 클래스
*/
public class Calculator {

    public Calculator(){

    }

    public double calculateTip(double cost, double tipPercentage){
        return cost * tipPercentage;
    }

    public double calculateTax(double cost, double taxPercentage){
        return cost * taxPercentage;
    }

}

JythonCalc.py

import Calculator
from java.lang import Math

class JythonCalc(Calculator):
    def __init__(self):
        pass

    def calculateTotal(self, cost, tip, tax):
        return cost + self.calculateTip(tip) + self.calculateTax(tax)

if __name__ == "__main__":
    calc = JythonCalc()
    cost = 23.75
    tip = .15
    tax = .07
    print "Starting Cost: ", cost
    print "Tip Percentage: ", tip
    print "Tax Percentage: ", tax
    print Math.round(calc.calculateTotal(cost, tip, tax))

결과

Starting Cost: 23.75
Tip Percentage: 0.15
Tax Percentage: 0.07
29

자바 애플리케이션 내에서 자이썬 사용하기

종종, 그것은 자바 애플리케이션 내에서 자이썬을 사용 할 수있는 능력을 가지고하는 것이 편리할 것이다. 아마도 더 나은 자바빈즈 같은 파이썬 구문에 구현될 클래스가 있다. 아니면 일부 자바 로직 이내에 유용하다고 자이썬 코드의 편리한 부분이 있다. 어떤 경우이든 이러한 조합을 달성하기 위하여 사용할 수 있는 여러 가지 방법이 존재한다. 이 섹션에서는, 자바와 함께 자이썬을 사용하는 오래된 기법을 다룬 다음, 현재와 앞으로의 모범 사례를 소개한다. 끝에 가서는, 자이썬 모듈, 스크립트 또는 몇 줄의 코드를 자바 애플리케이션 내에서 사용하는 방법에 대한 이해를 얻게 될 것이다. 또한 자이썬이 이러한 영역에서 어떻게 발전해왔는지에 대하여 전반적으로 이해하게 될 것이다.

개체 팩토리

오늘날 자바 애플리케이션 내에서 자이썬 코드를 통합하는 가장 널리 사용되는 기술은 아마도 개체 팩토리 디자인 패턴일 것이다. 이 개념은 기본적으로 개체 팩토리의 사용을 통해 자바와 자이썬 사이의 원활한 통합을 가능하게 한다. 논리의 서로 다른 구현이 있지만 그것들 모두 결국 같은 결과를 얻게 된다.

개체 팩토리 패러다임을 구현하면, 별도의 컴파일 단계를 거치지 않고도 자바 애클리케이션 내에 자이썬 모듈을 포함시킬 수 있다. 또한, 이 기술은 자바 인터페이스의 사용을 통해 자이썬과 자바 코드를 깔끔하게 통합할 수 있도록 해준다. 이 섹션에서는, 개체 팩토리 기술의 주요 개념을 설명한 후에 다양한 구현을 예시하도록 하겠다.

전체적인 절차에 대한 개요를 살펴보도록 하자. 기존의 자이썬 모듈을 자바 애클리케이션 내에서 개체 컨테이너로서 사용하고 싶다고 하자. 자바 애플리케이션에 노출시키고자하는 모듈에 포함된 메소드들에 대한 정의를 담는 자바 인터페이스를 코딩하는 것부터 시작한다. 다음으로, 새롭게 작성한 자바 인터페이스를 구현할 수 있도록 자이썬 모듈을 수정한다. 다음으로, PyObject를 자바 개체로 변환해주는 자바 팩토리 클래스를 작성한다. 최종적으로는, 새로 만든 자바 객체를 원하는대로 사용할 수 있다. 간단한 작업을 위해 여러 단계를 거치는 것 같아보이겠지만, 한번 살펴보고나면 그리 어렵지는 않을 것이다.

지금부터 몇 가지 예제를 살펴볼 것이다. 첫 번째 예제는 일대일 자이썬 개체와 팩토리 매핑을 포함하는 간단하고 우아한 방법이다. 두 번째 예제에서는, 기본적으로 하나의 팩토리만으로 모든 자이썬 개체에 대하여 사용할 수 있는 개체 팩토리를 활용하는, 아주 느슨하게 결합하는 접근방법을 살펴볼 것이다. 이러한 방법론 중에서 여러분에게 맞는 것을 취사선택할 수 있다.

일대일 자이썬 개체 팩토리

우리가 사용하고자 하는 각각의 자이썬 개체에 대하여 개별적인 개체 팩토리를 생성하는 것에 대하여 일단 논의하겠다. 이러한 일대일 기술은 관용적인 코드를 많이 만들어내게 되는 경향이 있지만, 나중에 주목하게 될 만한 몇 가지 장점이 있다. 이 기술을 사용하여 자이썬 모듈을 활용하기 위해서는, sys.path에 .py 모듈이 위치하도록 하거나, 모듈이 자바 코드 내에 있도록 경로를 하드 코드하여야 한다. 건물을 나타내는 자이썬 클래스를 사용하는 자바 애플리케이션에 이 기법을 적용하는 예를 살펴보자.

예제 10-3. 일대일 개체 팩토리 만들기

Building.py

# 건물 개체를 생성하기 위하여 자바 인터페이스를
#  구현한 파이썬 모듈
from org.jython.book.interfaces import BuildingType

class Building(BuildingType):
    def __init__(self, name, address, id):
        self.name = name
        self.address = address
        self.id = id

    def getBuildingName(self):
        return self.name

    def getBuildingAddress(self):
        return self.address

    def getBuldingId(self):
        return self.id

sys.path에 위치한 Building.py라는 이름의 모듈에서부터 시작한다. 그렇게 하기 이전에, 이름이 충돌되지 않는다는 것을 확실히 해두어야 예상치 못한 일이 벌어지는 결과를 피할 수 있다. 파일을 명시적으로 sys.path 이외의 곳에 둔 것이 아니라면, 애플리케이션의 소스 루트에 두는 것이 대체로 안전할 것이다. Building.py 개체는 건물의 정보를 담는 단순한 컨테이너라는 것을 알 수 있다. 우리는 명시적으로 우리의 자이썬 클래스 내에서 자바 인터페이스를 구현해야 한다. 이는 PythonInterpreter로 하여금 개체를 나중에 강제 변형하도록 해준다. 우리의 두 번째 코드 조각은 Building.py 내에 구현한 자바 인터페이스이다. 코드에서 볼 수 있듯이, 반환되는 자이썬 형은 자바 형으로 강제 변형될 것이므로, 우리는 eventual 자바 형을 사용하여 인터페이스 메소드를 정의한다. 다음으로는 이 자바 인터페이스를 살펴보자.

BuildingType.java

// 건물 개체에 대한 자바 인터페이스
package org.jython.book.interfaces;

public interface BuildingType {

    public String getBuildingName();
    public String getBuildingAddress();
    public String getBuildingId();

}

자바 인터페이스 내에 포함된 정의를 살펴보면, 그것을 서브클래스하는 파이썬 모듈이 각각의 정의를 단순히 구현한다는 것을 쉽게 알 수 있을 것이다. 파이썬 코드를 약간 변형하여 메소드 중 하나에 코드를 조금 추가하고자 한다면 자바 인터페이스를 건드리지 않고도 그렇게 할 수 있다. 다음으로 우리에게 필요한 코드 조각은 자바로 쓰여진 팩토리이다. 이 팩토리는 자바 클래스로 파이썬 모듈을 강제 변형하는 작업을 하고 있다.

BuildingFactory.java

/**
 *
 * 파이썬 모듈을 자바 클래스로 강제 변형하는 데 사용되는
 * 개체 팩토리
 */
package org.jython.book.util;

import org.jython.book.interfaces.BuildingType;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.util.PythonInterpreter;

public class BuildingFactory {

    private PyObject buildingClass;

    /**
     * 새 PythonInterpreter 개체를 생성한 다음, 파이썬
     * 코드를 실행하는 데에 사용. 이 경우, 강제 변형할
     * 파이썬 모듈을 수입한다.
     *
     * 일단 모듈이 수입되면 그에 대한 참조를 얻어서 자바
     * 변수에 참조를 할당한다
     */

    public BuildingFactory() {
        PythonInterpreter interpreter = new PythonInterpreter();
        interpreter.exec("from Building import Building");
        buildingClass = interpreter.get("Building");
    }

    /**
     * create 메소드는 참조되는 파이썬 모듈을 자바 바이트코드로
     * 실질적인 강제 변형을 수행할 책임이 있다
     */

    public BuildingType create (String name, String location, String id) {

        PyObject buildingObject = buildingClass.__call__(new PyString(name),
                                                         new PyString(location),
                                                         new PyString(id));
        return (BuildingType)buildingObject.__tojava__(BuildingType.class);
    }

}

위의 세번째 코드 조각은 가장 중요한 역할을 담당하는데, 이는 개체 팩토리가 우리의 자이썬 코드를 결과 자바 클래스로 강제 변형하기 때문이다. 생성자 내에서 PythonInterpreter의 새 인스턴스가 만들어진다. 그러면 우리의 자이썬 개체에 대한 참조를 얻기 위해 우리의 PyObject로 저장 인터프리터를 이용한다. 다음으로는, create라는 이름의 정적 메소드가 있다. 우리의 자이썬 모듈을 자바로 강제 변형하기 위하여 호출되며, 결과 클래스를 반환한다. PyObject 래퍼 자체에 __call__을 수행함으로써 그렇게 하며, 우리가 원한다면 인자를 전달하는 능력을 갖게 됨을 알 수 있다. 매개 변수는 또한 PyObjects로 포장해야 한다. 강제 변형은 PyObject 래퍼에 대하여 __tojava__ 메소드가 호출될 때 일어난다. 개체가 우리의 자바 인터페이스를 구현하도록 하기 위해서는, __tojava__ 호출에 EmployeeType.class 인터페이스를 전달해야한다.

Main.java

package org.jython.book;

import org.jython.book.util.BuildingFactory;
import org.jython.book.interfaces.BuildingType;

public class Main {

    private static void print(BuildingType building) {
        System.out.println("Building Info: " +
                building.getBuildingId() + " " +
                building.getBuildingName() + " " +
                building.getBuildingAddress());
    }

    /**
     * 팩토리의 create() 메소드를 호출함으로써 세 개의 건물 개체를
     * 생성.
     */

    public static void main(String[] args) {
        BuildingFactory factory = new BuildingFactory();
        print(factory.create("BUILDING-A", "100 WEST MAIN", "1"));
        print(factory.create("BUILDING-B", "110 WEST MAIN", "2"));
        print(factory.create("BUILDING-C", "120 WEST MAIN", "3"));

    }

}

제공된 코드의 마지막 부분인 Main.java는 팩토리를 사용하는 방법을 보여준다. 팩토리가 모든 무거운 일을 돌봐주기 때문에 Main.java는 아주 작음을 볼 수 있다. 단순히 factory.create() 메소드를 호출하여 새로운 PyObject의 인스턴스를 생성하고 그것을 자바로 강제 변형할 수 있다.

개체 팩토리 디자인을 사용하는 이러한 절차에는, 자바 코드 내에서 자이썬 개체에 대한 완전한 인식을 유지할 수 있다는 이점이 있다. 달리 말해, 각각의 자이썬 개체에 대한 개별적인 팩토리를 생성함으로 해서 자이썬 개체의 생성자로 인자를 전달하는 것이 가능하다. 팩토리는 특정한 자이썬 개체를 위하여 디자인되었기 때문에, 우리는 PyObject에 대하여 강제 변형된 자이썬 개체의 새로운 생성자로 전달될 특정한 인자를 가지고 __call__ 코드를 작성할 수 있다. 이는 생성자로 인자를 전달할 뿐 아니라, 자바 개발자가 새로운 생성자에 대하여 정확히 알 수 있으므로 좋은 문서화에 대한 잠재적인 가능성도 높여준다 이 하위 섹션에서 수행하는 절차는 아마도 자이썬 커뮤니티에서 가장 빈번하게 사용될 것이다. 다음 섹션에서는, 어떠한 자이썬 개체에도 사용할 수 있는 제너릭 개체 팩토리에 동일한 기법을 적용하는 것을 살펴보도록 하겠다.

일대일 개체 팩토리 요약

이 디자인 패턴의 핵심은 순서 원하는 자이썬 모듈을로드하기 위해 PythonInterpreter을 활용하여 팩토리 방식의 창조이다. 팩토리 PythonInterpreter을 통해 모듈이 로드되면, 이 모듈의 PyObject 인스턴스를 만든다. 마지막으로, 그 팩토리는 PyObject의 __tojava__ 메소드를 사용하여 PyObject를 자바 코드로 강제 변형한다.

그러한 아이디어는 그다지 어려운 것이 아니며 상대적으로 직관적인 편이다. 그러나, 자이썬 모듈 및 그에 상응하는 자바 인터페이스에 대한 참조를 전달하려고 하면 다른 구현이 등장하게 된다. 자이썬 개체를 초기화하고 자바로 탈바꿈하는 일을 팩토리가 처리해준다는 점에 유의하는 것이 중요하다. 결과 자바 개체에 대해 수행되는 모든 작업은 상응하는 자바 인터페이스에 대한 코드이다. 우리가 인터페이스에 포함된 정의를 변경하지 않고도 원하는 경우 우리는 자이썬 코드 구현을 변경할 수 있기 때문에 이것은 훌륭한 디자인이다. 자바 코드를 한번 컴파일할 수 있고 우리가 애플리케이션을 한방울 흘리지 않고 마음대로 자이썬 코드를 변경할 수 있다.

느슨하게 결합된 개체 팩토리 사용하기

개체 팩토리 디자인을 구현하는 방법에는 위의 예제에 묘사된 것과 같은 일대일 전략만 있는 것은 아니다. 어떠한 자이썬 개체에나 활용할 수 있을만큼 충분히 일반적인 방법으로 팩토리를 디자인하는 것이 가능하다. 이는 모든 자이썬 개체에 대하여 사용할 수 있는 단 하나의 싱글톤 팩토리를 필요로 하므로 상용구의 코딩을 줄일 수 있는 기법이다. 그것은 또한 사용을 쉽게 해주는데, 개체 팩토리 로직을 개별 프로젝트로 나눈 뒤에 원하는 곳에 어디든 적용할 수 있도록 해주기 때문이다. 예를 들어, 필자는 PlyJy(http://kenai.com/projects/plyjy)라는 프로젝트를 생성했는데, 이것은 기본적으로 자바 애플리케이션 내에서 사용되는 자이썬 개체 팩토리를 포함하며, 팩토리에 대하여 걱정하지 않고서도 자바에서 자이썬 개체를 생성할 수 있도록 하기 위한 것이다. 좀 더 살펴보고 싶다면 누리집에서 Kenai를 내려받아서 느슨하게 결합된loosely coupled 개체 팩토리에 대하여 학습하기 바란다. 이 섹션에서는 그 프로젝트 배후의 디자인과 작동원리에 대하여 살펴보고자 한다.

그러면 위에서와 동일한 예제를 살펴보고 느슨하게 결합된 개체 팩토리 디자인을 적용해보도록 하자. 이러한 기법은 자바 개발자로서는 팩토리로부터 개체를 생성하는 데에 좀 더 품이 들기는 하지만, 각각의 자이썬 개체에 대하여 개별적으로 팩토리를 생성하는 데에 쏟을 시간을 절약하도록 해주는 이점이 있는 것을 알게 될 것이다. 느슨하게 결합된 팩토리는 PyObject에 대하여 제너릭한 __call__을 하므로 개체에 인자를 전달하기 위하여 더 이상 생성자를 이용하지 않아도 되며, 따라서 자이썬 개체에 setter를 작성하고 이를 자바 인터페이스를 통하여 노출시킬 필요가 있다.

예제 10-4. 느슨하게 결합된 객체 팩토리 사용하기

Building.py

from org.jython.book.interfaces import BuildingType
# Building object that subclasses a Java interface

class Building(BuildingType):

    def __init__(self):
        self.name = None
        self.address = None
        self.id = -1

    def getBuildingName(self):
        return self.name

    def setBuildingName(self, name):
        self.name = name;

    def getBuildingAddress(self):
        return self.address

    def setBuildingAddress(self, address)
        self.address = address

    def getBuildingId(self):
        return self.id

    def setBuildingId(self, id):
        self.id = id

이러한 패러다임을 따르는 경우에는 자이썬 모듈을 일대일 예제와는 약간 다르게 작성하여야 한다. 주된 차이점은 초기자가 더 이상 인자를 취하지 않는다는 것이며, 따라서 우리의 개체 내에 setter 모듈을 코딩하였다. 그외에 자바 애플리케이션 내에서 호출하고자 하는 메소드를 노출하는 자바 인터페이스를 구현하여야 한다는 개념은 여전히 유효하다. 여기서는 BuildingType.java 인터페이스를 코딩할 때 필요로 하는 setter 정의를 포함시킴으로써 우리의 클래스를 값과 함께 적재하였다.

BuildingType.java

package org.jython.book.interfaces;

/**
 * Java interface defining getters and setters
 */

public interface BuildingType {

 public String getBuildingName();
 public String getBuildingAddress();
 public int getBuildingId();
 public void setBuildingName(String name);
 public void setBuildingAddress(String address);
 public void setBuildingId(int id);

}

우리의 다음 단계는 느슨하게 결합된 개체를 코드하는 것이다. JythonObjectFactory.java 클래스의 코드를 한번 살펴보면 그것이 싱글톤-한번만 인스턴스화되는 것-임을 알 수 있을 것이다. createObject()는 모든 일을 처리하는 중요한 메소드이므로 살펴보도록 하자.

JythonObjectFactory.java

import java.util.logging.Level;
import java.util.logging.Logger;
import org.python.core.PyObject;
import org.python.util.PythonInterpreter;

/**
 * Object factory implementation that is defined
 * in a generic fashion.
 *
 */

public class JythonObjectFactory {
    private static JythonObjectFactory instance = null;
    private static PyObject pyObject = null;

    protected JythonObjectFactory() {

    }
    /**
     * Create a singleton object. Only allow one instance to be created
     */
    public static JythonObjectFactory getInstance(){
        if(instance == null){
            instance = new JythonObjectFactory();
        }

        return instance;
    }

    /**
     * The createObject() method is responsible for the actual creation of the
     * Jython object into Java bytecode.
     */
    public static Object createObject(Object interfaceType, String moduleName){
        Object javaInt = null;
        // Create a PythonInterpreter object and import our Jython module
        // to obtain a reference.
        PythonInterpreter interpreter = new PythonInterpreter();
        interpreter.exec("from " + moduleName + " import " + moduleName);

        pyObject = interpreter.get(moduleName);

        try {
            // Create a new object reference of the Jython module and
            // store into PyObject.
            PyObject newObj = pyObject.__call__();
            // Call __tojava__ method on the new object along with the interface name
            // to create the java bytecode
            javaInt = newObj.__tojava__(Class.forName(interfaceType.toString().substring(
                        interfaceType.toString().indexOf(" ")+1,
                        interfaceType.toString().length())));
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(JythonObjectFactory.class.getName()).log(Level.SEVERE, null, ex);
        }

        return javaInt;
    }

}

코드에서 볼 수 있듯이, PythonInterpreter에는 우리가 문자열 값으로 메소드에 전달하는 자이썬 개체 이름에 대한 참조를 얻을 책임이 있다. PythonInterpreter가 일단 개체를 얻어서 PyObject에 저장하면, 그것의 __call__() 메소드가 아무 매개변수 없이 호출된다. 이것은 newObj*에 의해 참조된 다른 PyObject에 저장된 비어있는 개체를 retrive한다.끝으로, 새로 얻어진 개체는 우리가 자이썬 개체를 가지고 구현한 자바 인터페이스에 대한 fully qualified name을 취하는 *__tojava__() 메소드 호출에 의하여, 자바 코드로 강제 변형된다. 새로운 자바 개체가 반환된다.

Main.java

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jythonbook.interfaces.BuildingType;
import org.jybhonbook.factory.JythonObjectFactory;

public class Main {

    public static void main(String[] args) {

    // Obtain an instance of the object factory
    JythonObjectFactory factory = JythonObjectFactory.getInstance();

    // Call the createObject() method on the object factory by
    // passing the Java interface and the name of the Jython module
    // in String format. The returning object is casted to the the same
    // type as the Java interface and stored into a variable.
    BuildingType building = (BuildingType) factory.createObject(
        BuildingType.class, "Building");
    // Populate the object with values using the setter methods
    building.setBuildingName("BUIDING-A");
    building.setBuildingAddress("100 MAIN ST.");
    building.setBuildingId(1);
    System.out.println(building.getBuildingId() + " " + building.getBuildingName() + " " +
        building.getBuildingAddress());

    }

}

Main.java 코드를 살펴보면, 팩토리는 JythonObjectFactory.getInstance()를 통하여 초기화 또는 참조됨을 알 수 있다. 일단 팩토리의 인스턴스를 갖게 되면, createObject(**Interface, String)가 호출되어 인터페이스에 사용하고자 하는 모듈 이름에 대한 문자열 표현을 전달한다. 그 코드는 인터페이스를 사용할 뿐 아니라 반드시 강제 변형 개체를 형 변환cast하여야 한다. 이 예제는 개체가 sys.path의 어딘가에 존재하는 것으로 가정하며, 그렇지 않으면 강제 변형하고자 하는 모듈이 있는 경로를 나타내는 문자열을 받아들이는 createObjectFromPath(Interface, String)을 사용할 수 있다. 이는 하드 코딩된 경로를 포함하기 때문에 물론 선호되지 않는 기법이지만, 시험적으로 사용하기에는 유용할 수 있다. 예를 들어 두 자이썬 모듈은 암호화된 있는데 만약 그 중 하나는 다음이 기술은 시험 모듈을 가리 키도록 수 있도록, 테스트 목적으로 다른 개체 구현을 포함하고 있다.

느슨하게 결합된 개체 팩토리의 보다 효율적인 버전

비슷하지만 좀 더 세련된 구현은 PythonInterpreter를 생략하고, 그 대신 PySystemState를 사용하는 것이다. 동일한 결과를 만들어내기 위하여 왜 다른 구현을 하여야 할까? 그에 대하여 다음과 같은 몇 가지 이유를 들 수 있다. 이 섹션의 시작 부분에서 설명하는 느슨하게 결합된 개체 팩토리 디자인은 PythonInterpreter에 대한 인스턴스화 이후에 그것을 호출한다. 이는 인터프리터 입장에서는 비용이 많이 드므로, 성능 저하를 유발할 수 있다. 반면에, 우리는 PySystemState을 활용할 수 있으며, 자신에게 여분의 오버헤드를 인터프리터를 호출하는 부담의 문제를 줄일 수 있다. 다음의 예제는 이러한 기법을 어떻게 사용하는지를 보임과 함께, 강제 변형된 개체를 호출하는 동시에 인자를 전달하는 방법을 보인다.

예제 10-5. 느슨하게 결합된 팩토리 코드 PySystemState 사용

JythonObjectFactory.java

package org.jython.book.util;

import org.python.core.Py;
import org.python.core.PyObject;
import org.python.core.PySystemState;

/**
 * Jython Object Factory using PySystemState
 */
public class JythonObjectFactory {

 private final Class interfaceType;
 private final PyObject klass;

 // 생성자는 importer, module, 그리고 class 이름에 대한 참조를 얻음
 public JythonObjectFactory(PySystemState state, Class interfaceType, String moduleName, String className) {
     this.interfaceType = interfaceType;
     PyObject importer = state.getBuiltins().__getitem__(Py.newString("__import__"));
     PyObject module = importer.__call__(Py.newString(moduleName));
     klass = module.__getattr__(className);
     System.err.println("module=" + module + ",class=" + klass);
 }

 // 이 생성자는 다른 생성자에게 전달됨
 public JythonObjectFactory(Class interfaceType, String moduleName, String className) {
     this(new PySystemState(), interfaceType, moduleName, className);
 }

 // 다음의 모든 메소드는 팩토리로 전달된 정보의 조각들에 근거하여
 // 강제 변형된 자이썬 개체를 반환한다. 그것들 간의 차이점은
 // 개체에 대하여 전달 가능한 인자의 갯수이다.

 public Object createObject() {
     return klass.__call__().__tojava__(interfaceType);
 }


 public Object createObject(Object arg1) {
     return klass.__call__(Py.java2py(arg1)).__tojava__(interfaceType);
 }

 public Object createObject(Object arg1, Object arg2) {
     return klass.__call__(Py.java2py(arg1), Py.java2py(arg2)).__tojava__(interfaceType);
 }

 public Object createObject(Object arg1, Object arg2, Object arg3)
 {
     return klass.__call__(Py.java2py(arg1), Py.java2py(arg2),
         Py.java2py(arg3)).__tojava__(interfaceType);
 }

 public Object createObject(Object args[], String keywords[]) {
     PyObject convertedArgs[] = new PyObject[args.length];
     for (int i = 0; i < args.length; i++) {
         convertedArgs[i] = Py.java2py(args[i]);
     }

     return klass.__call__(convertedArgs, keywords).__tojava__(interfaceType);
 }

 public Object createObject(Object... args) {
     return createObject(args, Py.NoKeywords);
 }

}

Main.java

import org.jython.book.interfaces.BuildingType;
import org.jython.book.util.JythonObjectFactory;

public class Main{

    public static void main(String args[]) {

        JythonObjectFactory factory = new JythonObjectFactory(
            BuildingType.class, "building", "Building");

        BuildingType building = (BuildingType) factory.createObject();

        building.setBuildingName("BUIDING-A");
        building.setBuildingAddress("100 MAIN ST.");
        building.setBuildingId(1);

        System.out.println(building.getBuildingId() + " " +
            building.getBuildingName() + " " +
            building.getBuildingAddress());
    }

}

위의 코드에서 볼 수 있듯이, 앞서 보았던 개체 팩토리 구현과는 몇 가지 차이점이 있다. 첫째, 개체 팩토리의 초기화에 필요한 인자가 다르다. 여기서는 인터페이스, 모듈 및 클래스 이름을 전달한다. 다음으로, PySystemState는 importer PyObject에 대한 참조를 얻는다. 그러면 importer는 우리가 요청한 모듈에 대하여 __call__을 일으킨다. 요청한 모듈은 sys.path 어딘가에 포함되어 있어야한다. 끝으로, 우리는 모듈에 __getattr__ 메소드를 호출함으로서 클래스에 대한 참조를 얻는다.

이제 우리는 반환된 클래스를 사용하여 자이썬 개체를 자바로 강제 변형을 수행할 수 있다. 앞서 언급한 바와 같이, 특정 구현은 호출될 때 모듈에 인자를 다양하게 전달할 수 있도록 여러 개의 createObject()를 포함한다. 이것은 사실상, 우리에게 자이썬 개체의 초기화에 인수를 전달할 수있는 능력을 제공한다.

어떤 팩토리를 사용하는 것이 최선인가? 여러분의 애플리케이션이 처한 상황에 따라 선택할 부분이다. 개체 팩토리를 디자인하는 데에는 여러 방법이 있으며, 어떠한 방법을 택하든 자바 코드 내에서 자이썬 개체를 원활하게 사용할 수 있다.

이제 우리는 강제 변형된 자이썬 개체를 가지며, 자바 인터페이스에서 정의된 메소드를 활용할 수 있다. 당신이 볼 수 있듯이, 간단한 예제는 위의 몇 가지 값을 설정 다음 개체 값을 출력한다. 단일 개체 팩토리를 생성하여 어떠한 자이썬 개체를 위하여 사용할 수 있도록 하는 것이 얼마나 쉬운지 알게 되었기를 바란다.

__doc__ 문자열 반환

그것은 또한 매우 개체 자체에 접근 방법을 코딩하여 자이썬 클래스의 어느 __doc__ * 문자열 *을 쉽게 구할 수 있다. 우리는 이전의 예제에서 사용했던 건물 개체에 몇 가지 코드를 추가한다. 두 가지 팩토리 중 어느 쪽을 택하더라도, 이러한 트릭은 유효할 것이다.

예제 10-6. __doc__ Strings

Building.py

from org.jython.book.interfaces import BuildingType
# Notice the doc string that has been added after the class definition below

class Building(BuildingType):
    ''' Class to hold building objects '''

    def __init__(self):
        self.name = None
        self.address = None
        self.id = -1

    def getBuildingName(self):
        return self.name

    def setBuildingName(self, name):
        self.name = name;

    def getBuildingAddress(self):
        return self.address

    def setBuildingAddress(self, address):
        self.address = address

    def getBuildingId(self):
        return self.id

    def setBuildingId(self, id):
        self.id = id

    def getDoc(self):
        return self.__doc__

BuildingType.java

package org.jython.book.interfaces;

public interface BuildingType {

    public String getBuildingName();
    public String getBuildingAddress();
    public int getBuildingId();
    public void setBuildingName(String name);
    public void setBuildingAddress(String address);
    public void setBuildingId(int id);
    public String getDoc();

}

Main.java

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jython.book.interfaces.BuildingType;
import org.plyjy.factory.JythonObjectFactory;

public class Main {

    public static void main(String[] args) {

        JythonObjectFactory factory = JythonObjectFactory.getInstance();
        BuildingType building = (BuildingType) factory.createObject(
            BuildingType.class, "Building");
        building.setBuildingName("BUIDING-A");
        building.setBuildingAddress("100 MAIN ST.");
        building.setBuildingId(1);
        System.out.println(building.getBuildingId() + " " +
            building.getBuildingName() + " " +
        building.getBuildingAddress());

        // It is easy to print out the documentation for our Jython object
        System.out.println(building.getDoc());

    }
}

Result:

1 BUIDING-A 100 MAIN ST.
Class to hold building objects

다른 개체 형식으로 디자인을 적용

이 디자인은 플레인한 구식 자이썬 개체 뿐만 아니라 모든 유형에서 작동한다. 다음 예제에서, 자이썬 모듈은 간단한 계산기 방법을 포함하는 클래스이다. 팩토리 강제 변형은 동일한 방식으로 작동하며, 그 결과로는 자바로 변환된 자이썬 클래스를 얻는다.

예제 10-7. 다른 메소드 유형

CostCalculator.py

from org.jython.book.interfaces import CostCalculatorType

class CostCalculator(CostCalculatorType, object):
    ''' Cost Calculator Utility '''

    def __init__(self):
        print 'Initializing'
        pass

    # The implementation for the definition contained in the Java interface
    def calculateCost(self, salePrice, tax):
        return salePrice + (salePrice * tax)

CostCalculatorType.java

package org.jython.book.interfaces;

public interface CostCalculatorType {

    public double calculateCost(double salePrice, double tax);

}

Main.java

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jython.book.interfaces.CostCalculatorType;
import org.plyjy.factory.JythonObjectFactory;

public class Main {

    public static void main(String[] args) {

        // Create factory and coerce Jython calculator object
        JythonObjectFactory factory = JythonObjectFactory.getInstance();
        CostCalculatorType costCalc = (CostCalculatorType)
            factory.createObject(CostCalculatorType.class, "CostCalculator");
        System.out.println(costCalc.calculateCost(25.96, .07));

    }
}

Result

Initializing
27.7772

JSR-223

Java SE 6에 들어서 JVM 상의 동적 언어에 대한 지원이 추가되었다.JSR-223에서는 자바를 통하여 동적 언어를 매끄럽게 호출할 수 있도록 해준다. 자이썬 코드에 액세스하는 이 방법은 개체 팩토리를 사용하는 것만큼 유연하지는 않지만, 자바 코드 내에서 짧은 자이썬 스크립트를 실행하는 데 꽤 유용하다. 스크립팅 프로젝트(https://scripting.dev.java.net/)는 자바 내에서 다른 언어를 실행하는 데 사용할 수 있는 여러 엔진을 포함하고 있다. 자이썬 엔진을 실행하기 위해서는, 스크립팅 프로젝트에서 jython-engine.jar를 얻어서 classpath에 갖다놓아야 한다. 또한 classpath에 jython.jar를 두어야하며, 이는 자이썬 2.5에서 작동하지 않으므로 자이썬 2.5.1을 사용하여야 한다.

아래는 스크립팅 엔진의 활용을 보여주는 작은 예이다.

예제 10-8. JSR-223 사용

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws ScriptException {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("python");

        // Using the eval() method on the engine causes a direct
        // interpretataion and execution of the code string passed into it
        engine.eval("import sys");
        engine.eval("print sys");

        // Using the put() method allows one to place values into
        // specified variables within the engine
        engine.put("a", "42");

        // As you can see, once the variable has been set with
        // a value by using the put() method, we an issue eval statements
        // to use it.
        engine.eval("print a");
        engine.eval("x = 2 + 2");

        // Using the get() method allows one to obtain the value
        // of a specified variable from the engine instance
        Object x = engine.get("x");
        System.out.println("x: " + x);
    }

}

애플리케이션의 실행 결과를 확인해보자. 처음의 두 행은 자이썬 인터프리터가 시작될 때 자동으로 생성되며, CLASSPATH에 포함된 JAR 파일들을 표시한다.그 뒤에는 실제 프로그램의 출력이 뒤따른다.

결과

*sys-package-mgr*: processing new jar,'/jsr223-engines/jython/build/jython-engine.jar'
*sys-package-mgr*: processing modified jar,'/System/Library/Java/Extensions/QTJava.zip'
sys module
42
x: 4

PythonInterpreter 활용

자이썬을 임베딩하는 데에 사용되는 JSR-223과 유사한 기법은 PythonInterpreter를 직접적으로 사용하는 것이다. 코드를 임베딩하는 스타일은 스크립팅 엔진을 사용하는 것과 매우 유사하지만, 자이썬 2.5를 사용할 수 있다는 장점이 있다. PythonInterpreter의 또 다른 장점으로는 PyObjects를 직접적으로 사용할 수 있게 된다는 것을 들 수 있다. PythonInterpreter 기술을 활용하기 위해서는, classpath에 jython.jar가 필요하며, 그밖의 엔진은 필요하지 않다.

예제 10-9. PythonInterpreter 사용

import org.python.core.PyException;
import org.python.core.PyInteger;
import org.python.core.PyObject;
import org.python.util.PythonInterpreter;

public class Main {

    /**
    * @param args the command line arguments
    */
    public static void main(String[] args) throws PyException {

        // Create an instance of the PythonInterpreter
        PythonInterpreter interp = new PythonInterpreter();

        // The exec() method executes strings of code
        interp.exec("import sys");
        interp.exec("print sys");

        // Set variable values within the PythonInterpreter instance
        interp.set("a", new PyInteger(42));
        interp.exec("print a");
        interp.exec("x = 2+2");

        // Obtain the value of an object from the PythonInterpreter and store it
        // into a PyObject.
        PyObject x = interp.get("x");
        System.out.println("x: " + x);
    }

}

위의 클래스에서, 우리는 자바 클래스 내에서 파이썬 코드를 실행 PythonInterpreter을 사용한다. 첫째, 우리는 PythonInterpreter 개체의 인스턴스를 만들 수 있다. 다음으로, 그것에 대하여 exec() 호출을 함으로써, 그것에 전달되는 코드 문자열을 실행하도록 한다. 다음으로는 set() 메소드를 사용하여 인터프리터 인스턴스 내의 변수를 설정한다. 끝으로, 인터프리터 내의 변수 x에 저장된 개체의 사본을 얻는다. 우리는 우리의 자바 코드에 PyObject로서 그 개체를 저장해야 한다.

결과

<module 'sys' (built-in)>
42
x: 4

표 10-2에 PythonInterpreter 개체 내에서 사용 가능한 메소드와 그에 대한 설명을 나열하였다.

표 10-2. PythonInterpreter 메소드

메소드 설명
setIn(PyObject) 표준 입력 흐름을 사용하기 위한 파이썬 개체를 설정
setIn(java.io.Reader) 표준 입력 흐름을 사용하기 위한 java.io.Reader를 설정
setIn(java.io.InputStream) 표준 입력 흐름을 사용하기 위한 java.io.InputStream을 설정
setOut(PyObject) 표준 출력 흐름을 위한 파이썬 개체를 설정
setOut(java.io.Writer) 표준 출력 흐름을 위한 java.io.Writer를 설정
setOut(java,io.OutputStream) 표준 출력 흐름을 위한 java.io.OutputStream을 설정
setErr(PyObject) 표준 오류 흐름을 위한 파이썬 오류 개체를 설정
setErr(java.io.Writer) 표준 오류 흐름을 위한 java.io.Writer를 설정
setErr(java.io.OutputStream) 표준 오류 흐름을 위한 java.io.OutputStream을 설정
eval(String) 문자열을 파이썬 소스로서 평가하여 그 결과를 반환
eval(PyObject) 파이썬 코드 개체를 평가하고 그 결과를 반환
exec(String) 로컬 이름공간에서 파이썬 소스 문자열을 실행
exec(PyObject) 로컬 이름공간에서 파이썬 코드 개체를 실행
execfile(String filename) 로컬 이름공간에서 파이썬 소스 파일을 실행
execfile(java.io.InputStream) 로컬 이름공간에서 파이썬 소스의 입력 흐름을 실행
compile(String) 파이썬 소스 문자열을 표현식 또는 모듈로서 컴파일
compile(script, filename) 파이썬 소스 스크립트를 표현식 또는 모듈로서 컴파일
set(String name, Object value) 로컬 이름공간에서 개체 유형의 변수를 설정
set(String name, PyObject value) 로컬 이름공간에서 PyObject 유형의 변수를 설정
get(String) 로컬 이름공간에서 변수의 값을 얻음
get(String name, Class javaclass) 로컬 이름공간에서 변수의 값을 얻음. 얻은 값은 주어진 자바 클래스의 인스턴스로 반환됨.

요약

자이썬과 자바의 통합이야말로 자이썬 언어의 핵심이다. 자이썬 내에서 자바를 사용하는 것은 마치 다른 자이썬 모듈을 추가하는 것과 같으며, 이음매 없이 매끈하게 통합이 된다. 이를 통하여 자바의 모든 라이브러리 및 API를 자이썬 애플리케이션에서 사용할 수 있게 되니 더할 나위 없이 훌륭하다. 자이썬 내에서 자바를 사용할 수 있음으로 해서 또한 파이썬의 문법으로 자바 코드를 작성할 수 있는 이점이 있다.

자이썬 개체 팩토리와 같은 디자인 패턴을 활용하여, 우리는 또한 자바 애플리케이션 내에서 우리의 자이썬 코드를 활용할 수 있다. jythonc는 더 이상 자이썬의 배포판에 포함되지 않지만, 우리가 아직 효과적으로 자바 내에서 자이썬을 사용할 수 있다. 간단하게 자바 애플리케이션에서 JAR를 포함하여 개체 팩토리을 사용하는 능력을 주는 PlyJy(http://kenai.com/projects/plyjy)와 같은 개체 팩토리 예제를 사용할뿐만 아니라, 프로젝트가 있다.

우리는 또한 자바 내부에서 자이썬을 사용하는 더 많은 방법이 있음을 배웠다. 자바 언어에는 자바 6의 출시와 함께 JSR-223을 통한 스크립팅 언어에 대한 지원이 추가되었다. 자이썬 엔진을 사용하여, 자이썬 코드를 우리의 자바 애플리케이션에 뿌리기 위하여 JSR-223 방언을 활용할 수 있다. 마찬가지로, PythonInterpreter는 자바 코드 내에서 자이썬을 호출하는 데에 사용할 수 있다. 또한 클램프(http://github.com/groves/clamp/tree/master)와 같은 프로젝트를 눈여겨보도록 하자. 클램프 프로젝트는 자이썬 클래스에서 자바 클래스를 생성하기 위해서 어노테이션을 사용하는 것을 목표를 하고 있다. 이 프로젝트의 진행을 지켜보는 것은 흥분되는 일이 될 것이며, 프로젝트가 완료되면 문서화가 이루어질 것이다.

다음 장에서는, 통합된 개발 환경 내에서 자이썬을 사용하는 방법을 살펴보려고 한다. 특히, 우리는 이클립스와 넷빈을 사용한 자이썬 개발을 살펴볼 것이다. IDE는 쓰기에 따라 개발자의 생산성을 매우 높여주며, 모듈 및 JAR 파일을 classpath에 추가하는 등의 일을 편리하게 해준다.