不懂這幾點就落後了:Android、Python工程師必讀!

CSDN學院發表於2020-04-04

文章轉自:血色--殘陽   (文末有彩蛋)

Android 平臺的Python:


(本文以Python3為例,Python3是未來,大家都懂的) 
Python作為一個功能強大又語法簡潔的語言,其應用已無需多言。要想在Android平臺執行起Python,也有方案實現,其實質就是在Android系統上搭建Python環境。對此Google已經提供了SL4A(Scripting Layer for Android )方案,支援多種指令碼語言,除此之外,還可以使用一個叫QPython的app,可以直接在Android上編寫以及執行Python程式碼。但其實意義不大,寫好的Python程式碼並不是以一個獨立的app程式執行的,只不過是在QPython這個應用中執行而已。這兩者都不符合我現在要討論的東西,如題,筆者想要討論的是如何在Android平臺使用Java與Python程式碼相互呼叫,換言之,就是如何在Android工程中嵌入一個Python直譯器。

首先談一點,為什麼要在Android平臺使用Python?Python擁有眾多強大的第三方庫和框架,在機器學習、大資料處理等諸多方面都有不俗的應用。另外,就語法而言,Python比Java更加簡潔,同時又功能強大,既可程式導向亦可物件導向,而不像Java一樣,是一種純粹的面嚮物件語言,哪怕列印一句話也需要先建立類。Python作為一種指令碼語言,可以邊解釋邊執行,而不需編譯,另外Python中存在的元類,可以使我們動態的建立類,如此可以在不需要重新編譯安裝apk的情況下,動態的由遠端服務端為Android專案新增功能。我們還可以將Python已有的一些東西移植到Android平臺,例如tornado、django等,總之玩法多多。

在Android平臺,官方並不支援直接使用Python開發app,基於虛擬機器的Java(或kotlin)才是更好的選擇,其他語言是無法自如的使用官方Framework提供的api的,尤其是在程式介面的表現上,典型的反例就是kivy。什麼是kivy,可自行了解,但要解決Android平臺上Java與Python的互動,kivy確實是一個方向,而且是一個醍醐灌頂的方向。kivy實際上已經解決我們需要實現的目的,模仿Android平臺上的kivy實現機制即可。但是,kivy使用的是Cython直譯器,非CPython直譯器,需要學習Cython語法,並且在其他一些方面存在一些限制。kivy給我們提供的思路就是藉助Java的jni機制,實現Python與Java的互動。即在一個安卓apk工程中包含一個cython.so直譯器,通過jni機制呼叫直譯器去解釋執行Python程式碼,通過Java調C,C調Python實現互動。有一點需要說明,Python作為一門膠水語言,Python與C的互動是非常方便的,因此才能實現這一系列呼叫。

也為大家推薦了技術教程:

Android視訊編碼和直播推流

Android高手進階

一天掌握Scrapy爬蟲框架

【Python全棧】網路爬蟲體驗課

Django Web框架/Python最牛框架

探究Linux的匯流排、裝置、驅動模型

Linux 實用講解+實操+面試題

zabbix企業實戰應用

Linux視訊教程

關於該種方案,已有國外網友實踐,原理如下

這裡寫圖片描述

連結地址

除此之外,本部落格將通過另外兩種方案實現。其中第一種類似上述方案,但整合CPython直譯器,非Cython,因此需要掌握如何實現Python與C的互動。

Python與C互動基礎

C呼叫Python

  • 簡單使用 
    流程:

    • 初始化Python解析器
    • 執行Python程式碼,字串,物件或模組。
    • 關閉Python解析器。

    建立一個.c原始檔,程式碼如下,建立一個pytest.py檔案,實現一個printTime函式

    #include<Python.h>
    int main()
    {
        Py_Initialize();//初始化Python解析器
        if (!Py_IsInitialized())
        {
            printf("Initialize failed");
              return -1;
        }
        PyRun_SimpleString("print('hello C !')");
        PyRun_SimpleString("import pytest");
        PyRun_SimpleString("pytest.printTime()");
        Py_Finalize();/關閉Python解析器
        return 0;
    }       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

注意:除了用PyRun_SimpleString函式直接執行程式碼,還可以使用PyRun_SimpleFile函式執行一個Python指令碼 
原型:PyRun_SimpleFile(FILE *fp, const char *filename) ,由於版本差異,使用該方式可能會造成崩潰,推薦另一種替代方式 
PyRun_SimpleString(“execfile(“test.py”)”)

  • 呼叫Python函式 
    pytest.py

    import time
    
    def printTime():
        print('invoke printTime:'+str(time.time()))
        return (1,)#元組只有一個元素時,需在末尾加逗號
    • 1
    • 2
    • 3
    • 4
    • 5

    C 程式碼

    int main()
    {
        PyObject * module_name,*module,*func,*dic;
        char * fun_name = "printTime";//需呼叫的Python函式名
        PyObject *resultValue;
    
        Py_Initialize();
        if (!Py_IsInitialized())
        {
            printf("Initialize failed");
            return -1;
        }
    
        //匯入Python 模組並檢驗
        module_name = Py_BuildValue("s", "pytest");
        module = PyImport_Import(module_name);
    
    
        if (!module)
        {
            printf("import test failed!");
            return -1;
        }
    
        //獲取模組中的函式列表,是一個函式名和函式地址對應的字典結構
        dic = PyModule_GetDict(module);
        if (!dic)
        {
            printf("failed !\n");
            return -1;
        }
    
        func = PyDict_GetItemString(dic, fun_name);
        if (!PyCallable_Check(func))
        {
            printf("not find %s\n", fun_name);
            return -1;
        }
    
        int r;
        //獲取Python函式返回值,是一個元組物件
        resultValue = PyObject_CallObject(func, NULL);
        PyArg_ParseTuple(resultValue, "i", &r);
        printf("result :%d\n", r);
    
        Py_DECREF(module);
        Py_DECREF(dic);
        Py_Finalize();
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    基礎api

    C API 呼叫 Python 對應
    PyImport_ImportModel import module
    PyImport_ReloadModule reload(module)
    PyImport_GetModuleDict module.__dict__
    PyDict_GetItemString dict[key]
    PyDict_SetItemString dict[key] = value
    PyDict_New dict = {}
    PyObject_GetAttrString getattr(obj, attr)
    PyObject_SetAttrString setattr(obj, attr, val)
    PyObject_CallObject funcobj(*argstuple)
    PyEval_CallObject funcobj(*argstuple)
    PyRun_String eval(exprstr) , exec(stmtstr)
    PyRun_File exec(open(filename().read())
    • Py_BuildValue()函式 
      作用:將C/C++型別型別的資料轉變成PyObject*物件。

      原型:PyAPI_FUNC(PyObject*) Py_BuildValue(const char *format, …);

      引數解釋: 
      format及轉換格式,類似與C語言中%d,%f,後面的不定引數對應前面的格式,具體格式如下:

      “s”(string) [char *] :將C字串轉換成Python物件,如果C字串為空,返回NONE。

      “s#”(string) [char *, int] :將C字串和它的長度轉換成Python物件,如果C字串為空指標,長度忽略,返回NONE。

      “z”(string or None) [char *] :作用同”s”。

      “z#” (stringor None) [char *, int] :作用同”s#”。

      “i”(integer) [int] :將一個C型別的int轉換成Python int物件。

      “b”(integer) [char] :作用同”i”。

      “h”(integer) [short int] :作用同”i”。

      “l”(integer) [long int] :將C型別的long轉換成Pyhon中的int物件。

      “c”(string of length 1) [char] :將C型別的char轉換成長度為1的Python字串物件。

      “d”(float) [double] :將C型別的double轉換成python中的浮點型物件。

      “f”(float) [float] :作用同”d”。

      “O&”(object) [converter, anything] :將任何資料型別通過轉換函式轉換成Python物件,這些資料作為轉換函式的引數被呼叫並且返回一個新的Python物件,如果發生錯誤返回NULL。

      “(items)”(tuple) [matching-items] :將一系列的C值轉換成Python元組。

      “[items]”(list) [matching-items] :將一系列的C值轉換成Python列表。

      “{items}”(dictionary) [matching-items] :將一系類的C值轉換成Python的字典,每一對連續的C值將轉換成一個鍵值對。

      例: 
      後面為PyObject的返回值

       Py_BuildValue("")None
      
       Py_BuildValue("i",123) 123
      
       Py_BuildValue("iii",123, 456, 789) (123, 456, 789)
      
       Py_BuildValue("s","hello") 'hello'
      
       Py_BuildValue("ss","hello", "world") ('hello', 'world')
      
       Py_BuildValue("s#","hello", 4) '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))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    • PyArg_ParseTuple函式 
      作用:此函式其實相當於sscanf(str,format,…),是Py_BuildValue的逆過程,這個函式將PyObject引數轉換成C/C++資料型別,傳遞的是指標,但這個函式與Py_BuildValue有點不同,這個函式只能解析Tuple元組,而Py_BuildValue函式可以生成元組,列表,字典等。

      原型:PyAPI_FUNC(int) PyArg_ParseTuple(PyObject *args, const char *format,…)

      Args:一般為Python程式返回的元組。

      Foramt:與Py_BulidValue型別,就不在累述咯。

      元組操作函式: 
      因為程式之間傳遞的引數,大多數為Tuple型別,所以有專門的函式來操作元組:

      PyAPI_FUNC(PyObject *)PyTuple_New(Py_ssize_t size); 
      解釋:新建一個引數列表(除錯了下,發現其實是用連結串列實現的),size列表為長度的寬度

      PyAPI_FUNC(Py_ssize_t)PyTuple_Size(PyObject *); 
      解釋:獲取該列表的大小

      PyAPI_FUNC(PyObject )PyTuple_GetItem(PyObject , Py_ssize_t); 
      解釋:獲取該列表某位置的值

      PyAPI_FUNC(int) PyTuple_SetItem(PyObject ,Py_ssize_t, PyObject ); 
      解釋:設定該列表此位置的值。如PyTuple_SetItem(pyParams,1,Py_BuildValue(“i”,2));設定第2個位置的值為2的整數。 
      備註:對應的列表和字典也有對應的操作

更多的介面呼叫以及資料型別轉化,參照Python文件 
這裡寫圖片描述

Python 呼叫C

Python呼叫C有兩種方式

  • 使用ctypes模組,Python文件有詳細示例 
    這裡寫圖片描述

  • 使用C為Python編寫擴充模組 
    Python之所以如此強大,正是由於可以使用C\C++為其編寫擴充模組,手動編寫擴充模組的方式稍微有些繁瑣,可借用SWIG自動實現,簡潔快速。更多詳細的SWIG用法,見其官方文件 
    官網下載 windows包並解壓

    使用vs建立空專案,並配置vs。右鍵當前專案,選擇屬性 
    這裡寫圖片描述
    現在使用C為Python建立一個叫user的擴充模組,該模組包含一個showHello函式: 
    分別建立三個檔案 
    user.i 
    user.c 
    user_wrap.c

    在user.i中新增如下程式碼

    %module user
    %inline %{
    extern void showHello();
    %}
    • 1
    • 2
    • 3
    • 4

    user.c中新增

    
    #include <stdio.h>
    
    void showHello()
    {
        printf("hello Python!\n");
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    右鍵user.i 檔案並選擇屬性 
    這裡寫圖片描述
    點選應用後如下圖,完成配置 
    這裡寫圖片描述
    右鍵當前專案,選擇屬性,完成如下配置,確定 
    這裡寫圖片描述
    最後生成即可(選擇工具欄 生成 –> 批生成)

    建立測試程式碼呼叫C驗證

    import user
    user.show()
    • 1
    • 2

    在Linux下則無需如此麻煩的配置,可直接使用命令

    On Unix the compilation of examples is done using the file Example/Makefile. This makefile performs a manual module compilation which is platform specific. Typically, the steps look like this (Linux):
    
    
    % swig -python interface.i
    % gcc -fpic -c interface_wrap.c -I/usr/local/include/python1.5
    % gcc -shared interface_wrap.o $(OBJS) -o interfacemodule.so 
    % python
    Python 1.5.2 (#3, Oct  9 1999, 22:09:34)  [GCC 2.95.1 19990816 (release)] on linux2
    Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
    >>> import interface
    >>> interface.blah(...)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    此處.i檔案為SWIG的介面檔案,其中%module後面定義模組名,用%inline定義方法列表

    %inline %{
    包含匯出的函式
    %}
    • 1
    • 2
    • 3

有了Python與C的互動基礎,則還需要Android中的NDK開發基礎,關於Android平臺的jni呼叫,本文不在此處詳解,可看看我的JNI方面部落格,而此處我們需要使用Crystax NDK開發工具鏈,非官方NDK工具鏈,需自行下載。下一篇正式涉及Python for Android。

 

文末彩蛋:

1.CSDN學院助學會員:僅需百元- 1300門課+600次下載特權,一次購買,全年無憂!點選檢視。

2.CSDN學院知識週刊:每週更新學院優惠課程活動及精品上新內容,點選檢視!

3.本期推薦教程:

課程名稱 課程連結 技術分類
一天掌握Scrapy爬蟲框架 https://edu.csdn.net/course/detail/9141 程式語言 > Python
【Python全棧】網路爬蟲體驗課 https://edu.csdn.net/course/detail/9163 程式語言 > Python
Django Web框架/Python最牛框架 https://edu.csdn.net/course/detail/9159 程式語言 > Python
一天掌握Python爬蟲 https://edu.csdn.net/course/detail/9143 程式語言 > Python
Linux視訊教程 https://edu.csdn.net/course/detail/9128 系統/網路/運維 > Linux
zabbix企業實戰應用 https://edu.csdn.net/course/detail/9124 系統/網路/運維 > Linux
Linux 實用講解+實操+面試題 https://edu.csdn.net/course/detail/9116 系統/網路/運維 > Linux
跟著王進老師學Web前端開發第四季:JavaScript語法基礎 https://edu.csdn.net/course/detail/9156 Web全棧 > JavaScript
華為工程師 ,帶你實戰C++(2018版) https://edu.csdn.net/course/detail/9122 程式語言 > C/C++
精通 S T L https://edu.csdn.net/course/detail/9153 程式語言 > C/C++

相關文章