저자: 『Jython Essentials』의 공동 저자 노엘 라핀(Noel Rappin), 역 전순재
처음으로 자이썬(Jython)과 관련된 기사를 썼을 때, 필자는 자바 프로그래머들이 큰 흥미를 갖고 있는 파이썬 스크립팅 언어와 파이썬을 구현한 자이썬(Jython)의 특징에 대해 논의한 적이 있다. 자이썬(Jython)은 파이썬 프로그래밍 언어를 100 퍼센트 순수한 자바로 작성하여 완전하게 구현한 것이며, 그리고 자이썬(Jython)을 사용하면 쉽게 자바 라이브러리에 접근할 수 있다.
본 기사를 읽고있는 여러분이 이미 파이썬 프로그래머라면, 독자 여러분은 이미 파이썬 프로그래밍 언어의 특징을 잘 알고 있을 것이다. 그리고 이미 파이썬에 친숙한 사람들이라면, 자이썬(Jython)을 사용해야 할 가장 큰 매력은 파이썬을 프로그래밍 언어로 그대로 사용할 수 있으면서도 아주 광범위한 기존의 자바 라이브러리와 도구들을 어느 것이라도 사용할 수 있기 때문이라고 생각한다.
자이썬(Jython)덕분에 파이썬 프로그래머는 자바의 세부사항에 신경을 덜 써도 되지만, 자이썬(Jython)으로부터 자바 객체와 라이브러리를 아주 효과적으로 사용하려면 여전히 그러한 세부 사항들 중 약간은 알아두어야 한다.
본 기사에는 자이썬(Jython)에서 자바 객체를 사용하는데 유용하게 사용될 수 있는 10가지 팁을 다룰 것이다. 이미 자바를 잘 알고 있는 사람들을 염두에 두고 기사를 쓴 것은 아니지만 지면상의 제약때문에 모든 자바 용어들을 자세하게 정의하지 못한다. 참고로 본 기사에서 C로 구현된 표준 파이썬을 구별할 필요가 있을 때, 그 파이썬을 CPython이라고 지칭하겠다.
1. 자바 패키지 임포트하기
자바 패키지(Java package)는 자바 클래스 모음으로 보통 한 클래스에 한 파일씩 같은 디렉토리에 저장되어 있다. Python 모듈과 같이, 사용하기 전에 자바 패키지도 반드시 자이썬으로 임포트되어야 한다. 그리고 자바 패키지가 있어야 할 디렉토리는 자바의
classpath 또는 파이썬의
PYTHONPATH에 있어야 한다.
자바 패키지는 마치 파이썬 모듈처럼, 그냥 평범하게 import 서술문을 사용하여 임포트한다. 모듈의 이름은 완전하게 자격이 부여되어야 임포트할 수 있다.
java.lang 패키지와
java.util 패키지를 임포트하려면 다음과 같이 해주면 된다.
import java.lang
import java.util
Cpython와는 달리, 자이썬에서는 명시적으로 하부-패키지(sub-packages)를 지정할 필요가 없다. 다음과 같이 한 줄을 타이핑해 넣으면
import java
자이썬에서
java.lang과
java.util 모두(그리고
java의 다른 하부-패키지 모두)를 완전히 사용할 수 있다. 이 원리는 표준 파이썬 모듈에도 작동한다(자이썬에서,
import os라고 하면
os.path 모듈이 자동으로 임포트된다).
종종, 자이썬 프로그래머는 코드에서 자바 패키지의 완전한 이름을 생략하여 자바 객체의 생성을 강조한다. 그렇지만 그것이 반복적이라고 생각된다면
as 절을 사용하여 패키지 이름을 줄일 수 있다. 보통 다음과 같이 말이다.
import java.lang as lang
import java.util as util
모든 표준 라이브러리를 임포트해야 하는 부담과 또 잠재적인 문제 때문에 다음과 같이
from java import *
자이썬 코드의 생산에
from 버전의
import 서술문을 사용하지 않기를 권고한다. 일반적으로는 자바 패키지에 대해서
import를 고수하는 것이 좋다.
2. 자바 객체 만들기
임포트되기만 하면 자바 객체는 평범한 파이썬 객체처럼 똑같은 구문을 사용하여 만들 수 있다.
x = java.lang.String()
z = java.util.HashMap()
내부적으로 자이썬은 인수의 형과 개수가 주어진 클래스에 대하여 적절한 자바 구성자를 호출한다. (더 자세한 것은 아래의 팁 6 참조) 그래서 위에 있는 첫번째 줄은
java.lang.String에 대하여 인수가-없는 구성자를 호출하지만 다음과 같은 라인이라면
x = java.lang.String("Hi")
위의 라인은
String을 예상하는 인수가한 개인 구성자를 호출한다. 다시 말해 여러분 대신에 자이썬이 자바 형 정보를 관리하고 있다는 것이다.
자바 객체를 만들 때(그리고 오직 그 때만), 그 객체를 만들어 내는 자바 구성자의 형 서명(type signature)에 포함되지 않는다고 해도 키워드 인수를 자이썬 구성자에게 건네줄 수 있다. 예를 들면 다음과 같다.
win = javax.swing.JFrame(size=(200,200))
각 키워드 인수
x에 대해, 자이썬은 클래스
setX 중에서 일치하는 공용 메소드를 찾는다. 그리고 그 메소드를 자동으로 호출한다. 만약 그러한 set 메소드가 전혀 없다면 자이썬은 에러를 일으킨다.
3. 파이썬 서술문으로 객체 사용하기
일단 생성되면 자바 객체는 자이썬 서술문으로 사용될 수 있으며, 대개의 경우 원래부터 파이썬 객체였던 것처럼 동작할 것이다. (제한 사항들은 아래에 나열해 놓았다).
자바 객체는
if 또는
while 서술문 안에서 테스트로 사용될 수 있다. 다음 자바 객체 값은 파이썬 값으로는 거짓에 해당한다. 자세하게 설명한다면
java.lang.Boolean FALSE, 불리언 거짓(Boolean false),
java.util.Vector 또는
java.util.Hashtable의 빈 실체, 그리고
java.util.List,
java.util.Map,
java.util.Collection 또는
java.util.Dictionary 인터페이스를 구현하는 모든 빈 클래스 등등을 말하는 것이다. CPython과는 달리, 자바 문자열에서 빈(empty) 실체는 Python의 참과 동등하다(빈 파이썬 문자열은 여전히 거짓이다).
그리고
java.util.Vector,
java.util.Enumeration,
java.util.List, 또는
java.util.Iterator과 같은 형이라면 어느 것이든지 자이썬에서
for 서술문에 대한 리스트 표현으로 사용될 수 있다. 게다가
java.util.Vector,
java.util.Dictionary,
java.util.List, 또는
java.util.Map의 하부클래스 또는 구현자에 대하여 (x[3]과 같이) 표준적인 파이썬 인덱스 표기법을 사용할 수 있다. 그렇지만 자바 객체에 대해서 (x[3:5]와 같은) 슬라이싱 접근은 지원되지 않는다.
자이썬 안에서
java.lang.Exception의 모든 하부클래스를 파이썬의 표준
try와
raise 서술문을 사용하여 일으킬 수 있으며 나포할 수 있다.
4. 메소드(Methods)와 필드(Fields)에 접근하기
보통 자바 클래스에서
public으로 지정된 어느 자바 메소드나 필드(field)도 자이썬에서 호출할 수 있다. 자이썬의 시작 옵션을 변경하면 private 메소드에 접근할 수 있다.
자이썬은 또한 어디에나 있는 자바 get/set 메소드를 피할 수 있게 해준다. 덕분에 프로그램은 더욱 파이썬적인 스타일을 가질 수 있다. 자이썬이 다음과 같은 코드 한 줄을 보게 된다고 가정해보자.
x = javaObj.attribute
만약 그런 메소드가 존재한다면
javaObj.getAttribute()에 대한 자바 호출이 자동으로 만들어진다. 마찬가지로
javaObj.attribute에 대한 할당은
javaObj.setAttribute에 대한 호출로 변환된다.
5. 자바 배열 만들기
종종 자바 배열을 인수로 요구하는 자바 메소드를 호출해야 할 경우가 있다. 정상적인 상황에서, 자이썬은 필요할 때 자동으로 파이썬 리스트나 터플을 적절한 형의 배열로 변환한다. 자이썬은 리스트나 터플에 있는 각 항목이 적절한 형으로 변환될 수 있다고 가정한다(그렇지 않다면 자이썬은 런타임 에러를 일으킬 것이다). 그래서 다음 자이썬 리스트
[1, 2, 3]는 자바의
int[],
long[],
java.lang.Integer[], 또는
java.lang.Object[]으로 필요에 따라 변환될 수 있다 (그러나
java.lang.String[]로는 변환될 수 없다).
그렇지만 그러한 변환 중에 리스트의 복사본이 생성되는데, 어떤 경우에는 그것을 원하지 않는다. 예를 들어 호출하고자 하는 메소드가 복사된 리스트를 변경하더라도 변경된 그 배열이 자이썬 리스트의 유일한 복사본이기 때문에 그 변경 사실이 다시 자이썬 코드에 전달되지 않을 것이다. 자이썬은
jarray 모듈을 제공하여 필요할 때 자바 배열을 직접 쉽게 만들 수 있도록 해준다.
여러분은
zeros 메소드로 텅 빈 배열을 만들 수 있다.
>>> import jarray
>>> jarray.zeros(5, "i")
array([0, 0, 0, 0, 0], int)
첫 번째 인수는 배열의 길이이며, 두 번째 인수는 기본 형을 표현하는 문자이거나 자바 클래스 이름이다.
>>> jarray.zeros(4, java.lang.String)
array([None, None, None, None], java.lang.String)
array 메소드를 사용하면 파이썬 연속열로부터 직접 배열을 만들 수 있다.
>>> jarray.array([8, 9, 2, 1], "i")
array([8, 9, 2, 1], int)
첫 번째 인수는 파이썬 연속열이며, 두 번째 인수 역시 클래스 또는 형 서명(type signature)이다.
6. 오버로드된(Overloaded) 메소드
자바 메소드가 오버로드되고 하나 이상의 정의를 가질 경우, 자이썬은 인수의 개수와 인수의 실행시간 형을 근거로 하여 올바른 메소드에 그 호출을 일치시키려고 시도할 것이다.
세부사항은 복잡하겠지만 기본 개념은 간단하다. 자이썬은 먼저 같은 개수의 인수를 가진 메소드를 호출로 선택하려고 한다. 그리고 메소드가 여러 개 있다면 요구되는 형이 호출하는 객체와 가장 유사한 메소드를 선택한다. 일반적으로 자이썬은 자바 객체 형을 가진 메소드 보다는 기본 자바 형을 가진 메소드를 선호한다.
7. 자바 객체를 하부클래스화 하기
자이썬 객체는 표준 파이썬 구문을 사용하여 자바 객체의 하부클래스로 선언될 수 있다.
class JythonString(java.lang.String):
pass
자바 인터페이스 역시 같은 방식으로 구현될 수 있다. 자이썬은 한 인터페이스에 존재하는 모든 메소드가 실제로 정의되었는지 결정하기 위해 컴파일-시간 점검을 하지 않는다(미정의 메소드를 호출하면 런타임 예외가 발생할 것이다).
자이썬이 다중 상속을 하기는 하지만, 자바 객체에 대해서는 한가지 제한이 따른다. 자이썬 클래스는 오로지 하나의 자바 조상만을 가질 수 있다. 이 제한은 그 조상이 직접적인 부모이든지 혹은 또다른 자이썬 클래스를 통한 간접적 부모이든지 간에 모두 적용된다.
자바 하부클래스는 구성될 때 다르게 동작한다. 만약 자이썬 클래스가 자바 부모를 가진다면 그리고 그 부모가 인수없는 구성자라면, 그 구성자는 자이썬 클래스의
__init__ 메소드가 끝난 다음, 또는 그
__init__ 메소드 안에서 자바 속성이 사용되기 전에 자동으로 호출된다. 이런 행위는 부모 클래스 구성자가 절대로 자동으로 호출되지 않는 정상적인 파이썬 작동과는 다르다. 이렇게 하는 이유는 사용되기 전에 자바 객체가 적절하게 초기화되었음을 보증하기 위한 것이다.
8. 속성 추가하기
정상적인 파이썬 객체와는 달리, 단순히 할당한다고 해서 자바 객체에 새로운 속성을 만들 수 없다. 그 실체는 반드시 자바에서 선언되어 있어야 한다. 존재하지 않는 자바 클래스의 실체에 할당을 시도하면 에러가 일어날 것이다.
>>> x = java.util.ArrayList()
>>> x.language = "english"
Traceback (innermost last):
File "console", line 1, in ?
TypeError: can"t set arbitrary attribute in java instance: language
이 제한에 대처하려면 그 자바 클래스의 하부클래스 하나부터 만들어야 한다(하부클래스는 비어 있을 것이다). 그러면 그 하부클래스의 실체에 속성을 추가할 수 있다.
>>> class NewArrayList(java.util.ArrayList):
... pass
...
>>> x = NewArrayList()
>>> x.language = "english"
>>> x.language
"english"
9. 직렬화(Serialization)
자바 객체는 표준 파이썬의
pickle과
cPickle 모듈을 사용하여 직렬화할 수 없다. 단, 자바 객체와 파이썬 객체 모두 표준 자바 직렬화(serialization)를 사용하면 직렬화 할 수 있다. 그렇지만, 자이썬(Jython) 객체를 직렬환원(deserializing)할 때 표준
java.io.ObjectInputStream 클래스를 사용할 수 없다. 반드시 다음과 같이 자이썬에-고유한
org.python.util.PythonObjectInputStream을 사용해야 한다.
import java.io as io
import org.python.util as util
class TestClass(io.Serializable):
def __init_ _(self, value=0):
self.value = value
self.message = "Serialized"
def toString(self):
return "Message: %(message)s value:
is %(value)s" % self.__dict_ _
instance = TestClass(3)
outFile = io.FileOutputStream("test.ser")
outStream = io.ObjectOutputStream(outFile)
outStream.writeObject(instance)
outFile.close( )
inFile = io.FileInputStream("test.ser")
inStream = util.PythonObjectInputStream(inFile)
readInstance = inStream.readObject( )
print readInstance.toString( )
Message: Serialized value: is 3
PythonObjectInputStream을 사용하지 않으면 런타임 에러가 발생할 것이다. 평범한 자바
ObjectInputStream을 사용하면 동적으로 적재되어 자이썬의 자바 상속에 사용되는 임시위임(proxy) 클래스를 재생성하고 찾아내기가 힘들기 때문이다.
10. 특별 보너스, 차이점
마지막으로, 객체 사용법과 직접적으로 관련되지는 않지만 자이썬과 Cpython 사이의 주요한 차이점들은 다음과 같다.
- 모든 자이썬(Jython) 문자열은 유니코드에 기초한다. 그러므로 CPython에 존재하는 일반 문자열과 유니코드 문자열 사이의 모든 차이는 자이썬에서는 무시된다.
- 어떤 파이썬 키워드는 (print와 같은) 일반적인 자바 메소드이기 때문에 자이썬은 CPython보다 더 유연하게 ("."의 뒤와 같이) 그 의미가 명확한 곳에 키워드를 식별자로 허용할 수 있다.
- 자이썬은 메모리 관리를 위해 Cpython-스타일의 참조 횟수가 아니라 자바 가비지 컬렉션을 사용한다.
- 자이썬은 -O라는 최적화 플래그를 인식하지 않는다.
- 자이썬 파일 객체는 JVM에 기능이 빠져 있는 관계로 CPython에는 있는 어떤 기능들이 빠져 있다. 마찬가지로 어떤 CPython 모듈 중에 대표적인 os, sys, socket 모듈에는 기능이 약간 빠져 있다.
- C로 작성된 CPython 라이브러리는 자이썬에 이식되지 못할 수도 있으며 (예를 들면 Tkinter와 같이) 사용하지 못할 수도 있다.
자이썬(Jython)에 관한 더 자세한 정보는
http://www.Jython.org/ 또는
『Jython Essentials』를 참고하기 바란다.