見出し画像

Android開発 Java/Kotlin ⇔ Python で関数を相互呼び出し【Chaquopy】

ChaquopyでのAndroid+Pythonによるアプリ開発

このnoteは以下のような相互連携についてです

  • Kotlin/JavaからPython関数を呼び出し

  • Pythonからkotlin/java関数を呼び出し

この方法について具体的コード例で解説します

そもそもChaquopyを知らない人への概要

この記事をいきなり見ると意味不明だと思います。

Chaquopyというのは次のような技術です。

▼ Chaquopyの特長・特徴

  • Android内で独立したPythonを動かせる

  • アプリにPythonを組み込みできる

  • Java/Kotlinからpyコードを実行できる

▼ 導入・環境構築は次noteとかを参照

特別な外部アプリが必要、とかでもありません。

本当にAndroidアプリ内でPythonを組み込みでき、NDK経由でPythonインタプリタを呼び出してpyコードを単独実行できます。

そういう技術です。

Kotlin/JavaからPython関数を呼び出し

まずはKotlin/JavaからPython関数を呼び出し

例えば以下のような関数があるとします。

▼ hello.pyの内容

# 1つの引数を取る関数
def hello(name):
    return 'Hello '+name+" !!"

Chaquopyの導入でも書いたようにpyファイルは src/main/python 以下に配置してください。ここではhello.pyというスクリプト名です。

そしたらMainActivity側でPython関数を呼び出してみます。

▼ MainActivityの実行コード例

class MainActivity : AppCompatActivity() {

    companion object{
        val TAG:String = "MainActivity"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        /// Pythonインスタンス取得
        val py = Python.getInstance()
        /// hello.pyのモジュール取得
        val hello:PyObject = py.getModule("hello")

        /// python関数呼び出し
        val ret = hello.callAttr(
            "hello",  "Pisuke"
        )
        Log.d(TAG, "python hello : "+ret)
    }
}

▼ このコードの出力結果

python hello : hello Pisuke !!

ハイ、しっかりと呼び出せました。

ここまでは大丈夫ですね。

名前付き引数でpython関数を呼び出しする

少し悩むのは名前付き引数がある場合です。

▼ 例えば次のpython関数があるとする

import operator

ops = {
    '+' : operator.add,
    '-' : operator.sub,
    '*' : operator.mul,
    '/' : operator.truediv
}

def calc( x, y, *, opr="+"):
    return ops[opr](x, y)

Python3では引数に * を指定すると、それ以降の引数は名前のみでしか渡せなくなります(つまり calc(2, 4, opr="*" ) のような感じ)

これをkotlin/Javaから呼び出すにはこうします

▼ Kotlinからこのpython関数を呼び出してみる

class MainActivity : AppCompatActivity() {

    companion object{
        val TAG:String = "MainActivity"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        /// Pythonインスタンス取得
        val py = Python.getInstance()
        /// hello.pyのモジュール取得
        val hello:PyObject = py.getModule("hello")

        /// python関数呼び出し
        val ret = hello.callAttr(
            "calc", 2, 4, Kwarg("opr", "*")
        )
        Log.d(TAG, "python calc : "+ret)
    }
}

▼ このコードの出力結果

python calc : 8

上記コードみたく Kwarg("opr", "*") とすることでKotlin/Java側でも名前付き引数が使えます。ライブラリでは Kwarg を使う場面が多いので便利かも

Python側からKotlin/Javaメソッドを呼び出す

次は反対にPythonからkotlin/Javaの呼び出し

まず基本的に覚えておくべきことから

▼ Javaプリミティブ型は以下でサポートされる

  • java.jboolean

  • java.jbyte

  • java.jshort

  • java.jint

  • java.jlong

  • java.jfloat

  • java.jdouble

  • java.jchar

  • java.jvoid

これ以外には以下も用意されてます

  • java.jclass : Javaクラス・インターフェース

  • java.jarray : Java配列型

そして重要なことですが、ActivityだったりAlertDialogだったりPreferenceだったり・・・それらAndroidSDKに含まれるクラスは android.app.* androidx.* からインポートできます。

▼ pythonでのAndroidSDKのクラスのインポート例

from android.app import AlertDialog
from android.content import Context, DialogInterface
from android.graphics.drawable import ColorDrawable
from android.os import Bundle
from androidx.appcompat.app import AppCompatActivity
from androidx.fragment.app import DialogFragment
from androidx.preference import Preference

もちろん名前空間を指定すれば com.example.example のような自身のActivityだったり、独自クラスも容易にインポート可能です。

それでは実践的なコード例を1つ

ここではトースト表示してみたいと思います。

▼ MainActivity#showToastを定義

package com.example.example

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    /** 
     * トースト表示
     * @param msg  メッセージ
     **/
    public fun showToast(msg: String){
        Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
    }
}

▼ Python側でトースト表示を呼び出す

from com.example.example import MainActivity

def test_toast(main_activity):
    main_activity.showToast(
        "Chaquopy is working,\n" \
        "Shown by Python script"
    )

このコードでは test_toast にMainActivityインスタンスを受け取り、直接showToast()を実行してトースト表示を試してみました。

▼ 最後にMainActivityでpython呼び出し

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val py = Python.getInstance()
        val hello:PyObject = py.getModule("hello")
        
        /// python関数を実行
        hello.callAttr("test_toast", this)
    }

}

▼ 無事トーストが表示された!!

Python側からJava/Kotlinを呼び出すのは少ないかもしれません。とはいえ↑この例のように汎用的なトースト表示などをPythonでしたいなら使えます

以上、Chaquopyでの関数相互呼び出しでした。

この記事が気に入ったらサポートをしてみませんか?