チュートリアルと学ぶMojo🔥+Pixi
Mojo 🔥チュートリアルで基礎を学ぶ
興味がなく

このようなウィンドウが出来たら、チュートリアルは完成となる
全文はこちらから:
事前準備
今回は
sudo apt install nautilus -y
準備の流れ
Pixi について
以前記事にしたこともあるが、現在、「存在しない。
その「
以前の記事から引用
- 他のパッケージ管理ツールとの比較表
| 備考 | ||||||
|---|---|---|---|---|---|---|
|
|
✅ | ✅ | ❌ | ❌ | ✅ | 特定バージョンの |
| 多言語対応 | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
|
✅ | ❌ | ❌ | ✅ | ✅ |
|
| タスク実行 | ✅ | ❌ | ❌ | ❌ | ❌ | パッケージをビルドする作業に於けるタスクを言うか |
| プロジェクト管理 | ✅ | ❌ | ❌ | ✅ | ✅ | プロジェクト単位での管理ができることを言うか |
- 情報元
次世代とか新世代とか評する声もあり、今後一定の普及も期待できるのではなかろうか。
Pixi インストール
勿論既にインストールしてあれば、新たにインストールする必要はない。
curl -fsSL https://pixi.sh/install.sh | sh
再起動
初めてpixiコマンドが使えるようになる。
Pixi でプロジェクト作成
これからチュートリアルを進める上で、プロジェクトを一つ作る。プロジェクト名は、記事ではlifeとなっている。自由に決めると良い。
# プロジェクト作成
pixi init life -c https://conda.modular.com/max-nightly/ -c conda-forge
# プロジェクトに移動
cd life
今後の作業は全てここで作成したプロジェクトにて行う。
Modular インストール
プロジェクトに
pixi add modular
これ以降、プロジェクトの環境下で
Python インストール
現在、
馴染ない人には無駄に聞こえるかもしれないが、こうした問題を回避するべく、プロジェクト専用に
pixi add "python>=3.11,<3.13" "pygame>=2.6.1,<3"
タスク作成
追記:タスクの基本的な話題を記事にしてまとめている。
茲で言うタスクとは、
例えばmake_build_dirタスクを実行する。
pixi run make_build_dir
make_build_dir = "mkdir -p build"とある通り、これは次と同等である。
mkdir -p build
結局のところ、これらは次のような流れを演じる。
プログラムを実行したければexecuteタスクを実行する。
pixi run execute
生成された実行ファイルを削除したければcleanタスクを実行する。
pixi run clean
各タスクについて
各タスクに概説を付する。
-
make_build_dir
buildフォルダーを作るタスク。make_build_dir = "mkdir -p build"
単に整理整頓のため、コンパイルによって生成される実行ファイルを
buildフォルダーに配置することにしたものである。 -
build
実行ファイルを生成するタスク。 🔥は、実行ファイルを生成しないMojo コンパイルと、生成するコンパイルと、二つの方法を選ぶことができる。ここではコンパイルしている。JIT build = { cmd = "mojo build src/main.mojo -o build/main", inputs = ["src/main.mojo"], outputs = ["build/main"], depends-on = ["make_build_dir"] }inputsとoutputsにそれぞれファイルのパスを指定しているが、ここに指定したからと言って、cmdの方には省略できるというわけではなかった。本来の意味としては「キャッシュ」である。src/main.mojoに変更が無ければ、このタスクは省略され、無駄なコンパイルが行われなくなる(ものと思われる)。変更がない場合✨ Pixi task (build): mojo build src/main.mojo -o build/main Task 'build' can be skipped (cache hit) 🚀また、
depends-onはタスクの依存関係を示す。ここではmake_build_dirが指定されているため、「buildタスクが実行される前には必ずmake_build_dirタスクが実行される」。
-
exe_mojo
プログラムを実行するタスク。build/mainが生成された実行ファイルのパスであるため、単にそこを指している。exe_mojo = "build/main" -
execute
buildタスクとexe_mojoタスクの別名( )であり、二つのタスクの依存関係を定義しているに過ぎない。alias execute = [{ task = "build" }, { task = "exe_mojo" }]executeを実行することは、buildの後にexe_mojoを実行することに等しい。
-
clean
buildフォルダーを削除するタスク。存在しない場合への配慮は特にしていない。clean = "rm -rf build"
これらタスクはpixi.tomlファイルに記述されている。ファイルを直接編輯することは勿論、コマンドでもタスクを作成、削除することができる。
$ pixi task add make_build_dir "mkdir -p build"
✔ Added task `make_build_dir`: mkdir -p build
$ pixi task alias execute build exe_mojo
@ Added alias `execute`: , depends-on = [build,exe_mojo]
$ pixi task add clean "rm -rf build"
✔ Added task `clean`: rm -rf build
しかしながら、inputsとoutputsをコマンドで指定する方法が分からなかった。現状はコマンドに固執するのでなく、直接記述するほうが確実に思う。
本題
ここから漸うプログラミングに取り掛かる。とは言え、全てはチュートリアルの記事にて説明されている通りであるため、本記事ではその内容について詳細に触れることはない。
プログラム全容
百聞は一見に如かず、とりあえず動く様子を見た方が理解も早いだろう。なにより、事前準備の段階で躓いていた場合、この先を読んでも身が入らないだろう。
ここに全文を載せたため、以降は要所のみ引用する。
概説
内容に深く触れなければ何に触れるのかと言えば、
なお、やや程度が高い、または
1. エントリーポイントと標準入出力
プログラムに於いて、実行の端を発するところを「エントリーポイント」などと呼称する場合がある。多くの場合、main()と表記された箇所がエントリーポイントに当る。
main()を定めなければならない。
# エントリーポイント
fn main() raises:
# 標準入力
var name: String = input("Enter your name: ")
# 文字列のフォーマット
# f"Hello, {name}!" と書くことはできない
var greeting: String = String("Hello, {}!").format(name)
# 標準出力
print(greeting)
# 指定時間の待機
# time.sleep(1) と書く時は、小数を指定していないとしてエラーになる
time.sleep(1.0)
input()でキーボードによる文字入力を受け取ることができ、逆にprint()で文字を表示することができる。
コラム:変数定義
変数定義の方法
varを用いた定義もできる。こちらはdefとfnほど露骨ではないが、やはりその性質に異なるところがある。
手始めに簡単な例を見せよう。
初期化せず宣言のみ
1> value1: Int
2. var value2: Float64
3.
(Int) value1 = 0
(SIMD[float64, 1]) value2 = {
(f64) [0] = 0
}
3> value1 = 15
4. value2 = 4.77
5.
5> print(value1, value2)
15 4.77
値による初期化
値を代入して初期化することもできる。
1> value1 = -0.5
2. value2: Int8 = 16
3. var value3: List[Int] = [1, 3, 7]
(SIMD[float64, 1]) value1 = {
(f64) [0] = -0.5
}
(SIMD[int8, 1]) value2 = {
(si8) [0] = 16
}
(List[Int]) value3 = (size 3) {
(Int) [0] = 1
(Int) [1] = 3
(Int) [2] = 7
}
4> print(value1)
-0.5
5> print(value2)
16
7> for i in range(len(value3)):
8. print(value3[i])
1
3
7
スコープ
普通、変数には「使える場面」と「使えない場面」がある。
1> fn F1():
2. if True: # 必ず分岐する
3. var x = 0
4. print("undefined: ", x)
5.
[User] error: Expression [0]:4:24: use of unknown declaration 'x'
print("undefined: ", x)
^
ifブロックの中でxが定義されているが、ifブロックの外では使えるわけがない。スコープとはいわばそうした性質である。
varによる再定義
21> fn F2():
22. var x = 0
23. if True: # 必ず分岐する
24. print("outer the if block: ", x)
25. var x = "inner the if block"
26. print(x)
27. print("end the if block: ", x)
[User] warning: Expression [6]:3:6: if statement with constant condition 'if True'
if True: # 必ず分岐する
28> F2()
29.
outer the if block: 0
inner the if block
end the if block: 0
-
F2()では当初、xに0を代入した。ifブロックの中でもこれは健在であることがouter the if block: 0の結果から分かる。 -
ここで、
ifブロックの中に改めてxを宣言し、文字列で初期化した。そのことがinner the if blockの結果から分かる。 -
ifブロックを抜けた後、xは文字列ではなく数値になっている。ifブロックの中で定義したxは既に事切れていることが、end the if block: 0の結果から分かる。
このように、「同名の変数を定義できる」のがvarの特徴である。とはいえ、これがメリットになる場面はそう多くないだろう。
既存の変数への代入
反対にvarを使わない場合、同名の変数を定義することはできず、既存の変数に代入することとなる。多くの場合、こちらの性質を使うことになるだろう。
30> fn F3():
31. var x = 0 # var あり
32. if True:
33. print("before update: ", x)
34. x = 1000
35. print("after if block: ", x)
[User] warning: Expression [8]:3:6: if statement with constant condition 'if True'
if True:
^
36> F3()
37.
before update: 0
after if block: 1000
37> fn F3():
38. x = 0 # var なし
39. if True:
40. print("before update: ", x)
41. x = 1000
42. print("after if block: ", x)
[User] warning: Expression [10]:3:6: if statement with constant condition 'if True'
if True:
^
43> F3()
44.
before update: 0
after if block: 1000
定数はない
難:不変と可変
変数には、後から異なる値を代入できる場合と、そうでない場合とがある。こうした性質をそれぞれ「可変」、「不変」と言う。
Rust では馴染みある概念
mutを付けなければならない。
fn main() {
let immutable_value = 0; // 不変
let mut mutable_value = 0; // 可変
println!("immutable_value: {immutable_value}");
println!("mutable_value: {mutable_value}");
mutable_value = 1; // 可変のため後から代入できる
println!("変更後: {}", mutable_value);
}
immutable_value: 0
mutable_value: 0
変更後: 1
0から1に変わっていることが分かるだろう。
しかしながら、immutable_value = 1;という一文を加えると、不変のものには後から代入できないとしてエラーになる。
error[E0384]: cannot assign twice to immutable variable `immutable_value`
Mojo 🔥では可変が普通
簡単に解釈すると、
艱難:不変なパラメーターと一般的な定義
しかし厳密に言えば
但しその使い方は、敢えて定数と言うべきものとは言い難い。そのままパラメーターとでも呼んでおく方がよいだろう。チュートリアルとの関連が現れるまで、その使い方を深堀しておくこととする。
引数とパラメーターとの区別
パラメーターの用例として、繰り返し回数を指定するものが挙げられている。少し誇張した例を示す。
1> fn repeat[count: Int](msg: String):
2. @parameter
3. for i in range(count):
4. print(msg, end='')
5. print() # これが無いと表示されない
6.
7>
8> repeat[1]('*')
9.
*
9> repeat[2]('*')
**
10> repeat[5]('*')
*****
11> repeat[10]('*')
**********
12> repeat[100]('*')
****************************************************************************************************
引数自体は'*'という文字であり、これを「定められた数」だけ表示するというものに過ぎない。
但し「定められた数」を定めているのがパラメーターである。1を指定すれば100を指定すれば
このパラメーターは、しばしば「抽象化」に用いられる。
Generics (一般的)
ジェネリック医薬品などと言うが、あれは後発医薬品という意味合いが目立っており、原義の理解に障る。
先の例も、繰り返す回数にジェネリクスを適用したものと言える。しかし、整数型に限定したものであったため、ジェネリクスの真価を発揮したものとは言えない。
物事は一般的に考える程、厳密な定義を要する。例えばデータを「表示する」ことを考えよう。
配列や構造体を表示できない問題
基本的には、データはprint()で表示できる。しかし、中にはできないものもある。
1> i = 10
2.
(Int) i = 10
2> print(i)
10
3> l = [1, 2]
(List[Int]) l = (size 2) {
(Int) [0] = 1
(Int) [1] = 2
}
4> print(l)
[User] error: Expression [3]:1:6: invalid call to 'print': could not deduce parameter 'Ts' of callee 'print'
print(l)
~~~~~^~~
[User] Expression [3]:1:7: failed to infer parameter 'Ts', argument type 'List[Int]' does not conform to trait 'Writable'
print(l)
^
構造体を表示できるようにする
チュートリアルとしても後述されるため、構造体を例にするのが簡単であった。
fn main():
# 整数
generic_print(10)
# 小数
generic_print(-4.888)
# 構造体
s1 = S1(9998, 7.255)
generic_print(s1)
s2 = S2("Generics", -128)
generic_print(s2)
# 文字列に変換できるデータを表示する
fn generic_print[DataTrait: Stringable](data: DataTrait):
print( String(data) )
@fieldwise_init
struct S1(Copyable, Movable, Stringable):
var value1: UInt64
var value2: Float64
# 文字列への変換の定義
fn __str__(self) -> String:
var str: String = "["+String(self.value1)+", "+String(self.value2)+"]"
return str
@fieldwise_init
struct S2(Copyable, Movable, Stringable):
var value1: String
var value2: Int8
# 文字列への変換の定義
fn __str__(self) -> String:
var str: String = "("+self.value1+", "+String(self.value2)+")"
return str
10
-4.888
[9998, 7.255]
(Generics, -128)
Trait (特性)と制約
注目するのはこの箇所。Stringableという語が見えるが、字面の通り、「文字列に変換できる」ということを示す。
fn generic_print[DataTrait: Stringable](data: DataTrait):
print( String(data) )
つまり、generic_print()の引数には「文字列に変換できるもののみ受け入れる」、「文字列に変換できないものは受け入れない」という制約を与えている。
Stringableは文字列に変換できるという特性を表し、このような何らかの特性は「
更に、[DataTrait: Stringable](data: DataTrait)は、引数dataが特性Stringableを持つことを要求する制約である。このように、何らかの特性を要求するような制約は、「
制約を課す分には簡単でよいが、制約を課される側の構造体は、特性Stringableを満たすための定義を持たなければならない。
2. データ型
チュートリアルの記事では整数や文字列、配列(リスト)、構造体が見られる。
| 名 | 型 |
|
|---|---|---|
| 整数 | Int |
int |
| 文字列 | String |
str |
| 配列(リスト) | List |
list |
| 構造体 | struct |
- |
Pythonに構造体はないのか
structというモジュールが存在する。しかしこれは画像のようなバイナリーデータの扱いに優れたものであり、
classが極めて近い。実際のところ、dataclassを用いた真似事が行われている。
class C1:
value1: int
value2: str
def __init__(self, value1: int, value2: str):
self.value1 = value1
self.value2 = value2
struct S1:
var value1: Int
var value2: String
fn __init__(out self, value1: Int, value2: String):
self.value1 = value1
self.value2 = value2
なお、プログラムを実行するに当たり、必ずしも型を明記する必要はない。但し
1> i = 10
2.
(Int) i = 10
2> i = 10.5
3.
[User] error: Expression [1]:1:5: cannot implicitly convert 'FloatLiteral[10.5]' value to 'Int'
i = 10.5
^~~~
しかし敢えて型を明らかにしたい場合、

rowの型はIntであることが分かる
その他のデータ型に関する詳細な情報は、チュートリアルとは別に編輯されている。
コラム:Pythonにはない型の区別
詳細に区別された型
符号付き整数(signed integer )と符号なし整数(unsigned integer )
コンピューターの設計上、あらゆるデータは二進数で表現されている。数学であれば、二進数に負の記号を付して
従って、負の数の表現を諦めた符号なし整数と、一桁を符号と見なして、その一桁だけ数値の表現できる範囲を犠牲にした符号付き整数との二つが使われている。難儀な話であることよ。
| 桁 | 符号付き整数 | 符号なし整数 |
|---|---|---|
Int8 |
UInt8 |
|
Int16 |
UInt16 |
|
Int32 |
UInt32 |
|
Int64 |
UInt64 |
|
Int128 |
UInt128 |
|
Int256 |
UInt256 |
扱うデータの規模によって使い分けることで、無駄を省くことができる。
小数
小数も同様に桁数によって、Float16、Float32、Float64と分類がある。一般に桁が多い程、小数の表現精度が高まる。
SIMD (単一命令・複数データ)
並列処理のための設計と理解すればよい。Float16はSIMD[DType.float16, 1]の別名に過ぎない。
fn main():
var vec1 = SIMD[DType.float64, 64](
1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8, 8.9,
91.2, 87.2, 83.4, 79.5, 75.6, 71.7, 67.8, 63.9,
12.3, 22.4, 32.5, 42.6, 52.7, 62.8, 72.9, 82.0,
92.1, 102.2, 112.3, 122.4, 132.5, 142.6, 152.7, 162.8,
21.3, 31.4, 41.5, 51.6, 61.7, 71.8, 81.9, 91.0,
101.1, 111.2, 121.3, 131.4, 141.5, 151.6, 161.7, 171.8,
23.4, 33.5, 43.6, 53.7, 63.8, 73.9, 84.0, 94.1,
104.2, 114.3, 124.4, 134.5, 144.6, 154.7, 164.8, 174.9,
)
var vec2 = SIMD[DType.float64, 64](
7.5, -44.5, 12.3, -23.4, 56.7, -67.8, 89.0, -90.1,
11.2, -22.3, 33.4, -44.0, 0.5, -1.6, 2.7, 3.8,
4.9, -5.0, 6.1, -7.2, 8.3, -9.4, 10.5, -11.6,
12.7, -13.8, 14.9, -15.0, 16.1, -17.2, 18.3, -19.4,
20.5, -21.6, 22.7, -23.8, 24.9, -25.0, 26.1, -27.2,
28.3, -29.4, 30.5, -31.6, 32.7, -33.8, 34.9, -35.0,
36.1, -37.2, 38.3, -39.4, 40.5, -41.6, 42.7, -43.8,
44.9, -45.0, 46.1, -47.2, 48.3, -49.4, 50.5, -51.6,
)
var result = vec1 * vec2
print("Result of SIMD multiplication:")
print(result)
Result of SIMD multiplication:
[9.0, -102.35, 41.82, -105.3, 317.52, -454.26, 694.1999999999999, -801.89, 1021.4399999999999, -1944.5600000000002, 2785.56, -3498.0, 37.8, -114.72000000000001, 183.06, 242.82, 60.27000000000001, -112.0, 198.25, -306.72, 437.4100000000001, -590.32, 765.45, -951.1999999999999, 1169.6699999999998, -1410.3600000000001, 1673.27, -1836.0, 2133.25, -2452.72, 2794.41, -3158.32, 436.65000000000003, -678.24, 942.05, -1228.0800000000002, 1536.33, -1795.0, 2137.59, -2475.2, 2861.13, -3269.2799999999997, 3699.65, -4152.240000000001, 4627.05, -5124.079999999999, 5643.329999999999, -6013.0, 844.74, -1246.2, 1669.8799999999999, -2115.78, 2583.9, -3074.2400000000002, 3586.8, -4121.58, 4678.58, -5143.5, 5734.84, -6348.400000000001, 6984.179999999999, -7642.179999999999, 8322.400000000001, -9024.84]
Listがprint()で表示できなかったのに対し、こちらは配列のようにして普通に表示されていることにも注目される。
3. 関数と処理
defまたはfnによって関数を定めることができる。->で記載する。
条件分岐や繰り返しも
1> for i in range(10):
2. print(i)
0
1
2
3
4
5
6
7
8
9
6> for i in range(3):
7. for j in range(4):
8. print(i, j)
0 0
0 1
0 2
0 3
1 0
1 1
1 2
1 3
2 0
2 1
2 2
2 3
その他の繰り返し
1> for i in [1, 3, 5]:
2. print(i)
3.
1
3
5
4> for i in ["a", "b", "c"]:
5. print(i)
a
b
c
1> var i = 0
2. while i <= 10:
3. print(i)
4. i += 1
5.
0
1
2
3
4
5
6
7
8
9
10
(Int) i = 11
6> var j = 0
7. while True:
8. if j > 10:
9. break
10. print(j)
11. j += 1
12.
0
1
2
3
4
5
6
7
8
9
10
(Int) j = 11
1> var a = 10
2. if a > 0:
3. print("正")
4. elif a == 0:
5. print("0")
6. else:
7. print("負")
正
(Int) a = 10
現状Pythonにしかない処理
match~caseがある。言語によって表記に揺れがあり、switchであったり、whenであったりするが、これも分岐の一種であろう。
match data:
case 0:
...
case 1:
...
case _:
...
時折使いたくなるものだが、現状、
ところで、チュートリアルでは行数と列数を変数にしていたが、元の配列から算出できるので、敢えて変数としての定義を削除した。
fn main() raises:
︙
# var rows: Int = 8 # 不要
# var cols: Int = 8 # 不要
var glider: List[List[Int]] = [
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
]
︙
fn grid_str(
# rows: Int, # 不要
# cols: Int, # 不要
grid: List[List[Int]]
) -> String:
var str: String = String()
# 配列から取得する方式に変更
var rows: Int = len(grid)
var cols: Int = len(grid[0])
for row in range(rows):
for col in range(cols):
if grid[row][col] == 1:
str += "*"
else:
str += " "
if row != rows-1:
str += "\n"
return str
コラム:defとfn
関数定義の方法
この違いは真っ先に目に入るものだが、その裏にある思惑には微々として程度の高い問題が関与している。少なくとも、チュートリアルでプログラミングを学ぼうとする者までもが、わざわざ配慮を求められるようなものではない。
defであり、fnであり、これは
「失敗し得る」プログラムへの許容
驚くことに、defで定義するかfnで定義するかによって、許容される「記述の厳格さ」が変化する。
...
なんで?
具体的には、defで定めると、或る程度の粗雑な記述は許容される。そのためか、チュートリアルの記事では、関数の定義に全てdefを使用している。
反対にfnで定めた場合、defの場合には見過ごされていた箇所を追及されるようになる。そしてそのような箇所の例は既に現れている。
var name: String = input("Enter your name: ")
var greeting: String = String("Hello, {}!").format(name)
該当するのはinputとformatである。これらは「失敗し得る箇所」であり、その安全性を必ずしも保証できない。
try~exceptを使う。raisesと付することで茶を濁すこともできる。
fn main() raises:
エラーを起こすか起こさぬか
但しこれは「エラーへの未然対処を諦めた」ことを示しており、何の対策にもなっていないことを理解する必要がある。「fnで定義された関数はエラーを起こさない」ことが前提であり、その前提を覆す必要がある場合にraisesを用いるのが本来の使い方なのであろう。
fn Func(a: Int) raises:
if a < 0:
raise Error("aは非負")
...
これを踏まえると、inputやformat如きにraisesは使わず、「inputに失敗した場合はどうするか」や、「formatに失敗した場合はどうするか」を全て定めることが理想的である。とは言うものの、それはチュートリアルに学ぶような者が強いられることではない。従って本記事では、全ての例外処理をraisesで放棄している。
try~ecxeptで例外処理を行う例
ここでは一例として、input()について危惧される例外「
fn main():
var text: String
try:
text = input("input your name: ")
except e:
# Ctrl + D で EOF Error が発生する
print("\nAn error occurred: ", e)
# invalid call to '__add__': failed to infer implicit parameter 'value' of argument 'rhs' type 'StringLiteral'
# print("An error occurred: " + e)
else:
print("hello, " + text)
finally:
print("exit")
(mojo_env) $ mojo input.mojo
input your name: eof
hello, eof
exit
(mojo_env) $ mojo input.mojo
input your name: # ここで「Ctrl + D」を入力する
An error occurred: EOF
exit
関数input()の実装は
コメントを除外すると、実質これだけの行数となる。
これを見る限り、例外を起こす箇所raiseが見られない。そこで見慣れない記述_fdopenが使われていることに注目する。実際、この構造体_fdopenの定義中にraiseがある。
この定義により、「Ctrl + D(
より厳格な言語の場合
defのような逃げ道もなければraisesという逃げ道もなく、一つ一つ全ての失敗し得る箇所に適切な記述を施す必要がある。
let n: u64 =
env::args() // コマンドライン引数を取得
.nth(1) // index番号1のものを指定
.unwrap_or("0".to_string()) // index番号1の取得に失敗した場合、代わりに"0"を代入する
.parse::<u64>() // string型の文字列をu64型の数値に変換する
.unwrap_or(0); // 文字列から数値への変換に失敗した場合、代わりに0を代入する
ここで使われているunwrap_or()は、まさしく「失敗した場合はどうする」という対策を定めるための関数である。しかし、これに似たunwrap()という関数もよく用いられる。
unwrap()は、失敗し得る箇所に取り合えず付して置かれる関数であり、「失敗した場合はパニックを起こす」。つまりraisesのように、「エラーへの未然対処を諦めた」ことを示すものである。
定義した関数を使うにも特別なことは必要ない。
var glider: List[List[Int]] = [
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
]
var str1: String = grid_str(glider) # 定義した grid_str()
print()
print("row list version:")
print(str1)
4. ファイル分けとオブジェクト指向
プログラムを書く上で、機能や意味合いによるファイル分けは避けられない。オブジェクト指向のように複雑なプログラムは別ファイルに隔離し、単純な記述だけが残るように工夫すると、所謂モジュール化できたというような状態となる。
チュートリアルではgridv1.mojoというファイルを作っている。本記事ではより短くgrid.mojoと言うファイル名である点に注意。
オブジェクト指向によって、新たにGridというデータ型が使えるようになる。これまではList[List[Int]]型という複雑なものであったから、見た目にも分かりやすく、記述量も減った。
# @fieldwise_init
struct Grid(Copyable, Movable, StringableRaising):
var rows: Int
var cols: Int
var data: List[List[Int]]
fn __init__(
out self,
# rows: Int,
# cols: Int,
data: List[List[Int]]
):
self.data = data
self.rows = len(data)
self.cols = len(data[0])
︙
from grid import Grid # grid はファイル名 grid.mojo から
︙
var grid1: Grid = Grid(glider)
コラム:オブジェクト指向概説
オブジェクト指向に関わる記述
オブジェクト指向の基本的な記述について触れる。
クラスと構造体、関数の関係
- 構造体は「変数(データ)」を集めたものである。
- 関数は「処理」を集めたものである。
1> struct User:
2. var name: String
3. var age: Int
4.
5> fn show(user: User):
6. print("name: ", user.name, ", age: ", user.age)
7.
8> var user: User
9. user.name = "mj"
10. user.age = 45
11. show(user)
name: mj , age: 45
Cの例
#include <stdio.h>
typedef struct {
char* name; // 名前を表す文字列
int age; // 年齢を表す数値
} User; // User型を定義
// 名前を取得する
char* get_user_name(User user) {
return user.name;
}
// 年齢を取得する
int get_user_age(User user) {
return user.age;
}
int main() {
User user = {"mj", 40}; // 構造体の初期化
char* name = get_user_name(user); // 名前取得
int age = get_user_age(user); // 年齢取得
printf("name: %s, age: %d\n", name, age); // 表示
return 0;
}
name: mj, age: 40
古くはこのようにして、「データと処理が分かれていた」。しかしこれには、人間がデータと処理の対応を把握する負担がある。
そこで「関わりのあるデータと処理を一括りにした」のが、
1> struct User:
2. var name: String
3. var age: Int
4.
5. fn __init__(out self, name: String, age: Int): # 初期化
6. self.name = name
7. self.age = age
8.
9. fn show(self): # 表示
10. print("name: ", self.name, ", age: ", self.age)
11.
11> user = User("mj", 67)
12. user.show()
13.
name: mj , age: 67
コンストラクター定義の省略
user = User("mj", 67)のように変数を初期化しているが、初期化の処理は自分で定めなければならない。クラスから生成する変数を
__init__()として定める。
fn __init__(out self, name: String, age: Int):
self.name = name
self.age = age
単純なクラスの場合、コンストラクターの働きは代入のみである。その数が増えた時、全て網羅して記述することは大いに手を煩わすだろう。
@fieldwise_initを付けることで、初期化のみのコンストラクターを定めたことにしてくれる。
@fieldwise_init
struct User:
var name: String
var age: Int
fn show(self):
print("name: ", self.name, ", age: ", self.age)
fn main():
var user = User("mj", 30)
user.show()
name: mj , age: 30
オブジェクト指向には様々な役割や効果があるが、それらに共通するのは「人が見て分かりやすい」とか、「人が見て使いやすい」とかいうものである。本記事では、最終的に
var glider: List[List[Int]] = [
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
]
のような定義が
var grid: Grid = Grid.random(8, 8)
の一文に削減されることとなる。扱いやすいのは明らかに後者である。
コラム:クラスメンバー関数概説
各クラスメンバー関数について
クラスに属する変数と関数をそれぞれクラスメンバー変数、クラスメンバー関数ということがある。コンストラクター以外の各クラスメンバー関数について、簡単な補足を加えておく。
__str__()
構造体Gridが継承した特性の中に、「文字列への変換ができる」特性(StringableRaising)がある。その特性を満たすため、構造体は__str__()を定義しなければならない。
ところで元grid_str()の名前であったこの関数は、「配列からなるデータを文字列に変換する」仕組みを持っていた。このため、grid_str()をそのまま__str__()に改名している。
# fn grid_str(self) -> String:
fn __str__(self) raises -> String: # StringableRaisingの継承による
var str: String = String()
for row in range(self.rows):
for col in range(self.cols):
# if self.data[row][col] == 1:
if self[row, col] == 1: # __getitem__による
str += "*"
else:
str += " "
if row != self.rows - 1:
str += "\n"
return str
なお似た特性にStringableがあり、双方には分かりやすい違いがある。
Stringable |
StringableRaising |
|
|---|---|---|
| エラー発生 | しない | する |
チュートリアルではStringableRaisingを使っている。defの場合は不要であるも、fnで__str__()を定義する際には、エラーが発生することを示すraisesが必要となる。
__getitem__()
クラスメンバー変数は、クラスの外からは隠蔽されることが望ましいとされる。つまり、直接grid.rowsなどとアクセスされることは望ましくないのである。
このためオブジェクト指向では、クラスメンバー変数を取得するための
fn __getitem__(self, row: Int, col: Int) -> Int:
return self.data[row][col]
__setitem__()
同様に、直接クラスメンバー変数を設定されないよう、そのための
fn __setitem__(mut self, row: Int, col: Int, value: Int) -> None:
self.data[row][col] = value
random()
通常Gridは、二次元配列からなるデータを渡すことで初期化する。しかしそれでは使い勝手が悪い。
# 初期化用のデータ
var glider: List[List[Int]] = [
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
]
# 初期化
var str1: String = grid_str(glider)
そこでランダムなデータを自動で作り、初期化できるようにするのがこのrandom()である。
@staticmethod
fn random(rows: Int, cols: Int) -> Self:
random.seed()
var data: List[List[Int]] = [] # 空のデータ
for _ in range(rows):
var row_data: List[Int] = [] # 空の行
for _ in range(cols):
row_data.append(Int(random.random_si64(0, 1))) # 行にランダムなデータを与える
data.append(row_data) # ランダムに作られた行をデータとして追加する
return Self(
# rows,
# cols,
data
)
コラム:静的メンバー
静的メンバーとインスタンスの有無
@staticmethodと付いているものは、Grid.random()という使い方ができる。裏を返せば、他のものはGrid.evolve()などと使うことができない。grid = Grid()のようにインスタンスを作っていなければ(初期化していなければ)使えないのである。
Grid.random()は、インスタンス無しで使えるクラスメンバーである。このようなものを静的メンバーということがある。
Grid.random()がそうであるように、静的メンバーである関数は、しばしばインスタンス化のために使われることがある。
evolve()
ライフゲームのルールに基づき、次世代の生死を決定している。ライフゲームとしては肝となる箇所だが、文法上触れるべき重要な箇所は、演算子%である。
fn evolve(self) -> Self:
var next_generation: List[List[Int]] = List[List[Int]]()
var row_data: List[Int]
var row_above: Int
var row_below: Int
var col_left: Int
var col_right: Int
var num_neighbors: Int
var new_state: Int
for row in range(self.rows):
row_data = List[Int]()
row_above = (row - 1) % self.rows
row_below = (row + 1) % self.rows
for col in range(self.cols):
col_left = (col - 1) % self.cols
col_right = (col + 1) % self.cols
num_neighbors = (
self[row_above, col_left]
+ self[row_above, col]
+ self[row_above, col_right]
+ self[row, col_left]
+ self[row, col_right]
+ self[row_below, col_left]
+ self[row_below, col]
+ self[row_below, col_right]
)
new_state = 0
if self[row, col] == 1 and (num_neighbors == 2 or num_neighbors == 3):
new_state = 1
elif self[row, col] == 0 and num_neighbors == 3:
new_state = 1
row_data.append(new_state)
next_generation.append(row_data)
return Self(
# self.rows,
# self.cols,
next_generation
)
多くの言語と同様、%は割り算の余りを得る演算子である。
1> print(10 % 5)
0
2> print(10 % 4)
2
3> print(10 % 3)
1
この他、和は+、差は-、積は*、商は/など、演算子は常識的なものが殆どである。
5. Python を使う
疾うに忘れているかもしれないが、
# pygameのインポートはここではない
from python import Python, PythonObject
︙
fn display(
owned grid: Grid,
pause: Float64, # オーバーロードのため
window_height: Int = 600,
window_width: Int = 600,
background_color: String = "black",
cell_color: String = "green",
# pause: Float64 = 0.1, # オーバーロードのため
) raises -> None:
# pygameをインポート
var pygame: PythonObject = Python.import_module("pygame")
pygame.init()
var window: PythonObject = pygame.display.set_mode(
# Pythonの関数にはPythonの連想配列を使う必要がある
Python.tuple(window_height, window_width)
)
︙
コラム:オーバーロード
同名の関数を定義することを関数の
1> fn add(x: Int, y: Int):
2. print(x+y)
3> fn add(x: Float64, y: Float64):
4. print(x+y)
5> fn add(x: String, y: String):
6. print(x+y)
7>
8> add(10, 15)
25
9> add(1.5, 20.5)
10.
22.0
10> add("over", "load")
overload
オーバーロードは抽象化の手法の一つである。極端な話、引数の型を変えたらよいため、手軽な方法として馴染み深い人もあるだろう。
しかしこの方法は手軽な反面、定義の複製に近いため、対応する型を増やすとコード量が急激に増えかねない。コード量の増加を防ぎつつ、対応する型を増やしたい場合には、『コラム:変数定義』に述べたように、ジェネリクスを使うこととなる。
コラム:所有権について
難:所有権
所有権とは、変数に対する権限である。最も分かりやすいのは、「所有権を持たないものは変数の値を変更できない」というものである。同時に、「所有権を持つ者が存在する限り、その変数は生き続ける」。
所有権に関して
- 所有者は被所有者に対して唯一つ
- 所有者の命が尽きた時、被所有者は破棄される
- 被所有者への参照が存在する限り、所有者は延命する
しかし所有権は敢えて振りかざすものではなく、常に存在しているものである。特別な用が無ければ、所有権を意識したプログラミングをすることなどないはずである。
但し、チュートリアルの中にも所有権に関わる記述があるため、これのみ触れておく。
argument convention
直訳すると引数規則となるこれは、引数の所有権について表示するものである。
fn display(mut grid: Grid) raises -> None:
while True:
print(String(grid))
print()
if input("Enter 'q' to quit or press <Enter> to continue: ") == "q":
break
grid = grid.evolve()
これは一例だが、引数にmutとあるのが分かるだろう。
引数規則は、確認した限り
| 規則 | 概要 |
|---|---|
read |
借用 不変参照となる |
mut |
可変 可変参照となる |
owned |
一意の所有権(?) 可変コピーとなる(?) |
(var) |
エラーになるerror: Expression [7]:1:4: expected function name
|
ref |
不定の参照 呼び出し元が可変ならば可変 不変ならば不変 |
(out) |
初期化の保証 関数の開始時は未初期化だが、 関数の終了までには初期化される 主にコンストラクターに付される |
これらを簡単に確認する。
fn main():
var x = 10
var y = 20
readF(x, y)
print("After readF, x: ", x, ", y: ", y)
print()
mutF(x, y)
print("After mutF, x: ", x, ", y: ", y)
print()
ownedF(x, y)
print("After ownedF, x: ", x, ", y: ", y)
print()
# 不変参照
fn readF(read x: Int, read y: Int):
print("In readF:")
print("x: ", x, ", y: ", y)
# 変更できない
# x += 1
# y += 1
refF(x)
refF(y)
# 可変参照
fn mutF(mut x: Int, mut y: Int):
print("In mutF:")
print("x: ", x, ", y: ", y)
x += 1
y += 1
refF(x)
refF(y)
# 所有権譲渡
fn ownedF(owned x: Int, owned y: Int):
print("In ownedF:")
print("x: ", x, ", y: ", y)
x += 1
y += 1
print("x: ", x, ", y: ", y)
refF(x)
refF(y)
# 参照
fn refF[ is_mutable: Bool, //, origin: Origin[is_mutable] ](ref [origin] z: Int):
print("In refF:")
print("z: ", z)
@parameter
if is_mutable:
print("Mutable")
else:
print("Immutable")
In readF:
x: 10 , y: 20
In refF:
z: 10
Immutable
In refF:
z: 20
Immutable
After readF, x: 10 , y: 20
In mutF:
x: 10 , y: 20
In refF:
z: 11
Mutable
In refF:
z: 21
Mutable
After mutF, x: 11 , y: 21
In ownedF:
x: 11 , y: 21
x: 12 , y: 22
In refF:
z: 12
Mutable
In refF:
z: 22
Mutable
After ownedF, x: 11 , y: 21
重要な点は、「可変か不変か」と「参照か複製か」である。
- 値を変更する必要が無いならば、始めから不変を選ぶべきである。
- 値を変更する必要があるならば、可変を選ばなければならない。
- 複製には時間が掛かるため、複製する必要が無いならば、参照を選ぶべきである。
- 呼び出し元に影響を及ぼすことを防ぐためには、複製しなければならない。
import
Pythonであり、モジュールであるpygameをインポートすることはできない。
from python import Python, PythonObject
pygameのようなモジュールは全て、Python.import_module()により、変数のようにしてインポートすることになる。また詮ずるに、PythonObject型である。
var pygame: PythonObject = Python.import_module("pygame")
Python の連想配列とMojo 🔥の連想配列
pygameの記述中に、Python.tuple()を使った箇所が見られる。
var window: PythonObject = pygame.display.set_mode(
Python.tuple(window_height, window_width)
)
pygame.draw.rect(
window,
cell_fill_color,
Python.tuple(x, y, width, height),
)
print()での表示はできない。
1> t1 = Tuple[Int, String, List[Int]](12, "tuple", [3, 3, 3])
2.
(Tuple[Int, String, List[Int]]) t1 = {
(Int) [0] = 12
(String) [1] = "tuple"
(List[Int]) [2] = (size 3) {
(Int) [0] = 3
(Int) [1] = 3
(Int) [2] = 3
}
}
2> t2 = (3.3, 4.0, 1, "value")
(Tuple[SIMD[float64, 1], SIMD[float64, 1], Int, String]) t2 = {
(SIMD[float64, 1]) [0] = {
(f64) [0] = 3.2999999999999998
}
(SIMD[float64, 1]) [1] = {
(f64) [0] = 4
}
(Int) [2] = 1
(String) [3] = "value"
}
同様に[]で表現される「リスト」が存在する。そしてこのような二者は、概念上は同一であるもPython.list()関数で作成し、使用する。
1> from python import Python
2.
2> pl1 = Python.list(1, 2, 3)
(PythonObject) pl1 = {
(PyObjectPtr) _obj_ptr = {
(UnsafePointer[PyObject]) unsized_obj_ptr = {
(pointer<_stdlib::_python::__cpython::_PyObject>) address = 0x00007b18ec950a80
}
}
}
3> pl2 = Python.list(
4. Python.list(1, 2),
5. Python.list(3, 4)
6. )
(PythonObject) pl2 = {
(PyObjectPtr) _obj_ptr = {
(UnsafePointer[PyObject]) unsized_obj_ptr = {
(pointer<_stdlib::_python::__cpython::_PyObject>) address = 0x00007b18ec998c80
}
}
}
7> print(pl1)
[1, 2, 3]
8> print(pl2)
[[1, 2], [3, 4]]
print()で表示できないのに対し、
二者比較
Mojo 🔥の連想配列
1> mjList = [1, 2]
(List[Int]) mjList = (size 2) {
(Int) [0] = 1
(Int) [1] = 2
}
2> mjTuple = (1, 2)
(Tuple[Int, Int]) mjTuple = {
(Int) [0] = 1
(Int) [1] = 2
}
3> mjSet = {1, 2}
(Set[Int]) mjSet = {
(Dict[Int, NoneType]) _data = {
(Int) _len = 2
(Int) _n_entries = 2
(_DictIndex) _index = {
(UnsafePointer[NoneType]) data = {
(pointer<_stdlib::_builtin::_none::_NoneType>) address = 0x0000509ebfe02030
}
}
(List[Optional[DictEntry[Int, NoneType]]]) _entries = (size 0) {}
}
}
4> mjDict: Dict[String, Int] = {"a": 1, "b": 2}
(Dict[String, Int]) mjDict = {
(Int) _len = 2
(Int) _n_entries = 2
(_DictIndex) _index = {
(UnsafePointer[NoneType]) data = {
(pointer<_stdlib::_builtin::_none::_NoneType>) address = 0x0000509ebfe02020
}
}
(List[Optional[DictEntry[String, Int]]]) _entries = (size 0) {}
}
これらは全てprint()で表示できない。
5> print(mjList)
[User] error: Expression [4]:1:6: invalid call to 'print': could not deduce parameter 'Ts' of callee 'print'
print(mjList)
~~~~~^~~~~~~~
[User] Expression [4]:1:7: failed to infer parameter 'Ts', argument type 'List[Int]' does not conform to trait 'Writable'
print(mjList)
^~~~~~
Expression [0] wrapper:17:5: function declared here
mjList = [1, 2]
^
(null)
5> print(mjTuple)
[User] error: Expression [5]:1:6: invalid call to 'print': could not deduce parameter 'Ts' of callee 'print'
print(mjTuple)
~~~~~^~~~~~~~~
[User] Expression [5]:1:7: failed to infer parameter 'Ts', argument type 'Tuple[Int, Int]' does not conform to trait 'Writable'
print(mjTuple)
^~~~~~~
Expression [0] wrapper:17:5: function declared here
mjList = [1, 2]
^
(null)
5> print(mjSet)
[User] error: Expression [6]:1:6: invalid call to 'print': could not deduce parameter 'Ts' of callee 'print'
print(mjSet)
~~~~~^~~~~~~
[User] Expression [6]:1:7: failed to infer parameter 'Ts', argument type 'Set[Int]' does not conform to trait 'Writable'
print(mjSet)
^~~~~
Expression [0] wrapper:17:5: function declared here
mjList = [1, 2]
^
(null)
5> print(mjDict)
[User] error: Expression [7]:1:6: invalid call to 'print': could not deduce parameter 'Ts' of callee 'print'
print(mjDict)
~~~~~^~~~~~~~
[User] Expression [7]:1:7: failed to infer parameter 'Ts', argument type 'Dict[String, Int]' does not conform to trait 'Writable'
print(mjDict)
^~~~~~
Expression [0] wrapper:17:5: function declared here
mjList = [1, 2]
^
(null)
Python の連想配列
Python構造体には、リスト、タプル、辞書はあるものの、集合はない。
1> from python import Python
2> pyList = Python.list(1, 2)
(PythonObject) pyList = {
(PyObjectPtr) py_object = {
(UnsafePointer[PyObject]) unsized_obj_ptr = {
(pointer<_stdlib::_python::__cpython::_PyObject>) address = 0x00007bb82a004e00
}
}
}
3> pyTuple = Python.tuple(1, 2)
(PythonObject) pyTuple = {
(PyObjectPtr) py_object = {
(UnsafePointer[PyObject]) unsized_obj_ptr = {
(pointer<_stdlib::_python::__cpython::_PyObject>) address = 0x00007bb82a088800
}
}
}
4> pyDict = Python.dict(a = 1, b = 2)
(PythonObject) pyDict = {
(PyObjectPtr) py_object = {
(UnsafePointer[PyObject]) unsized_obj_ptr = {
(pointer<_stdlib::_python::__cpython::_PyObject>) address = 0x00007bb82a030680
}
}
}
これらはprint()で表示できる。
5> print(pyList)
[1, 2]
6> print(pyTuple)
(1, 2)
7> print(pyDict)
{'a': 1, 'b': 2}
コラム:ポインターについて
難:ポインターとアドレス
狭義にポインターとは、「変数のアドレス」を保有する変数である。このアドレスによって、「間接的に」変数の値をも保有していると考えられる。

引用:https://docs.modular.com/mojo/manual/pointers/
次のプログラムでは、dataをprint()で表示していない。しかしポインターによって、dataの値を間接的に表示している。
from memory import *
fn main():
var data = "mojo"
var p = Pointer(to=data)
print("Pointer: ", p[])
var op = OwnedPointer(data)
print("OwnedPointer: ", op[])
var ap = ArcPointer(data)
print("ArcPointer: ", ap[])
Pointer: mojo
OwnedPointer: mojo
ArcPointer: mojo
変数には、記憶領域上に配当されたアドレスがある。
Python 3.12.11 | packaged by conda-forge | (main, Jun 4 2025, 14:45:31) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> data = "python"
>>> data
'python'
>>> id(data)
134060789298832
>>> another = "python"
>>> id(another)
134060789298832
>>> another = "python3"
>>> id(another)
134060787848544
>>> p = id(another)
>>> p
134060787848544
>>> type(p)
<class 'int'>
ポインターはこのアドレスを保有している。
危険性
ポインターには大きく、安全なものと危険なものとがある。
- 上のように、変数に基づきそのアドレスを保有するポインターは安全とされる。
- 対して下のように、変数に基づかずにアドレスを直接扱うポインターは危険とされる。
from memory import *
fn main():
var up = UnsafePointer[Int].alloc(8)
up.init_pointee_move(20250713)
print("today: ", up[])
up[] -= 1
print("yesterday: ", up[])
var up2 = UnsafePointer[String].alloc(32)
up2.init_pointee_move("2025-07-13")
print("today: ", up2[])
up2[] = "2025-07-14"
print("tomorrow: ", up2[])
today: 20250713
yesterday: 20250712
today: 2025-07-13
tomorrow: 2025-07-14
このプログラムには数値型の変数も文字列型の変数も現れておらず、記憶領域上に指定分の範囲を割り当て(

引用:https://docs.modular.com/mojo/manual/pointers/unsafe-pointers
更に、何もデータを与えずに表示することもできる。
from memory import *
fn main():
var up3 = UnsafePointer[Int].alloc(1)
print("up3: ", up3[])
var up4 = UnsafePointer[Float32].alloc(1)
print("up4: ", up4[])
var up5 = UnsafePointer[String].alloc(4)
print("up5: ", up5[])
up3: 140728114870904
up4: 0.0
up5:
表示の結果以前に、何のデータもないところを表示すること自体が間違っている。またup3の値は実行の都度変わる。このような結果が予期せぬところで現れ、不具合となる可能性があるため、「
ポインターの分類
広義にポインターとは、「記憶領域上のアドレス」を保有する変数である。
ポインターが、既存の変数に基づいてそのアドレスを保有するとき、これをスマートポインターと言うことがある。「参照」はこれに該当し、実体の複製を回避して変数を扱うことができるため、処理を高速化する手法としてよく用いられる。
一方、単にアドレスを保有するとき、これを生(
生ポインターを使う場面
例えば、オペレーティングシステムが画面に文字を出力する仕組みの一つに、
文字を表示するためには、「0xB8000であると、あくまでも物理的に決まっている。これを指し示すために生ポインターを用いるのである。
2> var np = Python.import_module("numpy")
(PythonObject) np = {
(PyObjectPtr) py_object = {
(UnsafePointer[PyObject]) unsized_obj_ptr = {
(pointer<_stdlib::_python::__cpython::_PyObject>) address = 0x0000736a14142d90
}
}
}
3> var a1 = np.array([1, 2])
(PythonObject) a1 = {
(PyObjectPtr) py_object = {
(UnsafePointer[PyObject]) unsized_obj_ptr = {
(pointer<_stdlib::_python::__cpython::_PyObject>) address = 0x0000736a1412a1f0
}
}
}
4> var a1 = np.array([ [1, 2] ])
[User] error: Expression [4]:1:18: invalid call to '__call__': argument #1 cannot be converted from list literal to 'PythonObject'
var a1 = np.array([ [1, 2] ])
~~~~~~~~^~~~~~~~~~~~
5> var a1 = np.array(Python.list( Python.list(1, 2) ))
(PythonObject) a1 = {
(PyObjectPtr) py_object = {
(UnsafePointer[PyObject]) unsized_obj_ptr = {
(pointer<_stdlib::_python::__cpython::_PyObject>) address = 0x0000736a0f1d0e10
}
}
}
このように
跋
本記事では、チュートリアルを参考に基本的な
Discussion