1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python 自作パッケージに C 言語処理を組み込む方法

1
Last updated at Posted at 2026-01-12

はじめに

Python で自作パッケージを作る際に必要となる手順や注意点を、備忘録としてまとめた。
特に今回は ctypes を使って C のライブラリを読み込むパッケージを作るという、少し特殊なケースを扱ってみる。

目次

モジュールの呼び出し方

pythonファイルを呼び出す場合は、importfromを使う。

  • 同じ階層にあるpythonファイルを呼び出す場合
    import [ファイル名]で呼び出す。
    module_test.pyからmodule.pylib_python()を呼び出す場合は下記のようになる。
module.py
def lib_python():
    print('call:lib_python()')
module_test.py
import module
module.lib_python()
  • サブフォルダのpythonファイルを呼び出す場合
    from でフォルダパスを指定する。
    但し、フォルダ配下に__init__.pyファイルが必要になる。このファイルでフォルダー配下にpythonファイルがあることを認識する仕組みになっている。__init__.pyはimportされてた時に呼ばれる。ファイルは空ファイルでも良いし、初期化処理を記載しても良い。
フォルダ構成
project
├── lib/
│   ├── __init__.py
│   └── module.py
└── module_test.py
module.py
def lib_python():
    print('call:lib_python()')
module_test.py
from lib import modoule
module.lib_python()

戻る

if _name_ == '_main_' の役割

 Pythonでよく使われるこの記述は、単なるおまじないではなく、モジュール化を意識した設計に基づいている。

def function():
    print('call:function')
    
if __name__ == '__main__':
    function()

 ここで登場する __name__ は、モジュールの名前を表す特別な変数です。たとえば、モジュールを他のファイルからインポートした場合、 __name__ にはモジュール名が代入され、スクリプトで直接実行した場合には __main__ が代入される。
 以下のコードを実行すると、sys.__name__sys__name____main__ と出力される。

import sys

def main():
    print('call:main()')
    
if __name__ == '__main__':
    print(f'{sys.__name__=}')
    print(f'{__name__=}')

# sys.__name__='sys'
# __name__='__main__'

 この仕組みだけを見ると「何に使うの?」となりますが、自作モジュールを他のファイルから再利用する際にとても重要な役割を果たす。

  • if name == 'main':を使わない場合
    たとえば、module.pyというモジュールを作成し、それをmodule_test.pyからインポートすると、module.py 内の print(f'hello!') がインポート時にも実行されてしまう。
module.py
def lib_python():
	print('call:lib_python')

print(f'hello!') #ここが実行されてしまう

module_test.py
import module

def main():
    module.lib_python()
    
if __name__ == '__main__':
    main()
実行結果
hello!
call:lib_python
  • if name == 'main':を使った場合
    今度は module.py の print(f'hello!')if __name__ == '__main__': の中に入れる。すると、直接実行したときだけ呼ばれ、インポート時には実行されない。
module.py
def lib_python():
	print('call:lib_python')
    
if __name__ == '__main__':
    print(f'hello!') #自身を実行するときのみコール。

module_test.py
import module

def main():
    module.lib_python()
    
if __name__ == '__main__':
    main()
実行結果
call:lib_python

このように、if __name__ == '__main__': を使うことで、モジュールとしての再利用性を高めつつ、直接実行時のテストコードなども安全に書けるようになる。

戻る

C言語でライブラリ化:cytpes

Python の処理を高速化したい場合や、Python では直接扱いにくいデバイス制御・ドライバ連携を行いたい場合、対象となる処理を C 言語で実装し、その関数を Python から呼び出すという方法がある。
このときに便利なのがctypesで、C言語で作成した動的ライブラリ(.so)を Python から読み込み、関数をそのまま呼び出すことができる。

[参考にさせてもらった記事]

  • ライブラリの呼び出し方
    Cライブラリを呼び出すPythonコード。lib_c.cファイルをgcc lib_c.c -shared -o lib_c.soでコンパイルして動的ライブラリ化している前提。
import ctypes

# 呼び出すライブラリのファイル名を指定
lib = ctypes.cdll.LoadLibrary("./lib_c.so") 

# int func(int a); という関数呼び出す場合
ret = lib.func(100) #引数、戻り値の型設定は適宜実施。
                    #func()は引数、戻り値ともにint型なので型指定せずに直で設定している。
# 結果
print(ret) # 10000
lib_c.c
int func(int a){
    return a*100; 
}
//例なので単純に100倍した値を返答しているが、Pythonで実装難しい処理をさせる。
  • ライブラリの引数と戻り値の型(例)
C型 Python型
char c_char/c_byte
short c_short
int c_int
long c_long
unsigned char c_ubyte
unsigned short c_ushort
unsigned int c_uint
unsigned long c_ulong
float c_float
double c_double
char* (NUL terminated) c_char_p
void * c_void_p

引数の型設定

# int
i = ctypes.c_int(100)
# float
f = ctypes.c_float(100.15)
# unsigned char 型の配列
list = (ctypes.c_ubyte * 10)(0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a)
# 16進数データ列
bytearray_list=b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a'
# 文字列
str = b'hello!'

戻り値の型設定

# 戻り値------------
# int
ret = ctypes.c_int()
# uinsgned char型のポインタ
ret = ctypes.POINTER(ctypes.c_ubyte)

戻る

パッケージ化

パッケージ化の手順は 1〜4 が「作る側」、5 が「使う側」 の作業になる。順番に説明する。

  1. パッケージ化したいPythonコードを準備する。
  2. 公開したい API を__init__.pyに記述する。
  3. setup.pyにパッケージ化に必要な情報を記述する。
  4. distにする。
  5. 作成したパッケージを pip でインストールする。
フォルダ構成
project
├── dist/
│   └── sample_package-0.0.1.tar.gz -> 4.dist生成結果
├── sample_package/
│   ├── clib/       -> 1.Cライブラリ
│   │   ├── lib_c.c
│   │   └── lib_c.so 
│   ├── __init__.py -> 2.公開API設定
│   └── lib.py      -> 1.パッケージ化するコード
└── setup.py        -> 3.パッケージ情報記述

1.パッケージ化したいPythonコードを準備する。

 まず、Python 側の処理と C 言語側の処理を用意する。
例では、引数の値を 100 倍にして返す処理を Python 関数 func_python() と C 言語用関数 func_c() の両方で実装している。func_c() は、あらかじめコンパイルしておいた lib_c.so を ctypes で読み込んで実行する。

  • 重要な注意点
    C のライブラリ(.so)のパス指定にはimportlib.resourcesを使うこと。相対パス(例:"./clib/lib_c.so")を使うと、パッケージを pip install した後の環境では正しくロードできない。
lib.py
import ctypes
import sys
import importlib.resources

# Pythonで val を 100倍にする
def func_python(val):
   return f'Python_function : {val*100 = }'

# Cで val を 100倍にする
def func_c(val):
   # 呼び出すライブラリのファイル名を指定(相対パスでは×)
   with importlib.resources.path("sample_package.clib", 'lib_c.so') as path:
   	lib = ctypes.CDLL(str(path))

   return f'C lang_function : {lib.func(val) = }'

# 公開API	
def pkg_api(lang_type, val):
   if(lang_type=='python'):
       ret=func_python(val)
   elif(lang_type=='c'):
       ret=func_c(val)
   else:
       ret = f'{lang_type} in not available.' 
   
   return ret

if __name__ == '__main__':
   # test
   print(pkg_api('python',10))
   print(pkg_api('python',50))
   print(pkg_api('c',10))
   print(pkg_api('c',50))
   print(pkg_api('java',10))
   print(pkg_api('java',50))
lib_c.c
int func(int val){
    return val*100;
}

gcc lib_c.c -shared -o lib_c.so でコンパイルしてlib_c.soを生成する。
戻る

2.公開したい API を__init__.pyに記述する。

 パッケージ化を行う場合、フォルダ名とパッケージ名を一致させる必要がある。今回の例ではパッケージ名を 「sample_package」 としている。
 次に、パッケージの外部に公開したい API を __init__.py に列挙する。ここでは pkg_api() を公開したいので、__init__.py に以下のように記述する。

  • from には、公開 API が定義されているモジュール(lib.py)のパス
  • import には、公開したいAPI関数(pkg_api())を指定する。
__init__.py
from .lib import pkg_api

戻る

3.setup.pyにパッケージ化に必要な情報を記述する。

 パッケージを配布するためには、setup.py にパッケージ名・バージョン・含めるファイルなどの情報を記述する。今回の例では C 言語で作成したライブラリ(.so)をパッケージに含めるため、次の 2 つの設定が必須になる。

  • package_data : Cライブラリのフォルダ指定。
  • include_package_data=True

これらを指定しないと、clib/ ディレクトリ内の共有ライブラリが読み込まれず、インストール後に ctypes がロードできなくなる。以下は今回の例に対応したsetup.py

setup.py
from setuptools import setup, find_packages

setup(
	name         = 'sample_package',
	version      = '0.0.1',
	packages     = find_packages(),
    package_data = {'sample_package':['clib/*'],},
    include_package_data = True,
	url          = 'https://xxxx.sample.com',
	license      = 'free',
	author       = 'name',
	author_email = 'xxxx@sample.com',
	description  = 'A simple example package for learning Python packaging.'
)

戻る

4.distにする。

パッケージ化の準備が整ったら、sdist(ソース配布物)を作成する。以下のコマンドを実行すると、プロジェクト直下の dist/ フォルダにsample_package-0.0.1.tar.gzが生成される。

$ python setup.py sdist

戻る

5.作成したパッケージを pip でインストールする。

作成したパッケージは、pip install を使ってローカル環境にインストールできる。以下のコマンドを実行すると、dist/ にある sample_package-0.0.1.tar.gz がインストールされる。

$ pip install ./dist/sample_package-0.0.1.tar.gz

インストールが正常に行われたかどうかは、pip show で確認できる。

$ pip show sample_package

👇パッケージがインストールされていれば次のように表示される
Name: sample_package
Version: 0.0.1
Summary: A simple example package for learning Python packaging.
Home-page: https://xxxx.sample.com
Author: name
Author-email: xxxx@sample.com
License: free
Location: \xxxxx\Python\Python313\Lib\site-packages
Requires:
Required-by:

sample_package を import し、公開 API の pkg_api() を呼び出すと次のように動作する。

test.py
import sample_package

print(sample_package.__name__)

print(sample_package.pkg_api('python',10))
print(sample_package.pkg_api('c',10))
print(sample_package.pkg_api('java',10))

#-----実行結果-----
# sample_package
# Python_function : val*100 = 1000
# C lang_function : lib.func(val) = 1000
# java in not available.

戻る

目次に戻る

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?