# aardio 调用 Python 入门指南

## aardio 调用 Pyhon 模块与函数

```aardio
import console;  
import py3;

//导入 Python 模块
var math = py3.import("math");

//调用 Python 函数，  
var num = math.floor(22.3);

//在控制台输出数值。 
console.log(num);

console.pause();
```

## aardio 执行 Python 代码

```aardio
import console.int;
import py3; 
  
// Python 代码，注意 Python 对空格缩进有严格要求。 
var pyCode = /** 
def getList(a,b):  
    return a + b  
**/

//Python 代码 
py3.exec( pyCode ) 

//从 py3.main 模块调用 Python 代码定义的函数 
var ret = py3.main.getList(12,23);
 
console.log(ret);  
```

py3.exec 的参数可以指定 aardio 工程目录下的 *.py 或 *.aardio 文件路径。
如果指定 *.aardio 文件则可使用 [aardio 模块语法](../../../language-reference/templating/syntax.html) 生成 Python 代码。

## aardio 与 Python 数据类型转换规则
 
- 兼容的纯值类型：

    Python 返回的浮点数值、不大于 53 位的整型数值、布尔值、字符串、缓冲区（ buffer ）会自动转换为对应的纯 aardio 值，同样反过来 aardio 这些类型的值也可以自动转换为对应的 Python 类型。

- pyObject 对象：

    除了纯值以外的其他 Python 对象在 aardio 中存为 py3.object 对象（ 简称 pyObject ）。如果是 py2 扩展库就是 py.object ，原理与用法都相同。

## 使用 pyObject

pyObject 也可以在 aardio 中也可以像普通对象一样使用。
可以调用 pyObject 的成员函数、读写其属性、通过下标读写索引项、并支持各种常用运算符。

可通过 pyObject.parseValue() 函数转换为纯 aardio 值（通过 JSON 自动转换）。 

Python 对象（ pyObject ）在 aardio  中与类型转换有关的函数：

- `pyObject.parseValue()` pyObject 转换为 aardio 表对象
- `table.parseValue(pyObject)`pyObject 转换为 aardio 表对象
- `tostring(pyObject)` pyObject 转换为 aardio 字符串
- `tonumber(pyObject)` pyObject 转换为 aardio 数值 
- `pyObject.toString()` pyObject 转换为 aardio 字符串
- `pyObject.toDict()` pyObject 转换为字典，返回 py3.dict 字典对象
- `pyObject.toList()` pyObject 转换为列表，返回 py3.list 列表对象
- `pyObject.toTuple()` pyObject 转换为元组，返回 py3.tuple 元组对象
- `pyObject.type()` pyObject 返回 Pythton 类型名

aardio 中的 py3.dict,py3.list,py3.tuple  都继承自 py3.object ，本质都是 pyObject 对象（指 py3.object ）。

最常用的函数 pyObject.parseValue，示例：

```aardio
import console;
import py3; 

var pyCode = /** 
def getList(a,b):   
    return a,b,123 # Python 多返回值实际是返回一个 tuple
**/

py3.exec( pyCode ) 

//从 py3.main 模块调用 Python 代码定义的函数 
var pyList = py3.main.getList(12,23);

//pyObject 转换为纯 aardio 值
var list = pyList.parseValue() 
console.dump(list);  

/*
list 或 tuple 可用下标访问，
注意 Python 对象起始索引为 0，aardio 数组起始索引为 1。
*/
var num = pyList[0];

//可以如下遍历 pyObject 对象。
for( pyItem in pyList.each() ){
	console.log(pyItem) //基础类型已转换为纯 aardio 值，其他为 py2.object
}

console.pause();
```

## aardio 调用 Python 函数如何指定命名参数

在 aardio 中函数名前加一个 `$` 符号启用命名参数支持，示例：

```aardio
import py3;

//导入 Python 模块
var requests = py3.import("requests");

//创建 Python 对象
var ses = requests.Session();

//在函数名前加  $ 以启用命名参数。
var res = ses.$get(verify=false,"https://www.aardio.com");
```

当我们将 Python 函数名  `ses.get` 改为 `ses.$get` 以后，aardio 就会将调用参数 1 指定的表对象转换为 Python 命名参数。

在 aardio 中函数调用参数有且只有一个使用 `{}` 构造器构建的 table 参数（并且表不是结构体）时，并且第一个出现的成员是用 `=` 分隔的键值对（不能用冒号替代），就可以省略外层的 `{}` 。 使用这个语法特性，可以用一个表参数模拟类似 Python 命名参数的效果。

## aardio 获取 Python 的多返回值

aardio 与 Python 都支持多返回值，而 Python 的多返回值实际上是返回一个 tuple 对象。 

```aardio
import py3;  

var pyCode = /** 
def getTuple():  
    return 12,3
**/

//执行 Python 代码
py3.exec( pyCode ) 

//调用 Python 函数 
var pyObj = py3.main.getTuple();

//取 Python 的多返回值并转换为数组
var arr = pyObj.parseValue()

//输出返回值，aardio 起始下标为 1 
print(arr[1],arr[2]) 

//Python 对象也可以通过下标访问其成员
print(pyObj[0],arr[1]) // Python 起始下标为 0（ pyObj[0] ）,aardio 起始下标为 1（ arr[1] ）
```


## 安装 Python 模块

进程内安装 Python 模块：

```aardio

// 如果程序中用的是 py3.10，这一句也要改为 py3.10.pip，Python 3.7 以下版本不支持 pip 工具
import py3.pip;

//可选指定 pip 镜像服务器
py3.pip.setIndexUrl("aliyun"); 

//安装指定的模块
py3.pip("install","安装模块名称"); 
```

有些模块必须将 py3.pip 改为 py3.pip.process 以通过 Python.exe 在进程外安装模块，示例：

```aardio

import py3.pip;
py3.pip.setIndexUrl("aliyun"); 

//安装指定的模块
py3.pip.process("install","安装模块名称"); 
```

需要进程外安装的模块如果用 `py3.pip("install","安装模块名称")` 安装则会导致安装时误将当前 EXE（ 发布前则为 aardio.exe ）作为 Python.exe 重复启动，并且安装模块会失败。


## aardio 搜索 Python 模块的目录

aardio 默认在以下两个目录搜索 Python 模块: 

- "\py"  

	文件路径开始为单个斜杠（或反斜杠）表示应用程序根目录，
	应用程序根目录在开发时指工程根目录（工程外单独启动代码则为文件所在目录），
	发布后应用程序根目录就是指 EXE 所在的目录。
- "~\lib\py3\.res\DLLs"  

    aardio 规定文件路径开始为波浪线表示 EXE 所在的目录（开发时为 aardio.exe 所在目录）。
	

也可以自己添加模块搜索路径，示例：

```
py3.appendPath("\py\site-packages\");
```

## 在 Python 中调用 aardio 对象

aardio 在与 Python 交互时，
默认除了布尔值、浮点数值、小于53位的整数值转换为纯 aardio 值以外，其他 Python 对象在 aardio 中存为 pyObject，并保留对原始 Python 对象的引用。

而 aardio 对象在自动转换为 Python 对象时，
默认是传值而非传址，Python 中不保留对 aardio  对象的引用。

py3.export 的特别之处在于可以导出 aardio 模块到 Python，
在 Python 中引用与操作原始的 aardio 对象。

我们可以使用 py3.export 将 aardio 对象转换为 Python 模块，示例：

```aardio
py3.export.aardio = {
	exportFunction = function(){
		
	} 
}

```

然后就可以在 Python 代码中执行 `import aardio` 导入上面的 aardio 模块。

> ⚠️ py3.export 只能在 Python 主线程使用，在非 Python 主线程下返回 null。

我们也可以调用 py3.export( aardioObject ) 将参数中指定的 aardio 对象导出为 Python 对象，示例：

```aardio
import winex;
import console.int; 
import py3;

var pyCode = /** 
def testPy(console,winex):
    # 还能支持 aardio 创建的迭代器
    for(hwnd,title,threadId,processId) in winex.each():
    	console.log(title)
**/

//执行 Python 代码
py3.exec( pyCode ); 

//调用 Python 函数
py3.main.testPy(  
	py3.export(console),//将 aardio 对象转换为 Python 对象
	py3.export(winex) 
);   
```

py3.export( aardioObject )  可用于导入参数 aardioObject 指定的 aardio 对象为 Python 对象，这种「代理对象」在 Python 中将保持对原始 aardio 对象的引用，传址而非传值。

aardioObject 参数可指定 table,cdata,class,function 等 类型的 aardio 对象。

注意 py3.export 只能在 Python 启动线程中使用，这是 Python 本身的限制。 在非 Python 主线程下，py3.export 为 null 值。

> 使用 py3.mainThread 也可以检测是否  Python 主线程， 

## 创建后台线程运行 Python 。

参考文档： [多线程入门](../../../guide/language/thread.html)

Python 写多线程比较麻烦，我们一般会用单线程运行 Python 。但在有图形界面的线程中运行  Python 时，如果 Python 有耗时操作会阻塞界面导致卡顿。

这时候我们可以创建后台线程来运行 Python。要注意的是一个进程中应当只启动一个 Python 线程，避免复杂的 GIL 管理。

```aardio
import win.ui;
/*DSG{{*/
var winform = win.form(text="aardio - 后台线程运行 Python";right=1163;bottom=753)
winform.add(
button={cls="button";text="调用python线程";left=876;top=663;right=1102;bottom=723;z=2};
edit={cls="edit";left=11;top=16;right=1140;bottom=625;edge=1;multiline=1;z=1}
)
/*}}*/
 
//Python 后台线程
pyServerThread = function(winform){
    import win.ui;
	import py3; 
	
	//创建 messageOnly 窗体作为命令监听器
	var frmMsg = win.form().messageOnly();
	 
	//响应事件
	frmMsg.pyHash = function(){	
		
		// aardio 支持跨线程安全调用窗体控件,无需加锁
		winform.edit.print("子线程正在执行 pyHash 函数",tostring(time()))
		
		var hashlib = py3.import("hashlib"); 
		var md5 = hashlib.md5()
		md5.update( raw.buffer("注意这个函数的参数不是字符串而是字节串（aardio 的 buffer 类型）") );	
		sleep(1000)
		
		//调用界面线的函数
		winform.pyHashEnd( tostring(md5.hexdigest()) );
		
		return tostring(md5.hexdigest());
	}
	
	//退出线程
	frmMsg.pyExit = function(){
		win.quitMessage();
	}
	
	//将 frmMsg 传入界面线程
	winform.pyCommand = frmMsg;

	//在工作线程需要启用消息循环，才能响应事件
	win.loopMessage();	
}

//启动 Python 后台线程
thread.create( pyServerThread,winform ) 

//增加工作线程可以调用的函数
winform.pyHashEnd = function(str){
	winform.edit.print("主线程函数被调用，参数：",str)
}
 
winform.button.oncommand = function(id,event){
      
    //禁用按钮，避免重复提交
    winform.button.disabledText = "正在调用python线程";
    
    //异步调用python线程的函数，等待返回值
    var str = winform.pyCommand.pyHash() 
    winform.edit.print("主线程收到返回值：",str)
    
    //取消按钮禁用状态
    winform.button.disabledText = null;
}  

winform.onClose = function(hwnd,message,wParam,lParam){
    //退出 Python 线程
	winform.pyCommand.pyExit() 
}

winform.show(); 
win.loopMessage();
```

在 aardio 中窗口对象可以跨线程直接调用。

我们将界面线程的  `winform` 对象作为线程参数传入 Python 后台线程，然后又在 Python 后台线程中调用 `winform.pyCommand = frmMsg` 将后台线程的 `frmMsg` 回传到界面线程，就可以在界面线程中通过 `winform.pyCommand`  访问后台线程 `frmMsg` 对象了。

注意后台线程要调用  `win.loopMessage() ` 以启动窗口消息循环，使用 `win.quitMessage()` 退出消息循环。

## 进程外调用任意版本 Python ，兼容 32 位与 64 位 Python 。

py3 扩展库是进程内直接调用 Python 接口，仅支持 py3 扩展库自带的指定版本 Python，并且这是 32 位 Python。

可运行如下 aardio 代码查看 py3 扩展库自带的 Python 版本信息：

```aardio
import console.int;
import py3;

console.log( py3.sysObject("version") );

```

aardio 提供 process.python 库可通过进程外调用任意任意版本 Python ，兼容 32 位与 64 位 Python 。

示例：

```aardio
import console.int;
import process.python; 

//运行 Python 代码
var python = process.python.exec(`
?>import json
str = json.dumps(['foo', "<?= 
time() //可使用模板语法嵌入 aardio  代码
?>",{'bar': ('baz', None, 1.0, 2)}])

# print 写到进程标准输出，在 aardio 中可以读取
print( str )
`);

/*
这里的 python 对象就是进程管道（ process.popen ）对象。 
下面的 python.json 读取进程输出的下一行 JSON 并转换为 aardio 对象，忽略非 JSON 输出。
读取其他数据可以改为 readAll() 或 read() 函数。
*/
var info,err = python.json();

console.dump(info || err);

 
```

调用 process.python.exec 返回的是一个 process.popen 对象。 

process.python 默认会调用绿色嵌入版 Python ，如果没有找到 Python 会自动下载。

可以用 process.python.path 自定义 python.exe 的路径。如果执行 `process.python.path = "python.exe" ` 则会调用系统安装的 Python （ 如果系统没有安装 Python 则会自动安装 ）。

process.python.exec 的参数参数可以是*.py, *.aardio 文件路径（ 支持内存加载 aardio 工程资源目录的文件 ）。如果指定的 Python 代码开始为 aardio 模板标记，则启用 aardio 模板语法。

process.python.exec 可添加一个或多个启动参数，也可以用一个字符串包含多个参数（空格分隔）。示例：

```aardio
import console.int;
import process.python; 

var python = process.python.exec(`
import sys
print( sys.argv )
`,"参数1","参数2");

console.log( python.readAll(), )

```