Copilot in Windows (Bing Chat) をコードの作成や改善に利用してみる

## はじめに

Windows 11 の大型アップデート 22H2 がリリースされ、AI 支援機能「Copilot in Windows (プレビュー版)」が搭載されました。

> Copilot in Windows (プレビュー版)は、より速く、簡単に複雑な作業を完了できるように支援し、認知負荷を軽減してくれます。Copilot は、タスクバーに表示されているアイコンか、Win+C キーボードショートカットを使用することで、すべてのアプリケーションと共に表示できるため、職場、学校、自宅のあらゆる画面サイズでシームレスにアクセスできます。

Bing Chat と連携して、Windows に関わる操作をサポートしてくれるようです。Windows の機能として組み込まれたことが大きな特徴でしょうか。

今回は、Windows に関わる操作ではなく、他のサービスと比較しながら、デスクトップ版 Bing Chat という感じで、コード作成や既存コードの改善に利用してみます。
そのため、この記事に目新しさはありません…。

*Windows + AMD GPU 環境での画像生成のチューニングをしたかったのですが、Google がオープンソースで開発している機械学習ライブラリ「TensorFlow」から Windows のネイティブ対応が削除される、ROCm が動作しない、NVIDIAのドライバアップデートでStable Diffusionが2倍高速化した、などのため対応を検討中です。*

## [ChatGPT](https://chat.openai.com/)

> ChatGPT（チャットジーピーティー、英語: Chat Generative Pre-trained Transformer）[1]は、OpenAIが2022年11月に公開した人工知能チャットボットであり、生成AIの一種。

> GPTの原語のGenerative Pre-trained Transformerとは、「生成可能な事前学習済み変換器」という意味である[2]。OpenAIのGPT-3ファミリーの大規模な言語モデルに基づいて構築されており、教師あり学習と強化学習の両方の手法を使って転移学習され、機械学習のサブセットである深層学習を使って開発されている[3]。

GPT-3.5 は無料で利用できますが、より精度が高く応答も早い GPT-4 は、月額 20 ドルの有料プランである ChatGPT Plus にアップグレードする必要があります。

個人で利用するには月額 20 ドルはちょっと高いですね。

ChatGPT Plus では、最新の画像生成 AI である「DALL-E 3」も利用できるようになりました。生成した画像は商用利用することも可能なようです。

## [Bing Chat (Bing AI Chat)](https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx)

> Bing AI Chat（ビング エーアイ チャット）は、Microsoftによって開発され、OpenAIのGPT-4を用いた[1]、自然言語処理技術を用いた検索エンジン型のAIである。英語版Bingでは" AI-powered copilot for the web", "Copilot"[2], Bing公式YouTubeでは "AI-powered Bing","Copilot"[3]と呼ばれている。

> ユーザが自然言語で入力したテキストに対して、応答を返すことができる。ユーザが英語、中文（中国語）、日本語、Español（スペイン語）、Français（フランス語）、Deutsch（ドイツ語）などの自然言語でコミュニケーションをすることができる。

Bing Chat は、GPT-4 を無料で利用でき、今回の Copilot in Windows や Microsoft Edge の機能としても組み込まれています。

自然言語生成の「GPT」だけではなく、ChatGPT Plus と同様に最新の画像生成 AI である「DALL-E 3」も無料で利用できるようになりました。ChatGPT Plus と異なる点として、アスペクト比が 1:1 固定、商用利用は不可となっています。

表示される公告の量や内容、検索の精度など、以前からの「Bing」という名称に対してあまり良いイメージを持っていませんが、GPT-4 や DALL-E 3 を無料で利用する方法として、Bing Chat の選択は十分アリだと思います。

## [Google Bard](https://bard.google.com)

> Bard（バード）は、Googleが開発･提供している会話型人工知能。PaLM2で構築されており、現在は試験運用版が一般公開されている。

> 2023年3月21日にアメリカ合衆国及びイギリスで提供が開始され[1]、2023年5月10日に日本語と韓国語に新たに対応した[2]。同日、Google WorkspaceやAdobeと連携を発表したほか、全世界で一般公開され誰でも利用できるようになった[3]。

ChatGPT は、言語モデルとして GPT-3.5 や GPT-4 を活用していますが、Bard は PaLM2 を活用しています。

コーディングに強い、回答速度が速い、2021年9月以降の話題についても取り扱うことができる点が ChatGPT に対する優位性とのことですが、文章や長文の生成は苦手なようです。

なお、マイクロソフトの Bing Chat は GPT-4 を利用していますが、Bing 検索を利用して最新の情報も提供可能としているため、回答速度以外の優位性はそれほどないように個人的には思います。

## [GitHub Copilot](https://docs.github.com/copilot)

> GitHub Copilot（ギットハブ・コパイロット）は、GitHubが提供しているクラウド型人工知能ツールであり、OpenAIのGPTを使用し、プログラミングにまつわる各種作業を支援する。

> 2021年版の GitHub Copilot は、ソースコードの自動補完によって、Visual Studio Code、Visual Studio、Neovim、JetBrainsの各統合開発環境（IDE）でのプログラミングを支援する[1]。2021年6月29日にGitHubが発表し、有償サブスクリプションで提供されており、多数のプログラミング言語をサポートしている[2]。

コメントや関数から最適なコードを推測して提案してくれたり、文法の誤りなどを指摘してくれたりなど、プログラミングの効率化という点で、コード生成専用モデルである「Codex」が優秀です。Visual Studio Code などの開発環境と連携できるのも良いですね。

GitHub Copilot Chat を利用すれば、対話しながら設計やプロトタイプ開発も行えるようです。

なお、GitHub Copilot は 60 日間の無料トライアルの後、月額 10 ドルか年額 100 ドルの有料プランとなります。月額 10 ドルで得られる価値を考えれば高くはないです。

## [Copilot in Windows](https://www.microsoft.com/en-us/windows/copilot-ai-features)

> Copilot in Windows は、Windows 11 に搭載されている対話型の AI アシスタント機能です。生産性と創造性を高めるために設計されており、質問や要望を送信すると、内容に応じた回答が生成されます。  

> Copilot in Windows は、2023 年 9 月 26 日にリリースされた Windows 11 の大型アップデート 22H2 に搭載されました。チャットを使って、自然言語を使った検索や Windows 11 の一部操作、画像生成などが可能です。  

> Copilot in Windows は、Windows Update で「2023-09x64 ベースシステム用 Windows 11 Version 22H2 の累積更新」を適用することで利用できます。2023 年 10 月時点ではプレビュー版のため、正式リリースはされていません。

Copilot は、「OpenAI」が開発した「ChatGPT (GPT-4)」をベースにした対話型インタフェース機能で、英語や日本語などの自然言語による検索だけではなく、Windows 11のいくつかの操作も行えるのが特徴だそうです。「DALL-E」による画像生成AIも組み込まれています。

Win+C で画面右側に表示することができます。

![img01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/14c9a2e6-c451-dc87-7dbd-0581d7ec8afb.png)

画像生成もできます。生成速度は予想以上に速いです。

![img02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/19499212-040c-ee2e-cf0a-2007c0496b73.png)

ただし、Windows 11 の設定や操作についてできることはまだ少ないようです。

Windows 11 ユーザーであれば（どれだけの情報がやりとりされるかわかりませんが）、「GPT-4」や「DALL-E」を「無料」で試してみる方法のひとつとして、Copilot in Windows を利用してみるのも良いのではないでしょうか。

開発環境と直結できるわけではないため利用するには少々不便ですが、利用した場合にどのような感じになるかを試してみます。

## プログラミングを手伝ってもらう

Copilot in Windows に、プログラミングを手伝ってもらいます。

とはいえ、ブラウザで ChatGPT(GPT-4) や Bing Chat を利用する場合と大差はありません。GitHub Copilot のようにコーディング中に自然に提案してくれることもありません。

適宜必要な範囲のコードをコピーしながら、質問やコードの確認などをしてもらいます。入力可能な文字数は 4000 文字なので、コード全体を貼り付けることはできませんが、質問の文脈を理解してくれるので、クラスやメソッド単位で確認してもらうと前提や仕様を踏まえて回答してくれます。

まずは、新規にコードを書いてもらいましょう。

ちなみに、Win+C は Copilot in Windows の ON/OFF 切り替えだけではなく、他のアプリケーションを利用している際に Win+C で、Copilot in Windows がアクティブになるため、エディタ → Win+C → Copilot in Windows → Alt+Tab → エディタ のように行き来することができます。

<質問>
* Python でリストに含む要素を繰り返し処理するコードを教えてください。

<回答>
* Pythonでリストの要素を繰り返し処理するためには、`for`ループを使用します。以下に一例を示します：

  ```python
  # リストを定義
  my_list = ['apple', 'banana', 'cherry']
  # リストの各要素を繰り返し処理
  for item in my_list:
      print(item)
  ```

* このコードは、リスト`my_list`の各要素（この場合は'apple'、'banana'、'cherry'）を順番に出力します。`for`ループはリストの要素がなくなるまで繰り返されます。このように、Pythonの`for`ループはリストや他のイテラブル（繰り返し可能な）オブジェクトの要素を効率的に処理するための強力なツールです。ご参考までにどうぞ！

説明も含めて丁寧に回答してくれました。引き続き質問してみます。

<質問>
* Python のイテラブル（繰り返し可能な）オブジェクトの要素とはどのようなものですか。

<回答>
* Pythonのイテラブル（繰り返し可能な）オブジェクトは、その要素を一度に1つずつ返すことができるオブジェクトです³。これにより、`for`ループなどでその要素を順番に処理することができます。

  イテラブルなオブジェクトの例としては、すべてのシーケンス型（リスト、文字列、タプルなど）や、一部の非シーケンス型（辞書、ファイルオブジェクトなど）があります³。また、`__iter__()`や`__getitem__()`メソッドを定義した任意のクラスのオブジェクトもイテラブルとなります³。

  具体的には、リストやタプルの要素はその中に含まれる個々の値（数値、文字列など）であり、辞書の要素はキーと値のペアです。また、ファイルオブジェクトの要素はファイルの各行を表します。

  したがって、Pythonのイテラブルオブジェクトの「要素」とは、そのオブジェクトが持つ個々のデータを指します。これらの要素は、`for`ループなどを使って一つずつ取り出して処理することができます。³⁴⁵

  ソース: Bing との会話 2023/10/22

  (1) Python iterable object | Lists, tuples, dictionaries, and sets. https://tutorial.eyehunts.com/python/python-iterable-object-lists-tuples-dictionaries-and-sets/.
  (2) Iterator - Python Wiki. https://wiki.python.org/moin/Iterator.
  (3) Python Iterators - W3Schools. https://www.w3schools.com/python/python_iterators.asp.
  (4) [Python] クラスをiterableにする方法 #Python - Qiita. https://qiita.com/Lisphilar/items/35df827f3c8a26abd165.
  (5) Iterators and Iterables in Python: Run Efficient Iterations. https://realpython.com/python-iterators-iterables/.

このような質問にも補足も含めて丁寧に回答してくれます。

<質問>
* イテラブルなオブジェクト（クラス）のサンプルコードを示して下さい。

<回答>
* Pythonでイテラブルなクラスを作成するためには、そのクラスに`__iter__()`メソッドを定義します。このメソッドはイテレータオブジェクトを返す必要があります。イテレータオブジェクトは、`__next__()`メソッドを持つオブジェクトで、このメソッドは次の要素を返すか、要素がなくなった場合は`StopIteration`例外を発生させます。

  以下に、イテラブルなクラスのサンプルコードを示します：

  ```python
  class MyIterable:
      def __init__(self, items):
          self.items = items

      def __iter__(self):
          # イテレータオブジェクト（この場合は自分自身）を返す
          self.index = 0
          return self

      def __next__(self):
          # 次の要素を返すか、要素がなくなったらStopIteration例外を発生させる
          if self.index < len(self.items):
              result = self.items[self.index]
              self.index += 1
              return result
          else:
              raise StopIteration

  # イテラブルなクラスのインスタンスを作成
  my_iterable = MyIterable(['apple', 'banana', 'cherry'])

  # forループで要素を一つずつ取り出す
  for item in my_iterable:
      print(item)
  ```

* このコードでは、`MyIterable`クラスはイテラブルであり、そのインスタンス`my_iterable`の要素（この場合は'apple'、'banana'、'cherry'）を`for`ループで一つずつ取り出しています。

コードの正しさの確認は必要となりますが、どんな実装だったかなを確認する方法として有効ですね。続いてこのコードを拡張していきます。

<質問>
* 提示していただいたコードに、要素を追加する add メソッドと、要素のインデックスを指定して削除する remove_at メソッドを追加してください。

<回答>
* 要素を追加する`add`メソッドと、要素のインデックスを指定して削除する`remove_at`メソッドを追加したクラスは以下のようになります：

  ```python
  class MyIterable:
      def __init__(self, items):
          self.items = items

      def __iter__(self):
          # イテレータオブジェクト（この場合は自分自身）を返す
          self.index = 0
          return self

      def __next__(self):
          # 次の要素を返すか、要素がなくなったらStopIteration例外を発生させる
          if self.index < len(self.items):
              result = self.items[self.index]
              self.index += 1
              return result
          else:
              raise StopIteration

      def add(self, item):
          # 要素を追加する
          self.items.append(item)

      def remove_at(self, index):
          # インデックスを指定して要素を削除する
          if index < len(self.items):
              del self.items[index]
          else:
              print("Index out of range")

  # イテラブルなクラスのインスタンスを作成
  my_iterable = MyIterable(['apple', 'banana', 'cherry'])

  # 要素を追加
  my_iterable.add('orange')

  # インデックス1の要素を削除
  my_iterable.remove_at(1)

  # forループで要素を一つずつ取り出す
  for item in my_iterable:
      print(item)
  ```

* このコードでは、`MyIterable`クラスに`add`メソッドと`remove_at`メソッドが追加されています。これにより、リストの要素を動的に追加したり削除したりすることができます。最後の部分では、'apple'、'banana'、'cherry'のリストに'orange'を追加し、インデックス1（'banana'）の要素を削除した後、残った要素を順番に出力しています。結果として、出力は'apple'、'orange'、'cherry'となります。ご参考までにどうぞ！

<質問>
* 続いて、オブジェクトが持つ要素を列挙して csv ファイルとして出力する output_csv メソッドを追加できますか。

<回答>
* Pythonの`csv`モジュールを使用して、オブジェクトが持つ要素を列挙してCSVファイルとして出力する`output_csv`メソッドを追加することができます。以下にそのサンプルコードを示します：

  ```python
  import csv

  class MyIterable:
      def __init__(self, items):
          self.items = items

      def __iter__(self):
          # イテレータオブジェクト（この場合は自分自身）を返す
          self.index = 0
          return self

      def __next__(self):
          # 次の要素を返すか、要素がなくなったらStopIteration例外を発生させる
          if self.index < len(self.items):
              result = self.items[self.index]
              self.index += 1
              return result
          else:
              raise StopIteration

      def add(self, item):
          # 要素を追加する
          self.items.append(item)

      def remove_at(self, index):
          # インデックスを指定して要素を削除する
          if index < len(self.items):
              del self.items[index]
          else:
              print("Index out of range")

      def output_csv(self, filename):
          # 要素を列挙してCSVファイルとして出力する
          with open(filename, 'w', newline='') as f:
              writer = csv.writer(f)
              for item in self.items:
                  writer.writerow([item])

  # イテラブルなクラスのインスタンスを作成
  my_iterable = MyIterable(['apple', 'banana', 'cherry'])

  # 要素を追加
  my_iterable.add('orange')

  # インデックス1の要素を削除
  my_iterable.remove_at(1)

  # CSVファイルとして出力
  my_iterable.output_csv('output.csv')
  ```

* このコードでは、`MyIterable`クラスに`output_csv`メソッドが追加されています。このメソッドは、オブジェクトが持つ要素（この場合は'apple'、'orange'、'cherry'）を列挙し、それらをCSVファイル（この場合は'output.csv'）として出力します。ご参考までにどうぞ！

このようにコードを拡張（機能追加）していけるので、ざっくりとモック作成したい場合に便利です。

<質問>
* これまでのコードに、オブジェクトが持つ要素の順序を逆順にするメソッドを追加することはできますか。

<回答>
* Pythonのリストには、要素の順序を逆にする`reverse`メソッドがあります。これを利用して、オブジェクトが持つ要素の順序を逆にするメソッドを追加することができます。以下にそのサンプルコードを示します：

  ```python
  import csv

  class MyIterable:
      def __init__(self, items):
          self.items = items

      def __iter__(self):
          # イテレータオブジェクト（この場合は自分自身）を返す
          self.index = 0
          return self

      def __next__(self):
          # 次の要素を返すか、要素がなくなったらStopIteration例外を発生させる
          if self.index < len(self.items):
              result = self.items[self.index]
              self.index += 1
              return result
          else:
              raise StopIteration

      def add(self, item):
          # 要素を追加する
          self.items.append(item)

      def remove_at(self, index):
          # インデックスを指定して要素を削除する
          if index < len(self.items):
              del self.items[index]
          else:
              print("Index out of range")

      def output_csv(self, filename):
          # 要素を列挙してCSVファイルとして出力する
          with open(filename, 'w', newline='') as f:
              writer = csv.writer(f)
              for item in self.items:
                  writer.writerow([item])

      def reverse(self):
          # 要素の順序を逆にする
          self.items.reverse()

  # イテラブルなクラスのインスタンスを作成
  my_iterable = MyIterable(['apple', 'banana', 'cherry'])

  # 要素を追加
  my_iterable.add('orange')

  # インデックス1の要素を削除
  my_iterable.remove_at(1)

  # 要素の順序を逆にする
  my_iterable.reverse()

  # CSVファイルとして出力
  my_iterable.output_csv('output.csv')
  ```

* このコードでは、`MyIterable`クラスに`reverse`メソッドが追加されています。このメソッドは、オブジェクトが持つ要素（この場合は'apple'、'orange'、'cherry'）の順序を逆にします。したがって、このコードでは、CSVファイル（'output.csv'）への出力は'cherry'、'orange'、'apple'となります。ご参考までにどうぞ！

<質問>
* あらためて、イテラブルなオブジェクトが持つ、`def __iter__(self)` の役割について教えてください。

<回答>
* Pythonの`__iter__`メソッドは、イテレータプロトコルに従うオブジェクトを返す役割があります¹²³。イテレータプロトコルとは、`__iter__`と`__next__`という2つのメソッドを実装することを指します²。

  具体的には、`__iter__`メソッドはイテレータオブジェクト（自分自身）を返します¹²。このイテレータオブジェクトは、`next`関数を呼び出せるように`__iter__`メソッドを実装している必要があります³。そして、次の要素にアクセスするためには、そのオブジェクトに`__next__`メソッドを実装することが必要です³。

  したがって、Pythonのイテレータとは、その要素を一度に1つずつ返すことができるオブジェクトであり、そのためには`__iter__`メソッドと`__next__`メソッドの両方が必要です¹²³。これにより、for文などでその要素を順番に処理することができます¹。

  例えば、リストやタプルなどのシーケンス型のオブジェクトはイテラブルであり、その要素を一度に1つずつ返すことができます。これは、これらのオブジェクトが`__iter__`メソッドを持っているからです¹。また、自分で定義したクラスにも`__iter__`メソッドを実装することで、そのクラスのインスタンスをイテラブルなオブジェクトとして扱うことができます¹。

  以上がPythonの`__iter__`メソッドの役割となります。ご参考までにどうぞ！

  ソース: Bing との会話 2023/10/22

  (1) Python : `__iter__()`はイテレータを返し、`__next__()`は次の要素 .... https://commte.net/7366.
  (2) Python/イテレータ - Wikibooks. https://ja.wikibooks.org/wiki/Python/%E3%82%A4%E3%83%86%E3%83%AC%E3%83%BC%E3%82%BF.
  (3) Pythonにおけるイテレータとジェネレータについて学ぶ - Zenn. https://zenn.dev/ganariya/articles/python-iterator-generator.

<質問>
* ありがとうございます。
  これまでのサンプルコードでは、`def __iter__(self)` メソッド内で、`self.index = 0` としていますが、これはなぜですか。

<回答>
* `self.index = 0`は、イテレータが最初に呼び出されたときに、最初の要素から始めるための初期化です。

  Pythonのイテレータは、`__iter__`メソッドと`__next__`メソッドを持つオブジェクトで、これらのメソッドを使って要素を一度に1つずつ返すことができます。`__iter__`メソッドはイテレータ自体（この場合は`self`）を返し、`__next__`メソッドは次の要素を返します。

  このとき、`self.index = 0`とすることで、最初に`__next__`メソッドが呼び出されたときにリストの最初の要素（インデックス0の要素）が返されるようになります。そして、その後の`__next__`メソッドの呼び出しでは、インデックスが1つずつ増えていき、リストの次の要素が順番に返されます。

  したがって、`self.index = 0`はイテレータが最初に呼び出されたときに最初の要素から始めるための初期化であり、これによりforループなどでリストの要素を順番に処理することができます。ご参考までにどうぞ！

ここまでのやりとりからも、文脈を理解して回答してくれていることがわかります。
それでは、まったく異なる質問に変えてみます。

<質問>
* Python で、ログ出力を行うには、どのようなコードになりますか。

<回答>
* Pythonでログ出力を行うためには、`logging`モジュールを使用します。以下に基本的な使用方法を示します：

  ```python
  import logging

  # ログの設定
  logging.basicConfig(filename='app.log', level=logging.INFO)

  # ログの出力
  logging.info('This is an info message')
  logging.warning('This is a warning message')
  logging.error('This is an error message')
  ```

* このコードでは、`logging.basicConfig`関数を使用してログの設定を行います。`filename`パラメータにはログファイルの名前を、`level`パラメータにはログレベルを指定します。

  ログレベルは以下のようなものがあります：
  - `logging.DEBUG`: デバッグ用の詳細な情報
  - `logging.INFO`: 予想される通常の動作の確認
  - `logging.WARNING`: 何か問題が起こるかもしれないという予兆
  - `logging.ERROR`: より重大な問題で、プログラムが機能を実行できない
  - `logging.CRITICAL`: 非常に重大な問題で、プログラム自体が実行を続けられない

  そして、`logging.info()`, `logging.warning()`, `logging.error()`などの関数を使用して、それぞれのレベルのログメッセージを出力します。これらの関数に渡されたメッセージは、設定したログレベル以上の場合にログファイルに書き込まれます。例えば、ログレベルを`INFO`に設定した場合、`INFO`, `WARNING`, `ERROR`, `CRITICAL`レベルのメッセージがログファイルに書き込まれます。

  以上が基本的な使い方ですが、より詳細な設定や使い方を知りたい場合は、公式ドキュメントを参照してください。

<質問>
* 現時点の Python では、logging ではなく、 Logger の利用が推奨されていると思いますが、これについてはどうでしょうか。

<回答>
* Pythonの`logging`モジュールと`Logger`クラスの使い方については、以下のような違いがあります：

  - `logging`モジュールの関数（例：`logging.info()`, `logging.error()`など）は、ルートロガー（root logger）のメソッドを呼び出すラッパーとして機能します⁵。これは便利な方法ですが、コード全体で一貫したログ設定しか提供できません⁵。

  - 一方、`Logger`クラスのインスタンスを使用すると、異なる部分のコードで異なるログ設定を持つことが可能になります⁵。これは、特定のモジュールや機能に対して異なるログレベルや出力先を設定したい場合に有用です¹²。

  具体的には、各モジュールやクラスで`getLogger(__name__)`を呼び出すことで、そのモジュールやクラス専用の`Logger`インスタンスを取得できます¹²⁴。この方法では、ログメッセージにモジュール名が含まれ、どのモジュールがどのメッセージを生成したかを簡単に区別できます⁴。

  したがって、一般的には、より柔軟なログ設定とより詳細なログ情報を得るために、`Logger`クラスの使用が推奨されます¹²⁴⁵。ただし、プロジェクトの要件や個々の状況により、最適な方法は異なる場合があります。

  ソース: Bing との会話 2023/10/22

  (1) Why use logger instead of logging in python? - Stack Overflow. https://stackoverflow.com/questions/64960596/why-use-logger-instead-of-logging-in-python.
  (2) 【Python】LoggingとLoggerの使い方を簡潔にまとめました .... https://posipochi.com/2021/05/27/python-logging-logger/.
  (3) Pythonにおけるlogging徹底攻略 #Python - Qiita. https://qiita.com/Broccolingual/items/9838443aa6838a867041.
  (4) Python: What are differences between `logger.info` and .... https://stackoverflow.com/questions/61907797/python-what-are-differences-between-logger-info-and-logging-info.
  (5) python - Logging hierarchy vs. root logger? - Stack Overflow. https://stackoverflow.com/questions/4150148/logging-hierarchy-vs-root-logger.

ちょっとした疑問についても丁寧に回答してくれます。このような質問であれば違和感なくやりとりできていますね。
ただし、質問する側がある程度理解していることを前提としたやりとりになっていることに注意が必要です。

## 既存コードの改善

続いて、以前作成したホロジュールのスクレイピングと動画情報収集プログラムであるホロクローラーの改善を手伝ってもらいます。

<質問>
* Listに含まれる要素を MongoDB に登録する Python のコードがあります。実装やエラー制御について気になる点はありますか。気になる点があれば具体的に示してください。

  ```python
  def register_holodule_list(self, holodule_list: list[Holodule]):
      try:
          client = MongoClient(self.__mongodb_holoduledb)
          db = client.holoduledb
          collection = db.holodules
          for holodule in holodule_list:
              # video_id を条件としたドキュメントの削除
              video_id = holodule.video_id
              collection.delete_one( {"video_id":video_id} )
              # ドキュメントの挿入
              doc = holodule.to_doc()
              collection.insert_one(doc)
      except:
          self._logger.error("エラーが発生しました。", exc_info=True)
          raise
      finally:
          pass
  ```

<回答>
* コードを確認しましたが、いくつか改善点が見つかりました。以下に具体的な提案を示します。

  1. **エラーハンドリング**: `try/except` ブロックはエラーハンドリングに役立ちますが、現在のコードではすべての例外をキャッチしています。これは推奨されません。具体的な例外タイプを指定することで、予期しない問題が発生した場合にそれを見逃す可能性を減らすことができます。たとえば、`MongoClient` の接続エラーや `insert_one` のエラーなど、特定のエラーをキャッチすることができます。

  2. **データベース接続のクローズ**: 現在のコードでは、データベース接続が明示的に閉じられていません。これはリソースのリークを引き起こす可能性があります。`finally` ブロック内で `client.close()` を呼び出すことをお勧めします。

  3. **一括操作**: 現在、各ドキュメントに対して個別に削除と挿入を行っています。これはパフォーマンスに影響を及ぼす可能性があります。MongoDB は一括操作をサポートしているため、`delete_many` や `insert_many` を使用することでパフォーマンスを向上させることができます。

  以上の点を踏まえて、以下に改善されたコードの例を示します。

  ```python
  def register_holodule_list(self, holodule_list: list[Holodule]):
      client = MongoClient(self.__mongodb_holoduledb)
      try:
          db = client.holoduledb
          collection = db.holodules
          for holodule in holodule_list:
              video_id = holodule.video_id
              collection.delete_one({"video_id": video_id})
              doc = holodule.to_doc()
              collection.insert_one(doc)
      except pymongo.errors.PyMongoError as e:
          self._logger.error("MongoDB エラーが発生しました: %s", e, exc_info=True)
          raise
      finally:
          client.close()
  ```

* このコードでは、具体的な `PyMongoError` をキャッチし、データベース接続を適切に閉じています。ただし、一括操作はこの例では示していません。それは `holodule_list` の内容と要件によります。

<質問>
* delete_many や insert_many を使用する実装のサンプルコードを教えてください。

<回答>
* `delete_many` と `insert_many` を使用した実装のサンプルコードは以下の通りです。

  ```python
  def register_holodule_list(self, holodule_list: list[Holodule]):
      client = MongoClient(self.__mongodb_holoduledb)
      try:
          db = client.holoduledb
          collection = db.holodules
          # video_id のリストを作成
          video_ids = [holodule.video_id for holodule in holodule_list]
          # 一括削除
          collection.delete_many({"video_id": {"$in": video_ids}})
          # ドキュメントのリストを作成
          docs = [holodule.to_doc() for holodule in holodule_list]
          # 一括挿入
          collection.insert_many(docs)
      except pymongo.errors.PyMongoError as e:
          self._logger.error("MongoDB エラーが発生しました: %s", e, exc_info=True)
          raise
      finally:
          client.close()
  ```
* このコードでは、まず `video_id` のリストを作成し、そのリストに含まれるすべてのドキュメントを一度に削除します。次に、新しいドキュメントのリストを作成し、それらを一度に挿入します。これにより、データベースへの接続回数が減り、パフォーマンスが向上します。ただし、この方法では `delete_many` と `insert_many` の間でデータの整合性が保証されません。これが問題となる場合は、トランザクションを使用することを検討してください。また、大量のデータを扱う場合は、メモリ使用量に注意してください。

既存のコードに対しても、エラー処理や後処理などの改善提案を丁寧に行ってくれます。とりあえず動作するものを作って、あとからチェックしてもらうという使い方も良さそうです。

## 最後に

今回は Copilot in Windows (プレビュー版) を利用して、コードの作成や改善提案を行ってもらいました。Windows と統合したことによるメリットを確認していないため、ブラウザで Bing Chat を利用する場合と大差ありませんでしたが、コーディングを補助するツールとして十分に活用できるかなと思います。