Mojo 学习 —— 与 Python 交互
文章目录
为了使
Mojo
成为
Python
的超集,必须保证能与现有的
Python
程序兼容。
Python
使用者应能很快入手
Mojo
,并能够使用
Python
软件包生态系统。
但是,Mojo
仍处于早期开发阶段,许多功能尚未实现。目前还不能在 Mojo
中编写在 Python
代码。也还没有搭建好自己的软件包生态系统。
作为一个替代方案,Mojo
允许您导入 Python
模块,并调用 Python
函数,与 Python
对象交互。
Mojo
使用标准解释器(CPython
)运行 Python
代码,可与现有的 Python
代码完美兼容。
与 Python 集成
导入 Python 模块
使用 python
模块中的 Python.import_module
可以导入 Python
模块。例如:
from python import Python
fn use_array() raises:
# 相当于 import numpy as np
var np = Python.import_module("numpy")
# 使用 numpy
var array = np.array([1, 2, 3])
print(array)
use_array()
您也可以导入已安装的任何其他 Python
模块。
但是有几点需要注意:
- 目前还不支持单个成员(如单个类或函数)的导入,必须导入整个模块
- 由于
Mojo
尚不支持顶层代码,因此import_module
必须在函数内部调用。多次导入模块不会多次运行初始化逻辑,不会对性能造成任何影响。 import_module
函数可能会引发异常(例如,模块未安装)。需要在函数中处理异常(使用 try/except 子句,或者在函数签名中添加 raises 关键字)。
Mojo
会在运行时加载Python
解释器及Python
模块,所以它必须能够访问兼容的Python
解释器,并找到任何导入的Python
模块。
导入本地 Python 模块
如果您想在 Mojo
中使用本地 Python
脚本,只要将脚本所在路径添加到 Python
搜索路径即可。
例如,假设您在 path/to/module
有一个名为 mypython.py
的文件:
import numpy as np
def gen_random_values(size, base):
random_array = np.random.rand(size, size)
return random_array + base
在 Mojo
文件中导入并使用:
from python import Python
fn main() raises:
Python.add_to_path("path/to/module")
var mypython = Python.import_module("mypython")
var values = mypython.gen_random_values(2, 3)
print(values)
add_to_path
支持绝对路径和相对路径。例如,添加当前路径:
Python.add_to_path(".")
Python 环境
Mojo
SDK
依赖于 Python
环境。当您安装 Mojo
SDK
时,它会尝试查找兼容版本的 Python
解释器,并设置 Python
的 sys.path
以加载匹配的模块。
在大多数情况下,这都能正常工作,但有时候也会出现一些安装问题。
安装问题
安装程序运行时,会尝试使用 find_libpython
模块查找 CPython
的共享库。
如果包含以下情况,则可能会失败:
- 没有安装
Python
,或者安装的版本与Mojo
SDK
不匹配。 - 安装程序找不到
CPython
解释器的共享库(例如.so
或.dylib
文件)。有些Python
发行版不包含共享库,导致Mojo
无法嵌入解释器。
最好的方法是使用 conda
重新配置 Python
版本及环境
用 Conda 配置 Python 环境
可以查看前面介绍的环境配置章节
简单来说就是
- 快速安装
conda
(使用miniconda
)
mkdir -p ~/miniconda3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
rm -rf ~/miniconda3/miniconda.sh
- 为你的一个或多个
shell
初始化conda
:
~/miniconda3/bin/conda init zsh
或者
~/miniconda3/bin/conda init --all
- 重启
shell
- 配置
Mojo
使用conda
环境中的Python
共享库:
export MOJO_PYTHON_LIBRARY="$(find $CONDA_PREFIX/lib -iname 'libpython*.[s,d]*' | sort -r | head -n 1)"
echo "export MOJO_PYTHON_LIBRARY=$MOJO_PYTHON_LIBRARY" >> ~/.zshrc
如果使用的 shell
不是 zsh
,例如 bash
,则需要将 .zshrc
替换为你使用的 shell
配置文件,如 .bashrc
或 .bash_profile
。
- 安装任何要与
Mojo
一起使用的Python
软件包。例如
conda install numpy
- 重新测试上面的代码
from python import Python
fn use_array() raises:
# 相当于 import numpy as np
var np = Python.import_module("numpy")
# 使用 numpy
var array = np.array([1, 2, 3])
print(array)
use_array()
类型交互
当调用 Python
方法时,Mojo
会自动在 Python
对象和 Mojo
对象之间来回转换。但也存在一些 Mojo
无法处理的情况,您可能需要进行显式转换,或者调用一个额外的方法。
Python 使用 Mojo 类型
Mojo
原始类型如列表、元组、整数、浮点数、布尔和字符串,可以隐式转换为 Python
对象。
例如,在 jupyter notebook
中,定义一个 Python
函数:
%%python
def type_printer(value):
print(type(value))
用该函数打印 Mojo
类型:
type_printer(4)
type_printer(3.14)
type_printer(("Mojo", True))
type_printer(['A', "b", """c"""])
# <class 'int'>
# <class 'float'>
# <class 'tuple'>
# <class 'list'>
自动转换为了 Python
对象
在
jupyter notebook
中%%python
表示该单元中的代码为Python
代码,。第二个单元包含顶级Mojo
代码。
Mojo 使用 Python 类型
您还可以在 Mojo
中使用 Python
对象。例如,使用 Python
字典
from python import Python
fn use_dict() raises:
var dictionary = Python.dict()
dictionary["fruit"] = "apple"
dictionary["starch"] = "potato"
print("Fruit: ", dictionary["fruit"])
Mojo 封装对象
在 Mojo
中使用 Python
对象时,Mojo
会将其封装为 PythonObject
类型。
该对象包含许多常用的双下划线方法(魔法方法),如 __getitem__
和 __getattr__
,并将它们传递给底层 Python
对象。
我们可以使用 PythonObject
来显式地创建一个封装的 Python
对象:
from python import PythonObject
var py_list: PythonObject = [1, 2, 3, 4]
Mojo
字面量会转换为 PythonObject
类型,我们可以像 Python
对象一样使用它。例如
var n = py_list[2]
py_list.append(5)
如果 Mojo
中没有对应的字面形式,可以使用 Python.evaluate
方法,调用 Python
代码来创建。例如:
from python import Python
fn use_py_set() raises:
var py_set = Python.evaluate('set([2, 3, 5, 7, 11])')
var num_items = len(py_set)
print(num_items, " items in set.") # prints "5 items in set"
print(py_set.__contains__(6))
use_py_set()
PythonObject
当前的工作方式还不支持使用 in
来做成员判断,需要直接调用 __contains__
方法
目前,PythonObject
符合 Intable
和 Stringable
特性,所以可以使用内置的 int
和 str
函数将 Python
值转换为 Mojo
的 Int
和 String
类型,并使用内置的 print
函数打印 Python
值。
PythonObject
还提供了 __bool__
和 to_float64
方法,分别用于转换为布尔值和浮点数。
var i: Int = int(py_int)
var s: String = str(py_string)
var b: Bool = py_bool.__bool__()
var f: Float64 = py_float.to_float64()
我们前面说的
Python
类型会被封装为PythonObject
类型对象。但有一个除外,Python
字典被封装成了Dictionary
,但它并不是真正的字典类型,而是另一种封装形式。
比较 Python 类型
我们可以在条件表达式中使用 Python
对象,使用方式与 Python
类似。可以使用 Python.type
方法来获取其对应的 Python
类型,等同于 Python
的内置函数 type
。
您可以使用 Python.is_type
方法(相当于 Python
的 is
操作符)比较两个 Python
对象的标识,例如
fn python_types() raises:
from python import Python
from python import PythonObject
var value1: PythonObject = 3.7
var value2 = Python.evaluate("10/3")
var float_type = Python.evaluate("float")
if value1 > value2:
print(value1, '>', value2) # 3.7 > 3.3333333333333335
else:
print(value1, '<', value2)
print(Python.type(value1)) # <class 'float'>
print(Python.is_type(Python.type(value1), Python.type(value2))) # True
print(Python.is_type(Python.type(value1), float_type)) # True
print(Python.is_type(Python.type(value1), Python.none())) # False
python_types()
Python.is_type
方法的名称有误导性,因为它不是比较类型,而是比较对象身份,后续版本将会进行调整
简单示例
我们编写一个稍微复杂一点的例子,更贴合目前 Mojo
的使用方式。从 KEGG
中获取 TP53
基因所涉及到的通路信息。具体思路为:
- 首先,定义一个结构体(
Pathway
)来存储通路信息 - 然后,使用
requests
模块获取网页信息(使用KEGG
API
) - 使用
re
模块的正则表达式来进行字符串提取 - 最后将提取的通路信息存放到结构体列表中
from collections import List
from python import Python
@value
struct Pathway:
var id: String
var name: String
fn __str__(self) -> String:
return '(ID=' + self.id + '; ' + 'Name=' + self.name + ')'
def get_url(url: String) -> List[Pathway]:
var res = List[Pathway]()
try:
var requests = Python.import_module('requests')
var re = Python.import_module('re')
req = requests.get(url)
content = str(req.text)
var label: String = ''
for line in content.split('\n'):
if line[][0] != ' ': label = line[][:7]
if label == 'PATHWAY':
var col = re.findall('(hsa\\d+)\\s+(.+)', line[])[0]
var path = Pathway(col[0], col[1])
res.append(path^)
except e:
print(e)
return res
fn main() raises:
var url = "https://rest.kegg.jp/get/hsa:7157"
var res = get_url(url)
print('Pathway List: ')
for p in res[:6]:
print(p[])
输出结果
Pathway List:
(ID=hsa01522; Name=Endocrine resistance)
(ID=hsa01524; Name=Platinum drug resistance)
(ID=hsa04010; Name=MAPK signaling pathway)
(ID=hsa04071; Name=Sphingolipid signaling pathway)
(ID=hsa04110; Name=Cell cycle)
(ID=hsa04115; Name=p53 signaling pathway)