IronPython との付き合いかた

IronPython 3.4 の正式リリース（2022年12月）から１年以上が過ぎました。
マイナーな存在ですが、Python と .NET のいいとこ取りができるので、個人利用の小規模なスクリプトを書くのに適しています。

.NET 8 もリリースされた機会に、備忘録を兼ねて IronPython との付き合い方をまとめておきます。

(2024.3.6 追記)
[続編](https://qiita.com/Mt_SQ/items/a6276eb48682f3a8e97a)を投稿しました。

(2025.1.7 追記)
2024.12.19 にリリースされた 3.4.2 では、ターゲットに .NET 8 が追加されるとともに **.NET 9 でも動作可能**になりましたので、インストールと起動に関する記述を一部修正しました。

(2025.1.8 追記)
.NET 9 で LINQ に導入された機能を検証しました。

https://qiita.com/Mt_SQ/items/b1fbb1a27e0a1a03ad18

## 確認環境
+ Windows 11 Pro（Ver. 24H2）
+ .NET Runtime 9.0.0 (64-bit)
+ IronPython 3.4.2 (3.4.2.1000)
## IronPython のインストール
Windows 以外にも .NET や [Mono(www.mono-project.com)](https://www.mono-project.com/) 環境があれば、Linux, Mac を問わず動作します。（なお、[Linux へのインストールはすでに投稿](https://qiita.com/Mt_SQ/items/faf002370e6ec7dcb551)しています。）

### IronPython の手動インストール
まずは、こちらから **_IronPython.3.4.2.zip_** をダウンロードします。

https://github.com/IronLanguages/ironpython3/releases/tag/v3.4.2

zip ファイルを適当な場所に展開し、「*net8.0*」と「*lib*」の２つのディレクトリを任意の場所にコピーして完了です。zip は削除して構いません。
```text:配置例
c:\Users\User\IronPython
└── 📁 3.4
     ├── 📁 net8.0
     └── 📁 lib
```

:::note warn
msi ファイル（**_IronPython-3.4.2.msi_**）は **_ipy.exe_** をインストールします。
こちらは<b>.NET Framework </b>がターゲットです。
:::

インストールの詳細はこちらでご確認を。

https://github.com/IronLanguages/ironpython3/blob/master/Documentation/installing.md#installing-ironpython-released-binaries

## 起動
`dotnet` コマンドに _net8.0\ipy.dll_ を渡せば起動します。
.NET 9 で動かす場合は `--roll-forward LatestMajor` オプションが必要です。
上記オプションをつけない場合は .NET 9 がインストール済みでも .NET 8 上で動作します。
```posh
dotnet --roll-forward LatestMajor "C:\Users\User\IronPython\3.4\net8.0\ipy.dll"
```

（参考）環境による起動メッセージの違い
**Windows + .NET 9（`dotnet ipy.dll` で起動）**
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209885/1c73aa62-a4a8-316f-5272-1c9a4896594c.png width=640px>

**Linux + .NET 9（`dotnet ipy.dll` で起動）**
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209885/6d20e165-22b9-8c5f-2fca-70749724445f.png width=640px>

**Windows + .NET Framework 4.8（`ipy.exe` で起動）**
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209885/6715006f-9e4e-97b3-5d78-7a87d98cb7c9.png width=640px>

::: note warn
以降、本記事の内容は初回投稿時のまま
**Windows + .NET <font color="red">8</font>（`dotnet ipy.dll` で起動）**
で検証したものです。
:::

##  `import System`は必須
.NET API を利用する場合は、最低でも `import System` が必要です。

整数を代入した変数 `i` に対して `import System` 前と後とで `dir(i)` を比較してみます。
```Python:IronPython
i = 123
# import System 前
[attr for attr in dir(i) if attr[:2] != '__']
# ['bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator',
#  'real', 'to_bytes']

# import System 後
import System
[attr for attr in dir(i) if attr[:2] != '__']
# ['Abs', 'Add', 'Clamp', 'Compare', 'CompareTo', 'CopySign', 'CreateChecked',
#  'CreateSaturating', 'CreateTruncating', 'DivRem', 'Divide', 'Equals',
#  'GetBitLength', 'GetByteCount', 'GetHashCode', 'GetShortestBitLength', 'GetType',
#  'GetTypeCode', 'GreatestCommonDivisor', 'IsEven', 'IsEvenInteger', 'IsNegative',
#  'IsOddInteger', 'IsOne', 'IsPositive', 'IsPow2', 'IsPowerOfTwo', 'IsZero',
#  'LeadingZeroCount', 'Log', 'Log10', 'Log2', 'Max', 'MaxMagnitude', 'MaxValue',
#  'MemberwiseClone', 'Min', 'MinMagnitude', 'MinValue', 'MinusOne', 'ModPow',
#  'Multiply', 'Negate', 'One', 'Parse', 'PopCount', 'Pow', 'ReferenceEquals',
#  'Remainder', 'RotateLeft', 'RotateRight', 'Sign', 'Subtract', 'ToBigInteger',
#  'ToBoolean', 'ToByte', 'ToByteArray', 'ToChar', 'ToDateTime', 'ToDecimal',
#  'ToDouble', 'ToInt16', 'ToInt32', 'ToInt64', 'ToSByte', 'ToSingle', 'ToString',
#  'ToType', 'ToUInt16', 'ToUInt32', 'ToUInt64', 'TrailingZeroCount',
#  'TryConvertFromChecked', 'TryConvertFromSaturating', 'TryConvertFromTruncating',
#  'TryConvertToChecked', 'TryConvertToSaturating', 'TryConvertToTruncating',
#  'TryFormat', 'TryParse', 'TryWriteBigEndian', 'TryWriteBytes',
#  'TryWriteLittleEndian', 'WriteBigEndian', 'WriteLittleEndian', 'Zero', 'bit_length',
#  'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
```
## データ型について
Python の基本的な組み込み型は、.NET のクラスで実装されています。
`int`は要注意です。後で触れます。
|Python   |.NET                                          |
|:--------|:---------------------------------------------|
|`object` |`System.Object`                               |
|`int`    |`System.Int32`<br>`System.Numerics.BigInteger`|
|`float`  |`System.Double`                               |
|`str`    |`System.String`                               |
|`complex`|`System.Numerics.Complex`                     |
|`bool`   |`System.Boolean`                              |

IronPython では、<b>Python と .NET のクラスや属性を混用できてしまうので要注意です。</b>

Python の `str` 型と `String`(System.String) クラスを混用してみます。
```Python:IronPython
# 例1
from System import String
str.Join(', ', map(chr, range(65, 70)))
# 'IronPython.Runtime.Map'
```
`str.Join`（**J** は大文字）は .NET の `String.Join` メソッドとして解釈されます。
第２引数は “**map オブジェクトの文字列表現**” （`ToString`）になるため、想定外(?)の結果を返します。
```Python:IronPython
# 例1の補足 (map オブジェクトの文字列表現)
map(chr, range(65, 70)).ToString()
# 'IronPython.Runtime.Map'
```
`String.join`（**j** は小文字）は Python の `str.join` と解釈されます。
```Python:IronPython
# 例2
String.join(', ', map(chr, range(65, 70)))
# 'A, B, C, D, E'
```
おまけですが、`String.Join` メソッドでも`map`をアンパックすれば要素を結合します。
```Python:IronPython
String.Join(', ', *map(chr, range(65, 70)))
# 'A, B, C, D, E'
```
`Int32` や `Double` なども、`int`，`float`と解釈され、Python 的に振る舞います。
```Python:IronPython
from System import Double, Int32

Int32(1.0)   # int(1.0) と解釈
# 1
Int32('1')   # int('1')
# 1
Double(1)    # float(1)
# 1.0
Double('1')  # float('1')
# 1.0
```
なお、下の例のように**競合する記述は Python の解釈が優先されます。**
```Python:IronPython
ca = 'XYZ'.ToCharArray()            # System.Array[Char] を作って...
print(ca)
# Array[Char](('X', 'Y', 'Z'))

System.String(ca)                   # System.String コンストラクタで 'XYZ' を期待しても...
# "Array[Char](('X', 'Y', 'Z'))"      str 関数として機能する
```
:::note warn
IronPython では 型を`[ ]`で囲みます。`< >`ではありません。
:::
文字列を作る代替手段はいくらでもあるので大した問題ではありません。
```Python:IronPython
''.join(ca)               # 'XYZ'
System.String.Concat(ca)  # 'XYZ'
```

### `int`は要注意
下の例のように整数データは `int`, `Int32`, `System.Numerics.BigInteger`（以下`BigInteger`）それぞれの属性を持ちます。
```Python:IronPython
j = 1234          # 整数型データ j に対して
j.bit_length()    # Python のメソッド (int.bit_length) も
# 11
j.ToString('e3')  # .NET のメソッド (Int32.ToString) も
# '1.234e+003'
j.IsEven          # .NET のプロパティ (BigInteger.IsEven) も使える
# True
```
Python3 の仕様に合わせるため、**`int` は `Int32` と `BigInteger` の<font color=red>２つを使った特殊な実装です。</font>**

特に .NET API で整数を扱う場合は要注意です。
たとえば `int` は .NET 目線で見ると<b>「暗黙の型変換」</b>が発生している場合があります。
```Python:IronPython
big = 0x7fffffff
print(f'Value : {big}   Type : {type(big)} <{big.GetType()}>')
# Value : 2147483647   Type : <class 'int'> <System.Int32>

big += 1
print(f'Value : {big}   Type : {type(big)} <{big.GetType()}>')
# Value : 2147483648   Type : <class 'int'> <System.Numerics.BigInteger>
# Int32 の最大値を超えると、内部では BigInteger に変換されている

big -= 1
print(f'Value : {big}   Type : {type(big)} <{big.GetType()}>')
# Value : 2147483647   Type : <class 'int'> <System.Numerics.BigInteger>
# Int32 には自動的には戻らない
```
詳細はこちらに記載されています。

https://github.com/IronLanguages/ironpython3/blob/master/Documentation/upgrading-from-ipy2.md#int-type

## コレクションについて
Python のリスト、タプル と .NET の `System.Collections.Generic.List` や `System.Array` などの主なコレクションは簡単に相互変換できます。
### Python のコレクションを .NET のコレクションに変換
```Python:IronPython
# Python のコレクションを .NET のコレクションに変換する
from System import Array
from System.Collections.Generic import List

py_list = [1, 2, 3]
py_tuple = ('A', 'B', 'C')

List[int](py_list)
# List[int]([1, 2, 3])
List[str](py_tuple)
# List[str](['A', 'B', 'C'])

Array[int](py_list)
# Array[int]((1, 2, 3))
Array[str](py_tuple)
# Array[str](('A', 'B', 'C'))
```
渡した要素の型が指定と異なる場合、通常はエラーになりますが、`Array` は受け入れます。
```Python:IronPython
# List コンストラクタは指定以外の型を拒否する
List[int]([1, 2.5, 3])     # エラー（float 要素が入っている）
# TypeError: Error in IEnumeratorOfTWrapper.Current. Could not cast: from System.Double to System.Numerics.BigInteger
List[float]([1, 2.5, 3])   # エラー（int 要素が入っている）
# TypeError: Error in IEnumeratorOfTWrapper.Current. Could not cast: from System.Int32 to System.Double

# 型が混在する場合は object にします
List[object]([1, 2.5, 3])
# List[object]([1, 2.5, 3])

# Array コンストラクタは型変換して受け入れる
Array[int]([1, 2.5, 3])    # 小数部は切り捨てられる
# Array[int]((1, 2, 3))
Array[float]([1, 2.5, 3])
# Array[float]((1.0, 2.5, 3.0))
```
`Array` クラスのコンストラクタは言語（IronPython）側での実装なので型の緩さがあります。
また、`Array` 要素への代入でも同様に型変換されます。
### .NET のコレクションを Python のコレクションに変換
`list`，`tuple` に渡します。
```Python:IronPython
NET_List = List[str]()                                   # str型で空の List を作成
NET_List.Add('A'); NET_List.Add('B'); NET_List.Add('C')  # ３つの要素を追加
list(NET_List)
# ['A', 'B', 'C']
tuple(NET_List)
# ('A', 'B', 'C')

NET_Array = Array.CreateInstance(int, 3)                 # int型で要素数3の Array を作成
NET_Array[0], NET_Array[1], NET_Array[2] = 1, 2, 3       # 要素を代入
list(NET_Array)
# [1, 2, 3]
tuple(NET_Array)
# (1, 2, 3)
```
### 「`int`問題」と回避策
通常は、`Int32` と `BigInteger` の壁を IronPython が処理するので、ユーザーは意識する必要がありません。

例えば `List[int]` の実体として `List[BigInteger]` が作られます。
そこに `Int32` インスタンスを渡しても受け入れます。
```Python:IronPython
NET_List = List[int]([1, 2])
print(f'Value : {NET_List}   Type : {type(NET_List)} <{NET_List.GetType()}>')
# Value : List[int]([1, 2])   Type : <class 'List[int]'> <System.Collections.Generic.List`1[System.Numerics.BigInteger]>
# List[BigInteger] が作られていることがわかる

j1, j2 = 12, 123          # j1, j2 は Int32 のインスタンス
print(f'Value : {j1}   Type : {type(j1)} <{j1.GetType()}>')
# Value : 12   Type : <class 'int'> <System.Int32>
print(f'Value : {j2}   Type : {type(j2)} <{j2.GetType()}>')
# Value : 123   Type : <class 'int'> <System.Int32>

NET_List[1] = j1          # List[BigInteger] に対して
NET_List.Add(j2)          # Int32 を問題なく代入・追加できる

NET_List
# List[int]([1, 12, 123])
```
ただし、受け入れない場合もあります。
```Python:IronPython
NET_Array = Array[int]([1, 2, 3])
print(f'Value : {NET_Array}   Type : {type(NET_Array)} <{NET_Array.GetType()}>')
# Value : Array[int]((1, 2, 3))   Type : <class 'Array[int]'> <System.Numerics.BigInteger[]>

NET_Array.SetValue(123, 2)                    # Int32 は受け付けない
# TypeError: Object cannot be stored in an array of this type.

NET_Array.SetValue((123).ToBigInteger(), 2)   # 回避策

NET_Array
# Array[int]((1, 2, 123))
```
IronPython は回避策として `ToBigInteger` メソッドを実装していますが、<b>.NET API を使う場合は `Int32` など、可能な限り .NET の整数型を明示する方が無難でしょう。</b>
```Python:IronPython
from System import Int32
NET_Array = Array[Int32]([1, 2, 3])
NET_Array.SetValue(123, 2)                    # 問題ない

NET_Array
# Array[Int32]((1, 2, 123))
```
## コレクションの操作について
IronPython は `List` や `Array` にもスライスやアンパックが使えます。
`Array`に対しては負のインデックスも許可しています。
いろいろ試しましたが、おおむね対応できています。
```Python:IronPython
NET_List = List[str](['A', 'B', 'C'])
NET_Array = Array[Int32]([1, 2, 3])

NET_List[:2]                           # スライス
# List[str](['A', 'B'])
NET_List[::-1]                         # スライスによるリバース
# List[str](['C', 'B', 'A'])
print(*NET_List, sep=' - ')            # アンパック
# A - B - C

[*map(lambda s: s.lower(), NET_List)]  # map
# ['a', 'b', 'c']
[*zip(NET_List, NET_Array)]            # zip
# [('A', 1), ('B', 2), ('C', 3)]

NET_Array[-1]                          # Array は負のインデックスも許可
# 3
NET_Array[-1:-3:-1]                    # スライス
# Array[int]((3, 2))
[i * 2 for i in NET_Array]             # 内包表記
# [2, 4, 6]
NET_Array + NET_Array                  # シーケンス演算（結合）
# Array[int]((1, 2, 3, 1, 2, 3))
NET_Array * 3                          # シーケンス演算（繰り返し）
# Array[int]((1, 2, 3, 1, 2, 3, 1, 2, 3))
```
それぞれのコレクションで全ての操作が等しくサポートされているわけではありません。
```Python:IronPython
# Array ではできても List ではできないこと
NET_List[-1]         # 負のインデックスはエラー
# ValueError: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')

NET_List + NET_List  # シーケンス演算も不可
# TypeError: unsupported operand type(s) for +: 'List[str]' and 'List[str]'
NET_List * 3
# TypeError: unsupported operand type(s) for *: 'List[str]' and 'int'
```
未解決の問題も残っています。
```Python:IronPython
[*map(int, List[str](['1', '3', '5', '7', '9']))]   # 問題なし
# [1, 3, 5, 7, 9]
[*map(int, Array[str](['1', '3', '5', '7', '9']))]  # 問題発生...
# TypeError: int() takes at most 3 arguments (6 given)
```
この事象は報告済みですが、問題の根は深いようです。

https://github.com/IronLanguages/ironpython3/issues/1666

## `ImportError` について
<b>一部のクラスで `import` できない</b>ことがありますが、これは <b>IronPython ではよくある事象</b>です。
`clr` モジュールをインポートして、アセンブリへの参照設定 **`clr.AddReference(アセンブリ)`** で解決します。
アセンブリは [.NET API ブラウザ](https://learn.microsoft.com/ja-jp/dotnet/api/)で確認できます。
`LinkedList` の場合は _System.Collections.dll_ が参照先ですが <b>"_.dll_" は外します。</font></b>

<b>【`LinkedList` のエラーは参照設定で解決】</b>
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209885/9f7fbb53-9f12-668a-55bd-e18861d31e3d.png width=600px>

.NETCore と .NETFramework とでエラーになるクラスが異なる場合もあります。
また、参照するアセンブリも一部で異なります。

<b>【.NETFramework では `hashSet` がエラーになる】</b>
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209885/74f3dffe-0dad-1766-1982-78690eeac4f7.png width=600px>

## LINQ の利用
以下３行で LINQ の拡張メソッドが使えます。
```Python:IronPython
import System, clr
clr.AddReference('System.Core')
clr.ImportExtensions(System.Linq)
```
拡張メソッドはリストやタプルに対しても機能します。
### `Min` メソッド と `Max` メソッド
普通に使えるのですが...
```Python:IronPython
[2, 5, 1, 4].Min()    # list
# 1
(9, -5, 0, 4).Max()   # tuple
# 9
```
#### 「`int` は要注意」の具体例
`Int32` と `BigInteger` の混在によるトラブル例です。
```Python:IronPython
# 全要素が BigInteger なら問題ないが
[4000000000, 5000000000, 6000000000].Max()     
# 6000000000 

# Int32 と BigInteger が混在するとエラー
[1000000000, 2000000000, 3000000000].Max()
# ValueError: The parameter must be a BigInteger. (Parameter 'obj')
[3000000000, 2000000000, 1000000000].Max()
# ValueError: Object must be of type Int32.
```
`int` 型の明示で回避できます。
```Python:IronPython
# 解決策
[1000000000, 2000000000, 3000000000].Max[int]()
# 3000000000
[3000000000, 2000000000, 1000000000].Max[int]()
# 3000000000
```
#### 文字列では `min`, `max` 関数と挙動が異なる
文字列については `min`, `max`関数と異なる結果を返します。
LINQ の `Min` や `Max` メソッドは全角文字が混在しても辞書順です。
```Python:IronPython
# 'Ａ' だけ全角文字
# min 関数 や max 関数はコードポイント順
min(['Ａ', 'B', 'c'])
# 'B'
max(['Ａ', 'B', 'c'])
# 'Ａ'

# Min() や Max() は辞書順
['Ａ', 'B', 'c'].Min()
# 'Ａ'
['Ａ', 'B', 'c'].Max()
# 'c'
```
#### 変換関数の使用
変換関数を使う場合は `System.Func` を介します。
`lambda` 式も使えます。
```Python:IronPython
from System import Func             
(-9, 5, 2, -4).Max(Func[int, int](abs))
# 9
(-9, 5, 2, -4).Max[int](Func[int, float](lambda i: 1 / i))    # Max[int] の明示が必要
# 0.5
```
.NET6 で追加された `MinBy` や `MaxBy` メソッドは `System.Func` 不要です。
```Python:IronPython
(-9, 5, 2, -4).MaxBy(lambda i: abs(i))
# -9
(-9, 5, 2, -4).MaxBy(lambda i: 1 / i)
# 2
```
### `Sum` メソッド と `Average` メソッド
次の２点に注意します。
+ `AsEnumerable[T]()` や `Cast[T]()` を通す
+ 整数は `int` ではなく `System.Int32` など、.NETネイティブな整数型にする
```Python:IronPython
# 失敗例
(-9, 5, 2, -4).Sum()
# TypeError: Sum() takes exactly 1 argument (0 given)
(-9, 5, 2, -4).AsEnumerable[int]().Sum()
# TypeError: Sum() takes exactly 1 argument (0 given)

# 成功例
from System import Int32
(-9, 5, 2, -4).AsEnumerable[Int32]().Sum()  # -6 AsEnumerable または
(-9, 5, 2, -4).Cast[Int32]().Sum()          # -6 Cast を使う
```
#### 変換関数の使用
ここでも `System.Func` を介します。
`Func` を使うときは `AsEnumerable` や `Cast` は省略できます。

２乗和と２乗平均の例です。
```Python:IronPython
(-9, 5, 2, -4).Sum(Func[int, Int32](lambda i: i * i))
# 126
(-9, 5, 2, -4).Average(Func[int, Int32](lambda i: i * i))
# 31.5
```
その他応用例を示しておきます。
```Python:IronPython
# int と float の混在
[7.6, 2.5, -4, -1.1, 6].Average(Func[object, float](float))
# 2.2000000000000002

# 誤差を嫌う場合は Decimal で
from System import Decimal
[7.6, 2.5, -4, -1.1, 6].Average(Func[object, Decimal](Decimal))
# 2.2

# OfType[T] の使用
[7.6, 2.5, -4, -1.1, 6].OfType[float]().Average()  # float だけの平均
# 3.0
[7.6, 2.5, -4, -1.1, 6].OfType[Int32]().Average()  # Int32 だけの平均
# 1.0

# None が存在する場合は System.Nullable を使う
from System import Int32, Nullable
[1, None, 2, None, 3].AsEnumerable[Nullable[Int32]]().Average()
# 2.0
```
### その他あれこれ
#### `IEnumerable` もアンパックできる
LINQ では多くのメソッドが `IEnumerable` を返しますが、こちらもアンパックできます。
```Python:IronPython
# dict を SelectMany に渡してリストに展開
cities = {'新潟県': ('新潟市', '長岡市', '上越市'), '宮城県': ('仙台市', '石巻市')}
[*cities.SelectMany(lambda pref: cities[pref], lambda pref, city: f'{pref} {city}')]
# ['宮城県 仙台市', '宮城県 石巻市', '新潟県 新潟市', '新潟県 長岡市', '新潟県 上越市']
```
#### メソッドチェーン
見やすくできます。
```Python:IronPython
# 上の例を print で出力
(cities
 .SelectMany(lambda pref: cities[pref],
             lambda pref, city: f'{pref} {city}')
 .ToList()
 .ForEach(print))
# 宮城県 仙台市
# 宮城県 石巻市
# 新潟県 新潟市
# 新潟県 長岡市
# 新潟県 上越市
```
#### `GroupBy` `OrderBy` の使用例
LINQ の `GroupBy` は `itertools.groupby` と異なり事前のソートは不要で、元データの順番を維持します。
```Python:IronPython
# 40個のランダムな整数を用意（最小0 最大99)
from random import randint
data = [randint(0, 99) for _ in range(40)]
# 値を10刻みでグループ分け
groups = data.GroupBy(lambda i: i // 10).OrderBy(lambda group: group.Key)

# データと結果を表示
from pprint import pprint
pprint(data, width=40, compact=True)
# [2, 76, 37, 8, 22, 66, 43, 18, 19, 4,
#  39, 54, 42, 61, 45, 78, 98, 44, 12, 46,
#  45, 0, 28, 40, 99, 56, 62, 12, 47, 25,
#  87, 46, 76, 0, 61, 7, 2, 53, 17, 72]

for group in groups:
    print(f'{group.Key * 10:2} -{group.Key * 10 + 9:3} : {[*group]}')
#  0 -  9 : [2, 8, 4, 0, 0, 7, 2]
# 10 - 19 : [18, 19, 12, 12, 17]
# 20 - 29 : [22, 28, 25]
# 30 - 39 : [37, 39]
# 40 - 49 : [43, 42, 45, 44, 46, 45, 40, 47, 46]
# 50 - 59 : [54, 56, 53]
# 60 - 69 : [66, 61, 62, 61]
# 70 - 79 : [76, 78, 76, 72]
# 80 - 89 : [87]
# 90 - 99 : [98, 99]
```
#### map, zip との組み合わせ
```Python:IronPython
# わかりにくいコレよりも...
[*zip(*[iter(range(15))] * 5)]
# [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14)]

# Chunk がわかりやすい
[*map(tuple, range(15).Chunk(5))]
# [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14)]

# zip(*) を使って転置行列を作成
[*zip(*range(15).Chunk(5))]
# [(0, 5, 10), (1, 6, 11), (2, 7, 12), (3, 8, 13), (4, 9, 14)]
```
## 参照渡しについて
.NET API で参照渡しを必要とする場合があります。

たとえば、`Int32.TryParse` メソッドの引数は「変換したい文字列」と「変換結果を受け取る変数の**参照**」です。

ただし、IronPython は `TryParse` などに<b>「暗黙的に結果を受け取る」</b>手段を用意しているので、通常は参照渡しを回避できます。
```python:IronPython
Int32.TryParse('123')   # TryParse は「変換の成否」と「変換結果」をタプルで返す
# (True, 123)
Int32.TryParse('12-3')  # 変換できない場合
# (False, 0)
```
余談ですが、F# でも同様に実装されています。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209885/641da9e8-41a4-2006-3388-43a906f0e235.png width=480px>

参考までに、参照渡しの手順も示します。
```python:IronPython
i_ref = clr.Reference[Int32]()  # 変換結果を受け取る変数（の参照）を作成
Int32.TryParse('123', i_ref)    # TryParse は成否を bool で返す
# True
i_ref.Value                     # Value プロパティで変換結果を得る
# 123

Int32.TryParse('12-3', i_ref)   # 変換できない文字列を渡す
# False
i_ref.Value                     # 変換に失敗すると 0
# 0
```
## ファイル入出力について
単純なファイル出力であれば、`with open('file', 'w') as f:` よりも `WriteAllLines` が簡潔で使いやすいでしょう。リストのまま渡すこともできます。
```Python:IronPython
from System.IO.File import ReadAllLines, WriteAllLines

# リストをファイルに書き込む
dec_hex = ['10進\t2進'] + [f'{i:2}\t{i.ToString("B4")}' for i in range(16)]
WriteAllLines('Dec-Hex.txt', dec_hex)
```
**【結果】**
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209885/9551b4d5-eb2e-89bf-d1a9-21046b2675d9.png width=250px>
ファイルからの入力例として、`ReadAllLines` を使ってファイルの先頭５行を表示します。
```Python:IronPython
# Array.ForEach には Action で渡す
from System import Array, Action

# ファイルの先頭５行を表示
Array.ForEach(ReadAllLines('Dec-Hex.txt')[:5], Action[str](print))
# 10進    2進
#  0      0000
#  1      0001
#  2      0010
#  3      0011
```

## 日付の処理について
::: note
記事の趣旨から逸脱して .NET API の紹介ぽくなりますが、備忘録としての観点で書いておきます。
:::
**和暦**の処理に絞ったメモです。こちらの記事を参考にさせていただきました。

https://atmarkit.itmedia.co.jp/ait/articles/0306/06/news004.html

### 和暦から西暦へ
こちらは非常に簡単です。
```Python:IronPython
# 和暦を西暦に変換する
from System import DateOnly
DateOnly.Parse('R6.1.1').ToLongDateString()          # 元号の略号(R)もO.K.
# '2024年1月1日'
DateOnly.Parse('令和6年1月1日').ToLongDateString()    # 漢字表記もO.K.
# '2024年1月1日'

DateOnly.Parse('S100.1.1').ToLongDateString()        # 来年は昭和100年
# '2025年1月1日'
```
### 西暦から和暦へ
カルチャを設定して
```Python:IronPython
from System.Globalization import CultureInfo, JapaneseCalendar
ci = CultureInfo('ja-JP')
ci.DateTimeFormat.Calendar = JapaneseCalendar()
```
カルチャ付き `ToString` で和暦を取得できます。
```Python:IronPython
DateOnly(2024, 1, 1).ToString('gy年M月d日', ci)
# '令和6年1月1日'
```
元年表記にも対応します。
```Python:IronPython
d = DateOnly(2019, 4, 30)
d.ToString('gy年M月d日', ci)               # 改元前日
# '平成31年4月30日'
d.AddDays(1).ToString('gy年M月d日', ci)    # 改元初日
# '令和元年5月1日'
```
### 補足
和暦を `Parse` する場合、カルチャを指定すると「元年」も受け付けます。
```Python:IronPython
DateOnly.Parse('令和1年5月1日').ToLongDateString()
# '2019年5月1日'

# カルチャを指定しない場合「元年」はエラー
DateOnly.Parse('令和元年5月1日').ToLongDateString()
# SystemError: String '令和元年5月1日' was not recognized as a valid DateOnly.

# カルチャを指定するとO.K.
DateOnly.Parse('令和元年5月1日', ci).ToLongDateString()
# '2019年5月1日'
```

## まとめ
+ スクリプトの先頭に最低限書いておくもの
```Python:IronPython
import System, clr
from System import Int32, String
```
+ コレクションを使うために入れておきたいもの
```Python:IronPython
from System import Array, Action, Func
from System.Collections.Generic import List
clr.AddReference('System.Core')
clr.ImportExtensions(System.Linq)
```
+ ファイル処理をする場合入れておきたいもの
```Python:IronPython
from System.IO.File import ReadAllLines, WriteAllLines
```
+ 可能な限り `int` は `Int32` にする

## 参考
本家 Python との違いもありますので、こちらもご確認ください。

https://github.com/IronLanguages/ironpython3/blob/master/Documentation/differences-from-c-python.md