discord.pyでcogをnestする

# 注意
既にアナウンスされている通り、discord.pyは2021年8月下旬ころ、以降のサポートを停止する旨発表されております。
もし本記事を参考にして不具合が生じた場合、ライブラリのサポート終了による影響である可能性もあります。
詳しくは [the_future_of_dpy.md](https://gist.github.com/Rapptz/4a2f62751b9600a31a0d3c78100287f1) をご覧ください。

# 前提
- discord.py
- python3.6

この記事中のコードはあくまで説明用の簡略化されたコードなので、ナンセンスなコードしか登場しません。
ご了承ください。

# cogが複雑化してきた
discord.pyのコマンド拡張 (https://discordpy.readthedocs.io/ja/latest/ext/commands/index.html) には、cogといういくつかのコマンドやイベントをひとまとめにする機能があります。
このように

```python:hoge.py
from discord.ext import commands as c

class CogHogeFuga(c.Cog):
    def __init__(self, bot, hoge):
        self._bot = bot
        self._hoge = hoge

    @c.command()
    async def hoge1(self, ctx):
        await ctx.send("hoge")
    
    @c.command()
    async def hoge2(self, ctx):
        foo = await hoge3()
        return foo

    async def hoge3(self):
        return self._hoge
```
書くと、`CogHogeFuga`を読み込むだけで`hoge1`も`hoge2`も読み込まれるわけです。

ところが開発を進めるうちに以下のようになってきました。


```python:hoge.py
from discord.ext import commands as c

class CogHogeFuga(c.Cog):
    def __init__(self, bot, hoge):
        self._bot = bot
        self._hoge = hoge
        self._fuga = None

    @c.command()
    async def hoge1(self, ctx):
        await ctx.send("hoge")
    
    @c.command()
    async def hoge2(self, ctx):
        foo = await hoge3()
        await ctx.send(foo)

    async def hoge3(self):
        return self._hoge

    """
    ここから下が追加したコマンドたち
    """
    
    @c.command()
    async def fuga1(self, ctx):
        return ctx.send("fuga")

    async def fuga2(self, ctx, new_):
        self._fuga = new_

    @c.command()
    async def fuga3(self, ctx):
        foo = await fuga4()
        await ctx.send(foo)

    async def fuga4(self):
        return self._fuga

```

なんとなく書き足してみましたが、`hoge`に関するコマンドと`fuga`に関するコマンドが混在していますね。
もしかすると今後は`bar`に関するコマンドも増えるかもしれないしできれば分離したい。

しかしコグを単に増やしてしまうと、コグのロードの手間が増えてしまいます。`hoge`も`fuga`もやっていることは似ているので、できれば`bot.add_cog(CogHogeFuga(bot))`の一手間で済ませたいところです。

# cogをnestする
そこで、cogが入れ子にできると嬉しくなれそうだなと思うわけです。
実際以下のように書くことで、コグのロード自体は`bot.add_cog(CogHogeFuga(bot))`のままで済ませられます。


```python:hoge.py
from discord.ext import commands as c

class CogHogeFuga(c.Cog):
    def __init__(self, bot, hoge):
        self._bot = bot

        self._bot.add_cog(CogHoge(self._bot, hoge)) # ポイント
        self._bot.add_cog(CogFuga(self._bot)) # ポイント

class CogHoge(c.Cog):
    def __init__(self, bot, hoge):
        self._bot = bot
        self._hoge = hoge


    @c.command()
    async def hoge1(self, ctx):
        await ctx.send("hoge")
    
    @c.command()
    async def hoge2(self, ctx):
        foo = await hoge3()
        await ctx.send(foo)

    async def hoge3(self):
        return self._hoge
    

class CogFuga(c.Cog):
    def __init__(self, bot):
        self._bot = bot
        self._fuga = None

    @c.command()
    async def fuga1(self, ctx):
        return ctx.send("fuga")

    async def fuga2(self, ctx, new_):
        self._fuga = new_

    @c.command()
    async def fuga3(self, ctx):
        foo = await fuga4()
        await ctx.send(foo)

    async def fuga4(self):
        return self._fuga

```

上記のようにCogを追加する際にbot自身を渡すことで、初期化の際にほかのCogをbotに追加することができました。

# 終わりに
もっと簡単な方法があったら教えてください。


## 何がありがたいのか

チラ裏です。
上にも書いたように、cogはそれぞれbotにadd_cogする必要があります。`Extension`というファイルまるごとロードする機能もありますが、こちらも結局ファイル内の`setup()`メソッドを実行するので、ここには`add_cog`を書かねばなりません。

私はできるだけ

- 関連性の高いものはまとめて書いておきたい、分かるようにしたい
- 一つの改修で複数個所を編集しないで済ませたい

ので、Cogをネストすることで実現しています。
この書き方だと、例えばあるCogを分割したい場合、分割したいCogの書き換えだけで済ませられ、他の部分で新たにCogをロードするような処理を記載する必要もありません。