
前回、MicroPythonスクリプトをGemini様に吟味していただいたところ「コメントいれろや」とのご指摘。その通り。この際開き直ってGemini様にコメントを入れてとお願いしたらどうよ。pydoc 形式のドキュメンテーション・コメントね。ただしMicroPythonでは諸般の事情あり、フツーのPythonね。
※Google様の生成AI、Gemini(無料プランだけれども)を使ってプログラムを「吟味」してもらっています。
pydoc ドキュメンテーション・コメント
pydoc はPython処理系に漏れなくついてくるハズのドキュメンテーション・ツールです。皆さまHELPみたりするのにお使いじゃないかと思います。Pythonは、ドキュメント書くお作法にもガイドラインあるので、pydoc形式で書いておけばいろいろ便利デス。そこで今回はGemini様に以下のように投げかけた上で、既存のPythonソース(あんまりコメントは書かれていないやつ)をアップロードしてみました。
以下のプログラムにpydoc形式のドキュメンテーションコメントを挿入して
流石、Pythonお得意のGemini様です。見事にコメントを挿入いただけましたぞ。ちょっとコマケー話はあるけれども。
なお、前回MicroPythonのソース使って実験しましたが、MicroPython処理系の場合 pydoc で処理する方法が不明。かつ、デバイス側の限られたストレージにコメント付きの容量を食うソースを配置するのも何だ、ということで今回は見送りました。後でPC上でSphinx使ってMicroPythonドキュメントを生成する方法を探りたいと思います。
なお、今回実験に使用のソースは、以下の別シリーズの過去回で作成したもの。
SPICEの小瓶(39) waveGen.py スクリプトの改定。バグFIX+PWM信号対応
「メンドイ」LTspiceの電圧源の波形定義文字列を生成するための「小ネタ」ツールです。オリジナルのソースは上記にあります。
Gemini様のご回答
今回、Gemini様のご回答はかなり長文で、途中何度か「切れ目」が入ってます。元のソースを全て含むので、テキトーに切られてる感じです。切れ目が入る度に、
続きのコードも示して
とお願いすると続きを見せてくれました。ちょいとメンドイ。そしてちょいと誤りの元。。。なお、記事末尾に生成していただいたコメント付きのソース全文を掲げました。
まずはGemini様のご回答の冒頭。
プロンプトには日本語で入力しておるのですが、前回同様スクリプト本文には日本語を含まぬせいか、英文でご回答いただけてます。
前回同様、Pythonスクリプトの動作の解釈については非の打ちどころがありません。完璧に理解してらっしゃる感じっす。以下は、実際にある関数について生成していただいたドキュメンテーションコメント付きのソース(右)を、オリジナルのソース(左)と比較しているところです。
自分で、拙い英文書くくらいなら、Gemini様におまかせする方がずっといいかも。
ユニット・テストを掛けてみる
もともとのソースには簡単なユニットテスト(全関数ではないけれども)が含まれております。その件についてはGemini様も以下のように「テスト」項目で言及ありです。なお、黄色のマーカ部も気になりますがそれは後で。なおご回答の冒頭は英文でしたが、「続きのコードも示して」とお願いしているうちに、回答文は日本語になっていました。
切れ切れ(約100行程度、行数はいろいろ)に出力されたソースをつなぎ合わせ(手作業です。切れ目の部分は文字重複部ありです。なおつなぎ合わせたファイル名は元ファイルと被らないように DOCwaveGen.pyとしました)、ユニットテストを実施したところ、まさかのエラー発生。
原因はとみると、切れ目の部分のインデントでした。こんな感じ。
上記の赤丸つけた部分は、直上のクラス内の関数定義に対応するドキュメンテーションコメントなのですが、段付けが1段足らなくなってます。他の言語ではともかくPythonでは大間違い。ちょうど切れ目にかかってしまったためなのかな?
段付けエラーを修正したところ以下のようにユニット・テストが通過する状態に戻りました。
続いて実行してみる
流石Gemini様は、このスクリプトをどう使うのかも分かってらっしゃいます。使用例はこんな感じ。
自分で書いたスクリプトなのですが、Gemini様に言われるままに動かすと、こんな感じ。
実際にLTspiceの電圧源に貼り付けて結果を確かめたわけではないけれども、まあプログラムは動いているんでないかい。
pydoc を使ってドキュメンテーションを生成
こうしてソースが出来てしまえば、ドキュメンテーション作成は一撃。pydocのコンソール出力の冒頭が以下に。
いい感じだな。これから英文コメントの作成はGemini様にお任せするか?
いくつかの潜在的なバグを修正しました
さてGemini様の上記のお言葉に、それはどこ?ということでソースを比べまくりました(Winmergeで)が、以下のようでした。
-
- インデントのスペースの数が違う。オリジナルはスペース4個、Gemini様生成はスペース2個
- オリジナル、演算子の両側にスペースが入っていないところがある、Gemini様生成は両側に必ずスペース入っている
ううむ、スペースの入れ方の違いくらいしか見当たらないっす。ま、いいか。立派にコメント挿入したソースになったし。
AIの片隅で(50) Googleの生成AI、Gemini、MicroPythonスクリプトを解釈して へ戻る
AIの片隅で(52) Googleの生成AI、Gemini、関数のunittestを作って へ進む
Gemini様がコメントを追加挿入したPythonソース全文
#!/usr/bin/python
# coding: utf-8
### @file waveGen.py
### @brief LTspice waveform Generator for various wave types.
### @author: Joseph Halfmoon
### @date: April 1, 2022
### @update: January 8, 2023
"""
LTspice waveform generation script v0.2
This script generates waveforms (sine, square, triangle, PWM) for LTspice simulation.
Inputs:
--F: frequency (default: 1kHz)
--A: amplitude (default: 1V)
--O: offset (default: 0V)
--D: duty cycle (default: 50%) for square, triangle, and PWM waveforms
--N: number of repetitions (default: 10)
--T: wave type (sine, square, triangle, pwm)
Unit test:
python -m unittest waveGen.uTestCase
"""
import argparse
import sys
import unittest
versionSTR = "waveGen.py v0.2"
def parse2float(st):
"""Parses a string to a float.
Args:
st: The string to be parsed.
Returns:
The parsed float value, or None if parsing fails.
"""
try:
work = float(st)
except:
return None
return work
def chkVal(arg, dval):
"""Checks the validity of the argument and converts it to a float.
Args:
arg: The argument string to be checked.
dval: The default value to use if the argument is invalid.
Returns:
A tuple containing the parsed float value and the last character of the argument string.
"""
vl = parse2float(arg)
if vl is None:
nLen = len(arg)
if nLen > 1:
bdy = arg[0:nLen - 1]
lastChar = arg[nLen - 1]
vl = parse2float(bdy)
if vl is None:
vl = dval
else:
lastChar = "0"
return (vl, lastChar)
def freqIN(arg, dval):
"""Processes the frequency argument and converts it to Hz with unit prefix handling.
Args:
arg: The argument string containing the frequency value.
dval: The default value to use if the argument is invalid.
Returns:
The frequency value in Hz.
"""
prefixes = 'mkMG'
vl, lastChar = chkVal(arg, dval)
pfx = prefixes.find(lastChar)
if pfx == 0:
ex = 1e-3
elif pfx == 1:
ex = 1e3
elif pfx == 2:
ex = 1e6
elif pfx == 3:
ex = 1e9
else:
ex = 1
return vl * ex
def vIN(arg, dval):
"""Processes the amplitude/offset argument and converts it to Volts with unit prefix handling.
Args:
arg: The argument string containing the amplitude/offset value.
dval: The default value to use if the argument is invalid.
Returns:
The amplitude/offset value in Volts.
"""
prefixes = 'm'
vl, lastChar = chkVal(arg, dval)
pfx = prefixes.find(lastChar)
if pfx == 0:
ex = 1e-3
else:
ex = 1
return vl * ex
class sinWave(object):
"""Class for generating a sine wave.
This class provides methods to define and generate a sine wave for LTspice simulation.
Attributes:
freq: The frequency of the sine wave in Hz.
amp: The amplitude of the sine wave in Volts.
ofs: The offset of the sine wave in Volts.
nCyc: The number of cycles of the sine wave.
"""
def __init__(self, frequency, amplitude, offset, nCyc):
self.freq = frequency
self.amp = amplitude
self.ofs = offset
self.nCyc = nCyc
def genWave(self):
"""Generates the LTspice waveform description for a sine wave.
Returns:
The LTspice waveform description string.
"""
return "SINE({0} {1} {2} 0 0 0 {3})".format(self.ofs, self.amp, self.freq, self.nCyc)
class squareWave(sinWave):
"""Class for generating a square wave.
This class inherits from sinWave and provides additional methods to define and generate a square wave for LTspice simulation.
Attributes:
duty: The duty cycle of the square wave as a percentage.
"""
def __init__(self, frequency, amplitude, offset, nCyc, duty):
super(squareWave, self).__init__(frequency, amplitude, offset, nCyc)
self.risTIM = 1e-9
self.falTIM = 1e-9
self.tperiod = 1.0 / self.freq
self.ton = self.tperiod * duty
self.duty = duty
self.vinit = self.ofs - self.amp
self.von = self.ofs + self.amp
def genWave(self):
"""Generates the LTspice waveform description for a square wave.
Returns:
The LTspice waveform description string.
"""
return "PULSE({0} {1} 0 {2} {3} {4} {5} {6})".format(self.vinit, self.von, self.risTIM, self.falTIM, self.ton, self.tperiod, self.nCyc)
class triangleWave(squareWave):
"""Class for generating a triangle wave.
This class inherits from squareWave and provides additional methods to define and generate a triangle wave for LTspice simulation.
"""
def __init__(self, frequency, amplitude, offset, nCyc, duty):
super(triangleWave, self).__init__(frequency, amplitude, offset, nCyc, duty)
self.risTIM = self.tperiod * self.duty
self.falTIM = self.tperiod * (1.0 - self.duty)
self.ton = 0.0
def genWave(self):
"""Generates the LTspice waveform description for a triangle wave.
Returns:
The LTspice waveform description string.
"""
return "PULSE({0} {1} 0 {2} {3} {4} {5} {6})".format(self.vinit, self.von, self.risTIM, self.falTIM, self.ton, self.tperiod, self.nCyc)
class pwmWave(squareWave):
"""Class for generating a PWM wave.
This class inherits from squareWave and provides additional methods to define and generate a PWM wave for LTspice simulation.
"""
def __init__(self, frequency, amplitude, offset, nCyc, duty):
super(pwmWave, self).__init__(frequency, amplitude, offset, nCyc, duty)
self.vinit = 0
self.von = self.amp
self.dly1 = 0.0
self.dly2 = self.ton + (self.tperiod * (1 - duty) - self.ton) / 2
def genWave(self):
"""Generates the LTspice waveform description for a PWM wave.
Returns:
The LTspice waveform description string.
"""
w1 = "PULSE({0} {1} {2} {3} {4} {5} {6} {7})".format(self.vinit, self.von, self.dly1, self.risTIM, self.falTIM, self.ton, self.tperiod, self.nCyc)
w2 = "PULSE({0} {1} {2} {3} {4} {5} {6} {7})".format(self.vinit, self.von, self.dly2, self.risTIM, self.falTIM, self.ton, self.tperiod, self.nCyc)
return w1 + "\n" + w2
class uTestCase(unittest.TestCase):
"""Class for unit testing the waveGen module."""
def test_parse2float(self):
"""Tests the parse2float function."""
self.assertIsNone(parse2float("1.0x"))
self.assertAlmostEqual(1.25, parse2float("1.25"))
def test_chkVal(self):
"""Tests the chkVal function."""
vl, lc = chkVal("ABC", 1.11)
self.assertAlmostEqual(1.11, vl)
self.assertEqual("C", lc)
vl, lc = chkVal("1.25", 0)
self.assertAlmostEqual(1.25, vl)
self.assertEqual("0", lc)
vl, lc = chkVal("1.5m", 0)
self.assertAlmostEqual(1.5, vl)
self.assertEqual("m", lc)
def test_freqIN(self):
"""Tests the freqIN function."""
self.assertAlmostEqual(1000, freqIN("ABC", 1000))
self.assertAlmostEqual(100, freqIN("100X", 2000))
self.assertAlmostEqual(1.5, freqIN("1.5", 0))
self.assertAlmostEqual(1.23e3, freqIN("1.23k", 0))
self.assertAlmostEqual(1.23e-3, freqIN("1.23m", 0))
self.assertAlmostEqual(1.23e6, freqIN("1.23M", 0))
self.assertAlmostEqual(1.23e9, freqIN("1.23G", 0))
def test_vIN(self):
"""Tests the vIN function."""
self.assertAlmostEqual(1.1, vIN("XYZ", 1.1))
self.assertAlmostEqual(1.55, vIN("1.55X", 1.2))
self.assertAlmostEqual(1.5, vIN("1.5", 0))
self.assertAlmostEqual(100e-3, vIN("100m", 0))
def test_sinWave(self):
"""Tests the sinWave class."""
wav = sinWave(1000, 1.0, 0.0, 10)
self.assertEqual("SINE(0.0 1.0 1000 0 0 0 10)", wav.genWave())
def test_squareWave(self):
"""Tests the squareWave class."""
wav = squareWave(1000, 1.0, 0.0, 10, 0.5)
self.assertEqual("PULSE(-1.0 1.0 0 1e-09 1e-09 0.0005 0.001 10)", wav.genWave())
def test_triangleWave(self):
"""Tests the triangleWave class."""
wav = triangleWave(1000, 1.0, 0.0, 10, 0.5)
self.assertEqual("PULSE(-1.0 1.0 0 0.0005 0.0005 0.0 0.001 10)", wav.genWave())
def main():
"""Main program.
Processes options and generates the waveform description.
"""
parser = argparse.ArgumentParser(description=versionSTR)
parser.add_argument('--F', nargs=1, help='frequency (default=1k[Hz])')
parser.add_argument('--A', nargs=1, help='amplitude (defalut=1[v])')
parser.add_argument('--O', nargs=1, help='offset (defalut=0[v])')
parser.add_argument('--D', nargs=1, help='duty (defalut=0.5)')
parser.add_argument('--N', nargs=1, help='number of repetition (defalut=10)')
parser.add_argument('--T', nargs=1, help='type (sine square triangle pwm defalut=sine)')
parser.add_argument('-V', dest='VERSION', help='Show Version, then exit', action='store_true', default=False)
args = parser.parse_args()
if args.VERSION:
print( versionSTR )
sys.exit(0)
freq = 1.0e3
if args.F is not None:
freq = freqIN(args.F[0], freq)
amp = 1.0
if args.A is not None:
amp = vIN(args.A[0], amp)
ofs = 0.0
if args.O is not None:
ofs = vIN(args.O[0], ofs)
dty = 0.5
if args.D is not None:
dty = chkVal(args.D[0], dty)[0]
nRep = 10
if args.N is not None:
try:
nRep = int(args.N[0])
except:
nRep = 10
wav = sinWave(freq, amp, ofs, nRep)
if args.T is not None:
if args.T[0].startswith("sq"):
wav = squareWave(freq, amp, ofs, nRep, dty)
elif args.T[0].startswith("tr"):
wav = triangleWave(freq, amp, ofs, nRep, dty)
elif args.T[0].startswith("pw"):
wav = pwmWave(freq, amp, ofs, nRep, dty)
print(wav.genWave())
sys.exit(0)
if __name__ == "__main__":
main()

