見出し画像

🌃Pythonの言語ルールをめぐってChatGPTに文句言いまくった件でw


本題に入る前に、私のプログラミング環境について

私の主なプログラミングはPython言語、時々RとSQL。
環境は主にVSCode、クラウド上だとKaggleノートブックやDataLore、Google Collabを使う事もあります。主軸はVSCode上のJupyter Notebookです。
今年からはAWSのCodeWhisperをVSCodeに搭載してコードアシスタントとしてチェックしてもらってます。
そしてChatGPTはペアプログラミングの相手感覚で使用しています。
でも気を付けている事は、コードを書くのは自分であってChatGPTではないこと。頼りすぎると読解力などが下がるかもしれないので、例えば量が多めのコードを直してもらったり書いてもらったりする時は、行ごとに何が実行されているのかなどを理解しながら読み進めるようにしています。理解しながら進めないと後で厄介なことになる事があるからです。どんなにプロンプトを工夫したって、ChatGPTは間違ったアウトプットを出してくる事が度々あるからです。

で、前回このNoteでもシェアしたSpotify APIアプリケーションのコードを紹介しましたが、そのコードを書いている時にChatGPTと揉めましたw

あのアプリ、レーダーチャートだけじゃなくってBPM(テンポ)のバーチャートの比較も入れたいんだけど、どうも関数の配置の仕方だったり全体的なプログラムの構造が気に入らなかったので、ChatGPTにベターな方法がないか聞きました。

そしたら。。。


# ... (Your existing code)

# Load local file containing 2023's stream information
def load_stream_data():
    # ... (Your existing code)

    # Update the attribute list to include 'loudness' and 'bpm'
    att_list = ['danceability_%', 'valence_%', 'energy_%', 'acousticness_%', 'instrumentalness_%',
                'liveness_%', 'speechiness_%', 'loudness', 'bpm']

    df_sorted_100 = df_sorted.iloc[:100, :]

    # ... (Your existing code)
    return selected_atts

# Create the radar chart
def create_radar_chart(mean_values_top_100, audio_features, att_list):
    # ... (Your existing code)

    # Ensure att_list is not including 'bpm' and 'loudness'
    att_list = [att for att in att_list if att not in ['bpm', 'loudness']]

    # ... (Your existing code)

# Main section
st.title("🎤Spotify Trending Songs Attribute Comparison 2023")
...

if compare_button:
    st.header('Comparison Radar Chart:')
    # Error Handling
    try:
        # Load top 100 stream data
        df_sorted_100, mean_values_top_100 = load_stream_data()
        # Get Spotify Data
        audio_features = get_spotify_data(artist_name, track_name)
        if audio_features:
            # Update the attribute list to include 'loudness'
            att_list = ['danceability_%', 'valence_%', 'energy_%', 'acousticness_%', 'instrumentalness_%',
                        'liveness_%', 'speechiness_%']

            # Create radar chart with updated attribute list
            fig = create_radar_chart(mean_values_top_100, audio_features, att_list)
            st.plotly_chart(fig)
        else:
            st.error("Track not found or an error occurred. Please try again.")
            st.write("Either your artist name or track name did not match with Spotify's track catalogue.")
    except Exception as e:
        st.error(f'An error occurred: {str(e)}')

    # Show histogram for BPM
    st.header('BPM Distribution Comparison:')
    fig_bpm = plt.figure(figsize=(10, 6))
    sns.histplot(df_sorted_100['bpm'], bins=50, kde=True)
    plt.axvline(x=df_sorted_100['bpm'].mean(), color='red', linestyle='dashed', linewidth=2, label="Mean BPM")
    plt.axvline(x=129.964, color='green', linestyle='solid', linewidth=2, label='IMP. SWITCHing')
    plt.title('Distribution of Beats Per Minute (BPM)')
    plt.xlabel('BPM')
    plt.ylabel('Frequency')
    plt.legend()
    st.pyplot(fig_bpm)

    # Show histogram for Loudness
    ...

コードが長いので大事じゃない部分は省略していますが、こんな感じの返答が返ってきました。
これ、Python言語の基本原理において大きなミスをしています。どこにミスがあるか、分かりますか?

🐍Python言語のスコープとは?

Python言語を使用して関数を定義する際に、変数がどう扱われて設定されているかを注意する必要があります。スコープとは変数がアクセス可能かどうかを決定するルールで、そのルールに従っていないと、プログラムを実行した時に思い通りに動かなかったり、変数によるエラーが発生してしまいます。
Pythonには以下の4種類のスコープがあり、プログラム内で変数を使用する際、次の順序で変数を検索します。

ローカルスコープ

関数内で定義された変数はローカルスコープを持ちます。つまりこのレベルの変数はその関数内のみでアクセスが可能です。

def example():
    local_variable = 10
    print(local_variable)

example()  # 出力: 10

ここで、local_variableexample 関数内で定義されていて、ローカルスコープを持っています。ローカルスコープは、変数が特定の関数内でのみアクセス可能で、関数の実行が完了すると通常は破棄されるスコープです。
example 関数が呼び出されると、local_variable の値が出力されます。ただし、local_variableexample 関数の中でのみ有効であり、関数の外からはアクセスできません。

エンクロージングスコープ

これはネストされた関数(関数の中に定義されている関数)に適用されます。もし変数がローカルスコープで見つからない場合、Pythonはエンクロージングスコープで検索します。

def outer():
    outer_variable = 20

    def inner():
        print(outer_variable)

    inner()

outer()  # 出力: 20

ここで、outer_variableouter のローカルスコープに定義されています。inner outer の内部で定義されると、これはネストされた関数となり、そのローカルスコープ(inner のスコープ)だけでなく、エンクロージングスコープ(outer のスコープ)の変数にもアクセスできます。

この場合、outer_variableinner にとってエンクロージングスコープにあります。inner のローカルスコープに直接存在していなくても、Pythonは innerouter の内部で定義されていることを認識し、エンクロージングスコープの変数にアクセスできるようになります。

outer 内で inner が呼び出されると、outer_variable の値を出力し、エンクロージングスコープから変数をアクセスして使用できることが示されます。

このエンクロージングスコープから変数にアクセスする概念はクロージャとして知られており、内部の関数がその含まれる(エンクロージング)関数のスコープ内の変数の値を「覚えている」ことを可能にします。

グローバルスコープ

スクリプトまたはモジュールのトップレベルで定義された変数はグローバルスコープを持ちます。このグローバル変数はどの部分のコードからでもアクセスが可能です。

global_variable = 30

def print_global():
    print(global_variable)

print_global()  # 出力: 30

ここで、global_variable はスクリプトまたはモジュールのトップレベルで定義されており、グローバルスコープを持っています。これはどの部分のコードからでもアクセス可能です。

このコードでは、print_global 関数が呼び出されると、global_variable の値が出力されます。print_global 関数内で global_variable にアクセスできるのは、global_variable がグローバルスコープで定義されているためです。

グローバルスコープの変数はどこからでもアクセスできるため、関数の外で定義された変数は関数内で使用できます。

ビルトインスコープ

これはPythonで事前に割り当てられた名前を指します。例としてはprint()len()のような組み込み関数があります。このような名前は特にライブラリーなどを呼び込まなくても、常にコードのどの部分からでもアクセス可能です。

print(len("hello"))  # 出力: 5

変数を使用する際、Pythonは次の順序でそれを検索します:
ローカルスコープ、
エンクロージングスコープ、
グローバルスコープ、
そして最後にビルトインスコープです。
変数がこれらのスコープのいずれにも見つからない場合、Pythonは NameError を発生させます。スコープを理解することは、特に大きなプロジェクトでクリーンで保守可能なコードを書くために重要です。

🛑で、どこが間違ってたの?

もう一度ChatGPTのアウトプットに戻って見てみましょう。

# Load local file containing 2023's stream information
def load_stream_data():
    # ... (Your existing code)

    # Update the attribute list to include 'loudness' and 'bpm'
    att_list = ['danceability_%', 'valence_%', 'energy_%', 'acousticness_%', 'instrumentalness_%',
                'liveness_%', 'speechiness_%', 'loudness', 'bpm']

    df_sorted_100 = df_sorted.iloc[:100, :]

    # ... (Your existing code)
    return selected_atts

問題の始まりはこの関数です。この関数の中に
df_sorted_100 = df_sorted.iloc[:100, :]
この一行が見えますが、これはdf_sorted_100という変数を定義しています。

🤔では問題です:


df_sorted_100はどの種類のスコープに当てはまるでしょう?

  1. ローカル

  2. エンクロージング

  3. グローバル

  4. ビルトイン

💭💭💭💭💭💭💭💭💭

💡正解は

1.ローカルです。

この関数から抜け出してもう少し下のほうに読み進めていくと以下のIfから始まるコンディショナル・ステートメント(条件文)が見えます。

if compare_button:
    st.header('Comparison Radar Chart:')
    # Error Handling
    try:
        # Load top 100 stream data
        df_sorted_100, mean_values_top_100 = load_stream_data()
        # Get Spotify Data
        audio_features = get_spotify_data(artist_name, track_name)
        if audio_features:
            # Update the attribute list to include 'loudness'
            att_list = ['danceability_%', 'valence_%', 'energy_%', 'acousticness_%', 'instrumentalness_%',
                        'liveness_%', 'speechiness_%']

            # Create radar chart with updated attribute list
            fig = create_radar_chart(mean_values_top_100, audio_features, att_list)
            st.plotly_chart(fig)
        else:
            st.error("Track not found or an error occurred. Please try again.")
            st.write("Either your artist name or track name did not match with Spotify's track catalogue.")
    except Exception as e:
        st.error(f'An error occurred: {str(e)}')

    # Show histogram for BPM
    st.header('BPM Distribution Comparison:')
    fig_bpm = plt.figure(figsize=(10, 6))
    sns.histplot(df_sorted_100['bpm'], bins=50, kde=True)
    plt.axvline(x=df_sorted_100['bpm'].mean(), color='red', linestyle='dashed', linewidth=2, label="Mean BPM")
    plt.axvline(x=129.964, color='green', linestyle='solid', linewidth=2, label='IMP. SWITCHing')
    plt.title('Distribution of Beats Per Minute (BPM)')
    plt.xlabel('BPM')
    plt.ylabel('Frequency')
    plt.legend()
    st.pyplot(fig_bpm)

で、終わりの方にこの2行があります。

sns.histplot(df_sorted_100['bpm'], bins=50, kde=True)
plt.axvline(x=df_sorted_100['bpm'].mean(), color='red', linestyle='dashed', linewidth=2, label="Mean BPM")

ここがエラーです。
df_sorted_100という変数はload_stream_data()という関数内でのみ有効で、関数から外にでてしまったらこの変数にはアクセスできないからです。
つまりChatGPTはローカル変数であるdf_sorted_100をグローバル変数と勘違いして処理をしようとしてしまい、それによりエラーを引き起こした事になります。

ってか、おーーーーーい!!

How are you loading df_sorted_100 dataset under the `if compare_button:`? df_sorted_100 is loaded under the first function, and because of Python scoping, you cannot access the df_sorted_100 from this function. You have to load the data separately if I need to access the data again for the streamlit app main code. You are written in Python, correct? I think this is the fundamental python rule.

不満爆発w

スコープのルールは守らなきゃだめですよ、と言ってやったw
仮にもあなたの言語モデルはパイソンで書かれてるんでしょ?基本原理は理解しないとダメじゃん、って不満ぶちまけましたw

Certainly, you are correct. I apologize for the oversight. Let's address the scoping issue and adjust the code accordingly. Additionally, I'll make sure to exclude BPM and Loudness from the radar chart. Here's the revised code:

丁寧な回答ありがとう

それでもやはり言語モデル。ChatGPTは丁寧に返答してくれて、間違えもちゃんと直してくれました。

不満ぶちまけてごめんね、大人げなかったわ。


AIも私も学び途中

だからあくまでも、AIツールをコードアシスタントやペアプログラミングのパートナーとして効率化を図る目的で使うけれども、コードの隅から隅まで何が起こっているのか把握できるのは誰でもない自分自身なのです。

なので、間違ったアウトプットがいつこちらに送られてきてもどこが間違っているのかジャッジできるようにする為にも、バグを見つけられる方法(デバッグと呼びます)を幾つか知っておくと役立つかと思います。

時々頼りすぎてるかもなー、とか思う時もありますが、適度な距離を保ちながらデータの世界にもっと入り込んでもっと学んでいきたいと思います。

余談

ちなみにSpotifyの音楽データアプリですが、あれからまた色々な機能を付け加えました。最近流行っていると言われている日本の楽曲をこのアプリを使って調べたりしています。
これについてはまたアップデート記事を書きます。

追記:アップデート(11/30)

その後、ChatGPTのアウトプットを見返してみたところ、もっと間違えを見つけました。というかここの部分でもうすでにエラーが発生する事態。

try:
        # Load top 100 stream data
        df_sorted_100, mean_values_top_100 = load_stream_data()

このload_stream_data()がdf_sorted_100(データ)をリターンすると想定しているコードですが、このload_stream_data()という関数は、見てもらえれば分かるかと思いますが、df_sorted_100はリターンしないんです。スコープの問題に達する前にこのリターンするはずのオブジェクトが存在しないのでここでエラーが発生する事になります。また一つ勉強になりました。



🖊️何か気づいたことやご意見などがあればコメント欄からお願いします。チャンネルフォローもよろしくお願いします。

🚀 疑問に思った事はデータ化して自分なりの答えを探していこうと思います。そしてその答えがどこかにいる誰かの為になる事を願っています。


この記事が気に入ったらサポートをしてみませんか?