파이썬의 특징
1. 가독성
- 파이썬의 문법은 간결하고 가독성이 좋음
- 코드블럭을 들여쓰기(indentation)으로 구분하여 자연히 가독성이 높을 수 밖에 없음

2. 풍부한 라이브러리
- 광범위한 라이브러리 기본 포함
- 확장성이 큼

3. 접착성
- 기본적으로 제공되는 라이브러리들 외에 쉽게 라이브러리 추가 가능
- C로 구현되어 있는 모듈을 쉽게 만들어 붙일 수 있음
- 파이썬은 C보다 느려서 속도 문제가 생기는 부분을 C로 구현하여 붙일때 유용
- 파이썬에는 없고 C에는 이미 있는 기능들을 붙이는데도 유용, 반대로 파이썬의 기능을 C에서도 사용 가능

4. 무료
- 파이썬 소프트웨어 재단(Python Software Foundation)에서 관리
- 라이센스는 Python Software Foundation License를 따르고 있음

5. 유니코드
- 파이썬에서는 문자열이 모두 유니코드를 나타냄

6. 동적 타이핑
- 파이썬은 런타임시에 타입 체크를 하는 동적타이핑과 자동으로 메모리 관리를 해주는 특징이 있음

SWIG(Simplified Wrapper and Interface Generator)

- 파이썬은 다른 언어와의 결합이 쉬움

- 파이썬은 다른 언어와의 결합을 위해 많은 함수와 모듈을 지원하고 있음

- C/C++의 헤더파일을 분석해 파이썬과 확장이 가능한 코드를 생성해줌

- 참조 사이트: http://www.swig.org/index.php, http://www.joinc.co.kr/modules/moniwiki/wiki.php/article/Swig#AEN9


확장 모듈이 필요한 이유

1. 새로운 내장 객체 타입을 구현

확장 모듈을 이용하여 파이썬에서 C/C++ 라이브러리 함수 혹은 시스템 콜을 할 수 있도록 함


2. 파이썬의 연산 처리 속도

- 파이썬의 연산 처리 속도가 C에 비해서 약간 느리게 동작

- 사용자 인터페이스, 문자열 처리 및 자료형은 파이썬을 사용, 빠른 연산을 요구하는 작업은 C/C++의 모듈에서 실행하는 프로그램을 구현할 수 있음


3. 코드 은닉

- 파이썬 코드를 C/C++모듈이나 C확장형으로 구현 시, 작성한 파이썬 코드(*.py)들의 핵심적인 부분을 공개하지 않고 배포가능

- 파이썬은 인터프리터 언어 처럼 동작, 코드가 직접적으로 나타남


Mac에서 C/C++코딩 환경 구성

- MAC OS X에서 Eclipse CDT 설치하기 (http://eunguru.tistory.com/73) 포스트 참조


간단한 확장 모듈 예제

- spam이라는 확장 모듈 생성

- 새로 만드는 모듈에는 C 라이브러리 함수 중 문자열의 길이를 구하는 strlen()함수를 사용 할 수 있는 파이썬 프로그램을 구현 할 것


* 참고: strlen() 함수의 원형

- 한 개의 매개변수를 입력 받고, 문자의 개수를 반환함

size_t strlen(const char* str);


1. C함수 작성

#include <python.h>


static PyObject *


spam_strlen(PyObject *self, PyObject *args)

{

char* str;

int len;

if(!PyArg_ParseTuple(args, "s", &str))

return NULL;


len = strlen(str);

return Py_BuildValue("i", len);

}

1) #include <python.h>

- 헤더 파일을 인클루드 할 때 제일 먼저 포함 해야 함

- python.h 내부에 <studio.h>, <string.h>, <errno.h>, <stdlib.h>, <stddef.h>, <limits.h>, <assert.h>, <unistd.h>등이 선언 되어 있음, 다른 헤더 파일들을 인클루드 할 필요가 없음


2) PyObject, PyArg_ParseTuple, Py_BuildValue

- python.h에 선언되어 있음

PyObject: 파이썬의 객체를 C의 데이터 타입으로 표현할 수 있는 구조체, python.h내부의 object.h에 선언

PyArg_ParseTuple(): 파이썬에서 전달된 인수를 C의 자료형으로 변환해 줌,  python.h내부의 modsupport.h에 선언

Py_BuildValue(): C의 자료형 값을 파이썬에서 인식할 수 있도록 PyObject로 변경해 줌, python.h내부의 modsupport.h에 선언

* 참고: python.h 선언 시 unresolved incision error 발생 해결 방법

- python.h를 찾을 수 없어서 발생 되는 문제, python.h가 있는 위치를 추가 해서 에러 해결

- 에러 발생 확인



- python.h이 있는 경로 확인


- python.h 경로 추가


모듈 초기화

1. 모듈 임포트시 파이썬 내부의 작업

- 1단계: 모듈을 찾음

- 2단계: 모듈을 초기화

- 3단계: 지역 이름 공간(Local Namespace)에 이름을 정의


2. 작성 언어에 따른 모듈 초기화 작업

1) 파이썬으로 모듈 작성, 임포트 수행 시

- 파이썬 내부에서 자동으로 모듈 초기화 작업 수행

2) C/C++ 모듈 작성 후 파이썬에서 임포트 수행시

모듈 초기화 2,3단계를 직접 처리해 줘야 함


3. 모듈 초기화 2,3단계를 수행하는 부분 구현

1) 모듈 초기화 2,3단계를 수행하는 부분 예제

static PyMethodDef SpamMethods[] = {

{"strlen", spam_strlen, METH_VARARGS, "count a string length."},

{NULL, NULL, 0, NULL} // 배열의 끝

};


static struct PyModuleDef spammdule = {

PyModuleDef_HEAD_INIT,

"spam", // 모듈의 이름

"It is test module.", // 모듈의 설명을 적는 부분, 모듈의 __doc__에 저장됨

-1, SpamMethods

};


PyMODINIT_FUNC

PyInit_spam(void)

{

return PyModule_Create(&spammodule);

}

- PyInit_spam(): 초기화 함수, 파이썬 인터프리터에서 C/C++ 확장형 모듈을 호출하면 초기화 함수를 실행

PyModule_Create(): spammodule을 참고해서 모듈 생성

- spammodule: 생성할 모듈의 정보를 담고 있는 구조체, 마지막 매개변수는 모듈에 등록할 함수에 대한 정의를 담고 있는 배열

- SpamMethods: PyMethodDef 구조체 배열


2) 초기화 함수

- 초기화 함수의 이름: PyInit_<module_name> 형식으로 작성

- 파이썬 인터프리터에서 import를 실행하면 맨 처음 PyInit_<module_name>의 함수를 찾아서 실행

- 생성된 모듈 객체의 포인터를 리턴값으로 호출자에게 넘겨줘야 함, sys.module에 만든 모듈이 등록되기 위해 필요함

3) 모듈 생성

- PyModule_Create() 함수: 생성된 모듈 객체의 포인터를 넘겨 줌, 에러가 발생하면 NULL값을 리턴

PyObjectPyModule_Create(PyModuleDef *module)

Create a new module object, given the definition in module. This behaves like PyModule_Create2() with module_api_version set to PYTHON_API_VERSION.

4) 모듈에 등록할 함수에 대한 정의를 담고 있는 PyMethodDef 구조체 배열

- __dict__ 속성에 등록되는 함수들을 PyMethodDef 구조체 배열로 정의

PyMethodDef 구조체(method object.h에 정의)

- 1st 멤버 변수: 파이썬에서 사용하는 함수 이름

- 2nd 멤버 변수: 함수 포인터

- 3rd 멤버 변수: 파이썬에서 호출할 때 인수를 어떻게 자료형으로 받을지 결정하는 상수

(METH_VARARGS: 튜플 형태로 인수를 전달 받음, PyArg_ParseTuple 함수를 이용해 인수를 처리,

 METH_KEYWORDS: 사전 형식으로 인수를 전달 받음)

- 4th 멤버 변수: 함수에 대한 설명

struct PyMethodDef {

    const char  *ml_name;   /* The name of the built-in function/method */

    PyCFunction ml_meth;    /* The C function that implements it */

    int         ml_flags;   /* Combination of METH_xxx flags, which mostly

                               describe the args expected by the C func */

    const char  *ml_doc;    /* The __doc__ attribute, or NULL */

};

typedef struct PyMethodDef PyMethodDef;


모듈 빌드

- 위에서 작성한 spammodule.c 파일 저장

#include <python.h>


static PyObject *


spam_strlen(PyObject *self, PyObject *args)

{

char* str;

int len;

if(!PyArg_ParseTuple(args, "s", &str))         // 매개변수 값을 분석하고 지역 변수에 할당

return NULL;


len = strlen(str);

return Py_BuildValue("i", len);

}


static PyMethodDef SpamMethods[] = {

{"strlen", spam_strlen, METH_VARARGS, "count a string length."},

{NULL, NULL, 0, NULL} // 배열의 끝

};


static struct PyModuleDef spammodule = {

PyModuleDef_HEAD_INIT,

"spam", // 모듈의 이름

"It is test module.", // 모듈의 설명을 적는 부분, 모듈의 __doc__에 저장됨

-1, SpamMethods

};


PyMODINIT_FUNC

PyInit_spam(void)

{

return PyModule_Create(&spammodule);

}


1. 빌드 방법

1) 윈도우에서 빌드: c로 작성되는 코드를 dll로 작성하고 윈도우 dll과 파이썬용 dll를 구분하기 위해서 파일이름을 *.pyd로 수정 (자세한 방법 생략)

2) 리눅스 / Mac OS X에서 빌드: Makefile을 작성하여 빌드, distutils를 이용한 빌드

(책에는 리눅스에서 빌드하는 방법으로 소개, 

나는 Mac OS X에서 실습을 진행하고 있기 때문에 이방법을 Mac OS X에서 활용)

3) distutils 배포툴을 사용한 간단한 빌드 (윈도우, 리눅스, Mac OS X 에서도 모두 사용 가능, 간단한 방법)


2. distutils 배포툴을 사용한 간단한 빌드

- distutils에 대한 자세한 설명은 책의 뒷부분에 나와 있어서...자세한 설명은 일단 생략 ㅠㅠ

1) distutils를 사용하기 위한 문법 setup.py 작성

- spammodule.c와 동일 경로에 setup.py 파일 저장 후 작성

from distutils.core import setup, Extension

spam_mod = Extension('spam', sources = ['spammodule.c'])

setup(name = "spam",

      version = "1.0",

      description = "A sample extension module",

      ext_modules = [spam_mod],

)

2) 터미널에서 setup.py install 명령을 수행, 결과

- setup.py install:  필드와 설치가 동시에 진행



- 설치한 패키지는 /Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages

로 복사됨(서드파티 모듈을 위한 공간)

* 참고: setup.py --help 명령을 통해 여러가지 옵션과 사용 방법을 알아보기



3. 생성한 확장 모듈의 정상 동작 테스트
- 모듈 테스트를 위한 파이썬 코드 (testmodule.py 구현)
import spam

strcnt = spam.strlen("hello world")

print("strcnt:", strcnt)


strcnt: 11


파이썬/C API

- 파이썬/C API: 파이썬의 자료형을 C언어에서 사용할 수 있게 하는 함수


1. PyArg_ParseTuple() 함수

- 파이썬에서 C로 전달되는 인수를 C언어에 맞게 변경 시켜주는 역할을 하는 함수

PyArg_ParseTuple() 함수의 원형

int PyArg_ParseTuple(PyObject *args, const char *format, ...)

Parse the parameters of a function that takes only positional parameters into local variables. Returns true on success; on failure, it returns false and raises the appropriate exception.

PyObject *arg: 파이썬에서 C로 전달해주는 매개변수는 PyObject로 표현됨, PyArg_ParseTuple의 첫 번째 인수로 지정

- char *format: PyObject를 어떤 형식으로 변환 할지 지정(s: C언어 자료형 char* 형으로 변환, i: int형으로 변환)

- ...: 변경한 값을 저장할 변수를 지정

1) 예제

a. 위에서 나온 예제 (수정전)

spam_strlen(PyObject *self, PyObject *args)

{

char* str;

int len;

if(!PyArg_ParseTuple(args, "s", &str)) // 매개변수 값을 분석하고 지역 변수에 할당

return NULL;


len = strlen(str);

return Py_BuildValue("i", len);

}

b. 예제 수정 후

spam_strlen(PyObject *self, PyObject *args)

{

char* str;

int len;


// 매개변수 값을 분석하고 지역 변수에 할당

// s#은 문자열과 문자열 길이로 변환함, s# 기호를 사용하면 strlen()함수를 호출 하지 않아도 됨

if(!PyArg_ParseTuple(args, "s#", &str, &len))

return NULL;


//len = strlen(str);

return Py_BuildValue("i", len);

}

- s# 기호를 사용하면 strlen() 함수를 호출 하지 않아도 됨

- 문자열은 str, 문자열 길이는 len에 저장됨

2) PyObject를 어떤 형식으로 변환 할지 지정하는 기호

파이썬 객체 타입 

 기호

C 자료형 

 int

 i

 int

 unicode object

 s

 const char *

 float

 f

 float

 int

 l

 long

 bytes object

 y

 const char *

 unicode object

 u

 Py_UNICODE

 object

 O, S

 PyObject

(그 외의 내용들은 https://docs.python.org/3/c-api/arg.html를 참조)


3) 여러 개의 기호를 묶어서 포맷 생성

기호를 단독으로 사용해 파이썬 객체를 특정 C타입의 변수로 변환 하기도 하지만 여러 개의 기호를 묶어서 포맷을 만들기도 함

a. 파이썬에서 func(1, 2, 'spam')처럼 호출

- 1과 2는 k, l에 저장, 'spam'은 s에 저장

ok = PyArg_ParseTuple(args, "lls", &k, &l, &s)

b. 파이썬에서 func((1, 2), 'str')처럼 호출

- 튜플 안의 값을 읽어 k와 l에 저장

ok = PyArg_ParseTuple(args, "(ll)s", &k, &l, &s)

4) PyArg_ParseTuple()함수를 통해 읽어온 파이썬의 객체가 어떠한 객체인지 판단하는 함수

int PyLong_Check(PyObject* o)

int PyFloat_Check(PyObject* o)

int PyComplex_Check(PyObject* o)

int PyString_Check(PyObject* o)

int PyBuffer_Check(PyObject* o)

int PyList_Check(PyObject* o)

int PyTuple_Check(PyObject* o)

int PyDict_Check(PyObject* o)

* PyInt_Check() 함수는 2.7까지는 있었으나... 3부터는 사라졌는지 찾을 수 없었음


2. Py_BuildValue() 함수

- C의 자료형을 파이썬의 자료형으로 변환 (단, 매개변수가 포인터인 자료는 사용 할 수 없음)

- 변환에 사용되는 문자는 PyArg_ParseTuple()에서 사용하는 것과 같음

- Py_BuildValue()함수의 매개변수가 비어 있으면 자동으로 None 객체를 만듦

- Py_BuildValue() 함수의 원형

PyObjectPy_BuildValue(const char *format, ...)
Return value: New reference.

Create a new value based on a format string similar to those accepted by thePyArg_Parse*() family of functions and a sequence of values. Returns the value or NULL in the case of an error; an exception will be raised if NULL is returned.

Py_BuildValue() does not always build a tuple. It builds a tuple only if its format string contains two or more format units. If the format string is empty, it returns None; if it contains exactly one format unit, it returns whatever object is described by that format unit. To force it to return a tuple of size 0 or one, parenthesize the format string.

When memory buffers are passed as parameters to supply data to build objects, as for the s and s# formats, the required data is copied. Buffers provided by the caller are never referenced by the objects created byPy_BuildValue(). In other words, if your code invokes malloc() and passes the allocated memory to Py_BuildValue(), your code is responsible for calling free() for that memory once Py_BuildValue() returns.

In the following description, the quoted form is the format unit; the entry in (round) parentheses is the Python object type that the format unit will return; and the entry in [square] brackets is the type of the C value(s) to be passed.

The characters space, tab, colon and comma are ignored in format strings (but not within format units such as s#). This can be used to make long format strings a tad more readable.

- 사용 예제

Py_BuildValue("")                                         // None

Py_BuildValue("i", 123) // 123

Py_BuildValue("iii", 123, 456, 789) // (123, 456, 789)

Py_BuildValue("s", "hello") // 'hello'

Py_BuildValue("y", "hello") // b'hello'

Py_BuildValue("ss", "hello", "world") // ('hello', 'world')

Py_BuildValue("s#", "hello", 4) // 'hell'

Py_BuildValue("y#", "hello", 4) // b'hell'

Py_BuildValue("()") // ()

Py_BuildValue("(i)", 123) // (123,)

Py_BuildValue("(ii)", 123, 456) // (123, 456)

Py_BuildValue("(i,i)", 123, 456) // (123, 456)

Py_BuildValue("[i,i]", 123, 456) // [123, 456]

Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456)         // {'abc': 123, 'def': 456}

Py_BuildValue("((ii),(ii)) (ii)", 1, 2, 3, 4, 5, 6) // (((1, 2), (3, 4)), (5, 6))


에러 처리

- 파이썬 내부에서 발생한 에러는 파이썬 인터프리터가 자동으로 검출하고 사용자에게 알려줌

- C 확장 모듈 내부에서 에러가 발생 될 때는 에러 처리 작업을 해줘야 함


1. strlen()함수에 문자열 대신 숫자를 입력하는 예제

import spam

strcnt = spam.strlen(1)

print("strcnt:", strcnt)

Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Spammodule/src/testmodule.py", line 4, in <module>

    strcnt = spam.strlen(1)

TypeError: 'int' does not support the buffer interface

- 에러 설정을 해주지 않아도 예외 발생 됨

- 파이썬/C API 함수들은 이미 내부에 예외 설정이 되어 있음

- PyArg_ParseTuple() 함수를 수행할 때 예외 처리가 됨

- 사용자가 직접 함수를 만드는 경우는 사용자가 직접 예외 설정을 해줘야 함


2. 사용자가 만든 함수 혹은 C의 함수들에서 에러가 발생하는 경우의 예외 처리

- 함수 실행이 실패하면 에러 상태를 설정하고 에러값을 리턴 (보통은 에러값은 NULL을 리턴)


1) PyErr_SetString() 함수: 에러 상태를 설정하는 함수

void PyErr_SetString(PyObject *type, const char *message)

This is the most common way to set the error indicator. The first argument specifies the exception type; it is normally one of the standard exceptions, e.g. PyExc_RuntimeError. You need not increment its reference count. The second argument is an error message; it is decoded from 'utf-8‘.

- 1st 매개변수: 에러 타입 (표준 예외)

- 2nd 매개변수: 에러에 대한 메시지


a. division() 사용자 함수 추가 (C언어 코드 추가)

- 피제수를 제수로 나눠주는 함수 추가

static PyObject*

spam_division(PyObject *self, PyObject *args)

{

int quotient = 0;

int dividend, divisor = 0;


if(!PyArg_ParseTuple(args, "ii", &dividend, &divisor))

return NULL;


if(divisor)

quotient = dividend / divisor;

else

{

// PyExc_ZeroDivisionError는 0으로 나누려고 할 때 쓰는 예외

PyErr_SetString(PyExc_ZeroDivisionError, "divisor must not be zero");

// 예외 처리를 할 때는 반드시 NULL을 리턴

// PyErr_SetString 함수는 항상 NULL을 리턴

return NULL;

}


return Py_BuildValue("i", quotient);

}

static PyMethodDef SpamMethods[] = {

{"strlen", spam_strlen, METH_VARARGS, "count a string length."},

{"division", spam_division, METH_VARARGS,

"division function \n return quotient is dividend / division"},

{NULL, NULL, 0, NULL} // 배열의 끝

};

-  0으로 나누려고 할 때 PyExc_ZeroDivisionError가 발행되도록 설정

b. 파이썬 코드에서 division()함수 호출

- 정상적인 함수 호출 (에러 발생 안함)

import spam

div = spam.division(10, 2)

print("div:", div)


div: 5

- 에러 발생 코드, 설정한 예외가 리턴 됨

import spam

div = spam.division(10, 0)

print("div:", div)


Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Spammodule/src/testmodule.py", line 15, in <module>

    div = spam.division(10, 0)

ZeroDivisionError: divisor must not be zero

2) PyErr_SetFromErrno() 함수

- 문자열 대신 C의 전역변수인 errno 값을 반환

- errno은 에러가 발생했을 떄의 에러 번호가 저장되는 곳

PyObjectPyErr_SetFromErrno(PyObject *type)
Return value: Always NULL.

This is a convenience function to raise an exception when a C library function has returned an error and set the C variable errno. It constructs a tuple object whose first item is the integer errno value and whose second item is the corresponding error message (gotten from strerror()), and then calls PyErr_SetObject(type, object). On Unix, when the errno value is EINTR, indicating an interrupted system call, this calls PyErr_CheckSignals(), and if that set the error indicator, leaves it set to that. The function always returns NULL, so a wrapper function around a system call can write returnPyErr_SetFromErrno(type); when the system call returns an error.

3) PyErr_SetObject() 함수

- 사용자 문자열 대신 전달하고 싶은 데이터를 전해 줄 수 있음

void PyErr_SetObject(PyObject *typePyObject *value)

This function is similar to PyErr_SetString() but lets you specify an arbitrary Python object for the “value” of the exception.


참조 카운트

- C/C++에서는 동적 메모리 할당 및 해제를 직접 관리 해줘야 함(C: malloc()/free(), C++: new/delete)

- 메모리 누수: 할당 받은 메모리를 해제 하지 않으면 프로그램이 끝날 때까지 할당 받은 메모리를 재사용할 수 없음

- 메모리 누수는 잘못된 결과를 나타내거나 프로그램이 비정상 적으로 종료 될 수도 있음 (예: 세그먼트 폴트)


1. 파이썬의 객체 참조 카운트

- 레퍼런스가 필요할 때 메모리 공간을 확보하지 않음, 타입에 대한 인스턴스의 레퍼런스 카운트를 통한 객체 참조 전략을 가지고 있음

- 가비지 컬렉션 메모리 관리 방법을 사용: 모든 객체는 참조된 횟수를 관리하는 카운터를 가지고 있음

- 객체가 참조될 때마다 카운터를 하나씩 증가

- 참조가 해제 될 때마다 레퍼런스 카운터를 하나 감소, 카운터가 0이되면 객체는 메모리 공간에서 삭제

- 참조 카운트는 파이썬에서 자동으로 관리됨


2. C확장 모듈에서 참조 카운트 

- 사용자가 직접 참조 카운트의 증가/감소를 조정해야 함 

- 파이썬에서는 어떠한 것도 객체를 소유할 수 없고, 단지 객체의 참조(reference)만을 가질 수 있음

- 필요한 경우 레퍼런스를 생성하고 필요없을 경우 Py_DECREF()을 호출


1) Py_INCREF() 매크로 함수: 레퍼런스 카운트 증가

void Py_INCREF(PyObject *o)

Increment the reference count for object o. The object must not be NULL; if you aren’t sure that it isn’t NULL, use Py_XINCREF().

2) Py_DECREF() 매크로 함수: 레퍼런스 카운트 감소, 참조 카운트가 0이 되었을 때 메모리를 해제하는 역할도 함

void Py_DECREF(PyObject *o)

Decrement the reference count for object o. The object must not be NULL; if you aren’t sure that it isn’t NULL, use Py_XDECREF(). If the reference count reaches zero, the object’s type’s deallocation function (which must not beNULL) is invoked.

Warning

 

The deallocation function can cause arbitrary Python code to be invoked (e.g. when a class instance with a __del__() method is deallocated). While exceptions in such code are not propagated, the executed code has free access to all Python global variables. This means that any object that is reachable from a global variable should be in a consistent state before Py_DECREF() is invoked. For example, code to delete an object from a list should copy a reference to the deleted object in a temporary variable, update the list data structure, and then call Py_DECREF() for the temporary variable.

3) 레퍼런스 카운트를 빌려오는 경우

- 빌려온 레퍼런스 참조카운트는 감소시켜줄 필요가 없음

- 참조 카운트는 레퍼런스의 주인만이 증가, 감소 시킬 수 있음

setList = []

print("id(setList)", id(setList))

# borrow_Ref는 setList은 다른 이름을 가지고 있지만 똑같은 레퍼런스를 가리키고 있음 

borrow_Ref = setList

print("id(borrow_Ref):", id(borrow_Ref))


id(setList) 4302822408

id(borrow_Ref): 4302822408

* 참고: id()


레퍼런스 소유권 법칙

- 파이썬/C API 함수의 인터페이스에 따라 레퍼런스의 소유권한을 넘겨 주기도 하고 소유권한을 빌려주기도 함

- 대부분의 함수들은 레퍼런스와 함께 소유권한까지 함께 넘겨줌

- 레퍼런스 소유권을 같이 넘겨주는 함수: PyLong_FromLong(), Py_BuildValue()

- 빌려온 레퍼런스만 넘겨주는 함수: PyTuple_GetItem(), PyList_GetItem(), PyDict_GetItem(), PyDict_GetItemString()

(튜플, 리스트, 사전과 관련된 함수들은 빌려온 레퍼런스를 넘겨줌)


1. 기본 법칙

- C함수가 파이썬에서 사용될 때, 함수 매개변수를 통해 빌려온 레퍼런스가 전달됨

- 함수로 전달된 인수들은 모두 빌려온 레퍼런스, Py_DECREF()를 사용할 필요가 없음

- 호출한 곳에서는 함수가 정상적으로 수행 될 때까지 빌려온 레퍼런스의 라이프타임을 보장해줘야 함 

(Py_INCREF()를 호출해 참조 카운트를 하나 증가 시켜줘야 함)

- 호출한 쪽에서 소유권을 넘겨 받았으면 반드시 Py_DECREF()를 호출

- 빌려온 레퍼런스라도 호출한 쪽에서 소유권을 유지하고 싶으면 Py_INCREF() 사용

tuple = PyTuple_GetItem(list, 1); // tuple은 빌려온 레퍼런스이기 때문에 Py_INCREF를 호출할 필요가 없음 

Py_INCREF(list) // 참조 카운트를 증가 시켜서 라이프 타임을 보장 

long l = 0;

PyObject* item = PyLong_FromLong((long)l);    // item은 새로 생성된 레퍼런스의 소유권한도 같이 전달 되기 때문에 

// 참조카운트를 감소 시켜줘야함 

...


Py_DECREF(item)

Py_DECREF(list)

* 참고

- PyTuple_GetItem()

- PyLong_FromLong()


2. 예외 사항

- PyTuple_SetItem(), PyList_SetItem() 함수는 소유 권한을 빼앗아 가고 다시 돌려주지 않기 때문에 Py_DECREF()를 사용할 필요 없음(함수 호출이 실패해도 소유 권한을 빼앗아 감)

1) Py_DECREF()를 사용할 필요 없는 예제

list = PyList_GetItem(list, 1);

long l = 0;

PyObject* item = PyLong_FromLong((long)l);

PyList_SetItem(list, l, item) // item에 대한 소유권한을 빼앗아 가기 때문에 Py_DECREF(item)을 호출할 필요가 없음

* 참고

PyTuple_SetItem()

- PyList_SetItem()

- PyList_GetItem()

2) 빌려온 레퍼런스라도 Py_INCREF()를 반드시 해줘야 하는 경우

- 호출한 쪽에서 소유권을 유지 하고 싶은 경우, Py_INCREF()을 호출

a. 에러가 발생할 수 있는 예제

void

bug(PyObject *list)

{

PyObject *item = PyList_GetItem(list, 0);

PyList_SetItem(list, 1, PyLong_FromLong(0L));

PyObject_Print(item, stdout, 0);

}

- 파이썬에서 리스트는 모든 아이템의 레퍼런스를 소유 하고 있음

- 리스트 아이템에 대한 값을 변경하려고 하면 리스트의 아이템에 대한 모든 레퍼런스를 메모리 상에서 재배치하는 작업이 수행할 경우도 있음, 모든 레퍼런스를 삭제하고 다시 레퍼런스를 만드는 작업을 수행할 수 있음

- PyList_GetItem()이 빌려온 레퍼런스를 리턴하고, PyList_SetItem()에서 리스트의 값을 변경학려고 시도 하기 때문에 빌려온 레퍼런스의 값이 잘못된 메모리 주소 값을 가리킬수도 있음.

- 잘못된 메모리 주소 값을 가리키게 되는 경우 PyObject_Print()을 호출 할 떄 에러가 발생 할 수 있음

* 참고 - PyObject_Print()


b. a예제 문제 해결, 참조 카운트를 증가 시켜 소유 권한을 유지

void

bug(PyObject *list)

{

PyObject *item = PyList_GetItem(list, 0);

Py_INCREF(item); // 참조 카운트를 증가시켜 item에 대해 재배치가 일어나지 않도록 함 

PyList_SetItem(list, 1, PyLong_FromLong(0L)); // list에 대한 카운트 삭제는 할 필요가 없음 

PyObject_Print(item, stdout, 0);

Py_DECREF(item); // 참조 카운트를 감소 시킴 

}

- PyList_GetItem()이 리턴한 item의 값이 NULL 혹은 파이썬의 None일 때, Py_DECREF를 수행 하면 안됨


c. b의 예제 문제 해결

- item이 NULL이 아닐 때만 Py_DECREF를 수행하게 함

- NULL 혹은 None을 검사하는 매크로 함수는 없음

- Py_XINCREF(), Py_XDECREF()를 이용하여 해결, 

- 참조 카운트를 조정하려고 하는 오브젝트가 NULL일 경우, 참조 카운트에 대한 작업을 무시

- NULL이 아닌 경우, Py_INCREF(), Py_DECREF()와 동일한 역할을 수행

Py_XINCREF()

void Py_XINCREF(PyObject *o)

Increment the reference count for object o. The object may be NULL, in which case the macro has no effect.

Py_XDECREF()

void Py_XDECREF(PyObject *o)

Decrement the reference count for object o. The object may be NULL, in which case the macro has no effect; otherwise the effect is the same as forPy_DECREF(), and the same warning applies.


확장 타입

- C를 이용해서 모듈 뿐만 아니라 파이썬의 문자열이나 리스트 같은 타입을 만들 수도 있음

1. 예제1 - circle 타입의 기본형, 프로토타입 생성(circle_prototype.c)

#include <python.h>


typedef struct {

PyObject_HEAD

}circle_CircleObject;


static PyTypeObject circle_CircleType = {

PyObject_HEAD_INIT(NULL)

"circle.Circle", /* tp_name */

sizeof(circle_CircleObject),                 /* tp_basicsize */

0, /* tp_itemsize */

0, /* tp_dealloc */

0, /* tp_print */

0, /* tp_getattr */

0, /* tp_setattr */

0, /* tp_reserved */

0, /* tp_repr */

0, /* tp_as_number */

0, /* tp_as_sequence */

0, /* tp_as_mapping */

0, /* tp_hash */

0, /* tp_call */

0, /* tp_str */

0, /* tp_getattro */

0, /* tp_setattro */

0, /* tp_as_buffer */

Py_TPFLAGS_DEFAULT, /* tp_flags */

"circle objects", /* tp_doc */

};


static PyModuleDef circlemodule = {

PyModuleDef_HEAD_INIT,

"circle",

"Example module that creates an extension type",

-1,

NULL, NULL, NULL, NULL, NULL

};


PyMODINIT_FUNC

PyInit_circle(void)

{

PyObject* m;

circle_CircleType.tp_new = PyType_GenericNew;

if(PyType_Ready(&circle_CircleType) < 0)

return NULL;


m = PyModule_Create(&circlemodule);

if(m == NULL)

return NULL;


Py_INCREF(&circle_CircleType);

PyModule_AddObject(m, "Circle", (PyObject *)&circle_CircleType);

return m;

}

- 타입 생성 시 타입에 대한 모든 메서드와 데이터들이 메모리에 생성되는 것이 아님, 타입의 인스턴스가 만들어짐

- 인스턴스는 레퍼런스 카운트, 타입 객체 포인터, 인스턴스 멤버를 가지고 있음

- 타입의 메서드들은 인스턴스에 포함되지 않고 타입 객체에 저장되어 있고 인스턴스를 통해 사용됨

- 참조 카운트는 가비지 컬렉션을 위해서 존재하며, 타입의 객체 포인터는 타입 객체를 가리킴

1) 인스턴스 부분 생성

typedef struct {

PyObject_HEAD

}circle_CircleObject;

- PyObject_HEAD: 참조 카운트, 타입 객체에 대한 포인터를 PyObject_HEAD 매크로에서 포함

PyObject_HEAD는 매크로 문 이기 때문에 세미콜론을 붙이지 않음

- 별도의 인스턴스 멤버 추가 가능, circle_CircleObject 안에 추가

typedef struct {

PyObject_HEAD

int radius;

}circle_CircleObject;



PyObject_HEAD

This is a macro which expands to the declarations of the fields of the PyObjecttype; it is used when declaring new types which represent objects without a varying length. The specific fields it expands to depend on the definition of Py_TRACE_REFS. By default, that macro is not defined, and PyObject_HEADexpands to:

Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;

When Py_TRACE_REFS is defined, it expands to:

PyObject *_ob_next, *_ob_prev;
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;

2) PyTypeObject

- object.h에 정의 되어 있음 (/Library/Frameworks/Python.framework/Versions/3.4/include/python3.4m)

- 타입을 만들기 위한 모든 필드가 전부 선언되어 있음

object.h에 정의 되어 있는 PyTypeObject

#ifdef Py_LIMITED_API

typedef struct _typeobject PyTypeObject; /* opaque */

#else

typedef struct _typeobject {

    PyObject_VAR_HEAD

    const char *tp_name; /* For printing, in format "<module>.<name>" */

    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */


    /* Methods to implement standard operations */


    destructor tp_dealloc;

    printfunc tp_print;

    getattrfunc tp_getattr;

    setattrfunc tp_setattr;

    void *tp_reserved; /* formerly known as tp_compare */

    reprfunc tp_repr;


    /* Method suites for standard classes */


    PyNumberMethods *tp_as_number;

    PySequenceMethods *tp_as_sequence;

    PyMappingMethods *tp_as_mapping;


    /* More standard operations (here for binary compatibility) */


    hashfunc tp_hash;

    ternaryfunc tp_call;

    reprfunc tp_str;

    getattrofunc tp_getattro;

    setattrofunc tp_setattro;


    /* Functions to access object as input/output buffer */

    PyBufferProcs *tp_as_buffer;


    /* Flags to define presence of optional/expanded features */

    unsigned long tp_flags;


    const char *tp_doc; /* Documentation string */


    /* Assigned meaning in release 2.0 */

    /* call function for all accessible objects */

    traverseproc tp_traverse;


    /* delete references to contained objects */

    inquiry tp_clear;


    /* Assigned meaning in release 2.1 */

    /* rich comparisons */

    richcmpfunc tp_richcompare;


    /* weak reference enabler */

    Py_ssize_t tp_weaklistoffset;


    /* Iterators */

    getiterfunc tp_iter;

    iternextfunc tp_iternext;


    /* Attribute descriptor and subclassing stuff */

    struct PyMethodDef *tp_methods;

    struct PyMemberDef *tp_members;

    struct PyGetSetDef *tp_getset;

    struct _typeobject *tp_base;

    PyObject *tp_dict;

    descrgetfunc tp_descr_get;

    descrsetfunc tp_descr_set;

    Py_ssize_t tp_dictoffset;

    initproc tp_init;

    allocfunc tp_alloc;

    newfunc tp_new;

    freefunc tp_free; /* Low-level free-memory routine */

    inquiry tp_is_gc; /* For PyObject_IS_GC */

    PyObject *tp_bases;

    PyObject *tp_mro; /* method resolution order */

    PyObject *tp_cache;

    PyObject *tp_subclasses;

    PyObject *tp_weaklist;

    destructor tp_del;


    /* Type attribute cache version tag. Added in version 2.6 */

    unsigned int tp_version_tag;


    destructor tp_finalize;


#ifdef COUNT_ALLOCS

    /* these must be last and never explicitly initialized */

    Py_ssize_t tp_allocs;

    Py_ssize_t tp_frees;

    Py_ssize_t tp_maxalloc;

    struct _typeobject *tp_prev;

    struct _typeobject *tp_next;

#endif

} PyTypeObject;

#endif

- 타입 생성 시 모든 필드를 다 채울 필요는 없고 필요한 필드만 채워주고 나머지 필드는 0을 선언

- circle 타입 생성: 타입 이름, 기본 크기, 타입에 대한 설명만 추가했음

(Circle이 요구하는 함수를 구현하면 circle_CircleType에 연결해 줘야 함) 

static PyTypeObject circle_CircleType = {

PyObject_HEAD_INIT(NULL)

"circle.Circle", /* tp_name */

sizeof(circle_CircleObject),                 /* tp_basicsize */

0, /* tp_itemsize */

0, /* tp_dealloc */

0, /* tp_print */

0, /* tp_getattr */

0, /* tp_setattr */

0, /* tp_reserved */

0, /* tp_repr */

0, /* tp_as_number */

0, /* tp_as_sequence */

0, /* tp_as_mapping */

0, /* tp_hash */

0, /* tp_call */

0, /* tp_str */

0, /* tp_getattro */

0, /* tp_setattro */

0, /* tp_as_buffer */

Py_TPFLAGS_DEFAULT, /* tp_flags */

"circle objects", /* tp_doc */

};

3) circle 타입의 실제 생성

circle_CircleType.tp_new = PyType_GenericNew;

if(PyType_Ready(&circle_CircleType) < 0)

return NULL;

- PyType_GernericNew()를 통해서 미리 타입에 대한 메모리 공간을 확보

- PyType_Ready()를 통해서 초기화

Py_INCREF(&circle_CircleType);

PyModule_AddObject(m, "Circle", (PyObject *)&circle_CircleType);

- circle의 참조 카운트 증가

- PyModule_AddObject() 함수를 통해서 모듈 사전에 circle 타입을 등록하고 인스턴스를 생성 할 수 있게 함

4) distutils 배포툴을 사용한 간단한 빌드

a. distutils를 사용하기 위한 문법 setup.py 작성

- circle_prototype.c와 동일 경로에 setup.py 파일 저장 후 작성

from distutils.core import setup, Extension


setup(name = "circle",

      version = "1.0",

      ext_modules = [Extension("circle", ["circle_prototype.c"])]

)

b. 터미널에서 setup.py install 명령을 수행, 결과

- setup.py install:  필드와 설치가 동시에 진행


- 설치한 패키지는 /Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages

로 복사됨(서드파티 모듈을 위한 공간)

5) 생성한 확장 모듈의 정상 동작 테스트

- 모듈 테스트를 위한 파이썬 코드 (testmodule.py 구현)

import circle


print('circle.__doc__:', circle.__doc__)

= circle

= circle(1)


circle.__doc__: Example module that creates an extension type

Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Circle/testmodule.py", line 5, in <module>

    b = circle(1)

TypeError: 'module' object is not callable

- 현재 빈 타입이기 때문에 초기화 할 때 값을 입력하면 에러 발생


2. 예제 1에 살을 붙이는 작업 수행

- circle.c 전체 소스

/*

 * circle.c

 *

 *  Created on: 2015. 3. 14.

 *      Author: eunguru

 */


#include <python.h>

#include "structmember.h"         // PyMethodDef구조체를 사용하기 위해서 인클루드


#define PI 3.14


typedef struct {

PyObject_HEAD

PyObject *color; // 인스턴스 멤버: circle color

int radius; // 인스턴스 멤버: circle radius

}circle_CircleObject;


static PyObject *

Circle_new(PyTypeObject *type, PyObject *args, PyObject *keywords)

{

circle_CircleObject *self;

self = (circle_CircleObject *)type->tp_alloc(type, 0); // type allocation

if(self != NULL)

{

self->color = PyUnicode_FromString("");

if(self->color == NULL)

{

Py_DECREF(self);

return NULL;

}

self->radius = 0;

}

return (PyObject *)self;

}


static void

Circle_dealloc(circle_CircleObject* self)

{

Py_XDECREF(self->color);

Py_TYPE(self)->tp_free((PyObject*)self);

}


static int

Circle_init(circle_CircleObject *self, PyObject *args, PyObject *keywords)

{

PyObject *color = NULL, *tmp = NULL;

static char *keywordList[] = {"color", "radius", NULL};

if(!PyArg_ParseTupleAndKeywords(args, keywords, "|Si", keywordList,

&color, &self->radius))

return -1;


// 인수 초기화

if(color)

{

tmp = self->color;

Py_INCREF(color);

self->color = color;

Py_XDECREF(tmp); // 이전 인스턴스에 대해서 참조 카운트 감소

}

return 0;

}


static PyMemberDef Circle_members[] = {

{"color", T_OBJECT_EX, offsetof(circle_CircleObject, color), 0, "color of circle"},

{"color of circle"},

{"radius", T_INT, offsetof(circle_CircleObject, radius), 0,

"radius of circle"},

{NULL} // 구조체의 끝

};


// 사용자 정의 함수 구현부

static PyObject *

Circle_color(circle_CircleObject* self)

{

static PyObject *fmt = NULL;

PyObject *tmp, *result;

if(fmt == NULL)

{

fmt = PyUnicode_FromString("The circle color is %s");

if(fmt == NULL)

return NULL;

}


if(self->color == NULL)

{

PyErr_SetString(PyExc_AttributeError, "color");

return NULL;

}


tmp = Py_BuildValue("S", self->color);

if(tmp == NULL)

return NULL;


result = PyUnicode_Format(fmt, tmp);

Py_DECREF(tmp); // 참조 카운트를 감소 시켜 줌


return result;

}


static PyObject *

Circle_area(circle_CircleObject* self)

{

int area_circle = 0;


if(self->radius < 0)

{

PyErr_SetString(PyExc_AttributeError, "radius");

return NULL;

}


area_circle = (int)(2 * (PI*(self->radius)));


return Py_BuildValue("i", area_circle);

}


// 사용자 정의 함수

static PyMethodDef Circle_methods[] = {

{"color", (PyCFunction)Circle_color, METH_NOARGS, "Return the color of circle"},

{"area", (PyCFunction)Circle_area, METH_NOARGS, "the area of a circle."},

{NULL}

};


static PyObject* Circle_add(circle_CircleObject* self, circle_CircleObject* target)

{

self->radius += target->radius;

return Py_BuildValue("i", self->radius);

}


static PyObject* Circle_multiply(circle_CircleObject* self, circle_CircleObject* target)

{

PyErr_SetString(PyExc_NotImplementedError, "The multiply has been Implemented");

return NULL;

}


static PyNumberMethods circle_number = {

(binaryfunc) Circle_add, /* nb_add */

(binaryfunc) 0, /* nb_subtract */

(binaryfunc) Circle_multiply,         /* nb_multiply */

(binaryfunc) 0 /* nb_reminder */

};


static PyTypeObject circle_CircleType = {

PyObject_HEAD_INIT(NULL)

"circle.Circle", /* tp_name */

sizeof(circle_CircleObject), /* tp_basicsize */

0, /* tp_itemsize */

(destructor)Circle_dealloc, /* tp_dealloc */

0, /* tp_print */

0, /* tp_getattr */

0, /* tp_setattr */

0, /* tp_reserved */

0, /* tp_repr */

&circle_number, /* tp_as_number */

0, /* tp_as_sequence */

0, /* tp_as_mapping */

0, /* tp_hash */

0, /* tp_call */

0, /* tp_str */

0, /* tp_getattro */

0, /* tp_setattro */

0, /* tp_as_buffer */

Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */

"The color & radius of circle has been saved", /* tp_doc */

0, /* tp_traverse */

0, /* tp_clear */

0, /* tp_richcompare */

0, /* tp_weaklistoffset */

0, /* tp_iter */

0, /* tp_iternext */

Circle_methods, /* tp_methods */

Circle_members, /* tp_members */

0, /* tp_getset */

0, /* tp_base */

0, /* tp_dict */

0, /* tp_descr_get */

0, /* tp_descr_set */

0, /* tp_dictoffset */

(initproc)Circle_init, /* tp_init */

0, /* tp_alloc */

Circle_new, /* tp_new */

};


static PyModuleDef circlemodule = {

PyModuleDef_HEAD_INIT,

"circle",

"Example module that creates an extension type",

-1,

NULL, NULL, NULL, NULL, NULL

};


PyMODINIT_FUNC

PyInit_circle(void)

{

PyObject* m;


if(PyType_Ready(&circle_CircleType) < 0)

return NULL;


m = PyModule_Create(&circlemodule);

if(m == NULL)

return NULL;


Py_INCREF(&circle_CircleType);

PyModule_AddObject(m, "Circle", (PyObject *)&circle_CircleType);

return m;

}


- color, radius 멤버 변수, color 값을 출력해주는 메서드, cicle의 넓이를 구하는 메서드 추가

- 더하기 연산이 가능하도록 처리 하는 부분 추가


1) PyMethodDef 구조체를 사용하기 위해서 structmember.h를 인클루드

-  structmeber.h의 위치: Library/Frameworks/Python.framework/Versions/3.4/include/python3.4m/structmember.h

2) 변수 추가, 메모리 할당, 인스턴스 멤버값 초기화

- 인스턴스 멤버 PyObject형 color, int형 radius 추가

static PyObject *

Circle_new(PyTypeObject *type, PyObject *args, PyObject *keywords)

{

circle_CircleObject *self;

self = (circle_CircleObject *)type->tp_alloc(type, 0); // type allocation

if(self != NULL)

{

self->color = PyUnicode_FromString("");

if(self->color == NULL)

{

Py_DECREF(self);

return NULL;

}

self->radius = 0;

}

return (PyObject *)self;

}

3) 타입 생성시 매개변수를 넣을 경우 처리

- Circle_init()함수에서 처리

static int

Circle_init(circle_CircleObject *self, PyObject *args, PyObject *keywords)

{

PyObject *color = NULL, *tmp = NULL;

static char *keywordList[] = {"color", "radius", NULL};

if(!PyArg_ParseTupleAndKeywords(args, keywords, "|Si", keywordList,

&color, &self->radius))

return -1;


// 인수 초기화

if(color)

{

tmp = self->color;

Py_INCREF(color);

self->color = color;

Py_XDECREF(tmp); // 이전 인스턴스에 대해서 참조 카운트 감소

}

return 0;

}

- PyArg_ParseTupleAndKeywords()함수를 사용해서 매개변수 입력기 사전 형식을 이용해 아래와 같이 작성 가능

c1 = circle.Circle(radius=2)

c2 = circle.Circle(color='red'.encode('utf_8'), radius=3)

int PyArg_ParseTupleAndKeywords(PyObject *argsPyObject *kw, const char *format, char *keywords[], ...)

Parse the parameters of a function that takes both positional and keyword parameters into local variables. Returns true on success; on failure, it returns false and raises the appropriate exception.

4) 메모리 해제

- 인스턴스 멤버 color의 참조 카운트를 낮추고 할당 받았던 메모리 공간을 해제

static void

Circle_dealloc(circle_CircleObject* self)

{

Py_XDECREF(self->color);

Py_TYPE(self)->tp_free((PyObject*)self);

}

5) 타입을 위한 함수 추가

- 확장 모듈에서 사용했던 방법과 유사함(해당 함수를 생성하고 PyMethodDef 구조체에 연결)

// 사용자 정의 함수 

static PyMethodDef Circle_methods[] = {

{"color", (PyCFunction)Circle_color, METH_NOARGS, "Return the color of circle"},

{"area", (PyCFunction)Circle_area, METH_NOARGS, "the area of a circle."},

{NULL}

};

- Circle_methods() 함수 포인터를 PyTypeObject와 연결, 생성한 Circle_methods 구조체를 tp_methods 부분에 지정

static PyTypeObject circle_CircleType = {

(...중략...)

Circle_methods, /* tp_methods */

(...중략...)

};

6) 기본 메서드들을 처리 할 수 있는 모듈 처리

- 기본적인 메서드(사칙연산, 비트연산, 인덱싱, 슬라이스)는 미리 정의 되어 있는 각각의 구조체에 함수들을 연결 해주면 됨

- 수치형, 시퀀스형, 매핑형 메서드의 구조체

- 수치형 메서드(예 - 비트 연산): PyNumberMethods

- 시퀀스형 메서드(예 - 슬라이스): PySequenceMethods

- 매핑형 메서드: PyMappingMethods

a. 더하기, 곱하기 연산을 위한 함수 생성

static PyObject* Circle_add(circle_CircleObject* self, circle_CircleObject* target)

{

self->radius += target->radius;

return Py_BuildValue("i", self->radius);

}


static PyObject* Circle_multiply(circle_CircleObject* self, circle_CircleObject* target)

{

PyErr_SetString(PyExc_NotImplementedError, "The multiply has been Implemented");

return NULL;

}

b. 각각의 함수 포인터를 circle_number 구조체에 연결

-  더하기, 곱하기 연산은 수치형 연산에 속함, PyNumberMethods 구조체 사용

static PyNumberMethods circle_number = {

(binaryfunc) Circle_add, /* nb_add */

(binaryfunc) 0, /* nb_subtract */

(binaryfunc) Circle_multiply,         /* nb_multiply */

(binaryfunc) 0 /* nb_reminder */

};

c. PyTypeObject의 tp_as_number 부분에 지정

static PyTypeObject circle_CircleType = {

   (...중략...)

&circle_number, /* tp_as_number */

(...중략...)

};

7) distutils 배포툴을 사용한 간단한 빌드

a. distutils를 사용하기 위한 문법 setup.py 작성

- circle_prototype.c와 동일 경로에 setup.py 파일 저장 후 작성

from distutils.core import setup, Extension


setup(name = "circle",

      version = "1.0",

      ext_modules = [Extension("circle", ["circle.c"])]

)

b. 터미널에서 setup.py install 명령을 수행, 결과

- setup.py install:  필드와 설치가 동시에 진행




- 설치한 패키지는 /Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages

로 복사됨(서드파티 모듈을 위한 공간)

8) 생성한 확장 모듈의 정상 동작 테스트

- 모듈 테스트를 위한 파이썬 코드 (testmodule.py 구현)

import circle


= circle.Circle(color='red'.encode('utf8'), radius=1)

= circle.Circle(color='blue'.encode('utf8'), radius=3)

print('a.area():', a.area())

print('b.color():', b.color())

print('a+b:', a+b)

print('a*b:', a*b)


a.area(): 6

Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Circle/testmodule.py", line 14, in <module>

    print('a*b:', a*b)

NotImplementedError: The multiply has been Implemented

b.color(): The circle color is b'blue'

a+b: 4


Ctypes

1. 파이썬에서 외부 라이브러리를 쓰기 위한 방법

- 방법 1: 확장 모듈을 만들고 그 속에서 외부 라이브러리를 호출

- 방법 2(간단한 방법): 파이썬에서 사용하는 ctypes 모듈을 사용, ctypes 모듈을 이요하면 C의 데이터 타입이나 DLL 혹은 shared library의 함수들을 직접 사용할 수 있음

(자세한 내용은 https://docs.python.org/3/library/ctypes.html 참조)


2. ctypes를 사용하여 외부 함수를 호출하는 예제 (윈도우에서 테스트 하는 예제)

import ctypes

print('ctypes.windll.kernel32:', ctypes.windll.kernel32)

ctypes.windll.kernel32: <WinDLL 'kernel32', handle 76c00000 at 28ddad0>

print('ctypes.cdll.msvcrt:', ctypes.cdll.msvcrt)


ctypes.cdll.msvcrt: <CDLL 'msvcrt', handle 76b40000 at fc5750>

print(hex(ctypes.windll.kernel32.GetModuleHandleA(None)))

0x1c020000



3. ctypes를 사용하여 C의 데이터 타입을 바로 사용
- ctypes 함수가 지원하는 타입
ctypes typeC typePython type
c_bool_Boolbool (1)
c_charchar1-character bytes object
c_wcharwchar_t1-character string
c_bytecharint
c_ubyteunsigned charint
c_shortshortint
c_ushortunsigned shortint
c_intintint
c_uintunsigned intint
c_longlongint
c_ulongunsigned longint
c_longlong__int64 or long longint
c_ulonglongunsigned __int64 or unsignedlong longint
c_size_tsize_tint
c_ssize_tssize_t or Py_ssize_tint
c_floatfloatfloat
c_doubledoublefloat
c_longdoublelong doublefloat
c_char_pchar * (NUL terminated)bytes object or None
c_wchar_pwchar_t * (NUL terminated)string or None
c_void_pvoid *int or None

1) 예제1

from ctypes import *


= c_int(10)

print('ctypes:', i)

ctypes: c_int(10)

print('value:', i.value)

value: 10

2) 예제 2 - 포인터 지원

from ctypes import *


= c_int(10)

pk = pointer(k)     # c_int(10)의 포인터 값을 가져옴 

print('position:', pointer(k))

position: <__main__.LP_c_int object at 0x1021b4f28>

print('contents:', pk.contents)

contents: c_int(10)

3) 예제 3 - 구조체, 유니온 지원

- 구조체와 유니온은 파이썬에서 class로 표현 됨

- class 객체를 생성하고 _fields_ 멤버 변수를 이용해 각 멤버 변수를 설정

import ctypes


class POINT(ctypes.Structure):

    _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)]


point = POINT(10, 20)    

print('point.x:', point.x, 'point.y:', point.y)

point.x: 10 point.y: 20


point = POINT(y=5)

print('point.x:', point.x, 'point.y:', point.y)

point.x: 0 point.y: 5

4) 예제 4 -  실제 사용 예 (윈도우에서 테스트)

a. MessageBoxA

WINUSERAPI

int

WINAPI

MessageBoxA(

    __in_opt HWND hWnd,

    __in_opt LPCSTR lpText,

    __in_opt LPCSTR lpCaption,

    __in UINT uType);

from ctypes import c_int, WINFUNCTYPE, windll

from ctypes.wintypes import HWND, LPCSTR, UINT

prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, UINT)

paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", None), (1, "flags", 0)

MessageBox = prototype(("MessageBoxA", windll.user32), paramflags)


MessageBox(text="Please, programming with python")

MessageBox(text="Do you know about python?", flags=3)


Traceback (most recent call last):

  File "C:\Source\Python\CTypesEx\CtypesEx.py", line 19, in <module>

    MessageBox(text="Please, programming with python")

ctypes.ArgumentError: argument 2: <class 'TypeError'>: wrong type

- 에러 발생 됨...구글링 결과 string encoding 문제인 것으로 추측...정확한 원인은 모르겠음..

from ctypes import c_int, WINFUNCTYPE, windll

from ctypes.wintypes import HWND, LPCSTR, UINT

import locale

preferred_encoding = locale.getpreferredencoding(False)

prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, UINT)

paramflags = ((1, "hwnd", 0), (1, "text", "Hi".encode(preferred_encoding)),

                (1, "caption", None), (1, "flags", 0))

MessageBox = prototype(("MessageBoxA", windll.user32), paramflags)


MessageBox(text="Please, programming with python".encode(preferred_encoding))

MessageBox(text="Do you know about python?".encode(preferred_encoding), flags=3)




b. a의 예제가 에러가 발생해서 MessageBoxW를 호출해봄

WINUSERAPI

int

WINAPI

MessageBoxW(

    __in_opt HWND hWnd,

    __in_opt LPCWSTR lpText,

    __in_opt LPCWSTR lpCaption,

    __in UINT uType);

from ctypes import c_int, WINFUNCTYPE, windll

from ctypes.wintypes import HWND, LPCWSTR, UINT


prototype = WINFUNCTYPE(c_int, HWND, LPCWSTR, LPCWSTR, UINT)

paramflags = ((1, "hwnd", 0),

              (1, "text", "Hi"),

              (1, "caption", None),

              (1, "flags", 0))

MessageBox = prototype(("MessageBoxW", windll.user32), paramflags)


MessageBox()

MessageBox(text="Please, programming with python")

MessageBox(text="Do you know about python?", flags=3)

'컴&프로그래밍 > Python' 카테고리의 다른 글

14. 데이터베이스 사용  (0) 2015.08.03
13. 파일시스템  (0) 2015.07.02
8. 입출력  (0) 2015.01.14
7. 예외처리  (0) 2015.01.02
6. 모듈  (0) 2014.12.29
Mac OS X Yosemite에서 PyCharm 설치 후 실행  (0) 2014.12.27
5. 클래스  (0) 2014.12.20
4. 제어  (0) 2014.12.19
Google Python Tutorial - Basic Python Exercises #2 List  (0) 2014.12.11