Jupyterのcell magicでcellのC/C++ソースをgcc/g++でコンパイルしよう3(自分専用のkernelを作ろう)

# 概要

https://qiita.com/h6591/items/cb0ce62d5aae692312e9

上記の投稿記事で作成したCustom Magicの機能を専用のkernelを作成して実現しようとする投稿記事です。なので、上記の記事の内容を知っているものとして書きます。さらに、ソースコードも流用します。
kernelを作ったとしても使い方は上記の記事とほぼ同じです。違いはmagicの表し方とmagicを指定していない時の動作です。
なら、何のためにわざわざkernelを作るかというと、Cell内でのコードのハイライトがC/C++に出来て入力時のインデントもC/C++になり、 `{}`のペアでインデントされます。
後は、kernel作成の勉強にもなりました。

# 実行環境

```terminal
> sw_vers
ProductName:		macOS
ProductVersion:		15.4.1
BuildVersion:		24E263

-- 以下 anaconda3-2024.10-1 ----

> python  --version
Python 3.12.7

> jupyter --version
Selected Jupyter core packages...
IPython          : 8.27.0
ipykernel        : 6.28.0
ipywidgets       : 7.8.1
jupyter_client   : 8.6.0
jupyter_core     : 5.7.2
jupyter_server   : 2.14.1
jupyterlab       : 4.2.5
nbclient         : 0.8.0
nbconvert        : 7.16.4
nbformat         : 5.10.4
notebook         : 7.2.2
qtconsole        : 5.5.1
traitlets        : 5.14.3
```

テストは`JupyterLab`で行います。

# Custom Kernelを作ろう

## サンプルKernel

https://ipython.readthedocs.io/en/stable/development/wrapperkernels.html#example

上記に記載されている`echokernel.py`をそのまま実装します。
```echokernel.py
from ipykernel.kernelbase import Kernel

class EchoKernel(Kernel):
    implementation = 'Echo'
    implementation_version = '1.0'
    language = 'no-op'
    language_version = '0.1'
    language_info = {'mimetype': 'text/plain'}
    banner = "Echo kernel - as useful as a parrot"

    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            stream_content = {'name': 'stdout', 'text': code}
            self.send_response(self.iopub_socket, 'stream', stream_content)

        return {'status': 'ok',
                # The base class increments the execution count
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=EchoKernel)
```
cellの内容は`do_execute`の`code`に渡されます。
ブラウザの出力するには、基本的に`stream_content`の内容を作って、`send_response`するだけのようです。

#### kernel.json

kernelの起動等に必要な情報である`kernel.json`を以下のように作成します。

```kernel.json
{
    "argv":["python","-m","echokernel", "-f", "{connection_file}"],
    "display_name":"Echo",
    "env": {
        "PYTHONPATH": "/Users/xxxx/qiita/kernel"
    }
}
```
- `echokernel`は`.py`無しで指定
- `display_name`が**Jupyter notebook**に**kernel名**として表示
- `PYTHONPATH`は`echokernel.py`がある場所にパスが通っていないときに必要
    - パスが通っていれば不要


`echo`ディレクトリ(名前は何でも良い)を作って`kernel.json`をその下に移します。
```terminal
echo/
└── kernel.json
```

kernelをインストールします。

```terminal
> jupyter kernelspec install echo --user
[InstallKernelSpec] Installed kernelspec echo in /Users/xxxx/Library/Jupyter/kernels/echo
```
`install`の後はディレクトリ名(`echo`)を指定します。

`--user`を付けた時の`kernel`のインストール先は次のディレクトリです。
- Linux
    - ~/.local/share/jupyter/kernels
- Max
    -  ~/Library/Jupyter/kernels
- Windows
    - %APPDATA%\jupyter\kernels


Pythonやその他のpackageの組み合わせによっては以下のメッセージが出ますが動きます。
私が今現在使っている環境ではこれらのメッセージは出ません。

```terminal
/Users/xxxx/qiita/kernel/echokernel.py:1: DeprecationWarning: Parsing dates
involving a day of month without a year specified is ambiguious
and fails to parse leap day. The default behavior will change in Python 3.15
to either always raise an exception or to use a different default year (TBD).
To avoid trouble, add a specific year to the input & format.
See https://github.com/python/cpython/issues/70647.
  from ipykernel.kernelbase import Kernel
~~省略~~
[IPKernelApp] WARNING | Unknown message type: 'comm_open'
[IPKernelApp] WARNING | Unknown message type: 'comm_msg'
```

テストをします。 kernel名が右上にちゃんと表示されてます。

![echokernel01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/c2d980af-5d37-4899-a87d-d2fbced0da70.png)

:::note warn
Costuom KernelなのでPythonのkernelで使えた **%lsmagic**、**%%script**、**Custom magic**などのmagicコマンドは一切使えません
:::

`Jupyter console`ではプログラムの`banner`で設定した内容がトップに表示されています。しかし、`Jupyter console`は使うことはないと思います。
![echokernel02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/3d1480c7-2b7d-4a91-8918-74a613df5e2e.png)



## gcc Kernel

### 簡単なgcc Kernel

上記のサンプルを元に`gcc`でコンパイル＆実行だけの簡単なkernelをつくります。前回の投稿記事の`gcc`でコンパイル＆実行部分のコードは`Custom magic`とほぼ同じコードになります。
後は`stderr`がサンプルに無いコードです。


```gcckernel.py
import os
import sys
import subprocess
from ipykernel.kernelbase import Kernel

OBJFILE = "./a.out"
COMPILE_EXEC_CMD = "gcc -xc - -o {0} && {0}".format(OBJFILE)

class GccKernel(Kernel):
    implementation = 'gcc'
    implementation_version = '1.0'
    language_info = {
        'name': 'c',
        'mimetype': 'text/x-csrc',
        'file_extension': '.c',
        'version': '17.0.0',
    }
    banner = "Compile by gcc and execute　binary"
 
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            rc = self.compile_and_exec(code)
            stream_content = {'name': 'stdout', 'text': rc.stdout}
            self.send_response(self.iopub_socket, 'stream', stream_content)
            if rc.stderr != "":
                stream_content = {'name': 'stderr', 'text': rc.stderr}
                self.send_response(self.iopub_socket, 'stream', stream_content)
                
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

    def compile_and_exec(self, src):
        rc = subprocess.run(COMPILE_EXEC_CMD, text=True, 
                         capture_output=True, shell=True, input=src)
        if os.path.isfile(OBJFILE):
            os.remove(OBJFILE)
        return rc 

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=GccKernel)
```
`implementation`から`banner`については以下のサイトを参照してください。

https://jupyter-client.readthedocs.io/en/latest/messaging.html#msging-kernel-info

また、`language_info`の`name`、`mimetype`、`file_extension`については以下のサイトを参照してください。これが一致していない(特に`name`)とハイライトなどがC言語になりません。他のいろいろプログラミング言語についても載っています。

https://pygments.org/docs/lexers/#pygments.lexers.c_cpp.CLexer

#### kernel.json
`gcc`用の`kernel.json`を作ります。
`language`は`c`にします。`C++`のときは`C++`にしてください。もしかしたら、無くても良いかもしれません。これよりはプログラムの`language_info`の`name`が大事です。

```kernel.json
{
    "argv":["python","-m","gcckernel", "-f", "{connection_file}"],
    "display_name":"gcc",
    "language": "c",
    "env": {
        "PYTHONPATH": "/Users/xxxx/qiita/kernel"
    }
}
```


`gcc`ディレクトリを作って`kernel.json`をその下に移します。
```terminal
gcc/
└── kernel.json
```

kernelをインストールします。

```terminal
jupyter kernelspec install gcc --user
[InstallKernelSpec] Installed kernelspec gcc in /Users/xxxx/Library/Jupyter/kernels/gcc
```

テストです。
コンパイルエラーと実行結果がちゃんと表示されています。また、ハイライトがC言語になっています。cell magicでは`#include`がpythonのコメントに該当するのでイタリック体になっていましたが、ちゃんと普通に表示されています。

![gcckernel01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/c9ec5bb1-791b-4752-9bbd-36136202fd4a.png)

## Custom Magicと同等の機能をもったgcc Kernel
今度は`コンパイラをg++`にしますが、`gcc`とそんなに違いはありません。
また、Custom Magicで作成した`class GCCMagics`を一部変更して流用します。
magicの表現を`%%gcc`から`//gcc`と`%`を`/`に変更します。

ソースを前半と後半部分に分けて解説します。２つを一緒のソースにして使ってください。

```py:gcckernel.py 前半部分
import re
import sys
import subprocess
from ipykernel.kernelbase import Kernel

OBJFILE = "./a.out"
COMPILE_CMD = "g++ -xc++ - -std=c++17"
COMPILE_EXEC_CMD = "{0} -o {1} && {1}".format(COMPILE_CMD, OBJFILE)

ALL_SOURCE = """
#include <iostream>
{0}

int main(int argc, char *argv[]) {{

{1}
    
    return 0;
}}    
"""

def CompletedProcess(stdout='', stderr=''):
    return subprocess.CompletedProcess("", 0, stdout=stdout, stderr=stderr) 
    
class GCCMagics:
    main_source = {}  # main関数内のソース
    other_source = {} # main関数外のソース
    
    def gcc(self, line, cell):
        rc = self.compile_and_exec(cell)
        return rc

    def gmain(self, line, cell):
        src_key = line.strip()
        if src_key != "":
            self.main_source[src_key] = cell                
            rc = self.compile_and_exec(self.make_source())
        else:
            rc = self.compile_and_exec(ALL_SOURCE.format("", cell))           
        return rc
    
    def gfunc(self, line, cell):
        src_key = line.strip()
        if src_key == "":
            rc = CompletedProcess(stderr="lineパラメータがありません")
        else:
            self.other_source[src_key] = cell
            rc = self.compile_and_exec(self.make_source(), exec=False)
        return rc
    
    def glist(self, line):
        return CompletedProcess(stdout=self.make_source())

    def grun(self, line):
        return self.compile_and_exec(self.make_source())
        
    def make_source(self, src_key=None):
        other_src = ""
        main_src = ""
        for key in sorted(self.other_source):
            other_src += self.other_source[key]
                
        for key in sorted(self.main_source):
            main_src += self.main_source[key]

        src = ALL_SOURCE.format(other_src, main_src)
        return src
               
    def compile_and_exec(self, src, exec=True):
        if exec:
            rc = subprocess.run(COMPILE_EXEC_CMD, text=True, capture_output=True, shell=True, input=src)
        else:
            rc = subprocess.run(COMPILE_CMD, text=True, capture_output=True, shell=True, input=src)            
        if os.path.isfile(OBJFILE):
            os.remove(OBJFILE)
        return rc        
```
**上記のコードの解説**
- `def CompletedProcess`は`subprocess.run`が返す戻り値のクラスであるので、`subprocess.run`を使わない処理でも`stdout`や`stderr`を返すためと、簡略化のための関数
- `GCCMagics`クラスは`Custom Magic`で作成したクラスを流用したもので以下の主な変更点
    - `Magics`の継承をしない
    - `@cell_magic`などのデコレーションや`Docstring`を消す
    - `print`文をなくして`rc`などの`CompletedProcess`に`stdout`,`stderr`を設定して`retrun`で返すように変更

実はGCCMagicsを全く変更しなくても(デコレーションを消さない)使えますが、Custom Magicには依存していないと示したかったので消しました。そのまま使ったコードは[Appendix](#appendix)に記載します。
    

```py:gcckernel.py 後半部分 
# GCC用Custom Kernel
class GccKernel(Kernel):
    # cellデータの解析結果
    class MagicData:
        def __init__(self, magic="gcc", cell="", line="", iscell=True):
            self.magic = magic
            self.cell = cell
            self.line = line
            self.iscell = iscell
            
    implementation = 'gcc'
    implementation_version = '1.0'
    language_info = {
        'name': 'c++',
        'mimetype': 'text/x-c++src',
        'file_extension': '.cpp',
        'version': '17.0.0',
    }
    banner = "Compile by gcc and execute binary"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        magic_obj = GCCMagics()
        self.line_magics = {"glist": magic_obj.glist,
                            "grun": magic_obj.grun}
        self.cell_magics = {"gcc": magic_obj.gcc,
                            "gmain": magic_obj.gmain,
                            "gfunc": magic_obj.gfunc}    
        self.magic_obj = magic_obj
        
    def parse(self, code):
        mline = re.match("^.*$", code, re.MULTILINE)
        pos = mline.span()[1] + 1  # +1は改行コードを除くため
        cell = code[pos:]
        line = mline.group()
        magic_re = re.match(r" *(/{1,2})(\w+)", line)
        iscell = True            
        if magic_re == None:
            # 先頭行がmagicでないとき
            magic = "gcc"
            cell = code
            line = ''
        else:    
            line_pos = magic_re.span()[1] + 1
            line = line[line_pos:]
            magic_names = magic_re.groups()
            magic = magic_names[1]
            if magic_names[0] == "/":
                iscell = False
        return self.MagicData(magic, cell, line, iscell)
                
    def exec_magic(self, magic_data):
        if magic_data.iscell:
            if magic_data.magic in self.cell_magics:
                func = self.cell_magics[magic_data.magic]
                return func(magic_data.line, magic_data.cell)
            else:
                return CompletedProcess(stderr="該当するcell magicはありません")
        else:
            if magic_data.magic in self.line_magics:
                func = self.line_magics[magic_data.magic]
                return func(magic_data.line)
            else:
                return CompletedProcess(stderr="該当するline magicはありません")
        
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            src = code if code[-1] == "\n" else code + "\n"
            magic_data = self.parse(src)
            rc = self.exec_magic(magic_data)
            
            stream_content = {'name': 'stdout', 'text': rc.stdout}
            self.send_response(self.iopub_socket, 'stream', stream_content)
            if rc.stderr != "":
                stream_content = {'name': 'stderr', 'text': rc.stderr}
                self.send_response(self.iopub_socket, 'stream', stream_content)
                
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=GccKernel)
```
**上記コードの解説**

- `def __init__`はクラスGCCMagicsで定義されているline magic、cell magicの処理メソッドの辞書を作る

- `def parse`はcellのデータから１行目を**magic**として解析する
    - 解析した結果は以下の項目をMagicDataクラスで返す
        - magic名
        - cellデータ
        - lineデータ
        - line magicかcell magicのフラグ
    -  `re.match("^.*$", code, re.MULTILINE)`は１行目を取り出すために正規表現
    -  `re.match(r" *(/{1,2})(\w+)", line)`は`/`や`//`で始まるmagic名を取り出すための正規表現
        -  groups()で取り出すと例えば`'//gcc arg1 arg2'`は`('//', 'gcc')`のタプルになるので`groups()[1]`がmagic名になる
- `def exec_magic`は`def parse`解析したデータをもとに`GCCMagics`クラスの各magicメソッドを実行
    - 解析したmagicがline magicかcell magicによって対応する辞書から処理メソッドを取得し、処理を実行
    - 対応する処理メソッドが無いときはエラーメッセージを表示


#### kernel.json
`gcc`用でC++の`kernel.json`を作ります。

```kernel.json
{
    "argv":["python","-m","gcckernel", "-f", "{connection_file}"],
    "display_name":"C++",
    "language": "c++",
    "env": {
        "PYTHONPATH": "/Users/xxxx/qiita/kernel"
    }
}
```


`gcc`ディレクトリを作って`kernel.json`をその下に移します。
```terminal
gcc/
└── kernel.json
```

kernelをインストールします。

```terminal
jupyter kernelspec install gcc --user
[InstallKernelSpec] Installed kernelspec gcc in /Users/xxxx/Library/Jupyter/kernels/gcc
```

テストも下記の通り大丈夫なようです。

![gcckernel02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/7e048b71-cafb-4550-a64a-ff4a92745221.png)

![gcckernel03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/41cb88ce-99bf-420f-b401-3efdc48431c8.png)

![gcckernel04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/d89948aa-5912-496c-b80b-4d87517d5e65.png)




# 終わりに

全部でCell Magicの３部作となりましたが、Pythonはメイン言語ではないので時間が掛かったところもあります。しかし、知らないことを学びそれなりの成果がでると嬉しいものです。自己満足ですが。


# Appendix

自分ために以下の２つのコードを残します。

### GCCMagicsをそのまま使ったコード

- `print`は使えないので自分で再定義
- line magic、cell magicのメソッド名とメソッドオブジェクトの辞書は`GCCMagics`の`magics`変数に登録されているので、自分で辞書を作らなくてもOK
    - `@cell_magic`や`@line_magic`とデコレーションすることで、辞書が作成されるようです

Custom Magicとしては使っていないので、親クラスの`Magics`やデコレータが何も悪さしないと良いのですが、とにかく動きました。

<details><summary>ソースコード1</summary>

```gcckernel.py
import os
import re
import sys
import subprocess
from ipykernel.kernelbase import Kernel
from IPython.core.magic import (Magics, magics_class, line_magic,
                                cell_magic, line_cell_magic)


OBJFILE = "./a.out"
COMPILE_CMD = "g++ -xc++ - -std=c++17"
COMPILE_EXEC_CMD = "{0} -o {1} && {1}".format(COMPILE_CMD, OBJFILE)

ALL_SOURCE = """
#include <iostream>
{0}

int main(int argc, char *argv[]) {{

{1}
    
    return 0;
}}    
"""

def CompletedProcess(stdout='', stderr=''):
    return subprocess.CompletedProcess("", 0, stdout=stdout, stderr=stderr) 

GccKernel_obj = None

def print(str, file=None, end="\n"):
    if file == sys.stderr:
        stream_content = {'name': 'stderr', 'text': str+end}
    else:
        stream_content = {'name': 'stdout', 'text': str+end}
    GccKernel_obj.send_response(GccKernel_obj.iopub_socket, 
                                'stream', stream_content)
    
@magics_class
class GCCMagics(Magics):
    main_source = {}  # main関数内のソース
    other_source = {} # main関数外のソース
    
    @cell_magic
    def gcc(self, line, cell):
        """
        %%gcc コメント
        """      
        rc = self.compile_and_exec(cell)
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @cell_magic
    def gmain(self, line, cell):
        """
        %%gmain コメント
        """
        src_key = line.strip()
        if src_key != "":
            self.main_source[src_key] = cell                
            rc = self.compile_and_exec(self.make_source())
        else:
            rc = self.compile_and_exec(ALL_SOURCE.format("", cell))           
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @cell_magic
    def gfunc(self, line, cell):
        """
        %%gfunc コメント
        """
        src_key = line.strip()
        if src_key == "":
            print("lineパラメータがありません", file=sys.stderr)
            return
        else:
            self.other_source[src_key] = cell
        rc = self.compile_and_exec(self.make_source(), exec=False)
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @line_magic
    def glist(self, line):
        """
        %glist コメント
        """
        print(self.make_source(), end="")

    @line_magic
    def grun(self, line):
        """
        %grun コメント
        """
        rc = self.compile_and_exec(self.make_source())
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")
        
    def make_source(self, src_key=None):
        other_src = ""
        main_src = ""
        for key in sorted(self.other_source):
            other_src += self.other_source[key]
                
        for key in sorted(self.main_source):
            main_src += self.main_source[key]

        src = ALL_SOURCE.format(other_src, main_src)
        return src
               
    def compile_and_exec(self, src, exec=True):
        if exec:
            rc = subprocess.run(COMPILE_EXEC_CMD, text=True, capture_output=True, shell=True, input=src)
        else:
            rc = subprocess.run(COMPILE_CMD, text=True, capture_output=True, shell=True, input=src)            
        if os.path.isfile(OBJFILE):
            os.remove(OBJFILE)
        return rc        
    
# GCC用Custom Kernel
class GccKernel(Kernel):
    # cellデータの解析結果
    class MagicData:
        def __init__(self, magic="gcc", cell="", line="", iscell=True):
            self.magic = magic
            self.cell = cell
            self.line = line
            self.iscell = iscell
            
            
    implementation = 'gcc'
    implementation_version = '1.0'
    language_info = {
        'name': 'c++',
        'mimetype': 'text/x-c++src',
        'file_extension': '.cpp',
        'version': '17.0.0',
    }
    banner = "Compile by gcc and execute binary"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.magic_obj = GCCMagics()
        self.line_magics = self.magic_obj.magics["line"]
        self.cell_magics = self.magic_obj.magics["cell"]        
        global GccKernel_obj
        GccKernel_obj = self

    def parse(self, code):
        mline = re.match("^.*$", code, re.MULTILINE)
        pos = mline.span()[1] + 1  # +1は改行コードを除くため
        cell = code[pos:]
        line = mline.group()
        magic_re = re.match(r" *(/{1,2})(\w+)", line)
        iscell = True            
        if magic_re == None:
            # 先頭行がmagicでないとき
            magic = "gcc"
            cell = code
            line = ''
        else:    
            line_pos = magic_re.span()[1] + 1
            line = line[line_pos:]
            magic_names = magic_re.groups()
            magic = magic_names[1]
            if magic_names[0] == "/":
                iscell = False
        return self.MagicData(magic, cell, line, iscell)
                
    def exec_magic(self, magic_data):
        if magic_data.iscell:
            if magic_data.magic in self.cell_magics:
                func = self.cell_magics[magic_data.magic]
                return func(magic_data.line, magic_data.cell)
            else:
                return CompletedProcess(stderr="該当するcell magicはありません")
        else:
            if magic_data.magic in self.line_magics:
                func = self.line_magics[magic_data.magic]
                return func(magic_data.line)
            else:
                return CompletedProcess(stderr="該当するline magicはありません")
                
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            src = code if code[-1] == "\n" else code + "\n"
            magic_data = self.parse(src)
            rc = self.exec_magic(magic_data)
            if rc != None:
                stream_content = {'name': 'stdout', 'text': rc.stdout}
                self.send_response(self.iopub_socket, 'stream', stream_content)
                if rc.stderr != "":
                    stream_content = {'name': 'stderr', 'text': rc.stderr}
                    self.send_response(self.iopub_socket, 'stream', stream_content)    
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=GccKernel)
```

</details>

### GCCMagicsをそのまま使うが、親クラスやデコレータを自分で作ってみる

gccをでコンパイルする目的としてはこれを作るメリットがあまりないが、デコレーションの勉強にはなるので作ってみた。デコレーションについては使ったことはあるが、どう動作するかまでは調べなかったので今回調べてみて勉強になった。

ソースコード１からの変更点は`Python.core.magic`の**import**を削除して`##--------`で囲った箇所に自作したデコレータのコードを追加した。

**デコレータについて**
- クラスのインスタンスを生成しなくても関数のデコレータ、クラスのデコレータの順に呼び出される
- 関数デコレータに渡される`func`はクラスメソッドでオブジェクトメソッドではないのでオブジェクトメソッドとしては使えないのでメソッド名だけ取って、オブジェクトが生成されたときにオブジェクトメソッドとして取得した

その他として、**getattr**、**callable**を始めて知った。

<details><summary>ソースコード2</summary>

```gcckernel.py
import os
import re
import sys
import subprocess
from ipykernel.kernelbase import Kernel


OBJFILE = "./a.out"
COMPILE_CMD = "g++ -xc++ - -std=c++17"
COMPILE_EXEC_CMD = "{0} -o {1} && {1}".format(COMPILE_CMD, OBJFILE)

ALL_SOURCE = """
#include <iostream>
{0}

int main(int argc, char *argv[]) {{

{1}
    
    return 0;
}}    
"""

##----------------------------------------------------------------------------------
g_line_magics = []
g_cell_magics = []

def cell_magic(func):
    g_cell_magics.append(func.__name__)
    def _wrapper(self,*args, **kwargs):
        return func(self,*args,**kwargs)
    return _wrapper
def line_magic(func):
    g_line_magics.append(func.__name__)
    def _wrapper(self,*args, **kwargs):
        return func(self,*args,**kwargs)
    return _wrapper

class Magics:
    pass

def magics_class(cls):
    class NewClass(cls):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            cell_magics = {}
            line_magics = {}
            for fname in g_cell_magics:
                func = getattr(self, fname, False)
                if callable(func):
                    cell_magics[fname] = func 
                
            for fname in g_line_magics:
                func = getattr(self, fname, False)
                if callable(func):
                    line_magics[fname] = func

            self.magics = {'cell': cell_magics, 'line': line_magics}
    return NewClass

##----------------------------------------------------------------------------------

def CompletedProcess(stdout='', stderr=''):
    return subprocess.CompletedProcess("", 0, stdout=stdout, stderr=stderr) 

GccKernel_obj = None

def print(str, file=None, end="\n"):
    if file == sys.stderr:
        stream_content = {'name': 'stderr', 'text': str+end}
    else:
        stream_content = {'name': 'stdout', 'text': str+end}
    GccKernel_obj.send_response(GccKernel_obj.iopub_socket, 
                                'stream', stream_content)

    
@magics_class
class GCCMagics(Magics):
    main_source = {}  # main関数内のソース
    other_source = {} # main関数外のソース
    
    @cell_magic
    def gcc(self, line, cell):
        """
        %%gcc コメント
        """      
        rc = self.compile_and_exec(cell)
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @cell_magic
    def gmain(self, line, cell):
        """
        %%gmain コメント
        """
        src_key = line.strip()
        if src_key != "":
            self.main_source[src_key] = cell                
            rc = self.compile_and_exec(self.make_source())
        else:
            rc = self.compile_and_exec(ALL_SOURCE.format("", cell))           
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @cell_magic
    def gfunc(self, line, cell):
        """
        %%gfunc コメント
        """
        src_key = line.strip()
        if src_key == "":
            print("lineパラメータがありません", file=sys.stderr)
            return
        else:
            self.other_source[src_key] = cell
        rc = self.compile_and_exec(self.make_source(), exec=False)
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")

    @line_magic
    def glist(self, line):
        """
        %glist コメント
        """
        print(self.make_source(), end="")

    @line_magic
    def grun(self, line):
        """
        %grun コメント
        """
        rc = self.compile_and_exec(self.make_source())
        print(rc.stdout, end="")
        if rc.stderr != "":
            print(rc.stderr, file=sys.stderr, end="")
        
    def make_source(self, src_key=None):
        other_src = ""
        main_src = ""
        for key in sorted(self.other_source):
            other_src += self.other_source[key]
                
        for key in sorted(self.main_source):
            main_src += self.main_source[key]

        src = ALL_SOURCE.format(other_src, main_src)
        return src
               
    def compile_and_exec(self, src, exec=True):
        if exec:
            rc = subprocess.run(COMPILE_EXEC_CMD, text=True, capture_output=True, shell=True, input=src)
        else:
            rc = subprocess.run(COMPILE_CMD, text=True, capture_output=True, shell=True, input=src)            
        if os.path.isfile(OBJFILE):
            os.remove(OBJFILE)
        return rc        
    
# GCC用Custom Kernel
class GccKernel(Kernel):
    # cellデータの解析結果
    class MagicData:
        def __init__(self, magic="gcc", cell="", line="", iscell=True):
            self.magic = magic
            self.cell = cell
            self.line = line
            self.iscell = iscell
            
            
    implementation = 'gcc'
    implementation_version = '1.0'
    language_info = {
        'name': 'c++',
        'mimetype': 'text/x-c++src',
        'file_extension': '.cpp',
        'version': '17.0.0',
    }
    banner = "Compile by gcc and execute binary"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.magic_obj = GCCMagics()
        self.line_magics = self.magic_obj.magics["line"]
        self.cell_magics = self.magic_obj.magics["cell"]        
        global GccKernel_obj
        GccKernel_obj = self

    def parse(self, code):
        mline = re.match("^.*$", code, re.MULTILINE)
        pos = mline.span()[1] + 1  # +1は改行コードを除くため
        cell = code[pos:]
        line = mline.group()
        magic_re = re.match(r" *(/{1,2})(\w+)", line)
        iscell = True            
        if magic_re == None:
            # 先頭行がmagicでないとき
            magic = "gcc"
            cell = code
            line = ''
        else:    
            line_pos = magic_re.span()[1] + 1
            line = line[line_pos:]
            magic_names = magic_re.groups()
            magic = magic_names[1]
            if magic_names[0] == "/":
                iscell = False
        return self.MagicData(magic, cell, line, iscell)
                
    def exec_magic(self, magic_data):
        if magic_data.iscell:
            if magic_data.magic in self.cell_magics:
                func = self.cell_magics[magic_data.magic]
                return func(magic_data.line, magic_data.cell)
            else:
                return CompletedProcess(stderr="該当するcell magicはありません")
        else:
            if magic_data.magic in self.line_magics:
                func = self.line_magics[magic_data.magic]
                return func(magic_data.line)
            else:
                return CompletedProcess(stderr="該当するline magicはありません")
                
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        if not silent:
            src = code if code[-1] == "\n" else code + "\n"
            magic_data = self.parse(src)
            rc = self.exec_magic(magic_data)
            if rc != None:
                stream_content = {'name': 'stdout', 'text': rc.stdout}
                self.send_response(self.iopub_socket, 'stream', stream_content)
                if rc.stderr != "":
                    stream_content = {'name': 'stderr', 'text': rc.stderr}
                    self.send_response(self.iopub_socket, 'stream', stream_content)    
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=GccKernel)
```

</details>