はじめに
先日「IronPython との付き合いかた」を投稿しましたが、辞書や pip の扱いなど、割愛した内容を続編としてまとめました。
引き続き、個人の備忘メモとご理解ください。
以降、上記を「本編」と表記します。
確認環境
- Windows 11 Pro(Ver. 23H2)
- .NET Runtime 8.0.2 (64-bit)
- IronPython 3.4.1 (3.4.1.1000)
今回は、加えて Chromebook ASUS CX1500CKA-EJ0015(Celeron N4500、メモリ4GB) の Linux 開発環境(debian 12 + .NET Runtime 8.0.2)でも動作することを確認しています。
IronPython のバージョンについて
周回遅れのバージョン3.4 とはいえ、IronPython3 のリリースまでこぎつけた開発チームには心から敬意を表します。
バージョンにかかわらず一部の機能は先行導入されており、すでに本編でも利用しています。特に下記機能のおかげでコードが簡潔になります。
- PEP 448: Additional Unpacking Generalizations(アンパッキング演算子)--> 3.5 で追加
- PEP 498: Formatted string literals(f 文字列)--> 3.6 で追加
pip について
pip は使えますが、インストールできないパッケージも多く、残念ながら NumPy や Pandas はインストールできません。
公式にはわずかな情報が載っています。
上のリンクを見ると HTTP の活用に便利な requests ⧉ はインストールできそうです。
pip の準備
準備の前に
ipy.dll の場合 -m
オプションを使って pip を実行します。
本編のとおり IronPython を起動するためのコマンドラインが長すぎるので、バッチファイルやシェルスクリプトを書きます。
@dotnet --roll-forward LatestMajor "C:\Users\User\IronPython\3.4\net6.0\ipy.dll" %*
Linux の場合は、スクリプトファイルを作って(ファイル名を ipy3 としておきます)
#!/bin/sh
dotnet --roll-forward LatestMajor ~/ironpython/3.4/net6.0/ipy.dll $@
実行権限を与えます。
chmod +x ipy3
sudo mv ipy3 /usr/local/bin/
以降は ipy3.bat により例示します。
最初は ensurepip
IronPython 3.4 にも ensurepip が入っています。
最初だけ次の手順を踏んでください。
.\ipy3.bat -m ensurepip
.\ipy3.bat -m pip install --upgrade pip --user
スクショを載せておきます。
pip のバージョンは 19.1.1 です。
パッケージのインストール
準備が整ったら requests をインストールしてみます。
.\ipy3.bat -m pip install requests --user
投稿時における requests の最新バージョンは 2.31.0 ですが、バージョン 2.21.0 がインストールされます。
ダメもとでしたが、more-itertools ⧉ もインストールできました。
.\ipy3.bat -m pip install more-itertools --user
投稿時における more-itertools の最新バージョンは 10.2.0 ですが、インストールされたバージョンは 7.2.0 です。
これは Python 3.4 をサポートする最終バージョンです。
注意点(実際に経験したこと)
- パッケージの最新バージョンがインストールされるとは限らない
- インストールに成功しても、実行時エラーを吐く場合がある
- OS など、環境の違いによって成否が異なる場合もある
- 「インストールできたらラッキー」ぐらいの気構えがよい
辞書について
本編で割愛した dict
と Dictonary
, SortedDictonary
との相互変換を含め、辞書の扱いに関するメモです。
dict
から Dictonary
, SortedDictonary
に変換する
Dictionary[TKey,TValue]()
の引数として dict
を渡すとコントラクタとして働きます。
SortedDictonary
も同様です。
なお、SortedDictionary
を使用する場合は、本編で触れたように ⧉ アセンブリへの参照設定が必要です。
import System, clr
clr.AddReference('System.Collections') # アセンブリへの参照設定
from System.Collections.Generic import Dictionary, SortedDictionary
# とりあえず dict を用意
py_dict = {51: 'イチロー', 55: '松井', 17: '大谷'}
# コンストラクタに渡す
NET_Dict = Dictionary[int, str](py_dict)
NET_SortDict = SortedDictionary[int, str](py_dict)
確認します。
# 型の確認
type(NET_Dict)
# <class 'Dictionary[int, str]'>
type(NET_SortDict)
# <class 'SortedDictionary[int, str]'>
# 中身の表示
print(*NET_Dict)
# [51, イチロー] [17, 大谷] [55, 松井]
print(*NET_SortDict)
# [17, 大谷] [51, イチロー] [55, 松井]
Dictonary
や SortedDictonary
は KeyValuePair
(System.Collections.Generic.KeyValuePair) のコレクションなので、print
内で展開できます。
print
は KeyValuePair
を表示する際、リストと同じ様式を使います。
ToDictionary
の利用
LINQ の ToDictionary
も利用できます。
# LINQ の準備
clr.AddReference('System.Core')
clr.ImportExtensions(System.Linq)
# ToDictionary を使う
uniformNum = ['イチローの背番号は51', '松井の背番号は55', '大谷の背番号は17']
sep = 'の背番号は'
NET_DictLinq = uniformNum.ToDictionary(
lambda s: int(s.split(sep)[1]), lambda s: s.split(sep)[0]
)
type(NET_DictLinq)
# <class 'Dictionary[object, object]'>
print(*NET_DictLinq)
# [51, イチロー] [55, 松井] [17, 大谷]
余談ですが、引数なしのインスタンスメソッド dict.ToDictionary()
は失敗します。
用途はないと思いますが、静的メソッドなら成功しました。
# dict にも ToDictionary メソッドが使用できそうだが
'ToDictionary' in dir(py_dict)
# True
# 残念ながら失敗する
py_dict.ToDictionary()
# TypeError: ToDictionary() takes at least 1 argument (0 given)
# ところが静的メソッドは動作する
System.Linq.Enumerable.ToDictionary(py_dict)
# Dictionary[object, object]({51 : 'イチロー', 17 : '大谷', 55 : '松井'})
Dictonary
, SortedDictonary
の操作
dict
と Dictonary
, SortedDictonary
は別物ですので属性も異なります。
とはいえ、操作性は概ね直感どおりです。
dict
への変換
dict(NET_Dict)
# {51: 'イチロー', 17: '大谷', 55: '松井'}
dict(NET_SortDict)
# {51: 'イチロー', 17: '大谷', 55: '松井'} dict に変換すると順番は崩れる
辞書の追加・更新・削除
追加・更新は dict
と同じ操作です。
削除に del
文は使えないので Remove
メソッドで削除します。(削除の例示は省略)
# 追加
NET_Dict[11] = '野茂'
NET_SortDict[11] = '野茂'
print(*NET_Dict)
# [51, イチロー] [17, 大谷] [55, 松井] [11, 野茂]
print(*NET_SortDict)
# [11, 野茂] [17, 大谷] [51, イチロー] [55, 松井]
# 更新
NET_Dict[11] = 'ダルビッシュ'
NET_SortDict[11] = 'ダルビッシュ'
print(*NET_Dict)
# [51, イチロー] [17, 大谷] [55, 松井] [11, ダルビッシュ]
print(*NET_SortDict)
# [11, ダルビッシュ] [17, 大谷] [51, イチロー] [55, 松井]
要素の取り出し
例として、辞書の要素をタプルのリストにしてみます。
# dict に変換して items メソッドを使う方法
[*dict(NET_Dict).items()]
# [(51, 'イチロー'), (17, '大谷'), (11, 'ダルビッシュ'), (55, '松井')]
# Deconstruct メソッドを使う
[*map(lambda kvp: kvp.Deconstruct(), NET_Dict)]
# [(51, 'イチロー'), (17, '大谷'), (55, '松井'), (11, 'ダルビッシュ')]
# 内包表記のほうがスッキリ
[kvp.Deconstruct() for kvp in NET_SortDict]
# [(11, 'ダルビッシュ'), (17, '大谷'), (51, 'イチロー'), (55, '松井')]
ちなみに Deconstruct
メソッドは KeyValuePair[TKey, TValue]
のインスタンスメソッドですが、IronPython は返値をタプルにします。
[*map(lambda kvp: type(kvp.Deconstruct()), NET_Dict)]
# [<class 'tuple'>, <class 'tuple'>, <class 'tuple'>, <class 'tuple'>]
活用事例
以下の事項を織り込んだ活用事例を考えてみました。
- 外部パッケージ(requests と more-itertools)の利用
- 辞書の利用
- LINQ におけるジェネレータの扱い
事例 ~ 2024年度の3連休以上を探す ~
4月からの新年度で3日以上の連休を拾いあげます。
ありきたりですが、次の手順で進めます。
- 国民の祝日.csv ⧉ を取得(GET リクエスト)して、来年度の範囲を抽出する
- 来年度の土日と、上記 1. で抽出した祝日とを統合する
- 3日以上連続する休日を拾う
手順 1 には requests を、手順 3 には more-itertools を利用します。
手順 1
結果は次の変数に入ります。
pubHolsAll
(1955~2025年の全祝日)
pubHols24
(2024/4/1~2025/3/31の祝日)
pubHolsAll
は Dictionary[object, object]
ですが、pubHols24
は KeyValuePair[object, object]
のイテレータになります。
# LINQ 使用の準備
import System, clr
clr.AddReference('System.Core')
clr.ImportExtensions(System.Linq)
# 手順 1
import requests
from System import DateOnly
url = 'https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv'
pubHolsAll = (
requests.get(url) # 1955年からの祝日データ(csv)
.content.decode('cp932') # 文字コードは Shift-JIS
.rstrip() # 末尾の改行文字を除去
.split('\r\n')[1:] # CRLFで分割, 見出しを除去
.Select(lambda hol: hol.split(',')) # 日にちと名称を分割
.ToDictionary(
lambda holData: DateOnly.Parse(holData[0]), # キーは DateOnly
lambda holData: holData[1] # 値は str (祝日等の名称)
)
)
# 2024年度を抽出
start24 = DateOnly(2024, 4, 1)
start25 = start24.AddYears(1)
pubHols24 = pubHolsAll.Where(lambda hol: start24 <= hol.Key < start25)
pubHolsAll
と pubHols24
の内容を確認します。
# pubHolsAll を確認する
print(len(pubHolsAll))
# 1032
print(pubHolsAll[DateOnly(1955, 5, 3)])
# 憲法記念日
print(pubHolsAll[DateOnly(1966, 10, 10)])
# 体育の日
print(pubHolsAll[DateOnly(2025, 10, 13)])
# スポーツの日
# pubHols24 を確認する
print(*pubHols24, sep='\n')
# [2024/04/29, 昭和の日]
# [2024/05/03, 憲法記念日]
# ・・・ 途中省略 ・・・
# [2025/02/24, 休日]
# [2025/03/20, 春分の日]
pubHolsAll
は日数(1032日)と抽出による確認ですが問題ないようです。
pubHols24
も想定どおりの結果です。
手順 2
24年度の土日(holidays24
)を SortedDictionary
として作成し、祝日(pubHols24
の要素)を上書きすることで統合します。
# 手順 2
clr.AddReference('System.Collections') # SortedDictionary は要参照設定
from System.Collections.Generic import SortedDictionary
# 24年度の土日を作成
holidays24 = SortedDictionary[DateOnly, str](
range(start24.DayNumber, start25.DayNumber) # 24/4/1 から 25/3/31 の DayNumber
.Select(lambda dNum: DateOnly.FromDayNumber(dNum)) # DayNumber から DateOnly に変換
.Where(lambda d: not (0 < int(d.DayOfWeek) < 6)) # 土日を抽出 (not 平日)
.ToDictionary(lambda d: d, lambda d: '') # キーは DateOnly , 値は str ('')
)
# 祝日等を上書き追加
for hol in pubHols24:
holidays24[hol.Key] = hol.Value
Python と .NET の曜日の扱い
Python の datetime.date.weekday()
メソッドが返す値は、月曜日が 0 です。
.NET の System.DateOnly.DayOfWeek
プロパティの値(System.DayOfWeek
列挙)は、日曜日が 0 です。
holidays24
を出力して、土日と祝日が統合されているか確認します。
print(len(holidays24))
# 118
print(*holidays24, sep='\n')
# [2024/04/06, ]
# [2024/04/07, ]
# ・・・ 途中省略 ・・・
# [2024/04/28, ]
# [2024/04/29, 昭和の日]
# [2024/05/03, 憲法記念日]
# ・・・ 途中省略 ・・・
# [2025/03/20, 春分の日]
# [2025/03/22, ]
# [2025/03/23, ]
# [2025/03/29, ]
# [2025/03/30, ]
24年度の休日数は118日で、祝日も上書きされています。
手順 3
more_itertools.consecutive_groups
⧉ は、値が連続する要素ごとにグループ化するジェネレータです。ここに holidays24
を渡して、3日以上の連休を探します。
先にコードを示し、次の項で簡単に解説します。
# 手順 3
from more_itertools import consecutive_groups
# 連続する日でグループ化
moreHols = list(
consecutive_groups(holidays24, lambda hol: hol.Key.DayNumber)
.Cast[map]()
.Select(
lambda hols: [
f'{hol.Key.ToString("M/d(ddd)")} {hol.Value}'.strip() for hol in hols
]
)
.Where(lambda holsList: len(holsList) > 2)
)
3日以上の連休(moreHols
)になっているか出力してみます。
from pprint import pprint
pprint(moreHols)
# [['4/27(土)', '4/28(日)', '4/29(月) 昭和の日'],
# ['5/3(金) 憲法記念日', '5/4(土) みどりの日', '5/5(日) こどもの日', '5/6(月) 休日'],
# ['7/13(土)', '7/14(日)', '7/15(月) 海の日'],
# ['8/10(土)', '8/11(日) 山の日', '8/12(月) 休日'],
# ['9/14(土)', '9/15(日)', '9/16(月) 敬老の日'],
# ['9/21(土)', '9/22(日) 秋分の日', '9/23(月) 休日'],
# ['10/12(土)', '10/13(日)', '10/14(月) スポーツの日'],
# ['11/2(土)', '11/3(日) 文化の日', '11/4(月) 休日'],
# ['1/11(土)', '1/12(日)', '1/13(月) 成人の日'],
# ['2/22(土)', '2/23(日) 天皇誕生日', '2/24(月) 休日']]
目的の結果を得ることができました。
LINQ におけるジェネレータの扱いについて
前項「手順 3」の解説を兼ねて consecutive_groups
の返値を next
でたどってみます。
cg = consecutive_groups(holidays24, lambda hol: hol.Key.DayNumber)
type(cg)
# <class 'generator'> consecutive_groups はジェネレータ
type(next(cg))
# <class 'map'> 呼ぶたびにグループを map で返す
type(next(next(cg)))
# <class 'KeyValuePair[DateOnly, str]'> map は KeyValuePair を返す
consecutive_groups
がジェネレータであり、返値の構造も確認できました。
上記の map
を展開して値を出力してみます。
すでに next(cg)
を2回使っているので3番めのグループから表示されます。
print(*next(cg))
# [2024/04/20, ] [2024/04/21, ]
print(*next(cg))
# [2024/04/27, ] [2024/04/28, ] [2024/04/29, 昭和の日]
ジェネレータに LINQ は使えるか?
ここからが本題です。
手っ取り早く dir(cg)
で確認します。
pprint(dir(cg), width=80, compact=True)
# ['AsParallel', 'AsQueryable', 'Cast', 'Equals', 'GetHashCode', 'GetType',
# 'MemberwiseClone', 'OfType', 'ReferenceEquals', 'ToString', '__class__',
# '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
# '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__name__',
# '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__',
# '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code',
# 'gi_frame', 'gi_running', 'send', 'throw']
Cast
や OfType
が見つかりました。
dir(cg.Cast[map]())
してみます。
pprint(dir(cg.Cast[map]()), width=80, compact=True)
# ['Aggregate', 'All', 'Any', 'Append', 'AsEnumerable', 'AsParallel',
# 'AsQueryable', 'Average', 'Cast', 'Chunk', 'Concat', 'Contains', 'Count',
# 'DefaultIfEmpty', 'Dispose', 'Distinct', 'DistinctBy', 'ElementAt',
# 'ElementAtOrDefault', 'Equals', 'Except', 'ExceptBy', 'First',
# ・・・ 以下省略 ・・・
Cast[map]
(Cast[object]
も可)などを介することで、ジェネレータに対しても間接的に LINQ の拡張メソッドがサポートされます。
前項「手順 3」では、Select
メソッドで KeyValuePair
を文字列に整形し、Where
メソッドで要素数が3以上のものを拾っています。
おわりに
個人的な備忘メモとはいえ、どなたかのお役に立てば幸いです。