Aprende a ejecutar código python dado por el usuario con Chaquopy en tu aplicación de Android.

Cómo usar Chaquopy para ejecutar Código Python y obtener su resultado usando Java en tu aplicación de Android

Han pasado aproximadamente 4 meses desde el lanzamiento de Pocket Editor para Android . Una de las principales características que faltan en la aplicación es la posibilidad de ejecutar algún código sobre la marcha. Hay un montón de razones por las que la implementación de compiladores o intérpretes se ha retrasado tanto. Una de las principales razones se debe al problemático Storage Access Framework implementado desde Android 11 que hace que sea bastante complejo trabajar con archivos desde el dispositivo del usuario, además de las expectativas del usuario. Por ejemplo, algunos usuarios pueden esperar que ejecutar el código Python directamente en el dispositivo les otorgue automáticamente los derechos para manipular los archivos del dispositivo a su voluntad, algo que es bastante difícil de implementar, al menos por el conocimiento que poseo en este momento.

Entonces, aunque bajaré las expectativas de muchos usuarios, decidí continuar con una implementación muy útil de un intérprete de Python en Pocket Editor gracias a Chaquopy, el SDK de Python para Android . Chaquopy proporciona todo lo que se necesita para incluir componentes de Python en una aplicación de Android, incluyendo:

  • Integración completa con el sistema de compilación Gradle estándar de Android Studio.
  • API simples para llamar al código Python desde Java/Kotlin y viceversa.
  • Una amplia gama de paquetes de Python de terceros, incluidos SciPy, OpenCV, TensorFlow y muchos más.

En este artículo, te explicaré cómo ejecutar fácilmente el código de Python y obtener su salida usando Chaquopy en tu aplicación de Android.

1. Instalación de Chaquopy

En la mayoría de los casos, la instalación de Chaquopy es realmente fácil y sencilla, especialmente con proyectos limpios (recién creados). En mi caso, tuve algunos problemas con la instalación ya que Chaquopy es bastante estricto cuando se trata de la versión de Android Gradle y ya tenía una aplicación antigua que usaba una versión no tan reciente de Gradle. Recuerda, si tienes problemas al sincronizar tu gradle porque la versión de Chaquopy no se puede encontrar en el repositorio maven (pero la versión sí existe) o no se puede acceder al espacio de nombres de com.chaquo.python , asegúrate de que coincida con la versión requerida de Android Gradle con la lista de versiones compatibles en la documentación oficial de Chaquopy aquí. Por ejemplo, en el caso de mi proyecto, pude tener éxito con la instalación utilizando las siguientes versiones de Gradle:

Gradle Script Version

Y solicitando la instalación de Chaquopy 9.1.0:

classpath 'com.chaquo.python:gradle:9.1.0'

Una vez que sepas la versión de Gradle de tu proyecto y la versión de Chaquopy que puedes instalar, ahora puedes continuar con la instalación. Como se especifica en la documentación oficial, Chaquopy se distribuye como un complemento para el sistema de compilación basado en Gradle de Android. Se puede utilizar en cualquier aplicación que cumpla con los siguientes requisitos:

Cumpliendo con este criterio, ahora necesitas registrar el repositorio maven de Chaquopy e incluirlo como una dependencia en su build.gradlearchivo de nivel superior :

buildscript {
    repositories {
        maven { url "https://chaquo.com/maven" }
    }
    dependencies {
        classpath "com.chaquo.python:gradle:9.1.0"
    }
}

Como mencioné anteriormente, utilicé la versión 9.1.0 de Chaquopy para que funcionara en mi proyecto, el tuyo podría funcionar con la última versión de Chaquopy (10.0.1 hasta la fecha de redacción de este artículo). Después de realizar este cambio en el archivo Gradle, aplique el plugin com.chaquo.python debajo del plugin de com.android.application. Si estás utilizando la sintaxis de aplicación:

apply plugin: 'com.android.application'
apply plugin: 'com.chaquo.python'

O si está utilizando la sintaxis del complemento:

plugins {
plugins {
    id 'com.android.application'
    id 'com.chaquo.python'
}

Eso es todo lo que necesitas para la instalación por ahora, no sincronices su proyecto con Gradle Files todavía, ya que necesita configurar algunas cosas antes.

2. Configurar Chaquopy

Ahora, en tu aplicación, en el archivo build.gradle debes configurar los ajustes básicos de Chaquopy. Estos ajustes son los siguientes:

apply plugin: 'com.android.application'
apply plugin: 'com.chaquo.python'

android {
    ...

    defaultConfig {
        
        applicationId "com.yourcompany.yourappid"

        ...

        ndk {
            abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
        }

        sourceSets{
            main{
                python.srcDir "src/main/python"
            }
        }

        python {
            buildPython "C:/Python38/python.exe"
        }
    }
}
    • ndk (abiFilters): el intérprete de Python es un componente nativo, por lo que debe usar la configuración de abiFilters para especificar qué ABI desea que admita la aplicación. Los ABI disponibles actualmente son:
      • armeabi-v7a, compatible con prácticamente todos los dispositivos Android.

      • arm64-v8a, compatible con los dispositivos Android más recientes.

      • x86, para el emulador de Android.

      • x86_64, para el emulador de Android.

    • sourceSets: de forma predeterminada, Chaquopy utiliza el subdirectorio de Python predeterminado de cada conjunto de fuentes. Por ejemplo, el código de Python de la fuente principal debe almacenarse en el app/src/main/pythondirectorio.
    • buildPython: algunas funciones requieren que Python esté instalado en la máquina de compilación. De forma predeterminada, Chaquopy intentará encontrar Python en la RUTA con el comando estándar para su sistema operativo, primero con una versión secundaria coincidente y luego con una versión principal coincidente. En mi caso usando Windows, proporcioné la ruta absoluta al ejecutable con la misma versión de Python de Chaquopy 9.1.0 (Python 3.8.6).

    Finalmente, sincroniza tu proyecto con Gradle Files y espera a que finalice la compilación. Una vez que finalice, podrás usar Chaquopy en su proyecto.

    3. Configurando el interprete

    Como se mencionó al principio del artículo, la idea de la aplicación es ejecutar el código python proporcionado por el usuario y obtener el resultado. Por ejemplo, si el usuario proporciona el siguiente código de Python para imprimir la serie de Fibonacci:

    def fibonacci_of(n):
        if n in {0, 1}:  # Base case
            return n
    
        return fibonacci_of(n - 1) + fibonacci_of(n - 2)
        
    print([fibonacci_of(n) for n in range(10)])

    La aplicación debería poder capturar la salida que sería en este caso:

    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

    Después de leer la documentación, no pude encontrar una manera de ejecutar el código directamente sin tanta molestia usando Chaquopy (no existe un método simple como executeCode o algo así). En cambio, seguí un enfoque curioso que ejecutaría el código a través de un script de Python que recibirá el código y lo ejecutará en otro subproceso. El siguiente script de Python nos permitirá ejecutar código arbitrario utilizando la función exec de python. Obtuve este script de esta implementación de Chaquopy en Flutter ( ver repositorio oficial aquí ):

    # Source: https://github.com/jaydangar/chaquopy_flutter/blob/5fefecba7c918ce4c0e790a1a6494e70a701059c/example/android/app/src/main/python/script.py
    # interpreter.py
    import time,threading,ctypes,inspect
    
    def _async_raise(tid, exctype):
        tid = ctypes.c_long(tid)
        if not inspect.isclass(exctype):
            exctype = type(exctype)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("Invalid thread id")
        elif res != 1:
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("Timeout Exception")
    
    def stop_thread(thread):
        _async_raise(thread.ident, SystemExit)
        
    def text_thread_run(code):
        try:
            env={}
            exec(code, env, env)
        except Exception as e:
            print(e)
        
    #   This is the code to run Text functions...
    def mainTextCode(code):
        global thread1
        thread1 = threading.Thread(target=text_thread_run, args=(code,),daemon=True)
        thread1.start()
        # Define the
        timeout = 15 # change timeout settings in seconds here...
        thread1_start_time = time.time()
        while thread1.is_alive():
            if time.time() - thread1_start_time > timeout:
                stop_thread(thread1)
                raise TimeoutError
            time.sleep(1)

    Asegúrate de crear el archivo interpreter.py dentro de tu directorio de Android app/src/main/python y guarda el código anterior en él. Teniendo en cuenta la forma en que opera Chaquopy, necesitamos crear un PyObject que usará el interpreter.pyarchivo como un módulo y ejecutaremos el código usando el método mainTextCode del script de python. La siguiente lógica hará el truco en tu aplicación de Android para ejecutar el código de Python desde texto plano:

    import com.chaquo.python.PyException;
    import com.chaquo.python.PyObject;
    import com.chaquo.python.Python;
    import com.chaquo.python.android.AndroidPlatform;
    
    // 1. Inicie la instancia de Python si aún no se está ejecutando.
    if(!Python.isStarted()){
        Python.start(new AndroidPlatform(getContext()));
    }
    
    // 2. Obtener la instancia de python
    Python py = Python.getInstance();
    
    // 3. Declarar algún código de Python que será interpretado
    // En nuestro caso, la sucesión de fibonacci
    String code = "def fibonacci_of(n):\n";
    code += "   if n in {0, 1}:  # Base case\n";
    code += "       return n\n";
    code += "   return n\n";
    code += "\n";
    code += "print([fibonacci_of(n) for n in range(10)])\n";
    
    // 4. Obtenga el flujo de entrada del sistema (disponible en Chaquopy)
    PyObject sys = py.getModule("sys");
    PyObject io = py.getModule("io");
    // Obtener el módulo interpreter.py
    PyObject console = py.getModule("interpreter");
    
    // 5. Redirigir el flujo de salida del sistema al intérprete de Python
    PyObject textOutputStream = io.callAttr("StringIO");
    sys.put("stdout", textOutputStream);
    
    
    // 6. Cree una variable de cadena que contendrá la salida estándar del intérprete de Python
    String interpreterOutput = "";
    
    // 7. Ejecutar el código Python
    try {
        console.callAttrThrows("mainTextCode", code);
    
        interpreterOutput = textOutputStream.callAttr("getvalue").toString();
    }catch (PyException e){
        // Si hay un error, también puede obtener su salida
        // p. ej. si escribes mal el codigo
        // Missing parentheses in call to 'print'
        // Did you mean print("text")?
        // <string>, line 1
        interpreterOutput = e.getMessage().toString();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    
    // Salidas en el caso del script fibonacci:
    // "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]"
    System.out.println(interpreterOutput);

    Y así es como puedes ejecutar el código de Python de forma dinámica en su aplicación de Android usando Chaquopy.

    Que te diviertas ❤️!


    Interesado en la programación desde los 14 años, Carlos es un programador autodidacta, fundador y autor de la mayoría de los artículos de Our Code World.

    Conviertete en un programador más sociable

    Patrocinadores