instaviz

Table of Contents

iota and range

前前回だったか、pythonでファンクション プログラミングをやった時、 schemeのiotaって関数を使った。それに対抗してpythonではrangeを利用した。 両者微妙に目指す所が違う。

どんな風に実現されてるか、簡潔なhaskellで確認してみる。ソースも簡単に 参照したいので、25年前のシステムである、hugs98を使う。猫も杓子もghcじゃ、 大袈裟すぎるからね。こやつ、debianならサポートされてるよ。

take :: Int -> [a] -> [a]
Hugs.Prelude> take 5 [0, 997 ..]             -- like iota
[0,997,1994,2991,3988]
Hugs.Prelude> :t takeWhile
takeWhile :: (a -> Bool) -> [a] -> [a]
Hugs.Prelude> takeWhile (< 4000) [0, 997 ..] -- like range
[0,997,1994,2991,3988]

そして、その実装。

take                :: Int -> [a] -> [a]
take n _  | n <= 0  = []
take _ []           = []
take n (x:xs)       = x : take (n-1) xs

takeWhile1 :: (a -> Bool) -> [a] -> [a]
takeWhile1 p (x:xs) = x : if p x then takeWhile1 p xs else []

入力に相当する、(x:xs) の所で、既に、car,cdrに分解してある所が、非常な 狡獪さを感じる。後、右辺に出てくる、 ':' は、consに相当する。実に簡潔 だ。

同じことをpythonでやろうとすると、 Python のジェネレータ (4) - 無限リスト とか、 Pythonのitertools.count, cycle, repeatによる無限イテレータ のお世話になるのかな。

import itertools as it

def take(n, iterable):
    return list(it.islice(iterable, n))

def takeWhile(pred, iterable):
    return list(it.takewhile(pred, iterable))

take(5, it.count(0, 997))
takeWhile(lambda x: x < 4000, it.count(0, 997))

一応実行結果をば

Python 3.11.2 (main, Feb 18 2023, 14:41:59) [GCC 12.2.1 20230201] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
[0, 997, 1994, 2991, 3988]
>>>
[0, 997, 1994, 2991, 3988]

gdb for python

前回の最後に、 Your Guide to the CPython Source Code なんてのに眼をつけておいた。じっ と読んでいくと、こんな風に、インストールせずに使った例がでてた。しかも、 shard-libにもしていないし。楽な方法をとっていた。

$ ./configure --with-pydebug
$ make -j8 -s
$ gdb ./python
To enable execution of this file add
        add-auto-load-safe-path /home/sakae/Python-3.11.2/python-gdb.py
(gdb) r
Starting program: /home/sakae/Python-3.11.2/python
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Python 3.11.2 (main, Apr 14 2023, 05:49:22) [GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Program received signal SIGINT, Interrupt.
0xb7fd2559 in __kernel_vsyscall ()
(gdb) ;; Ctl-x a
(gdb) f 10

これでも、ちゃんとlibpythonの機能が利用できた。前回の苦労は何だったん だよう。おまけに、gdbのTUIモードも出てきてたし。。

gdbのTUI-modeで少し楽々コード解析

25 GDB Text User Interface

思わず調べちゃったぞ。

instaviz

そして、ダラダラ先に進むと、コンパイルした結果の木をWebから観測できる 方法が、紹介されてた。これは、やってみる鹿。

$ pip install instaviz
Requirement already satisfied: install in ./.local/lib/python3.9/site-packages (1.3.5)
Collecting instaviz
  Downloading instaviz-0.6.0-py3-none-any.whl (462 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 462.4/462.4 kB 2.2 MB/s eta 0:00:00
Requirement already satisfied: pygments in ./.local/lib/python3.9/site-packages (from instaviz) (2.15.0)
Collecting dill
  Downloading dill-0.3.6-py3-none-any.whl (110 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 110.5/110.5 kB 1.3 MB/s eta 0:00:00
Collecting jinja2
  Using cached Jinja2-3.1.2-py3-none-any.whl (133 kB)
Collecting bottle
  Downloading bottle-0.12.25-py3-none-any.whl (90 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 90.2/90.2 kB 1.3 MB/s eta 0:00:00
Collecting MarkupSafe>=2.0
  Downloading MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (25 kB)
Installing collected packages: bottle, MarkupSafe, dill, jinja2, instaviz
Successfully installed MarkupSafe-2.1.2 bottle-0.12.25 dill-0.3.6 instaviz-0.6.0 jinja2-3.1.2

パイプのコードを木にしてみる。

>>> def pipe(data, *funcs):
        for f in funcs:
            data = f(data)
        return data

>>> import instaviz
>>> instaviz.show(pipe)
Bottle v0.12.25 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.

んが、ローカルホストにアクセスしなさいって言われても、諸般の事情で困る のよ。こういう時は、Windowsに入れてるmingwじゃと思ったら、どうも役不足 というか、パッケージのエラーになった。深追いはヤメ。

ローカルホストでWebサーバーじゃなくて、与えられたIPでサーバーを起動し たい。変更可能か調査。 /site-packages/instaviz/web.py

def start(host="localhost", port=8080):
    """
    Run the web server
    """
    # set TEMPLATE_PATH to use an absolute path pointing to our directory
    abs_app_dir_path = os.path.dirname(os.path.realpath(__file__))
    abs_views_path = os.path.join(abs_app_dir_path, "templates")
    TEMPLATE_PATH.insert(0, abs_views_path)
    run(host=host, port=port)
    print(f"Running web-server on http://{host}:{port}/")

もう、ここを変更するんですよって書いてあったぞ。何の苦労もなく、変更終 了。

そして、肝はこちら。

bottle.py/run

Bottle is a fast and simple micro-framework for small web applications. It
offers request dispatching (Routes) with url parameter support, templates,
a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
template engines - all in a single file and with no dependencies other than the
Python Standard Library.

Homepage and documentation: https://bottlepy.org/

ちょいと使うには便利っぽい。

PythonフレームワークのBottle

Bottle(pythonフレームワーク)のチュートリアル日本語訳

127.0.0.1 - - [21/Apr/2023 06:46:37] "GET / HTTP/1.1" 200 26391
127.0.0.1 - - [21/Apr/2023 06:46:38] "GET /static/bootstrap.min.css HTTP/1.1" 200 155758
127.0.0.1 - - [21/Apr/2023 06:46:38] "GET /static/vis.js HTTP/1.1" 200 1854177
127.0.0.1 - - [21/Apr/2023 06:46:38] "GET /static/vis-network.min.css HTTP/1.1" 200 14426
127.0.0.1 - - [21/Apr/2023 06:46:38] "GET /static/json2html.css HTTP/1.1" 200 752
127.0.0.1 - - [21/Apr/2023 06:46:38] "GET /static/jquery.min.js HTTP/1.1" 200 97163
127.0.0.1 - - [21/Apr/2023 06:46:38] "GET /static/jquery.json2html.js HTTP/1.1" 200 4626
127.0.0.1 - - [21/Apr/2023 06:46:38] "GET /static/json2html.js HTTP/1.1" 200 13600
127.0.0.1 - - [21/Apr/2023 06:46:38] "GET /static/node.js HTTP/1.1" 200 2528
127.0.0.1 - - [21/Apr/2023 06:46:39] "GET /favicon.ico HTTP/1.1" 404 742

ログを見てもhtmlなファイルを見ても、オイラーが大嫌いなjavascriptしか出 てこない。これはもう、尻尾を巻いて逃げましょ。

ast

上記の元となる技術は、こちら

たった1行から始めるPythonのAST(抽象構文木)入門

cat pipe.py
def pipe(data, *funcs):
        for f in funcs:
            data = f(data)
        return data

毎度お馴染みの奴を実験台に据えてみる。

python3 -c 'import ast; print(ast.dump(ast.parse(open("pipe.py").read()), indent=4))'
Module(
    body=[
        FunctionDef(
            name='pipe',
            args=arguments(
                posonlyargs=[],
                args=[
                    arg(arg='data')],
                vararg=arg(arg='funcs'),
                kwonlyargs=[],
                kw_defaults=[],
                defaults=[]),
            body=[
                For(
                    target=Name(id='f', ctx=Store()),
                    iter=Name(id='funcs', ctx=Load()),
                    body=[
                        Assign(
                            targets=[
                                Name(id='data', ctx=Store())],
                            value=Call(
                                func=Name(id='f', ctx=Load()),
                                args=[
                                    Name(id='data', ctx=Load())],
                                keywords=[]))],
                    orelse=[]),
                Return(
                    value=Name(id='data', ctx=Load()))],
            decorator_list=[])],
    type_ignores=[])

dis

そして機械語を確認。

>>> import dis
>>> dis.dis(pipe)
  2           0 LOAD_FAST                1 (funcs)
              2 GET_ITER
        >>    4 FOR_ITER                12 (to 18)
              6 STORE_FAST               2 (f)

  3           8 LOAD_FAST                2 (f)
             10 LOAD_FAST                0 (data)
             12 CALL_FUNCTION            1
             14 STORE_FAST               0 (data)
             16 JUMP_ABSOLUTE            4

  4     >>   18 LOAD_FAST                0 (data)
             20 RETURN_VALUE

これ以上深入りしても面白い事は無さそう。

haskell

突然、鞍替えしてみる。

関数プログラミング―質の高いコードをすばやく直感的に書ける! 記事一覧

再帰ドリル

ArchLinuxだと、stackから使うのが便利かな。

[sakae@arch tmp]$ stack repl

Configuring GHCi with the following packages:
GHCi, version 9.2.7: https://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /tmp/haskell-stack-ghci/2a3bbd58/ghci-script

ghci> takeWhile (< 20) [1,3 ..]
[1,3,5,7,9,11,13,15,17,19]
ghci> take 5 [1,3 ..]
[1,3,5,7,9]

ちょっとこない間に随分バージョンが進歩してるな。

etc