多段階選抜 (Python _xxsubinterpreters)


|多段階選抜|解答日|シリーズ:yieldの練習/ジェネレータを入れ子に/整数平方根・立方根の実装|
|:--|:--|:--|
|問題|　|http://nabetani.sakura.ne.jp/hena/ord24eliseq/<br>https://qiita.com/Nabetani/items/1c83005a854d2c6cbb69|
|Ruby|2014/8/2(当日)|https://qiita.com/cielavenir/items/9f15e29b73ecf98968a5|
|C#/Python|2014/8/4|https://qiita.com/cielavenir/items/a1156e6a4f71ddbe5dcb|
| |　|ここから上はdrop_prev_square/drop_prev_cubicをまとめる前の答案|
|Go/C#/Ruby/Python|2014/8/5|https://qiita.com/cielavenir/items/2a685d3080862f2c2c47|
|PHP/JavaScript|2014/9/9|https://qiita.com/cielavenir/items/28d613ac3823afbf8407|
|VB|2014/9/10|https://qiita.com/cielavenir/items/cb7266abd30eadd71c04|
|D|2015/12/21|https://qiita.com/cielavenir/items/47c9e50ee60bef2847ec|
|Perl|2017/3/10|https://qiita.com/cielavenir/items/6dfbff749d833c0fd423|
|Lua|2017/3/13|https://qiita.com/cielavenir/items/c60fe7e8da73487ba062|
|C++20(TS)|2017/3/15|https://qiita.com/cielavenir/items/e1129ca185008f49cbab (MSVC)<br>https://qiita.com/cielavenir/items/1cfa90d73d11bb7dc3d4 (clang)|
|F#|2017/3/17|https://qiita.com/cielavenir/items/a698d6a26824ff53de81|
|Boo/Nemerle|2017/5/13|https://qiita.com/cielavenir/items/e2a783f0fe4b0fe0ed48|
|Perl6|2017/5/15|https://qiita.com/cielavenir/items/656ea17fa96c865c4498|
|Kotlin|2017/5/25|https://qiita.com/cielavenir/items/9c46ce8d9d12e51de285|
|Crystal|2018/5/8|https://qiita.com/cielavenir/items/1815bfa6a860fd1f90db|
|MoonScript|2018/6/16|https://qiita.com/cielavenir/items/8b03cce0386f4537b5ad|
|Julia/Rust|2018/12/20|https://qiita.com/cielavenir/items/3ddf72b06d625da0c4a5|
|Nim|2018/12/26|https://qiita.com/cielavenir/items/5728944867e609fd52a7|
|Tcl|2018/12/31|https://qiita.com/cielavenir/items/76cbd9c2022b48c9a2c9|
|Pascal/Cobra|2019/1/16|https://qiita.com/cielavenir/items/81b81baf8dfc1f877903|
|Icon|2019/1/17|https://qiita.com/cielavenir/items/889622dcc721f5a4da24|
|Swift|2020/5/31|https://qiita.com/cielavenir/items/3b0b84a218e35d538f7f|
|Java/Groovy/Scala|2020/5/31|https://qiita.com/cielavenir/items/7f058203a8fd03b65870|
|V|2020/10/17|https://qiita.com/cielavenir/items/df30a6c101a97a713df5|
|Zig/Zen|2020/10/17|https://qiita.com/cielavenir/items/9cced9e4a94dcd70df0f|
|Pike|2020/11/2|https://qiita.com/cielavenir/items/3a8248f41611302b34fd|
|Vala/Smalltalk|2020/11/29|https://qiita.com/cielavenir/items/085dabe593cd916af5e8|
|Objective-C|2020/11/30|https://qiita.com/cielavenir/items/a1736e38789a3dd5cc5a|
|Ruby(Ractor)|2021/1/2|https://qiita.com/cielavenir/items/f493c6d512b63cc571cc|
|Python(_xxsubinterpreters)|2021/6/29|https://qiita.com/cielavenir/items/f1f581a055db918954f1|
|Falcon/Scheme|2021/9/5|https://qiita.com/cielavenir/items/c13d12cf44f0d17f4a94|
|Clojure/Lisp|2021/9/7|https://qiita.com/cielavenir/items/7458ba076f4e5bc0f196|
|(icbrtの実装に関する)補題|2017/5/11|整数除算であってもn/(x*y)はn/x/yに等しい(ことの証明)<br>https://qiita.com/cielavenir/items/21a6711afd6be8c18c55|

RubyのRactorに相当するものがPythonの_xxsubinterpretersに存在することを知ったので試してみた。

- [test.support.interpreters](https://github.com/python/cpython/blob/main/Lib/test/support/interpreters.py) はうまく動かなかった([テストコード](https://github.com/python/cpython/blob/main/Lib/test/test_interpreters.py)もchannelを使ったものは動かないと書かれている)。
- --with-experimental-isolated-subinterpreters版は **無限リストを扱えるが高頻度でSEGVする** ので諦めた。なのでこの答案は無限リストを扱うことができない(将来直ることを期待して扱える「はず」としておく)。
- 関数をrun_stringに渡すのはどうやればいいんだろうか。自分自身をimportするのは流石に苦肉の策であろう(※Ruby RactorはMethodをmake_sharableすれば良い)。

通しのテストはPython3.9 on MacBookProにて実施(yhpg 全ケース通過)。

```python3
#!/usr/bin/env python3
#coding:utf-8

# _xxsubinterpreters https://vstinner.github.io/isolate-subinterpreters.html

#http://qiita.com/Nabetani/items/1c83005a854d2c6cbb69
#http://nabetani.sakura.ne.jp/hena/ord24eliseq/
import sys
import itertools
from functools import partial,reduce

### test.support.interpreters causes segmentation fault, so I have to use my own high-level function. ###
import _xxsubinterpreters
from time import sleep
from os.path import basename
from os.path import dirname
from os.path import realpath
from threading import Thread
def recv(id, _sentinel=object(), _delay=10.0 / 1000):  # 10 milliseconds
	obj = _xxsubinterpreters.channel_recv(id, _sentinel)
	while obj is _sentinel:
		sleep(_delay)
		obj = _xxsubinterpreters.channel_recv(id, _sentinel)
	return obj
def send_nowait(id, obj):
	_xxsubinterpreters.channel_send(id, obj)
channel_create = _xxsubinterpreters.channel_create
channel_close = _xxsubinterpreters.channel_close
create = _xxsubinterpreters.create
run_string = _xxsubinterpreters.run_string
interp_list = []
def myrun(fname,scid,rcid):
	interp = create(isolated=True)
	interp_list.append(interp)
	caller_module = basename(__file__).split('.')[0]  # todo use frame
	t = Thread(target=run_string,args=(interp,f"""
import sys
sys.dont_write_bytecode = True
sys.path.append('{dirname(realpath(__file__))}')
from {caller_module} import *
{fname}(scid,rcid)
""",dict(scid=scid,rcid=rcid)))
	t.start()
###

def isqrt(n):
	if n<=0: return 0
	if n<4: return 1
	x,y=0,n
	while x!=y and x+1!=y:
		x,y=y,(n//y+y)//2
	return x
def icbrt(n):
	if n<0: return -icbrt(-n)
	if n==0: return 0
	if n<8: return 1
	x,y=0,n
	while x!=y and x+1!=y:
		x,y=y,(n//y//y+y*2)//3
	return x

if sys.version_info[0]>=3: raw_input=input

def generate(scid,rcid):
	try:
		i=1
		while i<10000:
			# --with-experimental-isolated-subinterpretersを使えばTrueで良いはずだが、高頻度でSEGVとなる。
			send_nowait(scid,i)
			i+=1
	except _xxsubinterpreters.ChannelClosedError:
		pass

def drop_prev(check,scid,rcid):
	try:
		a=recv(rcid)
		b=recv(rcid)
		while True:
			if not check(b): send_nowait(scid,a)
			a,b=b,recv(rcid)
	except _xxsubinterpreters.ChannelClosedError:
		pass

def drop_next(check,scid,rcid):
	try:
		a=recv(rcid)
		b=recv(rcid)
		send_nowait(scid,a)
		while True:
			if not check(a): send_nowait(scid,b)
			a,b=b,recv(rcid)
	except _xxsubinterpreters.ChannelClosedError:
		pass

def drop_n(check,n,scid,rcid):
	try:
		i=0
		while True:
			i+=1
			a=recv(rcid)
			if not check(i,n): send_nowait(scid,a)
	except _xxsubinterpreters.ChannelClosedError:
		pass

is_sq=lambda n: isqrt(n)**2==n
is_cb=lambda n: icbrt(n)**3==n
is_multiple=lambda i,n: i%n==0
is_le=lambda i,n: i<=n

impl_S=partial(drop_next,is_sq)
impl_s=partial(drop_prev,is_sq)
impl_C=partial(drop_next,is_cb)
impl_c=partial(drop_prev,is_cb)
impl_h=partial(drop_n,is_le,100)
impl_2=partial(drop_n,is_multiple,2)
impl_3=partial(drop_n,is_multiple,3)
impl_4=partial(drop_n,is_multiple,4)
impl_5=partial(drop_n,is_multiple,5)
impl_6=partial(drop_n,is_multiple,6)
impl_7=partial(drop_n,is_multiple,7)
impl_8=partial(drop_n,is_multiple,8)
impl_9=partial(drop_n,is_multiple,9)

if __name__=='__main__':
	try:
		while True:
			first = True
			line = raw_input().rstrip()
			cids = []
			for e in line:
				if first:
					rcid = channel_create()
					cids.append(rcid)
					myrun('generate',scid=rcid,rcid=None)
					first = False
				rcid2 = channel_create()
				cids.append(rcid2)
				myrun('impl_'+e,scid=rcid2,rcid=rcid)
				rcid = rcid2
			print(','.join(str(recv(rcid)) for i in range(10)))
			sys.stdout.flush()
			for cid in cids:
				channel_close(cid, force=True)
	except EOFError:
		pass

```

## まとめ

社で、使ってみたい技術があったら教えてほしいと言われたので、RubyだったらRactorを使ってみたいがとか言ってみたのですが、Pythonのsubinterpreterは **実用性がない** ので無理でしたね…。