php에서는 print_r이라는 유용한 함수가 있어, 특정 객체가 가지고 있는 값들을 재귀적으로 따라 들어가며 모든 변수들의 값을 다 보여준다.

python에 pprint 라는 모듈이 있긴 하지만 변수가 객체인 경우에는 더 이상 재귀적으로 따라 들어가지 않고 해당 객체의 주소만 프린트하고 종료한다.

 

결론은 불편하다. 그래서 만들었다. 파이썬용 print_r :


만들고 보니 역시 세상은 넓고 천재는 많다는 사실을 다시 한번 깨달았다. var_dump(https://github.com/sha256/python-var-dump)라고 변수 이름 부터 타입까지 아주 깔끔하게 뽑아 주는 라이브러리를 누가 만들어 github에 올려 놓았다. 자세한 사항은 위 링크를 따라 가보면 된다(만일 링크가 깨져 있다면 어쩔수 없이 아래 코드를 쓰면 된다..;;)



Posted by kukuta
TAG Python

댓글을 달아 주세요

만일 현재 사용하고 계신 바이너리 & 설정 deploy tool이 없다면 'Fabric(http://docs.fabfile.org/en/1.7/)' 이라는 녀석을 한번 살펴 보실것을 권해 드립니다.

 

What is "Fabric"?

Fabric is a Python (2.5 or higher) library and command-line tool for streamlining the use of SSH for application deployment or systems administration tasks. - http://docs.fabfile.org/en/1.7/

간단하게 어플리케이션 배포나 시스템 관리를 위해 사용되는 툴이랍니다. 뭔 말인가 싶어 다른 검색을 좀더 해봅니다.

You can use Fabric as a tool for centralized configuration management. You can run administrative tasks on all of your machines simultaneously. - http://awaseroot.wordpress.com/2012/04/23/fabric-tutorial-1-take-command-of-your-network/

중앙 집중형 설정 및 디스트 관리 도구, 로컬 머신에서 리모트 머신의 어드민 작업을 할수 있답니다.

 

사족을 달자면..

- install 외에 별다른 설정 없음.

- python 기반

 

Install :

$ sudo apt-get install fabric
- 위와 같이하면 그냥 깔립니다. 설치의 모든 과정이 끝났습니다.

 

Quick Sample :

// fabfile.py
from fabric.api import run, env, execute, task
from fabric.operations import local, put

#환경 설정
env.hosts = ['some1.host.com',
                   'some2.host.com',
                   'some3.host.com'
                  ]


env.user = 'user_account'
env.password = 'password_boo'

def stop() :
           run ('/etc/init.d/deamon stop')

def copy() :
           put('./etc/init.d/daemon, '/etc/init.d/daemon') #로컬의 바이너리를 리모트로 복사

def start() :
           run ('/etc/init.d/deamon start')

def deploy() :
           execute(stop)
           execute(copy)
           execute(start)

 

 

간단하게 서버에 접속 해서 데몬을 stop 시키고, 로컬에 있는 바이너리를 리모트로 복사 그리고 재시작하는 스크립트를 작성했습니다.


- 파일 이름을 반드시 fabfile.py로 해야함.
- env : 환경 변수. 각종 설정 내용들이 들어감(서버 리스트, 아이디, 패스워드 등등..)

  (http://docs.fabfile.org/en/1.7/usage/env.html)
- run : 리모트 실행
- put : 로컬 -> 리모트 파일 복사
- execute : 로컬 함수 실행

  (http://docs.fabfile.org/en/1.7/usage/execution.html#execution-strategy)

 

How to Run :

$ fab deploy

위 과정을 거치면 실행 인자의 deploy 함수를 수행하면서 원격 머신(some1~3)의 실행중인 바이너리를 종료하고,
지정된 파일을 복사후 재시작 하게 됩니다.

 

ref.
- fabric official site : http://docs.fabfile.org/en/1.7/
- fabric tutorial site : http://awaseroot.wordpress.com/2012/04/23/fabric-tutorial-1-take-command-of-your-network/

 

 

Posted by kukuta

댓글을 달아 주세요

/**
 '눈이 올것 같은 날씨군..' 이라고 생각하는 순간 신기하게도 눈이 내리는 군요. 그것도 아주 펑펑... 커플 분들에게는 정말 즐거운 날씨 일것이라는 생각이 듬과 동시에 어쩌면, 올해 크리스마스는 화이트 크리스마스가 될지도 모르겠다는 불길한 느낌이 오는군요.

 제 느낌이 어쨌든, 내리는 눈이 너무 이뻐서 사진이라도 찍어 둘까하다가 관뒀습니다. 추억은 어디까지나 추억으로 남아야지, 기록으로 남겨진 추억은 나중에 감정이 사라져 버리고 나면 씁쓸함만이 남더군요. 헛소리를 하는 것을 보니 지병이 다시 도지고 있는가 봅니다. '후천적 연말 크리스마스 우려 증후군'이라고 솔로 기간이 길어지다 보면 이런 병도 생깁니다.

 이 글을 읽고 계시는 여러분들도 조심하시기 바랍니다. 언제 저 처럼 되 버릴지 모릅니다.ㅎㅎ

 오늘은 파이썬을 C++에 임베딩(embedding) 시켜 사용하는 법을 알아 보도록 하겠습니다. 원문은
linux jurnal 에 기재된 Embedding Python in Multi-Threaded C/C++ Applications 이란 글입니다. CodeProject 에도 Embedding Python in C/C++ 라고 임베딩 관련하여 비슷한 글이 올라와 있지만, 제가 직접 그 코드를 사용해 본 결과 버그 투성이의 엉망진창 코드 였습니다. 하지만 linux jurnal의 코드 예제는 실전에 적용하기에는 너무 간단한 면이 없지 않아 있기 때문에, linux jurnal의 기사를 보시고 기본을 익히신 이후 CodeProject의 기사를 보시고 실전에 적용 하시는 것이 정신 건강에 도움이 되리라 생각합니다.

*/

Embedding Python in Multi-Threaded C/C++ Applications

Introduction

일반적으로 C나 C++같은 언어는 빌드(컴파일과 링크)과정을 거치고 난 후에야 프로그램을 실행 할 수 있습니다. 만일 이미 빌드 된 프로그램의 로직을 변경 한다거나 하려면 수정을 하고, 다시 빌드 하는 과정을 거쳐야 하지요. 어찌 보면 별것 아닐 수 있지만 '빌드'라는 과정을 거친다는 것은 일단 런타임에 어떤 변화를 적용 할 수는 없다는 뜻이지요. 물론, 디자인 패턴 같을 적용하여 어느 정도 유연성(flexiblity)를 제공 할 수는 있지만, 그 역시 새로운 기능이나 로직을 추가 하려면 다시 빌드를 해야만 합니다. 그리고 작성하기도 까다롭지요. 메모리 누수나, 잘못된 메모리 참조, 변수 타입에 대한 신경도 써야 하고 이것 외에도 신경 써야 할 것들이 많이 있습니다.

스크립트 언어는 위의 불만 사항들을 해결 하는데 분명히 많은 도움이 될 것입니다. 일단 빌드라는 과정이 없지요. 코드를 작성하고, 실행만 하면 그만 입니다. 자료형에도 신경 쓸 필요 없습니다. 물론 메모리 할당과 해제는 스크립트 언어의 엔진이 다 알아서 해줍니다. 스크립트 언어의 가장 큰 장점 중에 하나는 코드의 작성이 빠르다는 것이지요. 하지만 성능이 C나 C++처럼 빠르지 못하다는 단점이 있습니다.

만일 여러분이 어떤 프로그램을 만든다고 가정 합시다. 게임이 좋겠군요. 여러분이 만드는 게임은 당연히 성능이 중요시 되므로 C++로 만들고 있을 겁니다. 그런데 그 중에 로직이 자주 변경 되면서, 그렇게 성능이 요구되지 않는 부분이 있습니다. NPC의 AI부분 정도로 하면 되겠군요. 아니면 여러분은 상당히 서비스 디펜던트한 서버를 만드는 프로그래머라고 합시다. 이 서버는 매주 사용자의 요구를 처리하는 로직이 변경 됩니다. 메시지를 받는 부분이나, 클라이언트와의 세션을 처리 하는 프레임워크가 되는 부분들은 C++로 만들면 성능도 좋고 안정적이고, 메모리도 적게 소모 하고 좋겠지요. 하지만 매주 변경하는 로직부분을 파이썬으로 만든다고 하면 어떨까요?
 
이런 부분에 스크립트 언어를 끼워 넣을 수 있다면 정말 환상적이지 않겠습니까? 파이썬에서는 임베딩이라고 하여 이런 기능을 제공하고 있습니다. 간단하게 말하자면 C++ 코드에서 파이썬 스크립트를 호출하고 실행 할 수 있는 기능이라고 정의 할 수 있겠습니다.

이 포스트에서는 파이썬을 어떻게 C/C++코드에서 호출 할 수 있으며, 멀티 쓰레드 프로그램에서 thread-safe하게 파이썬 스크립트를 실행 하는 방법에 대해서 다루도록 하겠습니다.

Overview of the Python C/C++ API
파이썬 API[각주:1]는 C와 C++에 대한 구분이 없습니다. 모든 파이썬 API들은 extern "C"를 이용해 선언되어져 있고, C++을 위한 어떠한 특별한 자료구조 같은 것도 제공 하지 않습니다. 그래서 C에서 임베딩을 하던, C++에서 임베딩을 하던 파이썬 API는 같은 형태를 유지 할 수가 있지요.

파이썬에서 C/C++에 제공하는 API는 두 가지 종류가 있습니다. 한 가지는 임베딩(embedding)을 위한것, 다른 한 가지는 확장(extending)을 위한 것이지요. 임베딩이라는 것은 C/C++ 코드에서 파이썬 스크립트를 읽고 실행 할 수 있도록 해주는 것이고요, 그와는 반대로 확장이라는 것은 파이썬 스크립트에서 C/C++의 라이브러리를 사용 할 수 있도록 해주는 것이지요. 확장에 관한 내용은 나중에 다른 포스트에서 좀 더 자세하게 알아 보도록 하고 이 포스트에서는 임베딩에 집중하도록 하겠습니다.

일반적으로 임베딩을 사용하면 C/C++ 어플리케이션은 파이썬 스크립트를 로드하고 실행 합니다. 이 때 어플리케이션은 파이썬 인터프리터 라이브러리와 링킹(linking) 되지요. 이 때 사용되는 라이브러리가 pythonXX.lib 파일 혹은 libpythonXX.a 파일인데요. 여기서 XX라는 것은 파이썬의 버젼을 나타냅니다. 만일 파이썬 2.4 버젼이라면 python24.lib 혹은 libpython24.a가 되겠지요.

Embedded Python
아래의 코드는 파이썬 인터프리터를 임베딩 하여 간단한 메시지를 출력 하는 코드 입니다.
#include <stdio.h>
#include <Python.h>
int main(int argc, char * argv[])
{
    // initialize the interpreter
    Py_Initialize();
    // evaluate some code
    PyRun_SimpleString("import sys\n");
    //ignore line wrap on following line
    PyRun_SimpleString("sys.stdout.write('Hello from an embedded Python Script\\n')\n");
    // shut down the interpreter
    Py_Finalize();
    return 0;
}

위의 예제에서 가장 먼저 나오는 Py_Initialize 함수는 파이썬 인터프리터 라이브러리를 초기화 하는 역할을 합니다. 파이썬 API를 사용하기 전에 반드시 이 함수가 가장 먼저 불려 져야 합니다. PyRun_SimpleString 은 간단한 파이썬 코드를 실행합니다. 예를 들어서 import sys 라인은 sys라는 파이썬 모듈을 로드 합니다. 각 PyRun_SimpleString에는 반드시 완벽한 파이썬 문장을 넘겨 줘야 합니다. Py_Finalize 함수는 파이썬 API 호출중 가장 마지막에 불려져야 합니다. 이 함수는 인터프리터를 종료하고, 그간 할당 되어 있던 자원들을 회수 하는 역할을 합니다. 이 함수는 반드시 모든 파이썬 인터프리터의 사용이 끝났다고 확신하는 시점에서 호출되어져야 합니다.

Python, C and Threads
C에서는 쉽게 쓰레드를 생성하고 사용합니다. 자세한 쓰레드의 생성과 사용에 대해서는 이 포스팅의 주제에 벗어 나므로 굳이 설명 하지 않도록 하겠습니다만 인터넷에서 조금만 검색하시면 쉽게 찾으실 수 있을 겁니다. 암튼 멀티 쓰레드를 지원하기 위해서 파이썬에서는 Global Interpreter Lock 이라는 뮤텍스를 사용하는데요, 모든쓰레드 내에서 파이썬 API를 이용하기 전에 이 Global Interpreter Lock을 획득해야 합니다. 그렇지 않으면 레이스 컨디션 상태에서 인터프리터가 정상적으로 동작 하지 않습니다. Lock을 잡고 풀어 주기 위해서 파이썬에서는  PyEval_AcquireLock 과 PyEval_ReleaseLock 두 함수를 제공 하고 있습니다.

그리고 파이썬은 각각 쓰레드의 상태를 유지하기 위해 각 쓰레드의 정보를 따로 저장 합니다. 각 쓰레드에 의존적인 정보들은 PyThreadState 객체에 저장 되는데요, C/C++쓰레드에서 파이썬 API를 호출 할 때 반드시 각 쓰레드 마다 PyThreadState 객체를 생성해서 가지고 있어야 합니다.

만일 여러분이 멀티 쓰레드 프로그래밍에 경험이 있으신 분이시라면 위에서 언급한 Global Interpreter Lock을 모든 파이썬 API를 호출 하기 전에 잡아 줘야 한다는 것에 상당한 거부감을 느끼실지 모르겠습니다. 모든 함수의 호출이 시리얼라이즈 된다는 것은 상당한 멀티 쓰레드의 장점을 버리는 것이고, CPU가 아무 것도 하지 않고 놀 수 있는 기회를 많이 만들게 된다는 의미니까요. 하지만 실질적으로 파이썬 스크립트를 실행하는 중에 인터프리터는 주기적으로 CPU를 다른 쓰레드에게 양보 해줍니다. 듣기로는 스크립트를 일정 바이트 수행하고 나면 다른 쓰레드에게 제어권을 넘긴다고 하더군요. 이것이 레이스 컨디션을 발생 시키지 않을까 우려를 했지만 실제 실행하는데 있어서 별다른 오류 상황을 만들지는 않았습니다. 아마도 내부적으로 어떤 메카니즘이 자동적으로 lock을 풀면서도 레이스 컨디션을 일으키지 않도록 하고 있다고 생각 됩니다만, 이에 대해서는 저도 정확히 잘 모르겠습니다.

Enabling Thread Support
멀티 쓰레드 어플리케이션에서 파이썬 API를 사용하기 전에 반드시 호출 되어야 하는 초기화 루틴들이 있습니다. 이 초기화 루틴들을 사용하기 위해서 파이썬은 컴파일 될 때 쓰레드를 지원한다는 옵션을 켜고 컴파일 되어야 합니다(그리고 보통은 기본으로 이렇게 되어 있습니다.). 일단 파이썬 인터프리터가 쓰레들를 지원하는 형태로 설치되어 있다면 스크립트가 멀티 쓰레드를 지원 할 지, 그렇게 하지 않을지 런타임에 결정할 수 있는 선택을 할 수 잇습니다.. 쓰레드를 지원하지 않는다고 선언하고(아무것도 하지 않고) 스크립트를 실행하면 lock을 잡는데 드는 오버헤드를 피할 수 있습니다. 만일 멀티 쓰레드 어플리케이션을 지원 한다는 옵션을 켜고자 한다면, 개인적으로 이 작업을 메인 쓰레드에서 할 것을 권장 합니다. 특히 어플리케이션이 시작하는 시점 말입니다.

// initialize Python
Py_Initialize();
// initialize thread support
PyEval_InitThreads();

위의 예제는 두 가지 함수를 보여 주고 있습니다. 하나는 이전에 언급한 Py_Initialize 입니다. 이 함수는 인터프리터 라이브러리가 사용 할 전역 자원들을 할당 해 줍니다. 그리고 PyEval_InitThreads 함수는 런타임 쓰레드 지원 옵션을 켜줍니다. 이 함수는 파이썬의 내부 lock 메커니즘을 구동하지요. 이 함수에 주목할 점은 내부적으로 Global Interpreter Lock을 잡아버린다는 것입니다. 그래서 이 함수를 호출 하고 난 후에는 반드시 PyEval_ReleaseLock을 이용해 lock을 풀어 줘야 합니다. 하지만 lock을 풀기 전에 한 가지 더해야 할 일이 있습니다. 현재(메인) 쓰레드의 PyThreadState 객체를 얻어 오는 것이지요. 이 객체는 나중에 새로운 파이썬 쓰레드를 만들고 인터프리터를 정상적으로 종료 시키기 위해서 꼭 필요한 중요한 것입니다.

PyThreadState * mainThreadState = NULL;
// save a pointer to the main PyThreadState object
mainThreadState = PyThreadState_Get();
// release the lock
PyEval_ReleaseLock();

Creating a New Thread of Execution
각 쓰레드에서 파이썬 코드를 실행하기 위해서는 PyThreadState 객체가 필요 하다고 이미 말씀 드렸습니다. 인터프리터는 각 쓰레드 마다 인터프리팅에 필요한 데이터를 저장하고 이 PyThreadState객체를 이용하여 관리합니다. 이렇게 각 쓰레드의 정보들이 따로 관리 됨으로써 한 쓰레드의 액션이 다른 쓰레드까지 전파 되는 것이 방지 됩니다. 예를 들어서 한 쓰레드의 파이썬 코드에서 예외가 발생 한다고 하더라도, 다른 쓰레드의 파이썬 코드는 자신의 실행을 아무런 상관 없이 계속 할 수 있습니다.

PyThreadState는 각 쓰레드 내에서 직접 만들어 줘야 하는데, 이 때 모든 쓰레드에 관계된 데이터를 저장하고 있는 PyInterpreterState 라는 객체가 필요 합니다. 이 PyInterpreterState 객체는 파이썬 이 초기화 될 때 만들어 지며 메인쓰레드의 PyThreadState겍체에 저장 됩니다. 아래의 예제를 보도록 하겠습니다.

// 파이썬 API를 사용하기 전에는 항상 lock을 잡아야 합니다.
PyEval_AcquireLock();
// 메인쓰레드의 PyThreadState 객체에서 인터프리터의 레퍼런스를 얻어 옵니다.
PyInterpreterState * mainInterpreterState = mainThreadState->interp;
// 인터프리터 객체를 이용해 새로운 PyThreadState객체를 만들어 냅니다.
PyThreadState * myThreadState = PyThreadState_New(mainInterpreterState);
// 파이썬 API의 사용이 끝났으므로 lock을 해제 합니다.
PyEval_ReleaseLock();

Executing Python Code
지금 까지 PyThreadState 객체를 만들므로써 여러분의 C/C++ 어플리케이션은 파이썬 코드를 실행 할 수 있는 준비가 되었습니다. 이제 파이썬 스크립트를 실행하기 위해서 몇 가지 지켜야 할 규칙이 있습니다. 첫째, 파이썬 스크립트를 실행하기 위한 어떠한 작업이라도 그 전에 반드시 Global Interpreter Lock을 잡아야만 합니다. 둘째, 파이썬 스크립트를 실행하기 이전에 반드시 새로운 쓰레드에서 얻어온 PyThreadState 객체를 인터프리터에 로드 해야  합니다. 그리고 작업이 끝나고 나면 PyThreadState 객체를 인터프리터에서 언로드 하고, lock를 풀어 주는 것을 잊어서는 안 되겠지요. 잊지 마세요 "lock -> swap -> execute -> swap -> unlock"  순서 입니다.

// Global Interpreter Lock을 잡습니다
PyEval_AcquireLock();
// 현재 쓰레드의 PyThreadState 객체를 인터프리터에 로드 합니다.
PyThreadState_Swap(myThreadState);
// 파이썬 코드를 실행 합니다.
PyEval_SimpleString("import sys\n");
PyEval_SimpleString("sys.stdout.write('Hello from a C thread!\n')\n");
// NULL을 넣어 줌으로써 인터프리터에 로드 되는 PyThreadState를 언로드 시킵니다.[각주:2]
PyThreadState_Swap(NULL);
// lock를 해제 합니다.
PyEval_ReleaseLock();

Cleaning Up a Thread
쓰레드 내의 모든 파이썬 스크립트의 사용이 끝나고 더 이상 쓰레드의 필요성이 없어지면, 지금 까지 할당 했던 자원들을 해제 해 주어야 합니다. 그 처음 과정으로 PyThreadState 객체를 지워 줍니다.

// Global Interpreter Lock을 잡습니다
PyEval_AcquireLock();
// PyThreadState 객체를 인터프리터에서 언로드 합니다.
PyThreadState_Swap(NULL);
// 다 쓴 PyThreadState 객체를 클리어 합니다.
PyThreadState_Clear(myThreadState);
// 그리고 지워버립니다.
PyThreadState_Delete(myThreadState);
// Lock를 해제 합니다.
PyEval_ReleaseLock();

위의 코드를 호출 하고 난뒤에 pthread_exit 함수나 TerminateThread 함수등으로 안전하게 쓰레드를 없앨 수 있습니다(하지만 가장 좋은 방법은 쓰레드 루틴에서 리턴하는 것입니다.).

Shutting Down the Interpreter
여러분의 어플리케이션이 더 이상 파이썬 인터프리터를 필요로 하지 않는다면, 이제 인터프리터를 종료 해야 겠지요. 사실 필요 없는 자원을 계속 유지 하고 있을 필요는 없잖아요? 아래의 코드는 파이썬 인터프리터를 해제 하는 과정을 보여 주고 있습니다.

// shut down the interpreter
PyEval_AcquireLock();
Py_Finalize();

Global Interpreter Lock를 해제 해주는 부분이 없다는 것이 이상하게 보일지도 모르겠습니다. 하지만 조금만 생각해 보면 lock을 해제 해야 할 필요성이 전혀 없고, 또 할 수도 없음을 알수 있을 겁니다. 파이썬 인터프리터는 이미 종료 되어버렸기 때문에 더 이상의 파이썬 API는 사용 할 수 없는 것입니다.

Conclusion
파이썬을 C/C++에 임베딩하여 쓴다는 것은 딱딱한 C/C++ 코드에 상당한 유연성을 제공 해 줌에 틀림이 없습니다. 스크립트 언어이기 때문에 성능면에서 약간의 믿음직스럽지 못한 면이 있긴하지만, 성능이 크리티컬하게 요구되지 않고, 로직의 변경이 잦은 부분이라면 충분히 적용해 볼 가치가 있다고 생각 됩니다. 이 포스트에서는 PyRun_SimpleString의 예제만 들어 복잡한 파이썬 스크립트 실행에 다소 적용이 어려워 보일지도 모르겠습니다만, 다음 포스팅에는 파이썬 스크립트 파일 자체를 로드하여 사용 하는 방법에 대해 다루도록 하겠습니다.

임베딩에 관련된 더 많고 정확한 정보를 얻고 싶으신 분들은 http://www.python.org/docs/api/ 의 Python C API를 살펴보시면 많은 도움이 될겁니다.

Python embedding 2 - Calling Python function from C/C++ Appilcation
Python embedding 3 - 캐싱 된 모듈 다시 로드 하기
Python embedding 4 - PyErr_Print() 를 파일로 남기기
 
Ref.
 파이썬 임베딩의 기본 - http://www.linuxjournal.com/article/3641
 파이썬 스크립트 파일 임베딩 - http://www.codeproject.com/KB/cpp/embedpython_1.aspx
 PyThreadState_Swap 관련 - http://archives.free.net.ph/message/20030904.011503.ce0099d7.en.html 

  1. 파이선에서 C/C++에서 임베딩 할 수 있도록 제공하는 API들을 편의상 파이썬 API라고 부르겠습니다 [본문으로]
  2. 어떤 코드에서는 아래의 파라메터로 메인 쓰레드의 PyThreadState 객체를 넘겨 주기도 합니다. 정확하게 언급되어 있는 자료를 찾지는 못 했지만, 인터넷에서 찾아 볼 수 있는 다양한 임베딩 관련 코드를 살펴 보았을 때, NULL을 넘겨 준다는 것은 메인 쓰레드의 PyThreadState 객체를 다시 로드 한다는 의미 인것 같습니다. 메인 쓰레드 내에서 PyThreadState_Get 함수를 호출 한 것과 같은 의미라고 생각 하시면 될 것 같습니다(Enabling Thread Support 항목 참고). [본문으로]

'진리는어디에' 카테고리의 다른 글

Logical clock  (2) 2008.05.02
BTree  (8) 2008.03.26
Python embedding 1 - Overview  (6) 2008.03.13
파이썬으로 utf-8 문서 읽기  (1) 2008.02.22
Python embedding 4 - PyErr_Print() 를 파일로 남기기  (4) 2008.02.15
C++ - The typename keyword  (0) 2008.02.07
Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2007.12.17 00:17  댓글주소  수정/삭제  댓글쓰기

    호...
    언제부터 파이썬을? 전엔 PHP로 하고 계신 것 같던데...
    근데 저 녀석이 상당히 느려서...
    전 C에 파이썬을 임베딩하는 것 보단, 파이썬에 C를 임베딩하는 쪽으로...^^;;

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2007.12.17 15:57 신고  댓글주소  수정/삭제

      ㅇㅇPHP를 사용하려고 했는데 PHP는 extension은 가능하나, embedding이 지원이 안되더라고...
      혹시 php도 embedding을 지원 할 수 있다..라는 문서를 보게 되면 횽한테 알려주길 바래

  2. Favicon of http://blog.naver.com/padagi20 BlogIcon 파다기 2008.03.13 11:13  댓글주소  수정/삭제  댓글쓰기

    덕분에 임벤딩 열씨미 공부하고 있습니다. 큰도움이 되네요. 그런데,

    PyRun_SimpleString("sys.stdout.write('Hello from an embedded Python Script\n')\n");

    PyRun_SimpleString("sys.stdout.write('Hello from an embedded Python Script\\n')\n");
    로 수정해야 합니다. 스트링 입력버그가 있네요 ㅋㅋㅋ

  3. 정성욱 2012.07.23 22:16  댓글주소  수정/삭제  댓글쓰기

    kukuta님, 파이썬 임베딩에 관해서 질문이 있는데 혹시 괜찮으시면 질문 몇개만 좀 해두 될까요..?

위키(웹)에서 작성된 텍스트 파일을 읽어 들여 특정 문자열을 변환 해야 하는 이슈가 있었습니다. 예를 들자면 :

= 제목 =
 * 내용1
 * 내용2

위의 내용중에서 '제목'이라는 문자열을 찾아 모두 '타이틀'로 변경 하는 작업이 필요하다고 생각하시면 됩니다.

변환 하는 방법이 그렇게 어렵지는 않을 것입니다. 간단히 생각을 해보면 :

  1. 디렉토리를 순회 하며 파일의 이름들을 얻어 온다.
  2. 파일을 열어 특정 문자열을 찾아, 변경 한다.
  3. 파일을 저장하고 다른 파일들에 대해 2번 과정을 적용한다.

그렇게 문자열을 처리하는데 편리한 기능들을 많이 제공하는 언어를 사용한다면 그렇게 어려운 일도 아닐 것 입니다. 파이썬을 이용하자면 아래와 같은 코드가 나올 수 있겠지요 :

import os
for root, dirs, files in os.walk('.') :
    for file in files :
        fp = open(file,'r')
        result = ''
        try :
            for line in fp :
                line = line.replace("변환대상문자열1", "변환문자열1")
                line = line.replace("변환대상문자열2", "변환문자열2")
                line = line.replace("변환대상문자열3", "변환문자열3")
                line = line.replace("변환대상문자열4", "변환문자열4")
                # 기타등등 문자열 변환
                result += line
        finally :
            fp.close()

        fp = open(file, 'w')
        fp.write(result)
        fp.close()

버그(변환 프로그램 자신도 변환 대상으로 인식해버리기 때문)가 있긴 하지만 아마도 한번(?) 정도는 원하는대로 돌아 갈 수도 있을 것입니다.

하지만 이 프로그램이 제대로 돌아갈까요? 제대로 돌아 갔었다면 제가 이 포스팅을 하고 있지도 않았겠지요. 물론 여러분의 기대와 같이 제대로 동작하지 않았습니다. 무엇이 문제 였을 까요?

바로 한글! 한글을 처리하는 인코딩에 관련된 문제 였습니다. 웹에서 작성된 텍스트 파일의 인코딩은 UTF-8이고 제가 작성한 파이썬 파일은 ASCII 였던 것이지요. UTF-8은 가변적인 길이를 가지는 인코딩 방법인지라 영어에서는 별 문제가 없지만 한글에서는 다른 인코딩 방법과 마찰을 일으킨답니다. 예를 들자면 유니코드의 '금'이라는 한글은 '0xAE08'입니다. 이진수로 변환해 보자면 '1010 1110 0000 1000'입니다. 하지만 이것이 UTF-8로 변환되면 '1110 1001 1011 1000 1000 1000'이라는 보다 더 긴 바이트를 가지게 되는겁니다.

컴퓨터가 보는 문자열은 당연히 치환하려는 문자열과 제가 파이썬 코드에서 작성한 문자열은 같지 않기 때문에 치환하고 싶은 문자열을 하나도 찾지 못 하겠다고 시치미를 뗄 뿐입니다.

자, 그럼 이걸 어떻게 알아 냈는가라는 것이 제가 이 포스트를 쓰게 된 첫 번째 이유이구요, 어떻게 해결 했는가 하는 것이 두 번째 이유입니다. 지금 부터 하나씩 알아가 보도록 하겠습니다.

첫 번째는 어떻게 알아냈는가 하는 문제에 관한 것입니다. 처음 변환 대상 문서를 열어 보니 :

吏€湲덇퉴吏€ vsftpd???먯껜 蹂댁븞 臾몄젣媛€ ?덉뼱 蹂댁븞沅뚭퀬媛€ ?섏삩 ?곸씠 ?놁쓣 ?뺣룄濡?蹂댁븞?깆씠 ?곗뼱?섎떎.[[BR]]

----
=== 湲곕낯 ?뺣낫 ===
 * OS : Red Hat Linux release 7.3
 * ?⑸룄 : GP 踰꾩텛???ъ씠?몄슜 ?쒕쾭 諛?媛쒕컻 ?뚯뒪?몄슜 ?ㅼ슫濡쒕뱶 ?ъ씠??

=== 二쇱슂 湲곕뒫 ===
- 媛€??IP蹂?蹂꾨룄???섍꼍 ?ㅼ젙 湲곕뒫 (?ㅼ젙?뚯씪??listen_address= ?댁슜) [[BR]]
- 媛€???ъ슜???ㅼ젙[[BR]]
- ?꾩넚 ?€??룺 吏€??[BR]]
- PAM 吏€??(踰꾩쟾 1.2.0遺€?곕뒗 PAM???듯븳 wtmp??濡쒓릿 濡쒓렇瑜??④?)[[BR]]
- xferlog ?쒖? 濡쒓렇 ?뚯씪蹂대떎 ?곸꽭???먯껜 濡쒓렇 ?뚯씪 ?뺤떇 吏€??[BR]]
- Standalone 諛⑹떇怨?inetd(xinetd)瑜??듯븳 ?댁쁺 紐⑤몢 吏€??[BR]]
- IP蹂??ㅻⅨ ?섍꼍 ?뚯씪 吏€??湲곕뒫 (tcp_wrappers?€ ?④퍡 ?ъ슜????[[BR]]

위와 같이 나옵니다. 딱봐도 인코딩이 깨진 것이 보이지요. 영어는 잘 보이지만, 한글 부분은 깨졌잖아요. vi가 기본적으로 보여 주는 인코딩 셋은 아마도 ASCII일 것입니다. 그렇다면 이 파일의 인코딩 셋은 무엇일까요? 리눅스에서는 'file'이라는 특정 파일의 정보를 얻어 내는데 도움이 되는 명령어를 제공하고 있습니다. 특히 인코딩 정보를 말입니다 :

[kukuta@server ~]$ file wikifile
wikifile: UTF-8 Unicode text
[kukuta@server ~]$ file pythonfile.py
pythonfile.py: ASCII Java program text

위의 두 파일의 인코딩 정보가 다르지요? 인코딩 셋이 맞지 않는 것 같다라고 생각이 되시면 언제든지 'file'을 이용하세요.

이제 문제를 해결 하는 것에 초점을 맞춰 보겠습니다. 지금 당장 우리에게 필요 한 것은 어떤 인코딩 방법으로 파일을 읽어들이느냐가 아닙니다. 어떻게 해서든지 파이썬 코드에서 작성된 '변환대상문자열1'이 웹에서 작성된 '변환대상문자열1'과 같다라는 것을 컴퓨터에게 알려야만 합니다. 크게 두 가지 방법이 있는데 한 가지 방법은 파이썬 버젼의 제약을 받는 것이고, 나머지 한 가지는 제가 아는 한도 내에서는 리눅스에서만 할 수 있는 것입니다(물론 윈도우에서도 할 수 있겠지만 제가 방법을 모릅니다).

첫번째 방법은 비교할 문자열을 'utf-8'로 인코딩 하여 비교하는 것입니다. 'How to use UTF-8 with Python'이라는 글에 잘 나와있습니다만, 저는 저 방법을 이용해서는 잘 안되더군요.

두 번째 방법은 파이썬 파일의 인코딩 셋을 utf-8로 변경 해 버리리는 것입니다. 만일 제가 '변환대상문자열1'이라는 문자열을 파이썬에 쓰고, 저장을 하게 된다면 단순 유니코드로 저장이 되겠지요. 하지만 이 파일의 인코딩 셋을 utf-8로 변경하게 된다면 우리가 변경하고자 하는 웹에서 작성된 텍스트 파일에 있는 문자열과 동일한 바이트를 가진 문자열로 변환 되는것 입니다. 리눅스에서는 파일의 인코딩 셋을 변경하기 위해 'iconv'라는 커맨드를 제공 하고 있습니다 :

[kukuta@server ~]$ iconv -f euc-kr -t utf-8 pythonfile.py --output pythonfile.py

위와 같이 하고 나면 유니코드로 작성되어 있던 한글이 utf-8로 변환 됩니다. 아까 제대로 보이던 한글은 이제는 깨져서 보일 것입니다만, 중요한 것은 컴퓨터가 변환하고자 하는 한글과 우리가 적은 한글이 동일함을 이제 부터는 안다는 것이지요.

Ref.

Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2008.02.24 23:45  댓글주소  수정/삭제  댓글쓰기

    저런 인코딩 문제 때문에 한글 프로그래밍이 힘들죠.
    맨날 하고 있는 짓이지만, 어떻게든 디코딩,인코딩을 없애고 싶은 사람중 하나임.
    utf8을 쓰자니, 한글 한 글자 뜯기 귀찮고, unicode 쓰자니 출력할 때 encoding 해야 하고...

    어떻게 해결 좀 해줘요.

파이썬 임베딩을 하면서 가장 유용하게 사용되는 함수 중에 하나가 PyErr_Print()라고 하면, 대부분 공감 하시리라 믿습니다. PyErr_Print()를 통해 우리는 파이썬 모듈을 로드하고 실행하는 중에 발생하는 모든 에러들과 예외 들에 대해 성실하고도 친절한 답변을 받게 됩니다.

그런데 가끔은 PyErr_Print()가 보여 주는 메시지들을 보지 못하는 환경에 놓일 때가 있습니다. 예를 들자면 윈도우의 서비스 프로세스나 리눅스의 데몬 프로세스 같은 경우지요. 앞에 말한 두 종류의 프로세스들은 프로세스의 특성상 stdout 이라던지 stderr과 같은 표준 출력을 지원하지 못하도록 막아 버리는 경우가 대부분 입니다. 그리고 우리의 PyErr_Print()함수는 stderr을 통해서 메시지를 보여 주도록 되어 있는데, stderr이 막혀 있으니 어디로든지 메시지를 전달 할 수 있는 방법이 없는 것 입니다.

그럼 이런 프로세스들은 아예 볼 방법이 없는가라고 물으 실지도 모르겠습니다. 당연히 방법이 있지요. 방법이 없었으면 포스트 시작 하지도 않았습니다. 간단하고 일반적 방법으로는 stderr의 출력을 파일 출력으로 리다이렉션 하는 방법이 있습니다. 그리고 가끔은 메시지 박스등에 출력 해주기 위해서 특정 버퍼로 출력을 원하시는 분들도 있습니다.

이 포스트에서는 첫 번째 방법에 대해서만 설명할 것입니다. 두 번째 방법에 보다 더 흥미를 느끼는 분은 'how to get string printed by PyErr_Print( )?"를 추천 드립니다.

자, 이제 부터 'PyErr_Print()의 메시지를 파일로 출력 하는 방법'에 대해서 살펴 보도록 하겠습니다. 기본 개념은 간단 합니다. sys.stderr에 우리가 출력하길 원하는 파일의 파일 객체를 넣어 주기만 하면 됩니다. 그러기 위해서는 먼저 파일 객체를 만들어 주어야 하겠지요. 아래의 코드를 한번 살펴 보도록 하겠습니다 :

PyObject* objFD = PyFile_FromString("FileNameWhatYouWant.txt", "a");

파일 객체를 만들어 리턴하는 함수 입니다. 첫번째 인자는 만들고자 하는 파일의 경로를 넣어 주고, 두 번째 인자로는 파일의 오픈 모드(open mode)를 설정 합니다. 다시 말해 쓰기 위해 여는지(w), 읽기 전용(r)으로 여는지 등을 지정하는 것입니다. C의 fopen함수와 동일한 의미와 갯수의 파라메터 리스트를 가지고 있습니다.

파일 객체가 정상적으로 생성되고 나면 파일 디스크립터의 버퍼를 0으로 만들어 버립니다 :

PyFile_SetBufSize(objFD, 0);  

PyFile_SetBufSize()함수를 이용해 버퍼 사이즈를 0으로 줄여 버렸습니다. 만일 여기서 버퍼 사이즈를 줄여 주지 않는다면 어떻게 될까요? 결론 부터 말하자면 분명히 에러는 나는데 파일에는 아무 것도 안 써지고 있는 현상이 발생 합니다. 좀 더 기다리시면(약 4096 바이트 정도의 메시지를 발생 시키고 난 후) 원했던 파일에 에러 메시지 들이 잔뜩 쌓여 있는 것을 보실 수 있으실 겁니다. 왜 이럴까요? 기본적으로 파일에 접근 한다는 것은 상당히 느린 연산에 속합니다. 그래서 대부분의 아키텍쳐에서는 파일에 접근하는 횟수를 줄이기 위해서 버퍼링을 사용하게 되고, 버퍼가 가득 차던지 아니면 flush를 하기 전까지는 파일에 접근하지 않지요. 하지만 우리가 원하는 것은 에러가 발생하는 즉시즉시 파일에 기록하길 원합니다. 그렇게 하기 위해서 버퍼 사이즈를 0으로 줄여 버리는 것입니다.

그리고 파이썬 문서에 따르자면 이 함수는 setvbuf() 함수를 지원하는 시스템에서만 사용이 가능하고, 파일 객체 생성 직후 바로 호출 되어야 한다고 적혀 있습니다.

이제는 새로 만들어진 파일 객체를 stderr 대신 사용 하도록 해야 합니다 :

int ret = PySys_SetObject("stderr", objFD);

sys.stderr 파이썬 모듈에 접근하여 stderr대신 새로 만든 파일 객체를 집어 넣어버리는 것입니다. 이로써 stderr로 출력되는 모든 내용들은 objFD가 가리키는 파일로 리다이렉션 되게 되는 것이지요. 물론 stderr에 모든 출력을 하고 있는 PyErr_Print() 함수의 메시지들 역시 그 파일로 기록 되겠지요.

위의 코드를 모아서 함수를 한번 만들어 보도록  하겠습니다. 함수의 이름은 RedirectStderrToFile이라고 정해 볼까요 :

#define CHECK_PYOBJECT(pyObj) (PyErr_Occurred() || NULL == pyObj)

bool RedirctStdErrToFile(char* pFileName, char* mode) {
    PyObject* objFD = PyFile_FromString(pFileName, mode);
    if(CHECK_PYOBJECT(objFD)) {
        return false;
    }
    PyFile_SetBufSize(objFD, 0);  
    int ret = PySys_SetObject("stderr", objFD);
    if(ret != 0){
        Py_XDECREF(objFD);
        return false;
    }
    Py_XDECREF(objFD);
    return true;
}

..

int main(int argc, char** argv) {
    Py_Initialize(); // 모든 파이썬 작업에 앞서서 초기화는 필수!!
    ... // 기타 다른 초기화 모듈 로드
    RedirctStdErrToFile("FileNameWhatYouWant.txt", "a"); // 리다이렉션!!

    ... // 파이썬 모듈 로딩과 실행 코드
    PyErr_Print(); // 화면이 아닌 파일로 출력!!!
    ... // etc

    Py_Finalize();
}

오늘도 이렇게 포스팅이 끝났습니다. 얼마 전에 한RSS라는 곳에서 제 블로그를 구독하고 계신 분이 네분이나 된다는 사실을 알고 조금 놀랐습니다. 반말 찍찍하던 포스트들에 존댓말을 쓰기 시작한 것도 아마 그 때 부터인 것 같습니다. 제 꿈중의 하나가 작가가 되는 것인데, 왠지 독자가 생겨서 뿌듯한 기분이군요.

감사합니다.
Posted by kukuta

댓글을 달아 주세요

  1. 2011.05.20 11:15  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  2. 2012.04.25 14:11  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  3. Favicon of https://kukuta.tistory.com BlogIcon kukuta 2012.04.29 22:57 신고  댓글주소  수정/삭제  댓글쓰기

    늦게 봤네요..^^
    사실 뭘 만드시려는지 잘 이해는 안되는데요..
    일단 embedding을 이용하는 방법이 있겠네요. embedding 이란, c++에서 파이썬 함수를 호출 할 수 있는 기능 입니다. c++ 어플리케이션에서 스레드를 생성하고, 그 스레드 내에서 파이썬 코드를 호출 후, 리턴 값을 받아서 사용하는 방법을 사용 할 수 있지 않을까 싶네요.

    둘째는..구현 가능한지 확실하진 않은데.. createprocess로 파이썬 프로세스를 실행 시키는 방법이 있을 수 있겠네요. 음..이거는 부모 프로세스에게 어떻게 값을 전달하느냐가 문제인데, 아마도 파이썬에서 부모 프로세스의 핸들을 리턴하는 함수가 있지 않을까 싶네요. 부모 프로세스의 핸들만 안다면 원하는 메시지를 전달 할 수 있지 않을까요?

    도움이 됐는지 모르겠습니다. 암튼 들려 주셔서 감사합니다.
    해결 된다면 저에게도 알려 주시면 감사하겠습니다.^^

Python embedding 1 - Overview
Python embedding 2 - Calling Python function from C/C++ Appilcation

얼마 전 C++코드에서 파이썬 모듈을 로드해 사용 할 수 있는 임베딩(Embedding)이라는 기술에 대해서 포스팅 한적이 있습니다(http://kukuta.tistory.com/83). 그 때 제가 임베딩을 사용한 목적은 '로직이 변경 되었을 때, 재 컴파일이 필요 없이 동적인 로직의 변경이 가능하도록 한다'였습니다만, 파이썬에서는 모듈이 한번 로드 되면 캐싱 되는 기능을 가지고 있더군요.

모듈이 캐싱되어 있다는 것이 보통의 경우에는 성능향상등 각종 이로운 현상을 만들어 주지만, 이번 제가 하려는 경우에는 그다지 도움이 되지 않더군요. 모듈을 변경하고 다시 저장한 후에 해당 모듈을 호출해 주는 C++코드를 호출하면 변경된 모듈이 반영되는 것이 아니라, 이전에 캐싱되어 있는 모듈이 항상 작동하더군요. 결국 재컴파일과 재시작이 필요 없이 로직을 변경 할 수 있는 어플리케이션의 제작은 물건너 가는 듯 싶었습니다.

하지만 뜻이 있는 곳에 길이 있다고, '파이썬 마을(http://bbs.python.or.kr )'라는 곳에서 답변을 찾게 되었습니다(http://bbs.python.or.kr/viewtopic.php?t=23522).
 
간단하게 요약을 하자면 :
 파이썬은 기본적으로 로드된 모듈은 sys.modules 라는 딕셔너리에 이름:경로 형태로 저장 캐싱을 한다고 합니다. 임베딩 사용시, 로드된 객체들을 메모리에서 삭제하기 위해 Py_DECREF()를 이용해 Reference 카운트를 낮추게 되는데, 이 카운트가 0이 되더라도 실질적으로 sys.modules에서는 로딩된 모듈이 언로드 되지 않는다그는 것이 문제의 원인이었습니다.

파이썬 코드에서라면 sys.modules에 직접적으로 접근하여 del 함수 등으로 삭제를 해버리면 되겠지만, 임베딩, 즉 C++에서는 PySys_GetObject() 라는 재밌는 함수[각주:1]를 이용해서 modules에 접근 할 수 있습니다 :

#include <Python.h>

...
PyObject* pSysModules = PySys_GetObject("modules");

파라메터는 특정한 모듈을 가리키는 것이 아니라 문자열 그대로 "modules"를 사용 합니다. sys.modules에 접근한다는 의미입니다. 다른 sys 하위 모듈을 가리키기 위해서는 다른 문자열을 넘겨 주면 되겠지요. 일단 sys.modules에는 접근 했습니다. 그렇다면 이제 해야 할 일은 리로드하고자 하는 모듈(좀 더 정확히 말하자면 리로드를 정상적으로 수행하기 위해 삭제 해야 할 모듈)을 찾아내는 것입니다.
 
모듈을 삭제하는 함수는 PyDict_DelItem() 입니다. 모듈을 삭제하기 위해 특화 되어 있는 함수라기 보다는, 딕셔너리에 있는 데이터를 지워주는 역할을 하는 함수지요. 이 함수는 딕셔너리 함수와 지워질 데이터의 키, 이렇게 두 개의 PyObject 객체를 인자로 받습니다.  위의 코드와 연결 하여 아래의 코드를 살펴 보도록 하지요 :

if(NULL != pSysModules) {
    PyObject* pModuleName = PyString_FromString("NameOfModule"); //삭제하고자 하는 모듈의 이름
    // ..에러 처리 코드 생략..
    int ret = PyDict_DelItem(pSysModules, pModuleName );
    // ..if OK, ret is 0, else -1
}

모듈의 이름을 PyObject로 변환하기 위해서 PySring_FromString함수를 썼다는 사실에 대해 별다른 설명은 하지 않도록 하겠습니다. 궁금하신 분들은 이전의 포스트를 참고 해 주세요. 여기서 주의 깊게 봐야 할 것은 :

  • PySys_GetObject()함수를 이용해 전역적으로 모듈이 등록 되어 있는 딕셔너리를 얻어 왔고,
  • 딕셔너리에서 캐싱 없이 완전한 리로드를 원하는 객체를 지웠다.

는 것입니다. 지워진 객체에 대해서 다시 로딩 요청이 들어 오더라도 이번에는 변경된 모듈을 반영 하겠지요.


Python embedding 4 - PyErr_Print() 를 파일로 남기기
  1. 파이썬 공식 문서에는 아무리 찾아봐도 PySys_GetObject() 함수에 대한 문서나 설명이 없습니다(최소한 2.5버젼까지의 문서에는요). 다만 다음의 링크 http://bugs.python.org/issue1246 를 통해서 해당 함수의 존재 유무는 판단 할 수 있습니다. [본문으로]
Posted by kukuta

댓글을 달아 주세요

/**

에구..날씨는 추워지고 옆구리는 허전해 지고, 덩달아 지갑까지 말썽이군요. 얼마전에 다녀가신 파산 신의 영험하심으로 아직도 가난에 허덕이고 있습니다. 인생 살아 오면서 여지껏 만원 이상의 돈을 빌려 본적이 없는 것 같은데, 태어나서 처음으로 20만원이라는 거금을 빌려 보았습니다. 하지만 중요한 것은 이 돈이 제 생활비로 들어 갈 것이 아니라, 여러 경조사금으로 모두 날아갈 돈이라는 것..ㅠㅠ

차마 더 어려우신 분들이 많기에, 여러분의 불우 이웃이 바로 접니다~..라는 말은 절대 하지 못하겠네요. 날씨가 추워지고 있습니다. 주변에 어려우신 분들 한번 둘러보시고, 살짝쿵 도움의 손길 한번 건네 보세요. 평소에는 아무렇지 않게 살다가 연말에만 도움의 손길 내밀려니 왠지 쑥스러워서 그러지 못하시는 분들~ 연말에 한번만 하시는 것도 상당히 고마운 겁니다. 부끄러워 하시지 않으셔도 됩니다.

올연말에는 우리 아그들 한번 보러 고향 가야 되는데..흑..ㅠㅠ

*/

Overview
지난번의 포스팅(http://kukuta.tistory.com/69)에서는 파이썬 스크립트를 C/C++에 임베딩(embedding) 하기위해서 필요한 기본 함수, 파이썬 인터프리터의 초기화와 해제 방법, 멀티 쓰레드 어플리케이션에서의 파이썬 인터프리터의 상태 유지에 대해서 집중적으로 알아 보았습니다.

하지만 정작 파이썬 스크립트를 실행 하는 것에 있어서는 PyRun_SimpleString이라는 단순한 예제만을 들었기 때문에 실전에 직접 적용하기에는 다소 부족한 면이 없지 않아 있었습니다.

오늘 포스팅에서는 파이썬 스크립트를 실행하는 부분에 초점을 맞추어 알아 보도록 하겠습니다. 하지만 파이썬 API의 자세한 해석 보다는 전체적인 사용 방법을 위주로 설명 할 것입니다. 함수의 파라메터나 리턴값, 함수의 역할 등에 대해서 궁금하시다면 파이썬 API 문서를 참고 하시면 됩니다.

앞으로 쓰일 예들을 위해서 간단한 파이썬 스크립트를 하나 작성 해 보도록 하겠습니다 :

# file : PythonFuncs.py
def PythonFunc_No_Parameter() :
    print 'This is PythonFunc no parameter'

def PythonFunc_With_Parameter(param) :
    print 'This is PythonFunc with parmeter()'+str(param)

파라메터를 받지 않는 파이썬 함수와 파라메터가 필요한 파이썬 함수, 이렇게 두 개의 함수를 준비 했습니다. 이제 이 파일을 로드하고 실행하는 C/C++코드를 살펴 보도록 하겠습니다. 성격 급하신 분들을 위해 전체 코드를 먼저 준비 했습니다.

Calling Python function from C/C++ Appilcation

코드가 다소 길고 복잡하군요. 한 번에 다 보기 힘드니 따로 떼서 차근차근 분석 해 보도록 합시다.

5     PyObject *pName, *pModule, *pDict, *pFunc, *pValue;

PyObject라는 것은 C/C++ 형태의 데이터들을 파이썬에서 인식할 수 있도록 해주는 객체 입니다. 아무래도 데이터 타입의 구분이 없는 파이썬과, 확실하게 구분을 해줘어야 하는 C/C++이 사이에서 서로를 이해시키기 위해서는 특별한 자료 구조가 필요 하겠지요? 그 역할을 하기 위해 제공되는 것이 PyObject 입니다. 이 PyObject라는 자료 구조를 통해서 C/C++에서 파이썬 인터프리터로 파라메터를 넘겨 줄 수 있고, 파이썬 스크립트에서 리턴하는 데이터를 C/C++에서 읽어 올 수 잇는 것입니다. 파이썬 API에서 제공하는 함수들을 통해 C/C++ 형태의 데이터들을 PyObject로 변환 할 수 있습니다.

13     // Initialize the Python Interpreter
14     Py_Initialize();

파이썬 인터프리터를 초기화 합니다. PyInitialize 함수에 대한 설명은 이전 포스트(http://kukuta.tistory.com/69)에 소개 되어 있습니다. 이 함수에 대해서 잘 모르시는 분은 이전 포스트를 먼저 살펴 보세요.

16     // Build the name object
17     pName = PyString_FromString(argv[1]);
18
19     // Load the module object
20     pModule = PyImport_Import(pName);

PyString_FromString 함수를 이용하여 C/C++의 문자열을 pName이라는 이름을 가진 PyObject라는 파이썬 객체로 변환 합니다. 파이썬 API 문서를 보면 이 함수의 리턴 값이 'New reference'라고 되어 있습니다. 이것에 의미는 뒤에 설명 됩니다만, 지금은 이 함수의 리턴 값이 'New reference'라는 것에만 주목 해 주세요.

그리고 PyString_FromString 함수를 통해 만들어진 pName을 이용하여 파이썬 모듈을 로드합니다. 정확히는 로드라기 보다는 파이썬 모듈을 __builtins__ 라는 글로벌 변수에 넣는 것입니다만, 여기서는 단순히 파이썬 스크립트 파일을 로드 한다라는 정도로 알고 넘어 가도록 하겠습니다. 이 때 모듈파일을 로드하기 위해서 PyImport_Import 라는 함수가 사용됩니다. 파이썬 API문서를 따르자면 이 함수도 'New reference'를 리턴하는 군요. 이렇게 스크립트 파일의 로드가 끝났습니다.

22     // pDict is a borrowed reference
23     pDict = PyModule_GetDict(pModule);
24
25     // pFunc is also a borrowed reference
26     pFunc = PyDict_GetItemString(pDict, argv[2]);

파일에 대한 로드를 마치고, 파이썬 함수를 로드하는 부분입니다. PyModule_GetDict 함수는 모듈 내의 함수들을 담고 있는 딕셔너리(dictionary) 객체를 리턴 합니다.

PyDict_GetItemString 함수를 이용하여 지정된 딕셔너리 객체에서 함수를 얻어 올 수 있습니다. 이 함수들의 리턴 값은 이전에 나왔던 두 함수와는 달리 "Borrowed reference"라는 것에 주목 해 주세요. 예제에서는 함수를 저정하기 위해서 메인 함수의 두번째 파라메터를 이용했습니다.

28     if (PyCallable_Check(pFunc))

함수의 호출 가능여부를 위해 PyCallable_Check 함수를 이용합니다. 이 함수는 인자로 넘어오는 객체가 호출 가능한 함수일 경우 1을, 그렇지 않은 경우 0을 반환 합니다.

33             pArgs = PyTuple_New(argc - 3);
34
35             for (i = 0; i < argc - 3; i++)
36             {
37                 pValue = PyInt_FromLong(atoi(argv[i + 3]));
38                 if (!pValue)
39                 {
40                     PyErr_Print();
41                     return 1;
42                 }
43                 PyTuple_SetItem(pArgs, i, pValue);
44             }
45
46             pValue = PyObject_CallObject(pFunc, pArgs);
47
48             if (pArgs != NULL)
49             {
50                 Py_DECREF(pArgs);
51             }

PyObject_CallObject 함수를 이용해 파이썬 스크립트의 함수를 호출 합니다. 이 코드에서 두 가지를 유의 깊게 보셔야 합니다. 첫째는 C/C++에서 파이썬 함수로 파라메터를 넘겨주는 방법입니다. C/C++에서 파이썬 스크립트의 함수를 호출 하면서 파라메터를 넘겨 주기 위해서는 파라메터들을 하나의 튜플에 넣어야 합니다. 그 튜플을 만들어 주는 함수가 PyTuple_New 이구요, 인자로 튜플의 사이즈를 받습니다.  이렇게 생성된 튜플을 PyObject_CallObject의 파라메터로 넘겨 주므로써 파이썬 스크립트의 함수는 C/C++이 넘겨주는 데이터들을 인식 할 수 있는 것입니다. 만일 아무런 인자도 넘겨주지 않기를 원한다면 튜플 객체의 포인터 대신 'NULL'을 넘기면 됩니다.

둘 째는 Py_DECREF라는 함수입니다. 파이썬 API 문서를 살펴 보면 PyTuple_New 함수도 위에서 언급된 PyString_FromString, PyImport_Import 함수들 처럼 새로운 레퍼런스를 생성 한다고 합니다. 파이썬에서는 C/C++에서 처럼 사용자가 명시적으로 메모리를 할당 하고 해제 하는 불편함을 제거하는 대신 각 변수마다 레퍼런스 카운터를 두어, 이 레퍼런스 카운터가 0이 되면 변수에 할당된 메모릴를 해제하는 메커니즘을 가지고 있습니다. 하지만 그 메커니즘은 어디까지나 파이썬 인터프리터의 관리 하에 있을 때에 적용되는 것이고, C/C++ 영역에서는, 인터프리터가 관리 할 수 없으므로 사용자가 직접 레퍼런스 카운터를 더하고 빼는 작업을 해야만 합니다. 그래서 새로운 레퍼런스를 생성하는 PyObject객체를 만들게 되면 Py_DECREF 함수를 이용해 레퍼런스 카운터를 감소 시켜야만 합니다. 하지만 PyModule_GetDict, PyDict_GetItemString 같이 PyObject 객체를 만들기는 하지만 새로운 레퍼런스를 생성하는 것이 아닌 레퍼런스를 빌려 오는(레퍼런스 카운터가 증가 하지 않는)경우에는 Py_DECREF 함수를 적용해서는 안됩니다.

62     // Clean up
63     Py_DECREF(pModule);
64     Py_DECREF(pName);
65
66     // Finish the Python Interpreter
67     Py_Finalize();

파이썬 스크립트의 사용이 끝나면 할당된 자원들을 해제 해 주어야 합니다. pModule과 pName 객체는 새로운 레퍼런스를 리턴하는 함수에 의해 생성 되었기 때문에 Py_DECREF 함수를 통해 레퍼런스 카운터를 감소시켜 줘야만 합니다. 그리고 모든 파이썬 스크립트의 사용이 끝나면 Py_Finalize를 호출하여 파이썬 인터프리터의 전역 자원들을 해제합니다.

요약
임베딩 된 파이썬 함수를 호출하는 순서는

  1. 파이썬 인터프리터 초기화
  2. 파이썬 모듈(파일) 로드
  3. 모듈내의 함수를 딕셔너리 형태로 얻어오기
  4. 딕셔너리 내에서 호출 대상 함수 지정
  5. 함수의 호출 가능 여부 테스트
  6. 파라메터 변환 또는 파라메터가 불필요 한 경우는 NULL 셋팅
  7. 호출
  8. 할당된 자원 해제

정도로 요약 할 수 있겠습니다.

Ref.
 http://www.codeproject.com/KB/cpp/embedpython_1.aspx

Posted by kukuta

댓글을 달아 주세요

  1. 농사꾼 2013.01.25 14:48  댓글주소  수정/삭제  댓글쓰기

    감사합니다. 좋은 설명. 필요한 부분이였는데 많은 도움 받았습니다.

반복 메타문자

문자

의미

*

0회 이상 반복

ca*t => ct, cat, caat, caaat, …

+

1회 이상 반복

ca+t => cat, caat, caaat

?

0회 혹은 1

ca?t => ct, cat

{m}

m회 반복

ca{2}t => caat

{m, n}

m회부터 n회까지 반복

ca{2,4}t => caat, caaat, caaaat

)


최소 매칭

기호

의미

*?

* 와 같으나 문자열을 최소로 매칭한다.

+?

+ 와 같으나 문자열을 최소로 매칭한다.

??

? 와 같으나 문자열을 최소로 매칭한다.

{m, n}

{m,n}와 같으나 문자열을 최소로 매칭한다.

매칭 매타 문자

메타 문자

의미

.

개행 문자를 제외한 모든 문자와 매칭 된다. re.DOTALL 플래그가 셋팅되어져 있다면 줄바꿈 문자 까지도 매칭 대상에 포함된다.

^

문자열의 시작과 매칭된다.

re.MULTILINE 모드에서는 각 라인의 시작과 매칭된다.

[] 메타기호 안에서는 반대의 문자열을 취한다

) [^a-zA-Z0-9] 모든 알파벳과 숫자를 제외한 것 들과 매칭된다

$

문자열의 마지막 또는 문자열 끝의 개행 문자와 매칭되며, MULTILINE 모드 역시 개행 문자의 앞까지 매칭된다. ‘foo$’가 오직 foo 매칭되는 것과는 반대로 foo foo foobar 다 매칭된다. 흥미로운 것은 foo.$ ‘foo1\nfoo2\n’에서 찾는다면 foo2는 매치가 되지만 foo1MULTILINE 모드에서만 매칭이 된다는 것이다.

[]

문자 집합을 나타낸다. [abc]‘a’, ‘b’, ‘c’ 문자 중 하나를 의미한다. [a-c]와 같이 나타낼 수도 있다.

|

a|b 는 해당 문자가 ‘a’이거나 ‘b’라는 뜻이다.

()

규식을 그룹으로 묶는다. 이것은 match 또는 search를 통해 리턴되는 Match 오브젝트의 group를 호출할 때 사용된다.

이스케이프 문자

문자

설 명

\\

역슬래쉬 문자

\d

모든 숫자와 매칭된다 [0-9]

\D

숫자가 아닌 모든 문자와 매칭된다 [^0-9]

\s

모든 화이트 스페이스 문자와 매칭된다 [\t\n\r\f\v]

\S

화이트 스페이스가 아닌 모든 문자와 매칭된다. [^\t\n\r\f\v]

\w

모든 숫자 또는 문자와 매칭된다. [a-zA-Z0-9]

\W

숫자 또는 문자가 아닌 모든 문자와 매칭된다 [^a-zA-Z0-9]

\b

단어의 경계를 나타낸다. 단어는 영문자 혹은 숫자의 연속 문자열로 가정한다.

\B

\b의 반대로 단어의 경계가 아님을 나타낸다.

'진리는어디에' 카테고리의 다른 글

const vs mutable  (4) 2006.12.21
PHP - 문자열 취급하기  (0) 2006.12.14
가변인자를 이용한 함수(va_list)  (10) 2006.12.13
SQL command  (0) 2006.12.13
Writing a Windows Service Program  (8) 2006.12.11
Python 정규 표현식  (1) 2006.12.11
Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc/ BlogIcon 깜쏘 2006.12.13 17:57  댓글주소  수정/삭제  댓글쓰기

    이런 건 작은 포켓레퍼런스로 해결하는 센스!
    비록 돈이 들지만, 사 놓으면 든든한 힘이 되는 친구^^?