不懂這幾點就落後了:Android、Python工程師必讀!
文章轉自:血色--殘陽 (文末有彩蛋)
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的互動是非常方便的,因此才能實現這一系列呼叫。
也為大家推薦了技術教程:
關於該種方案,已有國外網友實踐,原理如下
除此之外,本部落格將通過另外兩種方案實現。其中第一種類似上述方案,但整合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.pyimport 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++ |
相關文章
- 還不懂Redis?看完這個故事就明白了!Redis
- 搞懂了這幾點,你就學會了Web程式設計Web程式設計
- 掌握了這些Android高階工程師必備知識後,他拿到了 BAT OfferAndroid工程師BAT
- Android Fragment看這篇就夠了AndroidFragment
- Maven入門,讀完這篇就夠了Maven
- Android混淆——瞭解這些就夠了Android
- Android面試必問!View 事件分發機制,看這一篇就夠了!Android面試View事件
- java工程師linux命令,這篇文章就夠了Java工程師Linux
- 打造個人IP,做到這十點就OK了
- 圖解Transformer,讀完這篇就夠了圖解ORM
- 瞭解 HTTPS,讀這篇文章就夠了HTTP
- Redis 很屌,不懂使用規範就糟蹋了Redis
- 搞不懂深度學習中常用優化演算法背後的數學原理?看這篇就對了深度學習優化演算法
- Android APP Banner ,用這一個就夠了AndroidAPP
- 關於Socket,看我這幾篇就夠了(二)之HTTPHTTP
- CI/CD必知:落後master分支檢測AST
- 【python】遞迴聽了N次也沒印象,讀完這篇你就懂了Python遞迴
- Python有哪幾種編碼方式?這幾類必須知道!Python
- python 操作 mysql 只看這篇就夠了PythonMySql
- 網路訪問控制列表ACL(讀懂這篇就基本夠了,後面有配置案例)
- 還再用compile依賴?那你就落後啦Compile
- 這幾個python常用的庫你必須知道!Python
- Android View繪製流程看這篇就夠了AndroidView
- 在Python中實現非同步程式設計,只需要這幾步就夠了Python非同步程式設計
- 又面試了Python爬蟲工程師,碰到這麼幾道面試題,Python面試題No9Python爬蟲工程師面試題
- 【python】裝飾器聽了N次也沒印象,讀完這篇你就懂了Python
- Redis系列總結--這幾點你會了嗎?Redis
- Python工程師面試必備25條知識點Python工程師面試
- 避免HBase PageFilter踩坑,這幾點你必須要清楚Filter
- 那麼多一鍵分發工具哪個最好用?其實看這幾點就夠了
- 經驗分享篇丨測試小白如何做好功能測試,看這幾點就夠了
- 封裝vue外掛,讀懂這遍文章就夠了封裝Vue
- 區塊鏈是什麼?讀懂這些就夠了!區塊鏈
- Python3入門,看這篇就夠了Python
- Python操作MongoDB看這一篇就夠了PythonMongoDB
- 拒絕FileNotFoundException!總結了這幾個讀取jarExceptionJAR
- 自從裝了這個軟體後,就放心大膽摸魚了!
- Android App效能優化技能,看這篇就夠了AndroidAPP優化