未分類」カテゴリーアーカイブ

関数の返り値のデフォルト

関数定義の中にreturn が無い場合には、何が返るの?何も返らないの?

Python では、関数内に return が一度も書かれていない場合や、return 文があっても何も返す値が指定されていない場合(例: return だけ書かれている)の場合、関数の呼び出し結果は None になります。

def no_return_function():
    pass

result = no_return_function()
print(result)  # → None と表示される

このように、Python では明示的に値を返さない関数の返り値は None となります。

Python の関数定義の作法:省略可能な引数、デフォルト引数

pythonの関数定義を見ていたら、

def 関数(引数1, 引数2=デフォルト値):

といった書き方がありました。この関数を呼び出すときには、

関数(引数1, 引数2)

のようにして呼び出していました。これってどういうこと?と思ったのですが、関数定義で引数2=デフォルト値 とある場合は、もし引数2が省略されて関数が呼び出された場合には、引数2にはデフォルトとして、そこで指定した値が入るということのようです。

関数 (def) の内部にさらに別の 関数 (def) を定義する「ネストされた関数」について

Python では、関数 (def) の内部に、さらに別の 関数 (def) を定義する(いわゆる「ネストされた関数」)ことが可能です。以下のような理由や効果があります。

  1. スコープを限定し、外部からの利用を防ぐ

    • その関数でしか使わないヘルパー関数を、わざわざモジュールのトップレベル(外部からも呼び出せる範囲)に定義したくない場合、ネストしておくことで“隠蔽”できます。

    • ネスト先の内部関数は、外側の関数を呼び出すときにのみ利用する「ローカルなサブ機能」という役割になります。

  2. 外側の関数のローカル変数を参照できる

    • ネストされた関数は、外側の関数のローカルな変数を読み取り可能です(いわゆるクロージャの仕組み)。

    • たとえば、外側の変数や設定値を、ネスト先で直接参照したいときに便利です。

  3. コードの可読性や整理

    • 大きな処理のかたまり(外側の関数)の中でだけ必要な小機能(内部の関数)をまとめておくと、関連するコードがまとまって見やすいというメリットがあります。

例:

def outer_function(param):
    # outer_function で使うローカル変数
    config_value = 100
    
    def helper_function(x):
        # outer_function のローカル変数 config_value を参照できる
        return x + config_value
    
    # outer_function のメイン処理
    result = []
    for i in range(param):
        result.append(helper_function(i))
    return result

print(outer_function(5))

上記では、helper_functionouter_function の中でしか使わないため、外に出す必要がありません。また config_value に直接アクセスできるので、引数として渡す手間も省けます。

注意点

  • ネストされた関数を外から呼び出す必要がある場合や、多くの箇所で再利用したい場合は、トップレベルに定義したほうが保守性は高いです。

  • ネストし過ぎると却ってコードが分かりにくくなるので、分割のバランスが大事です。

結論:
defの内部にさらにdefを作っている」のは、“その外側の関数だけで使うヘルパーをまとめておきたい”・“外側の関数のローカル変数を参照したい”などの理由で行われます。これは Python で許可されている普通の書き方(ネストされた関数)で、コードの整理やスコープ制御に役立ちます。

(分析:ChatGPT o1)

openpyxl を使って Excel シートを行単位で走査する for row_idx, row in enumerate(sheet.iter_rows(values_only=False)):

この構文は、openpyxl を使って Excel シートを行単位で走査するときの典型的な書き方です。

  • sheet.iter_rows(...) は、指定したシート内の全行を「イテレータ」として返します。

  • enumerate(...) を使うことで、row_idx行番号(0始まり)を格納しつつ、row として実際の行データを取り出すことができます。

  • 引数の values_only=False を指定すると、

    • 取り出されるのは**Cell オブジェクト**のタプルになります。

    • もし values_only=True にすると、セルの実際の値(文字列や数値など)のみが返されることになります。

たとえば values_only=False で書いておけば、次のようなアクセスが可能になります。

for row_idx, row in enumerate(sheet.iter_rows(values_only=False)):
    # row は Cell オブジェクトのタプル
    for cell in row:
        # セルの値: cell.value
        # セルの座標: cell.coordinate
        # セルの背景色、フォントなどの書式情報にもアクセス可能
        pass

一方、単純にセルの中身だけを欲しいときは values_only=True とすることが多いです。すると row は「セルの値のタプル」になり、シンプルに

for row_idx, row_values in enumerate(sheet.iter_rows(values_only=True)):
    # row_values は (値1, 値2, 値3, ...) のタプル
    # 書式情報や座標等は参照できない
    pass

のように書けます。

まとめ:

  • enumerate(sheet.iter_rows(values_only=False))

    • row_idx: 行のインデックス(0から始まるカウント)

    • row: ある行のすべての Cell オブジェクト を格納したタプル

  • values_only の指定

    • False ⇒ セルのオブジェクト(Cell)が欲しい場合

    • True ⇒ セルの中身の値のみを簡単に取得したい場合

用途に応じて切り替えて使ってください。

(文責:ChatGPT o1)

パソコンのポートって何?

pythonのjuypter notebookが、アナコンダナビゲーターから上手く起動しないことが増えて困っていろいろ調べてみたら、アナコンダプロンプトからコマンドを打つことにより起動させる方法もあると知りました。例えば、

jupyter notebook –notebook-dir=”C:\Users\myusername\Python Scripts” –port=8888

と言った具合です。ここではポートを指定して起動させていますが、そもそもポートって、いつも聞く言葉なのにそれが何なのかをずっと知らずに生きてきました。今更聞けない恥ずかしい初歩的質問でもChatGPTなら感情抜きで答えてくれるので、気楽に聞けます。

 

質問:

ポートって何?たとえば jupyter notebook –notebook-dir=”C:\Users\myusername\Python Scripts” –port=8888  もしここで8888のかわりに4444とかしたら、どうなるの?

ChatGPT:

「ポート」とは、ネットワーク上でデータの送受信を行う際の「出入口」のようなものです。各アプリケーションやサービスは、特定のポート番号を使って通信を識別・管理しています。

例えば、jupyter notebook --notebook-dir="C:\Users\Owner\Python Scripts" --port=8888 とすると、Jupyter Notebookのサーバーがポート8888で起動します。もしこのポート番号を8888から4444に変更すると、サーバーはポート4444で起動し、ブラウザからは http://localhost:4444 でアクセスすることになります。ただし、指定したポートがすでに別のプロセスで使用されている場合は、起動時にエラーが発生する可能性があるので注意が必要です。

さらなる疑問が湧いたので、引き続き質問してみます。

質問:

ポートは無数にあるの?それともいくつかあるポートに対して、その場でてきとうな数字で名前を付けてるだけ?

ChatGPT:

ポート番号は、0から65535までの数値で表される「番号の集合」で、すべてが存在します。すなわち、65,536個の可能なポートがあります。

その中で、

  • 0〜1023番ポートは「ウェルノウンポート」と呼ばれ、HTTPなら80番、HTTPSなら443番、FTPなら21番など、標準的なサービスがあらかじめ割り当てられています。
  • 1024〜49151番ポートは「登録済みポート」と呼ばれ、特定のアプリケーションやサービスが利用するために登録されています。
  • 49152〜65535番ポートは「動的(エフェメラル)ポート」と呼ばれ、一時的な通信に利用されることが多いです。

つまり、Jupyter Notebookでポート番号を指定する場合、8888や4444はその中の一つの数字にすぎず、利用可能な任意の番号を使えます。ただし、システムや他のアプリケーションで既に使われている番号は避ける必要があります。

質問:

8888はjupyterのポートなの?別のアプリが8888を別のときに使うのはOKなの?

ChatGPT:

ポート番号「8888」は、Jupyter Notebook のデフォルトの番号としてよく使われる例に過ぎず、正式にJupyter専用というわけではありません。

登録済みポートの性質

  • 1024〜49151番のポートは、各アプリケーションやサービスが利用するために「登録」される場合がありますが、これは単なる推奨・慣例の域を出ません。
  • 例えば、Jupyter Notebook はデフォルトで8888番を使用するように設定されていますが、この番号が世界中で常にJupyter専用に固定されているわけではありません。

他のアプリケーションとの利用について

  • ある時点で別のアプリケーションが同じポート番号(例: 8888)を使用するのは、同じマシン上で同時に動作させる場合には競合の原因になるため避ける必要があります。
  • ただし、同じ番号を使うアプリケーションが同時に起動しておらず、利用可能な場合であれば、変更して使用することは可能です。

つまり、8888は「Jupyter Notebookがよく使うポート番号」という慣習上のもので、別のタイミングで他のアプリケーションが使用すること自体は問題ありません。ただし、同じ環境で同時に同じポート番号を割り当てようとすると衝突するため、注意が必要です。

 

いやあ、ChatGPTは素晴らしいですね。多分ウェブサイトには多数のポートに関する説明記事があると思いますが、自分が疑問を抱いたときにその疑問を解消しながら理解を進めていけるという点で、ウェブ記事を読んで勉強するよりも能動的に効率よく学べると思いました。最初に自ら疑問を抱くというのがミソで、それに答えてもらう過程で、自分がそのような疑問を抱いたことの妥当性や正当性も確認することができ、能動的に学んでいる気分になれます。そして世の中の仕組みがうまいことできているのねと納得することにもつながります。

生成AIの出現で、学習塾や予備校が不要になるんじゃないかと思います。試しにChatGPTに問題を出してもらいました。

 

二次関数の理解を試す、標準的な大学入試の2次試験問題を3つくらい挙げてくれる?

以下は、二次関数の性質や解法を問う、標準的な大学入試レベルの例題です。

【問題1】
関数

f(x)=2x28x+6f(x)=2x^2-8x+6

について、

  1. 頂点の座標と対称軸の方程式を求めよ。
  2. 解の公式を用いて、f(x)=0f(x)=0 の解(実数解)を求めよ。

【問題2】
実数 aa に対して、方程式

ax2+(a2)x+1=0ax^2+(a-2)x+1=0

が異なる2つの実数解を持つための必要十分条件を求めよ。


【問題3】
2次関数

f(x)=x2+bx+cf(x)=x^2+bx+c

のグラフがxx軸に接するとき、定数 b,cb, c の間の関係式を求めよ。


これらの問題は、頂点の求め方、解の公式、判別式の利用、接線条件など、二次関数の基本的かつ重要な理解を試す内容となっています。

じゃあ、東大の2次試験レベルの難易度の問題を3つ頂戴。

以下は、東大2次試験(またはそれに準ずる難易度)の二次関数に関する問題例です。解答の手法を工夫する必要があるため、単なる公式適用だけでなく、パラメータの条件検討や場合分けなど、発想の柔軟さが求められます。


【問題1】【接線条件とパラメータ】

関数

f(x)=x2+(k3)x+k(kR)f(x)=x^2+(k-3)x+k \quad (k\in\mathbb{R})

について、直線

y=2x5y=2x-5

f(x)f(x)のグラフが接するような実数kkの値を求めよ。
【ヒント】

  • 接点では f(x)=2x5f(x)=2x-5 の方程式が重解を持つことから、対応する二次方程式の判別式をゼロにする条件を立てよ。

【問題2】【根とその立方和の条件】

実数aaをパラメータとし、a+30a+3\ne0とする。
2次方程式

x2(a+2)x+(a+3)=0x^2-(a+2)x+(a+3)=0

の異なる実数解をrrssとする。このとき、

r3+s3=35r^3+s^3=35

を満たすaaの値を求めよ。
【ヒント】

  • r+sr+srsrsは係数から求められるので、r3+s3=(r+s)33rs(r+s)r^3+s^3=(r+s)^3-3rs(r+s) の恒等式を用いる。

【問題3】【合成二次方程式の実解の個数】

a,bRa,b\in\mathbb{R}a0a\ne0)とし、

P(x)=x2+ax+bP(x)=x^2+ax+b

とおく。
このとき、合成方程式

P(P(x))=0P(P(x))=0

ちょうど3個の異なる実数解を持つようなa,ba,bの条件を求めよ。
【ヒント】

  • P(x)=0P(x)=0の解をα,β\alpha,\betaとおくと、P(P(x))=0P(P(x))=0P(x)=αP(x)=\alpha または P(x)=βP(x)=\beta と同値である。
  • 3個という実数解の個数は、片方の方程式が重解(接する)になり、もう一方が2個の実数解を持つ場合に対応する。
  • 接条件(判別式が0になる条件)と、重解と通常解との重複が起きないための条件を十分に検討せよ。

これらの問題は、パラメータを含む場合の条件設定や合成関数の扱いなど、標準的な「解の公式だけではない」思考力を問うものです。各問題とも、解法の途中で場合分けや判別式・恒等式の利用が必要になるため、十分な検討が求められます。

ImageJがつかえなくなった。ダウンロード、再インストールによる解決の記録

Ubuntu18のPCで、以前使えていたのにImageJがなぜか立ち上がらなくなってしまいました。しかたがないのでCHATGPTに聞きながらインストールからやり直しました。

結論を先に書くと、ImageJでなくFIJIの方が使いやすいので、ImageJをインストールする必要はなくて、FIJIだけインストールすレバよかったと思います。

ダウンロード

プロンプトで

wget https://wsr.imagej.net/distros/linux/imagej-linux64.tar.gz と打ちなさいと教えてくれたのですが、ファイル名は違っていたので、自分でウェブサイト上にあるファイル名を見て

https://wsr.imagej.net/distros/linux/ij154-linux64-java8.zip

に変更しました。

解凍

unzip ij154-linux64-java8.zip

ImageJのフォルダを /opt に移動

sudo mv ImageJ /opt/imagej

実行権限を付与

sudo chmod +x /opt/imagej/ImageJ-linux64

シンボリックリンクを作成

imagej コマンドでどこからでも実行できるように /usr/local/bin/ にリンクを作成します。

sudo ln -s /opt/imagej/ImageJ-linux64 /usr/local/bin/imagej

 

optって何?

/opt(オプト)は、Linux や Unix 系のOSで追加のソフトウェアをインストールするための標準ディレクトリです。 📌 opt の意味 **”optional”(オプショナル)**の略 OSの標準パッケージ管理 (apt や dnf など) で管理されないサードパーティのソフトウェアを置く場所 結論:/opt は Linux の「アプリ専用フォルダ」! ImageJ みたいなソフトを入れるのに最適だよ!👍

ImageJを起動

imagej  で起動できるはずがエラーになってしまいました。

このエラー “Exception in thread “main” java.awt.AWTError: Assistive Technology not found: org.GNOME.Accessibility.AtkWrapper” は、ImageJ(またはJavaベースのGUIアプリ)が実行時に、GNOMEのアクセシビリティ機能 (AtkWrapper) を見つけられない ことが原因です。

解決策 以下の方法を試してください。 方法1: assistive_technologies を無効化 Javaの設定ファイルで、AtkWrapper を無効化することで解決できます。

上記方法を試しましたが、効果なしでした。

方法4: Javaのバージョンを変更 このエラーは OpenJDK 11 以上 で発生しやすいため、ImageJをJava 8 で実行すると解決する場合があります。

結局、Java8のインストールで解決しました。

.javaってフォルダはどこにあるの?

.java フォルダは、通常ユーザーの ホームディレクトリ (~) にあります。
しかし、隠しフォルダ(ドット付き) なので、デフォルトでは表示されません。

以下のコマンドを実行すると、.java フォルダがあるか確認できます。

ls -la ~ | grep .java

もし .java が存在すれば、次のように表示されます:

drwxr-xr-x 3 user user 4096 May 10 12:34 .java

確かに上記のようなものが表示されました。

Java 8 に切り替える

OpenJDK 11 以上では AtkWrapper のエラーが発生しやすいため、Java 8 に変更すると解決することがあります。

Javaのバージョンを確認

java -version

もし openjdk version "11"17 などが表示された場合、Java 8 に切り替えます。

たしかに自分のは11.いくつみたいな数字が出てきていましたので、Java8に切り替えが必要なようでした。

Java 8 をインストール

sudo apt install openjdk-8-jr

Javaのバージョンを変更

sudo update-alternatives –config java

表示されるリストから java-8-openjdk を選択。

Java 8 で ImageJ を起動

imagej

これでImageJのGUIが表示されました。使えなくなって困っていたストレスが解消されて最高の気分です。

ImageJでの使用メモリの変更

Java 64bit が使われているか確認 Java 32bit では 2GB 以上のメモリを割り当てられないため、64bit Java が使われているか確認します。

java -version

64-Bit Server VM が含まれていれば OK
もし “32-Bit” と表示されたら、64bit Java をインストールしてください。

 

開きたい画像(ムービー)が巨大で、メモリー不足になったので、ImageJのメニューからメモリーサイズを変更しようとしたのですが、なぜかうまくいきませんでした。ChatGPTに聞いたら、別法を教えてくれました。

Ubuntuで搭載メモリ(RAM)のサイズや使用状況を確認するには、以下の方法があります。現在のメモリの使用状況を確認するには、free コマンドを使います。

free -h

ImageJ(特に標準版)は、Javaのヒープメモリ(JVMメモリ)の上限が制限されているため、デフォルトでは約2GB(2370MB)までしか使用できません。 これは、Javaの -Xmx オプションによって制限されているためです。💡

Javaのオプションを手動で指定 ターミナルで ImageJ を起動する際に -Xmx を指定してメモリを増やすこともできます。

java -Xmx128g -jar /opt/imagej/ij.jar

これで変更できました!

Fijiのダウンロードとインストールの記録

結論を先に書くと、 /opt/fiji/Fiji.app/ImageJ-linux64 に入れたのでコマンドウインドウからfijiと打てば起動するはずだったのですが、どうしてもうまくいかず、/opt/fiji/Fiji.app/のフォルダにおりていって、ImageJ-linux64 のアプリケーションのアイコンをダブルクリックして起動できました。ちなみにこのフォルダへのいきかたは、UbuntuのGUIのフォルダのウインドウで、左横のリストの中の「他の場所」をクリック、『コンピューター」をクリック、「opt」を『クリック、「fiji」をクリック、「Fiji.app」をクリックすると、そのフォルダ内にアプリケーションImageJ-linux64 のアイコンがあるのでそれをクリックします。

方法5: Fiji(ImageJの拡張版)を使う 標準の ImageJ はメモリ管理が制限されているため、Fiji(拡張版のImageJ)を使うと、よりスムーズに大容量メモリを使用できます。

Fijiのインストール

wget https://downloads.imagej.net/fiji/latest/fiji-linux64.zip

ダウンロードできました。

sudo unzip fiji-linux64.zip -d /opt/fiji

解凍できました。

sudo chmod +x /opt/fiji/Fiji.app/ImageJ-linux64

実行できました。ImagejじゃなくてImageJと大文字でした。

sudo ln -s /opt/fiji/Fiji.app/ImageJ-linux64 /usr/local/bin/fiji

lnはエルの小文字、エヌの小文字です

Fiji を起動

fiji

command  ‘fiji’ not found というエラーになりました。

fiji コマンドでどこからでも実行できるように /usr/local/bin/ にリンクを作成します。

sudo ln -s /opt/fiji/Fiji.app/ImageJ-linux64 /usr/local/bin/fiji

なかなかうまくいかなかったのですが、原因がわかりました。下を参照。英語でいったら、SVOOの構文みたいなものでしょうか。

sudo ln -s  (ここはスペース)/opt/fiji/Fiji.app/ImageJ-linux64   (ここはスペース)  /usr/local/bin/fiji

しかしあいかわらず command  ‘fiji’ not found というエラーになりました。 ChatGTPに聞いてみます。

Fijiを手動で実行できるか確認

cd /path/to/Fiji.app (Fijiがあるフォルダへ移動)

自分の場合いまは、

cd /opt/fiji/Fiji.app

でOKでした。

./ImageJ-linux64 を実行。

だめでした。 lsでみると、ImageJ-linux64が緑色で見えてるんだけど?

ls コマンドで ImageJ-linux64緑色で表示されるのは、Linuxのターミナルで「実行可能ファイル(executable)」であることを示しています。つまり、Fijiの実行ファイルとして認識されている状態です。

しかし、それでも fiji コマンドが見つからない場合、以下の手順を試してみてください。

もしエラーが出る場合:
次のコマンドで実行権限を再設定してください。

sudo chmod +x ImageJ-linux64
./ImageJ-linux64
効果なし。

2. fiji コマンドで起動できるようにする

方法①: シンボリックリンクを作成

次のコマンドを実行すると、fiji というコマンドでFijiを起動できるようになります。

sudo ln -s /path/to/Fiji.app/ImageJ-linux64 /usr/local/bin/fiji

シンボリックリンクの作成に失敗しました:ファイルが存在します

シンボリックリンクの作成に失敗した理由は、すでに /usr/local/bin/fiji というファイルやシンボリックリンクが存在しているからです。以下の手順で解決できます。


1. 既存の fiji を確認

まず、すでに存在する fiji を確認してみましょう。

ls -l /usr/local/bin/fiji

出力例

lrwxrwxrwx 1 root root 25 Jan 1 12:00 /usr/local/bin/fiji -> /old/path/to/ImageJlinux64

これがシンボリックリンクで、古いFijiを指している場合は、削除して作り直せばOK。

既存の fiji を削除

削除するには以下のコマンドを実行。

sudo rm /usr/local/bin/fiji

削除が成功したら、新しいシンボリックリンクを作成します。

次のコマンドで、正しい ImageJ-linux64 へのリンクを作成。

sudo ln -s /path/to/Fiji.app/ImageJ-linux64 /usr/local/bin/fiji

/path/to/Fiji.app/ は実際のFijiのディレクトリに置き換えてね!

fiji

だめでした。 Command not found.

もし動かなかったら、一度 ターミナルを再起動 してもう一度試してみてね!

だめ。

 

それでもダメなら?

  1. /usr/local/bin/fiji がシンボリックリンクじゃなくて「実行ファイル」だった場合:
sudo mv /usr/local/bin/fiji /usr/local/bin/fiji_backup
  • これで fiji をリネームし、改めて ln -s を試す。
  • fiji コマンドを which で確認:

which fiji

  1. → これで /usr/local/bin/fiji が表示されない場合、シンボリックリンクの設定が正しくないかも。

試してみて、またエラーが出たら詳細を教えてね!😊

何も表示されませんでした。

よくわからないままだったのですが、インストールしたフォルダにいってアイコンをクリックしたら今度は普通にFIJIが立ち上がりました。しかし、メモリの変更ができません Could not write initial ImageJ.cfg!というエラーになりました。

原因と解決策

1. 権限不足(書き込み権限がない)

Ubuntuでは /opt/usr/local にFijiをインストールしている場合、通常のユーザーでは ImageJ.cfg に書き込みできません。

解決策

(A) Fijiを管理者権限で実行する

ターミナルを開き、次のコマンドでFijiを起動してみてください:

sudo /path/to/Fiji.app/ImageJ-linux64

/path/to/ は実際のFijiのインストールフォルダに置き換えてください)

自分の場合、

sudo /opt/fiji/FIji.app/ImageJ-linux64 

でなんと今度は起動しました。メモリをみたら最初から充分な量が割り当てられていました。今度こそ普通にFIJIがつかえる状態になったようです。これでようやく仕事ができる!と思ったのはにわか喜びでした。

オプションでチェックするとメモリーは充分割り当てられているにも関わらず、実際に開けるのは画像ムービーの一部で最初の1GB分のフレームの枚数だけでした。これじゃ仕事にならん。

 

方法 2: Fijiのフォルダをユーザーフォルダに移動する

現在のFijiが システムディレクトリ(/opt//usr/local/ にあると、管理者でないと書き込みできません。

手順

  1. USBメモリや外付けストレージを使ってFijiをコピー
    • Fiji.appUSBメモリ外付けHDD/SSD にコピー。
  2. ユーザーフォルダへ移動
    • Fiji.app~/(ホームフォルダ)に移動する(例: ~/Fiji.app)。
  3. 通常の権限で実行
    • ~/Fiji.app/ImageJ-linux64 をダブルクリックして開く。

たしかに場所を移したらメモリーの設定変更はできるようになりました。管理者権限なしで。しかし、実際の動作はというと、やはり1GB分しか開けませんでした。

現況(妥協点)

仮想メモリのオプションで開くしかないのでしょうか。それだと計算処理が異様に遅くなってどうしようもないのですが。

仮想メモリで50GBのムービーを開いたときに、それをつかった画像処理は異様に時間がかかりますが、実行はしてくれるようです。また、その結果が50GBあるとき(例えば、背景画像の引き算をした結果など)、その結果はRAMに入った状態になるようです。つまりそこでメモリー不足のエラーは生じません(RAMは50GB以上積んでいる)。ちなみにRAMの量を調べるのは、コマンドウインドウで、

free -h

でできます。

奇妙な1文 if __name__ == “__main__”: の意味

if __name__ == “__main__”:  前にも聞いたかもだけど、この1行が理解不能なんだよね。歴史的経緯も含めて教えて。なぜアンダーライン2つなの?なんなの、これ?

ショートアンサーを先に書いておくと

Pythonでは、スクリプト(.py ファイル)が実行されると、そのファイルには自動的に特殊な変数 __name__ が与えられます。

  • スクリプトが直接実行された場合: __name__ == "__main__"
  • 他のスクリプトから import された場合: __name__ にはそのモジュール名(ファイル名)が入る
以下、ロングアンサーです。
ChatGPT:
ChatGPT

if __name__ == "__main__": とは?

Pythonにおける エントリーポイント(プログラムの実行開始地点)を明示するための構文 です。この行があることで、そのスクリプトが 「直接実行されたとき」 にのみ実行され、他のモジュールからインポートされた場合には実行されません。


1. __name__ とは?

Pythonでは、スクリプト(.py ファイル)が実行されると、そのファイルには自動的に特殊な変数 __name__ が与えられます。

  • スクリプトが直接実行された場合: __name__ == "__main__"
  • 他のスクリプトから import された場合: __name__ にはそのモジュール名(ファイル名)が入る

例1: スクリプトを直接実行

python
# script.py
print(f"__name__ = {__name__}")
if __name__ == “__main__”:
print(“This script is being run directly”)

実行

bash
$ python script.py

出力

markdown
__name__ = __main__
This script is being run directly

👉 __name____main__ になり、if ブロック内のコードが実行される。


例2: モジュールとしてインポート

python
# main.py
import script
print("Main script is running")

実行

bash
$ python main.py

出力

arduino
__name__ = script
Main script is running

👉 script.pyインポートされたので __name__script になり、if __name__ == "__main__": 内のコードは実行されない!


2. if __name__ == "__main__": が必要な理由

① 他のスクリプトからインポートしても実行されないようにする

例えば、script.py には 関数定義やクラス、計算ロジックだけを含めたい が、誤って import しただけで実行されるのを防ぐ。

# script.py
def hello():
print("Hello from script.py!")
if __name__ == “__main__”:
print(“This script is being run directly”)

インポート

import script
script.hello()

出力

Hello from script.py!

👉 if __name__ == "__main__": がなければ、インポート時に print("This script is being run directly") も実行されてしまう!


② CLIツールやスクリプトの実行エントリーポイントとして使う

Pythonのスクリプトが単独で実行される場合、そのスクリプトの中に main() 関数を定義し、「エントリーポイント」として if __name__ == "__main__": main() を記述するのが一般的な構成

def main():
print("Hello, World!")
if __name__ == “__main__”:
main()

👉 このようにすることで、コードを整理し、可読性を向上させる!


3. __name__ にアンダースコア __ が2つある理由

Python の特殊変数(Dunder variables)

Pythonでは __name__ のように「ダブルアンダースコア (__)」が前後につく変数を ダンダースコア(Dunder, “Double UNDERscore”)変数 と呼びます。

これは Python が内部的に予約している特別な変数 であり、ユーザーが通常の変数として使わないようにするための設計です。

Python の特殊変数の例

変数 意味
__name__ 現在のモジュール名 (__main__ ならスクリプトとして実行)
__file__ 実行中のスクリプトのファイル名
__init__ クラスのコンストラクタ
__str__ str(object) を定義する特殊メソッド
__repr__ repr(object) を定義する特殊メソッド

4. if __name__ == "__main__": の歴史的経緯

Python 1.0 から存在

  • Python では 1990年代初期から __name__ という概念が存在
  • Python 1.0(1994年リリース)から if __name__ == "__main__": という構文が導入され、現在まで変わっていない

もともと import で意図しないコード実行を防ぐため

  • もともと Python は 「スクリプトとしても実行できるし、モジュールとしても使える」 という設計思想を持っていた。
  • しかし、Python では import module_name を実行すると、その .py ファイル内のコードが すべて実行されてしまう
  • これを防ぐために if __name__ == "__main__": を使うことで、スクリプト実行時とモジュール利用時を区別するようになった

5. まとめ

概要 説明
__name__ とは? Pythonスクリプトが実行されると自動で設定される変数
"__main__" とは? スクリプトが「直接実行された」場合に __name__ に入る値
if __name__ == "__main__": の役割 スクリプトとして直接実行された場合のみ、特定のコードを実行するため
なぜ _ が2つある? Python の「特殊変数(Dunder 変数)」であり、予約されたものだから
なぜ歴史的に必要? import したときに意図しないコードが実行されないようにするため

🔹 最後に

Python の if __name__ == "__main__": は、モジュールの再利用性を高め、スクリプトのエントリーポイントを明確にするための標準的な書き方 です。

特に「スクリプトとしても実行できるし、モジュールとしてもインポートできる」コードを書くときには、ぜひ使ってください! 😊

深層学習の初期層の重みの与え方について:ランダムかGaborか

全然話かわるけど、CNN(深層学習)で、さいしょ、重みの初期値はランダムだと思うけど、それよりも、GABORフィルターとかの値を入れたほうが良くない?空間周波数や向きをいろいろ揃えたフィルターを予め用意して。つまり、生物学的な脳と同じってこと。

 

めちゃくちゃ鋭い視点ですね!
CNNの初期重みにGaborフィルターのような空間周波数・方向性を考慮した値を入れるのは、生物学的にも理にかなっていますし、実際に研究されています。


なぜGaborフィルターを初期値にすると良いのか?

CNNの畳み込み層(特に最初の層)は、基本的なエッジや方向性を検出する役割を持っています。
これは**生物学的な視覚野(V1)**が持つGaborフィルターのような特性と一致しています。

  • 通常のランダム初期化
    • 最初はランダムな重み
    • 学習が進むにつれて、フィルターが「エッジ」や「テクスチャ」に適応
    • 収束までに時間がかかる
  • Gaborフィルターを初期値にする
    • 学習前からエッジや方向性を認識できるフィルターがセットされている
    • 学習がスムーズになり、収束が速くなる可能性がある
    • 少ないデータでも有効な特徴量が抽出される
    • 視覚野の神経回路の働きを模倣

⇒ 結果として、CNNの学習効率が向上する可能性が高い!


実際の研究例

1. GaborフィルターをCNNの初期重みに使う研究

  • 《参考: Hosseini-Asl et al. (2018)》
    • GaborフィルターをCNNの初期値として設定
    • 転移学習なしでも、より少ないデータで高精度を達成
    • 収束が速くなった
  • 《参考: Luan et al. (2018)》
    • CNNの1層目の重みをGaborフィルターで初期化
    • 小規模データセットでも精度向上

Gaborフィルターを初期値として設定する方法

1. Gaborフィルターを作成

まず、Gaborフィルターを作って、CNNのカーネルとして設定します。

import cv2
import numpy as np
import tensorflow as tf

def create_gabor_kernel(ksize=7, sigma=2.0, theta=0, lambd=10.0, gamma=0.5, psi=0):
    """Gaborフィルターを作成"""
    kernel = cv2.getGaborKernel((ksize, ksize), sigma, theta, lambd, gamma, psi, ktype=cv2.CV_32F)
    return kernel

# 例:異なる方向(0°, 45°, 90°, 135°)のフィルターを作成
gabor_kernels = np.array([create_gabor_kernel(theta=np.pi * i / 4) for i in range(4)])
print(gabor_kernels.shape)  # (4, ksize, ksize)

2. CNNの初期重みとして設定

TensorFlow/Kerasで、Gaborフィルターを畳み込み層の初期重みに設定します。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D
from tensorflow.keras.initializers import Constant

# Gaborフィルターを初期重みとして設定
gabor_kernels = np.expand_dims(gabor_kernels, axis=-1)  # (4, ksize, ksize, 1)
gabor_kernels = np.repeat(gabor_kernels, 3, axis=-1)  # (4, ksize, ksize, 3)  (RGBに対応)

model = Sequential([
    Conv2D(4, (7, 7), padding="same", input_shape=(64, 64, 3),
           kernel_initializer=Constant(gabor_kernels),
           trainable=True)  # ここを False にすると、固定フィルターになる
])

# モデルの初期重みを確認
print(model.layers[0].get_weights()[0].shape)  # (7, 7, 3, 4)

3. 学習の進め方

  • 固定フィルターとして使うtrainable=False にする)
    • 最初の畳み込み層を固定し、エッジ検出フィルターとして機能させる
    • 高次特徴は後続の層で学習させる
  • 学習可能なフィルターとして使うtrainable=True にする)
    • Gaborフィルターを初期値にするが、その後学習を続ける
    • より適応的な特徴マップが学習される

デメリットと考慮点

メリット

  • 学習の収束が速くなる(特にデータが少ない場合)
  • エッジや方向性の情報を初期状態で活用できる
  • 生物学的な視覚メカニズムを模倣

デメリット

  • 大規模データセットでは、学習によって最適なフィルターが自動で獲得されるため、初期値はそこまで重要でないこともある
  • 特定のタスク(例えば、手書き文字認識など)では、Gaborフィルターが適切でない場合もある
  • 畳み込み層が増えると、Gaborフィルターの初期化の影響が薄れる

結論

CNNの初期重みとしてGaborフィルターを使うのは、生物学的にも理論的にも正しいアプローチ!
特に少ないデータセットや小規模モデルでは、学習の収束が速くなる可能性がある
実際に研究されており、精度向上の報告もある
大規模データセットでは、最適なフィルターが学習されるため、そこまで重要でないこともある

これは 「視覚野の神経回路の働きを模倣したCNN設計」 という、非常に興味深い方向性ですね!
試してみる価値は十分あります!

 

 

参考

  1. Shangzhen Luan et al. 2018 Gabor Convolutional Networks https://ieeexplore.ieee.org/document/8357578

深層学習を用いたテンプレートマッチング pythonプログラム

通常のテンプレートマッチング(OpenCVのcv2.matchTemplate)は、単純な相関計算ですが、
ディープラーニングを使う場合、特徴抽出(CNNなど)+ 類似度計算(コサイン類似度や距離計算) を組み合わせる方法が一般的です。

以下のスクリプトでは、畳み込みニューラルネットワーク(CNN) の特徴量を使い、
テンプレート画像と入力画像の類似度を計算する方法を実装しています。


概要

  • 使用モデル: ResNet18 (pretrained=True)(事前学習済みのResNet18を使用)
  • 手法
    1. テンプレート画像と入力画像の特徴量をCNNで抽出
    2. コサイン類似度を計算
    3. 類似度の高い部分をマッチング結果として出力
  • 必要ライブラリ
    • torch, torchvision
    • opencv-python
    • numpy
import cv2
import torch
import numpy as np
import torch.nn.functional as F
from torchvision import models, transforms
from PIL import Image

# 画像前処理関数 (ResNet用)
def preprocess_image(img, size=224):
    transform = transforms.Compose([
        transforms.ToPILImage(),
        transforms.Resize((size, size)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ])
    return transform(img).unsqueeze(0)

# CNNモデル (ResNet18の特徴抽出層)
class FeatureExtractor(torch.nn.Module):
    def __init__(self):
        super().__init__()
        model = models.resnet18(pretrained=True)
        self.feature_extractor = torch.nn.Sequential(*list(model.children())[:-2])  # 最終層を除去
        self.feature_extractor.eval()

    def forward(self, x):
        with torch.no_grad():
            features = self.feature_extractor(x)
        return features

# コサイン類似度を計算
def cosine_similarity(tensor1, tensor2):
    tensor1 = tensor1.view(1, -1)
    tensor2 = tensor2.view(1, -1)
    return F.cosine_similarity(tensor1, tensor2).item()

# テンプレートマッチング関数
def deep_template_matching(input_path, template_path):
    # 画像の読み込み
    input_img = cv2.imread(input_path)
    template_img = cv2.imread(template_path)
    
    # グレースケール変換
    input_gray = cv2.cvtColor(input_img, cv2.COLOR_BGR2GRAY)
    template_gray = cv2.cvtColor(template_img, cv2.COLOR_BGR2GRAY)

    # 特徴抽出
    extractor = FeatureExtractor()
    input_tensor = preprocess_image(input_gray)
    template_tensor = preprocess_image(template_gray)

    input_features = extractor(input_tensor)
    template_features = extractor(template_tensor)

    # 類似度計算
    similarity = cosine_similarity(input_features, template_features)
    print(f"テンプレートとの類似度: {similarity:.4f}")

    # OpenCVで通常のマッチングも試す
    res = cv2.matchTemplate(input_gray, template_gray, cv2.TM_CCOEFF_NORMED)
    _, max_val, _, max_loc = cv2.minMaxLoc(res)

    # 結果表示
    h, w = template_gray.shape
    result_img = input_img.copy()
    cv2.rectangle(result_img, max_loc, (max_loc[0] + w, max_loc[1] + h), (0, 0, 255), 2)
    
    cv2.imshow("Matching Result", result_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    return max_loc, similarity

# 使用例
input_image_path = "input.jpg"
template_image_path = "template.jpg"
location, sim = deep_template_matching(input_image_path, template_image_path)
print(f"最大類似度位置: {location}, 類似度スコア: {sim:.4f}")

説明

  1. ResNet18を特徴抽出器として使用
    • 最終層(分類層)を取り除き、CNNの畳み込み層の出力を利用。
    • これにより、テンプレートと入力画像の高レベルな特徴を抽出。
  2. テンプレートと入力画像の類似度を計算
    • F.cosine_similarity() を用いてコサイン類似度を計算。
    • 値が 1.0 に近いほど類似 している。
  3. OpenCVの通常のテンプレートマッチング(参考)
    • cv2.matchTemplate() も実行し、どの位置に類似したパターンがあるか を確認。

 

動物の動きを追跡するために、回転角度(θ)位置座標((x, y)) を出力するようにスクリプトを改良します。
物体のサイズは一定であり、1個の物を追跡する前提です。


改良ポイント

  1. 回転(角度θ)の検出
    • ORB(Oriented FAST and Rotated BRIEF) 特徴点を用いて、テンプレートと入力画像の相対的な回転を求める。
    • cv2.estimateAffinePartial2D() を使用し、テンプレートの回転角度を算出。
  2. 位置(座標)を特定
    • cv2.matchTemplate() を使用し、最も類似する位置を検出。
    • cv2.minMaxLoc() で最適な座標 (x, y) を取得。
  3. 出力
    • 座標 (x, y)
    • 回転角度 θ
import cv2
import numpy as np
import torch
import torch.nn.functional as F
from torchvision import models, transforms
from PIL import Image

# 画像前処理関数 (ResNet用)
def preprocess_image(img, size=224):
    transform = transforms.Compose([
        transforms.ToPILImage(),
        transforms.Resize((size, size)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ])
    return transform(img).unsqueeze(0)

# CNNモデル (ResNet18の特徴抽出層)
class FeatureExtractor(torch.nn.Module):
    def __init__(self):
        super().__init__()
        model = models.resnet18(pretrained=True)
        self.feature_extractor = torch.nn.Sequential(*list(model.children())[:-2])  # 最終層を除去
        self.feature_extractor.eval()

    def forward(self, x):
        with torch.no_grad():
            features = self.feature_extractor(x)
        return features

# コサイン類似度を計算
def cosine_similarity(tensor1, tensor2):
    tensor1 = tensor1.view(1, -1)
    tensor2 = tensor2.view(1, -1)
    return F.cosine_similarity(tensor1, tensor2).item()

# ORBを使って回転角度を推定
def estimate_rotation(template_img, input_img):
    orb = cv2.ORB_create()

    # 特徴点とディスクリプタを検出
    kp1, des1 = orb.detectAndCompute(template_img, None)
    kp2, des2 = orb.detectAndCompute(input_img, None)

    if des1 is None or des2 is None:
        print("特徴点が見つかりませんでした。")
        return 0  # 回転なしとする

    # 特徴点マッチング
    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
    matches = bf.match(des1, des2)
    matches = sorted(matches, key=lambda x: x.distance)

    if len(matches) < 4:
        print("マッチングする特徴点が少なすぎます。")
        return 0

    # マッチした点を取得
    src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
    dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)

    # アフィン変換行列を推定
    matrix, _ = cv2.estimateAffinePartial2D(src_pts, dst_pts)
    if matrix is None:
        print("アフィン変換が推定できませんでした。")
        return 0

    # 回転角度を計算
    angle = np.arctan2(matrix[1, 0], matrix[0, 0]) * (180 / np.pi)
    return angle

# テンプレートマッチング関数
def deep_template_matching(input_path, template_path):
    # 画像の読み込み
    input_img = cv2.imread(input_path)
    template_img = cv2.imread(template_path)

    # グレースケール変換
    input_gray = cv2.cvtColor(input_img, cv2.COLOR_BGR2GRAY)
    template_gray = cv2.cvtColor(template_img, cv2.COLOR_BGR2GRAY)

    # 特徴抽出(CNN)
    extractor = FeatureExtractor()
    input_tensor = preprocess_image(input_gray)
    template_tensor = preprocess_image(template_gray)

    input_features = extractor(input_tensor)
    template_features = extractor(template_tensor)

    # 類似度計算
    similarity = cosine_similarity(input_features, template_features)
    print(f"テンプレートとの類似度: {similarity:.4f}")

    # OpenCVで通常のテンプレートマッチング
    res = cv2.matchTemplate(input_gray, template_gray, cv2.TM_CCOEFF_NORMED)
    _, max_val, _, max_loc = cv2.minMaxLoc(res)

    # 回転角度を推定
    angle = estimate_rotation(template_gray, input_gray)

    # 結果表示
    h, w = template_gray.shape
    result_img = input_img.copy()
    cv2.rectangle(result_img, max_loc, (max_loc[0] + w, max_loc[1] + h), (0, 0, 255), 2)
    cv2.putText(result_img, f"Angle: {angle:.2f} deg", (max_loc[0], max_loc[1] - 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

    cv2.imshow("Matching Result", result_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    return max_loc, angle, similarity

# 使用例
input_image_path = "input.jpg"
template_image_path = "template.jpg"
location, angle, sim = deep_template_matching(input_image_path, template_image_path)
print(f"最大類似度位置: {location}, 回転角度: {angle:.2f} 度, 類似度スコア: {sim:.4f}")

改良点

  1. 回転角度(θ)を算出
    • ORB (Oriented FAST and Rotated BRIEF) を使用し、テンプレートと入力画像の特徴点の相対角度 を計算。
    • cv2.estimateAffinePartial2D() を利用し、アフィン変換行列から回転成分を抽出。
  2. 位置((x, y))を出力
    • cv2.matchTemplate() で最もマッチする位置 (x, y) を取得。
  3. 類似度スコア(テンプレートとの一致度)
    • CNN(ResNet18)で特徴を抽出し、コサイン類似度を計算。

出力例

テンプレートとの類似度: 0.9234
最大類似度位置: (120, 80)
回転角度: 15.2 度

応用

  • 動物行動解析
    • 動物の移動パターン(座標)
    • 体の向き(回転角度)
  • ロボットビジョン
    • 回転や位置が変化する対象の追跡
  • スポーツ解析
    • プレイヤーの位置と姿勢の変化

まとめ

座標 (x, y) + 回転角度 (θ) を出力
ディープラーニング(CNN)とORB特徴点を併用
動物1匹の追跡に最適化

このスクリプトで、動物の位置と回転を検出できます

ヤフーニュース記事の大量のコメントを自動取得する方法 pythonスクリプト, selenium, chromedriver, ChatGPT

ヤフーニュース記事で国民の関心が高いものに関しては数千件、場合によっては数万件ものコメントが付きます。いちいち全部読むのはもはや無理で、要約するとか、分析して傾向ごとに分けるとか何らかの機械学習的な処理の助けを借りない限りどうしようもないでしょう。

仮に、本を読むようなつもりでコメントを全部読みたいと思っても、10件ずつブラウザをクリックしながら2000ページ(2万コメントの場合)もクリックし続けるのは誰もやりたくないことです。一つの文書に落とし込めれば、まだ全部読むというオプションにも現実味があります。

そんなときに、とりあえずヤフーコメント全てを文書データとしてダウンロードして保存する必要があります。ネットにそのような処理をするスクリプトがいくつか公開されていましたが、自分が試したかぎりなにも修正せずに動作するものはありませんでした。

そこで、修正するだけのプログラミング能力もないためChatGPTに自分のやりたいことを伝えて、スクリプトをゼロから書いてもらいました。一度で欲しいものが得られたわけではなく、エラーが出たりうまくデータが取得できなかったりで10回くらいのやり取りの末にようやく正常に動作する完成版ができたようなので、ここにシェアします。

 

ウェブブラウザはグーグルChromeを使います。Seleniumというライブラリーで自動的にウェブブラウザを開くので、Seleniumのインストールを予めしておく必要があります。またChromeのドライバー(chromedriver)を正しいパスに置いておく必要があります。

  1. chromedriverのダウンロードサイト https://googlechromelabs.github.io/chrome-for-testing/

コメントはエクセルファイルに保存されます(ダウンロードフォルダ内)。1行につき1つのコメントです。

ちなみに自分の場合は、ウインドウズ11でAnaconda3、jupyter notebookという環境です。環境に依存するようなことは何もないはずなので、多分ウインドウズであろうが何であろうが、Selenium, Chromedriverが使える条件であれば、動くと思います。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
import time
import pandas as pd

# 使い方:
# 下のbase_urlとmax_pageの値は、自分で決めて入れてください。
# base_url ヤフーニュース記事の「コメント」を表示し、「新着順」を選んだときのurl
base_url = "https://news.yahoo.co.jp/articles/0149d024655889eadd8f3fb4a8fcfd1838451a89/comments?order=newer"
# max_pages コメントはページ当たり10件なので、総コメント数÷10を切り上げた数字もしくはもっと大き目な数字でもよい
max_pages = 100  # コメントの最大ページ数

def scroll_page(driver):
    """
    ページをスクロールしてコメントをすべて表示する。
    """
    last_height = driver.execute_script("return document.body.scrollHeight")
    while True:
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(2)  # 動的ロードの待機
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height


def get_comments_from_page(driver, page):
    """
    現在のページからすべてのコメントを取得。
    """
    comments = []
    try:
        print(f"Processing page {page}...")
        scroll_page(driver)  # ページ全体をスクロール

        # 全てのコメント要素を取得(異なる構造に対応)
        comment_elements = driver.find_elements(By.XPATH, "//p[contains(@class, 'sc-z8tf0-11') or contains(@class, 'sc-169yn8p-10')]")
        print(f"Found {len(comment_elements)} comments on page {page}.")

        for index, element in enumerate(comment_elements):
            try:
                # 「もっと見る」がある場合はクリック
                more_button = element.find_element(By.CLASS_NAME, "sc-1nejiws-0")
                driver.execute_script("arguments[0].click();", more_button)
                time.sleep(1)
            except:
                pass  # 「もっと見る」がない場合はスキップ
            
            # コメント全体を取得
            comments.append(element.text.strip())
            print(f"Comment {index + 1}: {element.text.strip()[:30]}...")  # ログに表示
    except Exception as e:
        print(f"Error processing page {page}: {e}")
    return comments


def scrape_comments(base_url, max_pages):
    """
    全ページからコメントをスクレイピングする。
    """
    driver = webdriver.Chrome()  # ChromeDriverを使用
    driver.get(base_url)
    all_comments = []

    for page in range(1, max_pages + 1):
        print(f"Scraping page {page}...")
        if page > 1:  # 2ページ目以降
            next_page_url = f"{base_url}&page={page}"
            driver.get(next_page_url)
            time.sleep(3)  # ページロード待機

        # コメントを取得
        comments = get_comments_from_page(driver, page)
        if not comments:  # コメントがない場合終了
            print("No more comments found. Exiting loop.")
            break
        all_comments.extend(comments)  # 各ページのコメントをリストに追加
    
    driver.quit()
    return all_comments

# スクレイピング実行
comments = scrape_comments(base_url, max_pages)

# コメントをExcelに保存
if comments:
    # 別名で保存してファイル競合を防ぐ
    output_file = f"yahoo_news_comments_{int(time.time())}.xlsx"
    df = pd.DataFrame(comments, columns=['Comment'])  # 全コメントをDataFrame化
    df.to_excel(output_file, index=False)
    print(f"Comments saved to {output_file}")
else:
    print("No comments found or failed to scrape.")

正常に動作することを確認しましたが、もしもっと自分が使いやすいように細かく調整したければ、上のスクリプトをChatGPTに投げて、カスタマイズすれば、プログラミング能力は全く不要でやりたいことができると思います。

プログラミング能力がほぼゼロの自分でもChatGPTがあればやりたいことが実現できてしまうというのが驚きです。