ChatGPTがEffective Pythonのプログラムを書けるか試してみた

## はじめに
ChatGPTはプログラミングに関して高い能力を発揮しているそうなので、自分もテストしてみました。

今回取り扱う書籍は以下のEffective Pythonです。

https://www.oreilly.co.jp/books/9784873119175/

Effective PythonはPython中級者から上級者が対象の書籍で、使いやすく理解しやすいテクニック(一部はかなりマニアック)が紹介されています。

この記事はChatGPTに敬意を示しつつも、今回は効率の良い実用的なPythonのプログラムを書くことができるかどうかを調べる目的で作成しています。

少しでも参考になれば幸いです！

## 関数のプログラム

### 2変数徐算する関数(項目20)
まず2変数を徐算する関数の作成を指示したら以下の関数が出力されました。

```Python
def divide_numbers(num1, num2):
    return num1 / num2
```

この関数は、`num2=0`の時にエラーを吐くので例外処理が必須ですが、ChatGPTはdefaultで例外処理を書いてくれませんでした。

次に例外処理を書くように指示した時の関数は以下です。

```Python
def divide_numbers(num1, num2):
    try:
        result = num1 / num2
    except ZeroDivisionError:
        print("Error: Cannot divide by zero.")
        return None
    return result
```

しっかり例外処理が記述されています。

ですがEffective Pythonでは上記のNoneを返すプログラムよりも例外を送出する関数が推奨されています。


```Python
def divide_numbers(num1, num2):
    try:
        return num1 / num2
    except ZeroDivisionError:
        raise ValueError('Cannot divide by zero')
```


### ラッパー関数の作成(項目26)
プログラムの実行時間を計測するラッパー関数を作成するように指示したら以下のように出力されました。

```Python
import time

def measure_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Execution time of '{func.__name__}': {(end_time - start_time):.6f} seconds")
        return result
    return wrapper
```

内容は大正解なのですが、Effective Pythonでは`functools.wraps`を用いることが推奨されています。

```Python
import time
from functools import wraps

def measure_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Execution time of '{func.__name__}': {(end_time - start_time):.6f} seconds")
        return result
    return wrapper
```

詳しくはこちらで解説しています。

https://zenn.dev/sergicalsix/articles/a1f5ff0b6a4a4c

## 頑健性と性能
### 性能確認のプログラム(項目70)
pythonのプログラムがしばしば想定外の振る舞いを引き起こすことがあります。(早いと想定したプログラムが異常に遅いなど)

このようなケースでは、プログラムを最適化する前にプログラム自体の性能を測ることが重要です。

例えば以下のような関数の性能を調査します。
```Python
def insert_sort(data):
    result = []
    for v in data:
        insert_value(result, v)
    return result

def insert_value(array, v):
    for i,existing in enumerate(array):
        if existing > v:
            array.insert(i,v)
            return
    array.append(v)
```

Effective Pythonは以下`cProfile`を用いて性能を評価する手法を模範回答としています。(プロファイルする)


```Python
from random import randint
from cProfile import Profile

data = [randint(0,10**4) for _ range (10**4)]
test = lambda: insert_sort(data)

profile = Profile()
profile.runcall(test)

stas = Stas(profile)
stats.strip_dirs()
stas.print_stats()
```

ChatGPTに、「具体的な一つ一つの処理の性能を調査」、または「プログラムを最適化する前にプログラム自体の性能を調査」を指示をします。

その結果は以下です。

```Python
import time
import random

def measure_performance(func, data):
    start_time = time.time()
    result = func(data)
    end_time = time.time()
    return end_time - start_time, result

data_sizes = [10, 100, 1000, 10000]

for size in data_sizes:
    data = list(range(size))
    random.shuffle(data)
    time_taken, result = measure_performance(insert_sort, data)
    print(f"Data size: {size} - Time taken: {time_taken:.5f} seconds")
```

`data_size`でバリエーションを豊富にしてくれましたが、肝心の性能は実行時間を計測する関数で評価する仕様のようです。
ですが、上記プログラムは今回意図しているようなプログラムの性能を詳しくは評価できません。

次に、プロファイルをしてください。というように、プロファイルという専門用語を加えて指示しました。
結果は以下です。

```Python
import cProfile

data_sizes = [10, 100, 1000, 10000]

for size in data_sizes:
    data = list(range(size))
    random.shuffle(data)

    # プロファイルの開始
    pr = cProfile.Profile()
    pr.enable()
    insert_sort(data)
    pr.disable()

    # プロファイリング結果の出力
    print(f"Data size: {size}")
    pr.print_stats(sort="cumulative")
```

バッチリです！！

プログラムの性能を測ると指示するよりも、プロファイルといった専門知識を投げかけることが必要でした。

## まとめ
ChatGPTは要件に合わせてプログラムを書くことができます。

一方で、ChatGPTの作成したプログラムが最適でないケースも多く見受けられると予想されます。

最適なプログラムを書くためには、まだ我々には、専門的な知識が必要になるでしょう。