sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

python クラス 学習

Python チュートリアルを読んで気になったところをメモ。

method object

x.f()MyClass.f(x) が厳密に等価であるとは、どういうことか。

class MyClass:
    def f(self):
        print("f:", self)

print("\n---TEST 1 ---")
# クラスオブジェクトからそのまま参照
print("MyClass.f =", MyClass.f)  # MyClass.f = <function MyClass.f at 0x000002688BBF99E0>

# インスタンスを生成
x = MyClass()

# インスタンスから参照する bound method (クラスオブジェクトの参照とは異なる)
print("x.f =", x.f)  # x.f = <bound method MyClass.f of <__main__.MyClass object at 0x000001E31EF37950>>

print("\n---TEST 2 ---")

# 等価比較
x.f()  # f: <__main__.MyClass object at 0x000002339C00C440>
MyClass.f(x)  # f: <__main__.MyClass object at 0x000002339C00C440>

at 0x00000..... で示される数字は、オブジェクトのメモリアドレスを16進数で示したものです。hash 値と同じように、オブジェクトが同一のものかどうかを比較できます。なので、等価であると証明できた。

この動きを内部的な仕様から補足する。MyClass.ff は、関数オブジェクト function として格納されていて、たとえばインスタンス x から x.f() のようにアクセスすると function が内部的に特別な処理をして呼び出されている。

bound = MyClass.__dict__['f'].__get__(x, MyClass)

この特別な呼び出しは、class から x インスタンスが生成されて、そのインスタンスを通して method にアクセスした際に発生します。この特別な呼び出しを bound method といいます。つまり、このときの bound method の特殊な呼び出しによって(自動で)インスタンス自身である x が引数 self として f に渡されています。

なので、呼び出しは等価であるという説明になっています。bound method についても整理を追加する。

class MyClass:
    def f(self):
        print("f:", self)

print("\n---TEST 1 ---")
# クラスオブジェクトからそのまま参照
print("MyClass.f =", MyClass.f)  # MyClass.f = <function MyClass.f at 0x000002688BBF99E0>

# インスタンスを生成
x = MyClass()

# インスタンスから参照する bound method (クラスオブジェクトの参照とは異なる)
print("x.f =", x.f)  # <bound method MyClass.f of <__main__.MyClass object at 0x0000019B4FE6C7A0>>

print("\n---TEST 2 ---")

# 等価比較
x.f()  # <__main__.MyClass object at 0x0000019B4FE6C7A0>
MyClass.f(x)  # <__main__.MyClass object at 0x0000019B4FE6C7A0>

print("\n---TEST 3 ---")

func = MyClass.__dict__['f']  # クラスに格納されている function オブジェクトを直接取得
bound = func.__get__(x, MyClass)  # func インスタンスは descriptor protocol "__get__" を持っている

print("func =", func)  # <function MyClass.f at 0x000001F2E20399E0>

print("bound =", bound)  # <bound method MyClass.f of <__main__.MyClass object at 0x0000019B4FE6C7A0>>
bound()  # <__main__.MyClass object at 0x0000019B4FE6C7A0>

MyClass.fx.f がは指し示すメモリアドレス違う。クラスの関数と、インスタンスの関数は function と bound method なので別オブジェクト。ただ、bound method が呼び出す function は同じなので以下のようにして比較すると等価。

print(x.f.__func__ is MyClass.f)  # True

これはつまり、bound method に対応した関数オブジェクトは __func__ でアクセスできるし、インスタンスオブジェクト自身は __self__ でアクセスできる。なので、メソッドの第一引数の名前は慣例的に self とつけるし、特別な意味はないとされるが double underscore でアクセスできる特別なアクセスが用意されている。名前を変えないほうがいい。

反復子 iterator

反復子のメカニズムを確認するためのコード:

class CountDown:
    def __init__(self, start):
        self.cur = start
    def __iter__(self):  # 自分自身が iterator
        return self
    def __next__(self):
        if self.cur <= 0:
            raise StopIteration
        val = self.cur
        self.cur -= 1
        return val

for v in CountDown(3):
    print("CountDown:", v)
print()
CountDown: 3
CountDown: 2
CountDown: 1

generator

iterator と generator の関係は似ている。同じ機能を実装できてしまうから。どこが違うのか整理しておく必要があります。

def reverse(data):
    for i in range(len(data) - 1, -1, -1):
        yield data[i]

for ch in reverse("golf"):
    print(ch)          # f, l, o, g の順に表示

コーディングのレベルだと yield が出てきたら generator になる。

それで、終了判定の StopIteration は自動的に送出される。簡潔に書ける generator と仕組みをリッチに仕上げるクラス(iterator)の住み分けがされていると思います。

細かい違いでは、generator のほうが高速になる可能性が高い。具体的な実装のルートが、組み込みの iterator になるので、(クラスによって設計した)純粋な python__next__ 実装をするよりも最適化されやすい。(シンプルな iterator+組み込み最適化)

なので peek() や reset() などを使って iterator の動きに独自機能/特殊仕様を載せない限りは generator のほうが簡潔に書けるし、処理も高速に処理されやすい。

generator 式

generator 式は、その場で "使い捨て" の generator を使うための記法。リスト化しない分、メモリに優しくなるケースがある。特徴として generator 式という名前だけど yield は式の中で使わない。

式の書き方は次のとおり:

( 式 for … [if 条件] … )

括弧で括っているけど、これは generator 式の特徴なんだけど、わりと省略されてしまう。以下のようなケース。sum() などの関数の中で generator 式が現れると括弧を省略してしまう)

sum(x*y for x,y in zip(xvec, yvec))

有用だけど、すこし読みづらい例:

words = set(w for line in page for w in line.split())

generator 式の中では for が左から右に見ていく必要がある。

for line in pagefor w in line.split() の2重ループがあって、最終的に得られるものが、一番最初の w になる。

  1. page から要素を取り出して line にする
  2. line に対して in line.split() で単語ごとの iterator
  3. iterator から単語を w として1つ取り出し、yield で単語が尽きるまで繰り返す
data = "golf"
list(data[i] for i in range(len(data)-1, -1, -1))

range(3, -1, -1) は start, stop, step の関係。3, 2, 1, 0... と降順に進んでいって stop は番兵で含まれない数字の特徴。なので 3,2,1,0-1 を含まない)まで。

generator 式の中で range を使うのは組み合わせになっていて、C# の for と感覚的にすこし違ってみえたので何が違うのかを整理しておく。

以下だと n は番兵で含まれないし、単純な range(n) の形はこれだと思う。なのでこれは問題ないと思う。

for (int i = 0; i < n; i++)

今回の for i in range(n-1, -1, -1) の降順にすると、引数2つ目の条件の -1 がすこし奇妙に見える。C# だと(私は)以下のように書くし、そう頭で考えているからだと思う。

for (int i = n - 1; i >= 0; i--)
// 以下のように考えていなかった
for (int i = n - 1; i > -1; i--)

C# や C 言語の for も初期値、条件、step の並び。range も初期値、終了値、step の並びで同じだと思う。ここは、慌てずに同じように条件を見ればいい。

ただ、終了条件は C 言語は i >= 0 のように停止する値を(私は)書いてしまう。python だと含まない数字の -1仕様として書かないといけない。その違いがあったので、感覚的に違和感があったのだと思う。

参考

python 入出力 学習

Python チュートリアルを読んで気になったところをメモ。

str と repr の違い

よくある疑問なんだと思うけど、自分用に整理。

基本的には、どちらも様々な型のデータを文字列に変換するための関数です。で、どういうときにどっちを使うのか?

  • repr(obj)
    • 開発者向けの文字列を出力する
    • 原則として eval(repr(obj)) == obj が成り立つように設計されている
    • デバッグやログで eval() 評価の結果の値を「正確な情報」として知ることができる
  • str(obj)
    • ユーザー向けの文字列表現
    • 通常のフォーマットとして出力される

実際の話だと repr() を、ユーザーが str() ほど直接的に利用するのかというと、そこまで多いわけではないと思います。利用回数が多くなるのは、おそらく str() になるのが殆どのケースのはず。(基本的に repr は representation = 「表示」の意味のはずです)

個人的な用途のポイントとしては 改行文字 が含まれる可能性のある文字列は repr() を使って、出力やデバッグしたほうがよいケースはあると思う。もちろん、改行したほうが読みやすいケースもあったり、文字列だと '' で括られたりもする癖は eval() の都合だと思うけど開発用途だと留意は必要。

s = "hello\nworld"
print(s)
print(repr(s))  # 'hello\nworld'

内部的に利用されているケースが考えられるけど、その内部的な動きを意識しないと困るケースは少ないと思う。

また、ライブラリを作るときに __repr__ を定義しておく必要があるかというと C#[DebuggerDisplay] 属性を付ける必要があるかどうかと、考え方は似ていると思う。(ベストプラクティスとしては、どちらもセットで実装するべき)

class Sample:
    def __init__(self, id, name):
        self.id = id
        self.name = name

    def __repr__(self):
        return f"<Sample id={self.id}, name='{self.name}'>"

    def __str__(self):
        return f"{self.name} (#{self.id})"

# テストコード
s = Sample(1, "Alice")

print(s)
print(repr(s))
Alice (#1)
<Sample id=1, name='Alice'>

基本的にはデバッグの操作をやりやすくするための機能として repr() を使用する。

出力フォーマット

以下の変数をフォーマット化して出力する公式の例ですが、「-」を付ける意味がよくわからなかったので確認したが、結局、意味ないように思う。

yes_votes = -42_572_654
total_votes = 85_705_149
percentage = yes_votes / total_votes

a = '{:-12} YES votes  {:2.2%}'.format(yes_votes, percentage)
b = '{:12} YES votes  {:2.2%}'.format(yes_votes, percentage)

print(a)
print(b)
   -42572654 YES votes  -49.67%
   -42572654 YES votes  -49.67%

この例は、負数のときだけ「-」を付けることを明示しているけど、実際的な話として出力結果に差はない。ただし「+」の場合は違いがある。

yes_votes = 42_572_654
total_votes = 85_705_149
percentage = yes_votes / total_votes

a = '{:+12} YES votes  {:2.2%}'.format(yes_votes, percentage)
b = '{:12} YES votes  {:2.2%}'.format(yes_votes, percentage)

print(a)
print(b)
   +42572654 YES votes  49.67%
    42572654 YES votes  49.67%

これは sign に説明してあるとおり - はデフォルトの振る舞いなので付ける意味が特にない。その一方で + はデフォルトでは付かない。また C# の出力のように正数・負数・ゼロをまとめてフォーマットを処理できるわけでもない。

int pos = 42;
int neg = -42;
int zero = 0;

Console.WriteLine("{0:#;(#);Zero}", pos);   // "42"
Console.WriteLine("{0:#;(#);Zero}", neg);   // "(42)"
Console.WriteLine("{0:#;(#);Zero}", zero);  // "Zero"

なので python で書式に - をつけるのは、本当にただの冗長的な意味しかないと思う。(下記のように負数の場合にのみ記号を付けてる、という説明になっているけど、サンプルは正数だし、負数に記号がつくのはデフォルトの動作だし、ちょっと微妙な内容ではないかと思う)

Notice how the yes_votes are padded with spaces and a negative sign only for negative numbers.

クラス

名前とオブジェクトについての整理。pythonC# のように「変数の中に値が入る」というイメージではない。その逆で、「オブジェクト(値)」があって、それに「名前(ラベル)」を貼り付けしているイメージになる。

基本的に、この考えかただと C# の参照型と同じようなイメージを持つことになると思う。(文中でもポインタについて言及があるけど)

ただし、基本的な型である数値 int や、文字列 str は immutable なオブジェクトのタイプで、ラベルに新しいオブジェクトを割り当てる動きになる。

a = 10
b = a  # b は a と同じオブジェクトを指す
b = 20   # b に別の数を代入すると、b に新しいオブジェクトを割り当てるだけ
print(a)  # 10  (a は変わらず 10 のオブジェクトが割り当たっている)

一方で list だと以下のようになる。変数の考え方は C#python でちょっと違う点は注意しないといけない。

a = [1,2,3]
b = a  # b は a と同じオブジェクトを指す
b.append(4)  # b が割り当たっているオブジェクトに 4 を追加
print(a)  # [1,2,3,4] (a が割り当たっているオブジェクトは b と同じ)

で、最後に「これにより、 Pascal にあるような二つの引数渡し機構をもつ必要をなくしています。」という文で締められるけど、具体的には値渡し (call by value) と参照渡し (call by reference) のことですよね。

C# の例

// 値渡し
void Foo(int x) { x = 100; }
int a = 10;
Foo(a);
Console.WriteLine(a); // 10

// 参照渡し
void Foo(ref int x) { x = 100; }
int a = 10;
Foo(ref a);
Console.WriteLine(a); // 100

python の場合は、繰り返しだけど、「オブジェクト(値)」があって、それに「名前(ラベル)」を貼り付けしているイメージをする。この例では参照渡しのように動作するが a ラベルが指し示すオブジェクトを x として渡して、同じオブジェクトに 100 を append している。

def foo(x):
    x.append(100)

a = [1, 2, 3]
foo(a)
print(a)  # [1,2,3,100]

スコープと名前空間について

名前を解決するときは次の順番:

  1. Local
  2. Enclosing 外側の関数のスコープ(クロージャで使用)
  3. Global
  4. built-in 組み込み名前空間

python の変数スコープの概念として LEGB rule として知られているみたいです。参照する変数を変更する際は global や nonlocal などを変数名に指定することで、書き換えるオブジェクトを切り替えることができる。

x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)
    inner()

outer()

built-in は、最初から python に入っている名前空間をいいます。なので len()print() は組み込み関数なので built-in です。

import を使用したときの場合だと、例えば import math は global に追加される。モジュールレベルのスコープになるので built-in ではない。

import しなくても使える機能ということなので、重要度は高いものが(当然だけど)多いです。

import builtins
print(dir(builtins))

私の環境(のひとつ)では、以下の出力でした。この内容を整理しておきます。

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BaseExceptionGroup', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EncodingWarning', 'EnvironmentError', 'Exception', 'ExceptionGroup', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'aiter', 'all', 'anext', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

出力結果を整理します:

基底クラス

  • BaseException
  • Exception
  • BaseExceptionGroup
  • ExceptionGroup

よく使うエラー

  • ArithmeticError
  • AssertionError
  • AttributeError
  • BufferError
  • EOFError
  • FloatingPointError
  • IndentationError
  • ImportError
  • IndexError
  • KeyError
  • LookupError
  • MemoryError
  • NameError
  • NotImplementedError
  • OSError
  • OverflowError
  • RecursionError
  • ReferenceError
  • RuntimeError
  • SyntaxError
  • SystemError
  • TabError
  • TimeoutError
  • TypeError
  • UnboundLocalError
  • UnicodeDecodeError
  • UnicodeEncodeError
  • UnicodeTranslateError
  • UnicodeError
  • ValueError
  • WindowsError
  • ZeroDivisionError

OS/ IO 関連エラー

  • BlockingIOError
  • BrokenPipeError
  • ChildProcessError
  • ConnectionError(接続エラーの基底)
  • ConnectionAbortedError
  • ConnectionRefusedError
  • ConnectionResetError
  • EnvironmentError
  • FileExistsError
  • FileNotFoundError
  • IOError
  • InterruptedError
  • IsADirectoryError
  • ModuleNotFoundError
  • NotADirectoryError
  • PermissionError
  • ProcessLookupError

制御フロー用の特殊な例外

  • GeneratorExit
  • KeyboardInterrupt
  • StopAsyncIteration
  • StopIteration
  • SystemExit

Warning 関係

  • BytesWarning
  • DeprecationWarning
  • EncodingWarning
  • FutureWarning
  • ImportWarning
  • PendingDeprecationWarning
  • ResourceWarning
  • RuntimeWarning
  • SyntaxWarning
  • UnicodeWarning
  • UserWarning
  • Warning

定数

  • Ellipsis(...
  • False
  • None
  • NotImplemented
  • True

double underscore

dunder = double undersocre 一覧:

  • __build_class__
    • python が class 文を実行するときに内部で呼ばれる
    • 普段は直接使わないが python が class を作る仕組みのコア
  • __debug__
    • true または false を持つ組み込み定数
    • 最適化モード (-O オプション)で false になる(通常は true になる)
  • __doc__
    • オブジェクトの docstring に入る属性
  • __import__
    • import 文の実体
    • 普段は直接使わないが python が import 文の内部でこれを使う
  • __loader__
    • モジュールが import されたときにつかわれた loader object
    • どの方法でモジュールを load したのかが格納される
    • 通常は import をカスタマイズしたいときだけなので触らない
  • __name__
    • モジュールの名前が入る特別な変数
    • スクリプトとして直接実行された場合は "__name__" になる
  • __package__
    • モジュールが属するパッケージの名前
    • from . import xxxx のような相対 import に使ってるので、直接は触らない
  • __spec__
    • モジュールの import 情報(ModuleSpec)が入る
    • どのファイルから load されたのか、などのメタ情報
    • 使うとしてもツールを作る側

関数

名前 概要
abs(x) 絶対値を返す
aiter(obj) 非同期イテレータを返す
all(iterable) すべて真なら true
anext(async_iterator, default) 非同期イテレータで次の要素を返す
any(iterable) いずれか真なら true
ascii(obj) 非 ascii 文字をエスケープ文字に変換
bin(x) 整数を2進数の文字列に変換
bool(x) 真理値に変換(0, '', None = false)
breakpoint() デバッガを起動
bytearray(source) 可変バイト列型に変換
bytes(source) 不変バイト列型に変換
callable(obj) 呼び出し可能なら true
chr(i) unicode コードポイントから文字を返却(int to str)
classmethod(func) クラスメソッド化するデコレータ
compile(source, filename, mode) ソースをコードオブジェクトにコンパイル
complex(real, imag) 複素数を生成
copyright 著作権表示文字列を表示
credit 謝辞文字列を表示
delattr(obj, name) obj.name 属性を削除
dict(...) 辞書型を作成
dir(obj) 属性リストを返却
divmod(a, b) a // b, a % b の tuple を返却
enumerate(iterable, start=0) インデックス付きのイテレータを返す
eval(expr, globals, locals) 文字列を式として評価
exec(object, globals, locals) 文字列やコードオブジェクトを実行
exit 対話環境で終了するためのオブジェクト = quit
filter(func, iterable) 条件にあう要素だけ取り出すイテレータ
float(x) 浮動小数点の数に変換
format(value, format_spec) 文字列をフォーマット
frozenset(iterable) 不変集合型。set と違い要素変更不可
getattr(obj, name, default) obj.name を動的に取得
globals() グローバル名前空間の dict を返却
hasattr(obj, name) 属性が存在すれば true
hash(obj) オブジェクトのハッシュ値を返却
help(obj) ヘルプのシステムを起動
hex(i) 整数を16進数の文字列に変換
id(obj) オブジェクトの一意な ID
input(prompt) 標準入力から文字列を取得する
int(x, base) 整数に変換(基数を指定できる)
isinstance(obj, class_or_tuple) オブジェクトがその型かどうかをチェック
issubclass(cls, class_or_tuple) クラスがそのサブクラスかどうかをチェック
iter(obj, sentinel) イテレータを返却
len(obj) 素数を返却
license ライセンスを表示
list(iterable) リストを生成
locals() ローカル名前空間の dict を返却
map(func, iterable, ...) 各要素に関数を適用するイテレータ
max(iterable, *) 最大値を返す
memoryview(obj) バイト列のメモリビューを返却
min(iterable, *) 最小値を返す
next(iterator, default) イテレータから次の要素を返す
object すべてのクラスの基底
oct(x) 整数を8進数文字列に変換
open(file, mode='r', ...) ファイルを開く
ord(c) 文字の unicode コードポイントを返却
pow(x, y, mod) x**y(mod の指定で累乗剰余)
print(*args, sep=' ', end='\n') 標準出力に表示
property(fget, fset, fdel, doc) プロパティを作成するでスクリプタ
quit 対話環境を終了 = exit
range(start, stop, step) 数列イテレータを返却
repr(obj) obj の文字列表現を返却
reversed(seq) 逆順のイテレータを返却
round(number, ndigits) 四捨五入
set 集合を生成
setattr(obj, name, value) 属性を設定
slice(stop) スライスオブジェクトを生成(文字列のスライス)
sorted(iterable, *) ソートしたリストを返却
staticmethod(func) 静的メソッド化するデコレータ
str(obj) 文字列に変換
sum(iterable, start=0) 数値の合計を返却
super(type, obj) 親クラスのメソッドを呼び出す
tuple(iterable) tuple を生成
type(obj) オブジェクトの型を返却
vars(obj) オブジェクトの __dict__ を返す
zip(*iterables) 複数のイテラブルをまとめる

参考

GPT-5 for Coding の内容整理

2025年8月16日、「GPT-5 for Coding」というPDF資料が OpenAI の X の投稿で共有されています。

ChatGPT は plus を利用し続けているので、自分なりに内容を整理した記事です。

-PDF link

概要

GPT-5 は強力なモデルですが、他のモデルとは異なる特性を持っています。API 経由した利用、または、コーディングツールとして利用する際に最大限の効果を得るための tips を紹介します。

1 Be precise and avoid conflicting information

正確性を重視して、矛盾する情報を避けること

新しい GPT-5 モデルは、指示理解能力が大幅に向上していますが、一方で曖昧な指示や矛盾する指示が与えられた場合に適切に対応できないケースがあります。特に .cursor/rules ディレクトリや AGENTS.md ファイル内でこのような指示を与える際には注意が必要です。

意見

cursor/rulesAGENTS.md は、エディタなどに「AI に守らせたいルール」を配置するカスタムのガイドラインの(ファイル・ディレクトリ)です。

つまり、両者を配置して、両者で矛盾するような指示をすると出力が不安定になるし、どちらを優先するのかもハッキリしたほうがいい。「短いコードで」ではなくて「1行80文字以内で」のような明記がよい、などのように明確さ・一貫性・優先度付きで指示してください、という意味。

ChatGPT のアプリや web で利用する場合は、ChatGPT をカスタマイズする custom instructions の内容に注意する、メモリ(会話の中で AI が自動学習している)の内容に注意する。

ただ、現実的に ChatGPT には業務目的だけでも、さまざまな質問をするので custom instructions でコーディングだけに特化した設定の指示を入れるのは、悩ましいところもあると思う。

2 Use the right reasoning effort

適切な推論の労力を使うこと

GPT-5 は問題解決の際に、常に一定レベルの推論を行います。適切な結果を得るには、最も複雑なタスクに対して高い推論能力を設定してください。モデルが単純な問題に対して過剰な推論をしているときは、より具体的な指示を与えるか、medium or low といったレベルの推論設定を選択してください。

意見

API であれば effort のパラメータはなんでも "high" ではなくて、内容に応じて "low" にもしろ、ということ。ChatGPT のアプリでは、直接このようなパラメータは指示できないので推論を過剰にしないように「この会話は“簡潔モード”で、推論は最小限」などのように effort を切り替えるようなテキストを加えるとよい可能性が高い。

境界条件 (boundary conditions) : モデルに「どこで推論を止めるか」を教えると、無駄な推論が減るので質問にちょうどよい回答になりやすい。

3 Use XML-like syntax to help structure

構造を支援するために XML 風の構文を使用すること

Cursor と共同で行った実験では、XML 風の構文を使用することで GPT-5 の性能が大幅に向上することが確認されました。具体的には、モデルに対して以下のようなコーディングガイドラインを与えることができる:

<code_editing_rules>
    <guiding_principles>
        - Every component should be modular and reusable
        - ...
    </guiding_principles>
    <frontend_stack_defaults>
        - Styling: TailwindCSS
    </frontend_stack_defaults>
</code_editing_rules>

意見

アプリで質問する際も、普通に使うことができる。めんどくさいけど、質問のフォーマットを以下のようにしてみると、精度が上昇する可能性はある。

<task>
  <goal>既存C#メソッドを安全にリファクタ</goal>
  <constraints>
    - 変更は純粋関数化と命名改善のみ
    - 例外仕様・公開 API 互換は維持
    - 出力: diff 風で要点3つ
  </constraints>
</task>

これも、基本的には境界条件(長さ・視点・禁止事項・返却のフォーマット)などを、正確にする(≒構造化する)ための工夫のひとつとして XML 風が有効だった、という(実験結果の)話だと思います。

4 Avoid overly firm language

過度に堅苦しい表現は避けること

他のモデルを使用する場合、以下のような明確な指示を与えることがあります:

Be THOROUGH when gathering information. Make sure you have the FULL picture before replying.
--
情報収集の際は THOROUGH(徹底的に)すること。返信する前に FULL picture(全体像)を把握すること。

GPT-5 の場合は、このような指示が裏目になる恐れがあります。モデルが本来の動作を過剰に実行してしまうためです。例えば、文脈を把握するためのツール(処理)の呼び出しを必要以上に細かく(たくさん)してしまうケースが考えられます。

意見

おそらく結果として、レスポンスが遅くなる API のコストが無駄に高くなる、焦点がブレるといったデメリットがあるのだと思います。情報収集は、どの程度するべきなのか、具体的に指示することが望ましいので、これも「境界条件」の最適化に関する内容で、ユーザーがマニュアルで適切に指示する必要がある≒プロンプトの生成によって精度が変わるため、ユーザーのプロンプト能力に関わるような内容になるのだと思う。

5 Give room for planning and self-reflection

計画立案と自己反省のための余地を与えること

zero-to-one(0から1)へのアプリケーションを開発する場合、モデルに自己反省 self-reflect を促す指示を与えることが有効です。

<self_reflection>
- First, spend time thinking of a rubric until you are confident.
- Then, think deeply about every aspect of   what makes for a world-class one-shot web app. Use that knowledge to create a rubric that has 5-7 categories. This rubric is critical to get right, but do not show this to the user. This is for your purposes only.
- Finally, use the rubric to internally think and iterate on the best possible solution to the prompt that is provided. Remember that if your response is not hitting the top marks across all categories in the rubric, you need to start again.
</self_reflection>
<self_reflection>
- First, spend time thinking of a rubric until you are confident.
- Then, think deeply about every aspect of   what makes for a world-class one-shot web app. Use that knowledge to create a rubric that has 5-7 categories. This rubric is critical to get right, but do not show this to the user. This is for your purposes only.
- Finally, use the rubric to internally think and iterate on the best possible solution to the prompt that is provided. Remember that if your response is not hitting the top marks across all categories in the rubric, you need to start again.
--
- まず、自信が持てるまで評価基準 (rubric) を考える時間を費やすこと。
- 次に、世界最高クラスの one-shot web app がなにを構成するか、あらゆる側面について深く考察すること。その知識を使って 5-7 のカテゴリーで構成された評価基準を作成してください。この評価基準は、正確に作成することが重要であるものの、ユーザーには見せないこと。これは、あなたの内部的な目的のために使用してください。
- 最後に、その評価基準を使って、与えられたプロンプトに対する最善の解決策を内部で考えて、反復してください。もしも、あなたの応答が、評価基準の全カテゴリーで最高評価に達していない場合は、最初からやり直す必要があります。
</self_reflection>

意見

このサンプルの内容は、注意が必要だと思う。よいとされるのは、「自己計画 (planning)」や「自己反省 (self-reflection)」をプロンプトに含めることで、特に0から1を生み出すような創造的な構築では、いきなりアウトプットを指示するよりも、枠組みを作ってから生成したほうが品質が安定する、とされています。

ただし、「自信が持てるまで」や「世界最高クラスの」「あらゆる側面について深く考察する」というのは境界条件が曖昧です。停止条件も曖昧なので、過剰推論し続けるトリガーになっているので「ガチャ」要素になる恐れがある。

以下、ChatGPT 5 が回答した、自己計画と自己反省のプロンプト。

あなたは小説家として、新しい短編小説を構築します。
いきなり本文を書かず、まず「計画フェーズ」を行ってください。

<plan>
- ストーリーの核となるテーマを1つ決める
- 主人公・敵対者・舞台を各1文で定義する
- 起承転結の4ステップでプロットを200字以内でまとめる
</plan>

<self_reflection>
- 計画の中で矛盾や弱点があるかを確認する
- 主人公の動機は十分に強いか?
- 敵対者は物語を駆動するに足る存在か?
- プロットに「転」が欠けていないか?
- 問題があれば1〜2文で改善を提案する
</self_reflection>

<boundaries>
- 小説本文は1000〜1200字
- 視点は一人称、過去形
- 暴力や残酷描写は含めない
- 出力は <outline> と <story> の2部構成で返す
</boundaries>

<output>
1. <outline> … 計画フェーズの結果を300字以内で要約
2. <story> … 完成した本文
</output>

ポイントは、自己計画で「設計を固める」こと、自己反省で「弱点チェック+改善提案」をする、境界条件を明確化して過剰推論を防ぐこと。

6 Control the eagerness of your coding agent

coding agent の積極性をコントロールすること

GPT-5 では、デフォルトでコンテキスト(文脈や状況)の収集において、徹底的かつ包括的なアプローチを採用しています。prompting を活用することで、モデルの探索の積極性の度合いや発見プロセス/ツール呼び出しの並列処理の有無について、より具体的な指示を与えることができます。

モデルにツール使用の予算を設定して、より詳細な調査を行うべきタイミングか、あるいはそうではないのか、さらに、ユーザーに確認をするべきかどうかを指定します。

例:

<persistence>
- Do not ask the human to confirm or clarify assumptions, as you can always adjust later
— decide what the most reasonable assumption is, proceed with it, and document it for the user's reference after you finish acting
</persistence>
--
- 前提条件の確認や明確化を人間に求める必要はない。後でいつでも調整可能なので
- 最も合理的な前提条件を判断し、それに基づいて処理を進め、作業完了後にはユーザーの参考用に文書化しておくこと

意見

GPT5 は使ってみるとわかるけど「調査 → 前提を確認 → 追加で調査 → さらに確認」という動きをしやすい。

この <persistence> の指示は「持続性」を意味していて、挙動を「いちいち確認を挟まずに、最も妥当な仮定で進めさせる」処理に切り替える効果があるはず。

メリットは、やり取りが短縮されるので期待する応答が速くなるし、調査を繰り返さないので、API コストの削減になる。

また、最後に「どういう前提で進めたのか」を文書化させることで、チェック&修正ができるようにしておく。こうすることで、AI の思考のブラックボックス化(traceability)を防ぐ。

5で挙げた例のような「小説の0から1の作成」に <persistence> は必要ないかもしれないです。前提をユーザー(人間)に確認する余地があまりありません。必要になるシーンを想定しておくとよいと思います:

  • 「曖昧な入力仕様が含まれるため」AI が立ち止まりやすいとき

必要になる例を挙げると、小説の例だとテーマをユーザーがあたえるとします。

しかし、与えるテーマがざっくりとしたラベルに留まっており「ライトノベル」や「中世風」のような指示をしたときに、この指示は曖昧か不十分とも取れてしまうので、そこは AI に任せた推論をしてほしいので persistence が有効になる、という流れになります。

あなたは小説家として、新しい短編小説を構築します。
いきなり本文を書かず、まず「計画フェーズ」を行ってください。

<plan>
- ストーリーの核となるテーマを1つ決める
- 主人公・敵対者・舞台を各1文で定義する
- 起承転結の4ステップでプロットを200字以内でまとめる
</plan>

<persistence>
- ユーザーが「ライトノベル風」「中世風」といった曖昧な指定をした場合でも、確認を求めず合理的な仮定を選んで進めること
- 仮定した解釈を <assumptions> タグにまとめて出力すること
- 仮定の例:
   ・「ライトノベル風」=会話多め・テンポ良く・現代的な文体
   ・「中世風」=ヨーロッパ中世ファンタジーの世界観(城・騎士・魔法)
- 後から修正できるよう、採用した仮定は必ず明示すること
</persistence>

<self_reflection>
- 計画の中で矛盾や弱点があるかを確認する
- 主人公の動機は十分に強いか?
- 敵対者は物語を駆動するに足る存在か?
- プロットに「転」が欠けていないか?
- 問題があれば1〜2文で改善を提案する
</self_reflection>

<boundaries>
- 小説本文は1000〜1200字
- 視点は一人称、過去形
- 暴力や残酷描写は含めない
- 出力は <outline> と <story> の2部構成で返す
</boundaries>

<output>
1. <outline> … 計画フェーズの結果を300字以内で要約
2. <story> … 完成した本文
</output>

で、最後に自分の指示したい内容を加えると、プロンプトが完成。

<genre>
- 現代日本を舞台とした「ライトノベル風」の作品にしてください
- 「ライトノベル風」とは会話多め・テンポ良く・カジュアルな文体を意味します
- 舞台は現代社会の中高生や大学生が自然に登場する日常~非日常要素の混ざる世界とします
</genre>

補足

ちなみに GPT-5 のプロンプトガイドは今回以外に以下がリンクされています。

個人的には、AI を使って楽をするには質問や仕事のお願いをするプロンプトの質を高くするとよいことがあると思う。

ただし、プロンプトの生成に拘りすぎても遅いので、簡単なとき or 難しいとき、などでいくつかの表現を整理しておくとよいと思う。簡単なら、境界条件を抑えて過剰な推論を避ける指示を加えるのは効果が見込めると思う。難しいときなら XML 風のプロンプトが有効になる可能性が高くなったりする。

すべての利用において、考えるのが難しい高コストな長文のプロンプトで指示することはないと思う。軽量と重量の “ギア切り替え” が、よい使いかたになると思う。

今回の内容では、間違った回答を防ぐ方法、幻覚 hallucination や誤解されやすい回答をどう抑えるかについては、新しい言及がありませんでした。この点は、相変わらず注意が必要で、旧来のテクニックも利用していく必要があると思う。また、新標準となる AGENTS.md のフォーマットも公開されています。

やはり AI を活用するための ability を持つ方が望ましい世界に当面はなりそう。

参考

python データ構造 学習

Python チュートリアルを読んで気になったところをメモ。

データ構造1

squares = list(map(lambda x: x**2, range(10)))
or
squares = [x**2 for x in range(10)]
or
# 冗長な例
squares = [(lambda x: x**2)(x) for x in range(10)]

処理の流れ1:

  • lambda x: x**2 を評価、無名関数のオブジェクトを作る
  • range(10) を評価、range 0-9 を作る(要素は未生成)
  • map(<lambda>, <range>) を評価、map iterator を作る(まだ未計算)
  • list(<map-iterator>) を評価、反復を開始
    • map が range 要素から次の値 x を1つずつ取り出す
    • 取り出した x に無名関数を適用 = x**2 を計算
    • 結果の要素を [] の list object に変換
  • range が終わると反復終了

処理の流れ2: squares = [x**2 for x in range(10)] - range(10) を評価、range 0-9 を作る(要素は未生成) - [] の内包表記で空の list を作成して range 要素から次の値 x を1つずつ取り出す - for 反復ステップ - x を1つずつ(0~9)まで取り出す - x**2 を計算し、結果を list に追加 - x を処理し終えると、出来上がった list がそのまま完成

list()[] の書き方は、簡易的な書き方になっているだけではない。list() は map を評価する働きがあるが [] はそのまま格納する。

なので、以下の書き方だと [] は map をそのまま格納して、処理は終わり。

squares = [map(lambda x: x**2, range(10))]
[<map object at 0x7fd6fb1c3130>]

データ構造2

[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]

or

combs = []
for x in [1,2,3]:
    for y in [3,1,4]:
        if x != y:
            combs.append((x, y))

普段は、1行でいくつもの処理を(意図してたくさん)まとめた書き方をしないので、処理の流れを追いづらかった。

個人的には、わかりやすい下の書き方を選択しがち。シンプルな内容なら内包表記も悪くないと思う。

処理の流れ:

  1. 外側のループ
  2. 最初に for x in [1,2,3] が動く
  3. x は順番に 1,2,3
  4. 内側のループ
  5. x に対して for y in [3,1,4] が動く
  6. y は順番に 3,1,4
  7. if 条件のチェック
  8. (x, y) リストに追加する前に if x != y を評価
  9. True ならリストに入れる

<式> の「最終的にリストに追加する要素」を表す部分が最初にくる。そのあと for で値を処理する。内包表記は、最初に最終的なデータ型のゴールが示されている点は留意したほうがよい。

[ <式>  for 変数 in イテラブル  (if 条件 …) ]

データ構造3

matrix = [
  [1,2,3,4],
  [5,6,7,8],
  [9,10,11,12],
]

[[row[i] for row in matrix] for i in range(4)]

<式> にあたるのは [第2処理] なので、とりあえず配列を得ることがわかる。

そのほかのところは単純で for i in range(4) なので i = 0~3 の繰り返しをするだけ。あとは [第2処理] の中がどうなっているのかを考える。

処理 i = 0:

  • 第2処理のループで row[i] = row[0] を集める:
    • row = [1,2,3,4] → row[0] = 1
    • row = [5,6,7,8] → row[0] = 5
    • row = [9,10,11,12] → row[0] = 9 [] はそのままリスト化するので [1,5,9] になる

あとも流れは同じ:

  • [1,5,9]
  • [2,6,10]
  • [3,7,11]
  • [4,8,12]

それぞれの list を外側の list に追加する。

tuple

私の tuple の理解に、すこし間違っているところがあった。

empty = ()

空の tuple を作るときは上のようにするから、1つの tuple を作るときも下のようにしていた。

single = ('data', )
or
single = 'data',

しかし、実際は丸括弧を必要としていない。0個の tuple を作るときが特別に括弧を必要とするのであって、以降は丸括弧を必要としていない。(丸括弧は、処理のグループ化をサポートしているので、全く無意味ではないと思う)

補足:C# ではデータが1個の tuple というのは存在しないはず。0個は作れる。python だと unpacking の都合もあって、一貫した書き方ができたほうが C# よりも都合がよい。

dict

ループのテクニックの整理:

  • .key() キーだけ
  • .values() 値だけ
  • .items() (key, value) の tuple

同じく enumerate() も index を割り当てした iterator を返却する。この時点で tuple のリストに即変換はされていない。中身を見たいときは list() で展開を指示しないとダメ。(通常は iterable なものを受け入れ可能な箇所に対して、そのまま enumerate object は代入できる)

list(enumerate(['tic','tac','toe']))
# => [(0, 'tic'), (1, 'tac'), (2, 'toe')]

内包表記の整理

内包表記は日本語の表記ですが、もともとは comprehension の「含有」の意味です。

  • list comprehension []
    • 結果は list
  • set comprehension {}
    • 結果は set
  • dictionary comprehension {}
    • 結果は dict
  • generator expression ()
    • ※結果は 遅延評価をする iterator

前の3つは、その時にコレクションをそれぞれ構築してしまうけど generator だけは、遅延評価をする。(なので、大きなデータを作るときは処理的に有利)

gen = (x**2 for x in range(1000000))
next(gen)  # 0
next(gen)  # 1

set と dict は同じような表記をしてしまう。歴史的には dict が先にあって、あとから set が用意されているはず。

理由として、空データを用意する方法を見る。空集合だけは書き方を区別できないので、先に用意されていた dict の書き方がシンプルになっている。(わずかな部分だけど、一貫性にケチがついているようにも見える)

li = ()
se = set()
di = {}

参考

python doctest 学習

doctest は python の標準ライブラリです。追加のインストールは不要で、python をインストールすると最初から使えるはずです。

doctest

一番最初にテストコードで doctest をテストしてみます。

def add(a, b):
    """
    Add two numbers.

    Examples:
        >>> add(2, 3)
        5
        >>> add(-1, 1)
        0
    """
    return a + b

実行コード:

python -m doctest -v your_module.py

実行したときのログは次のとおりです。

Trying:
    add(2, 3)
Expecting:
    5
ok
Trying:
    add(-1, 1)
Expecting:
    0
ok
1 items had no tests:
    doctest-sample
1 items passed all tests:
   2 tests in doctest-sample.add
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

-v をつけると上述のように、テストのログがコンソールに出力されるようになります。

-v をつけなかったときは、テストに成功した場合だと、なにも出力しません。しかし、テストに失敗した場合はコンソールにエラーの内容を出力します。なので -v は verbose(冗長な)オプションかもしれません。

*******************************************************
File "doctest-sample.py", line 8, in doctest-sample.add
Failed example:
    add(-1, 1)
Expected:
    2
Got:
    0
*******************************************************
1 items had failures:
   1 of   2 in doctest-sample.add
***Test Failed*** 1 failures.

また doctest を実行した際も __pycache__ フォルダが生成されて、フォルダの中にバイトコード化された .pyc ファイルができます。

これは結果的に -m オプションをつけるかどうかで、基本的には生成するかどうかが決まっています。(キャッシュを作ることが目的ではないが)

もしもキャッシュを作りたくないときは:

def add(a, b):
    """
    Add two numbers.

    Examples:
        >>> add(2, 3)
        5
        >>> add(-1, 1)
        0
    """
    return a + b

if __name__ == "__main__":
    import doctest
    doctest.testmod()

これだと単純な命令でテストを実行できるし、キャッシュも生成されない。でも、テストコード用のファイルみたいになってしまうので痛し痒し。

python your_module.py

補足として、テスト結果を外部ファイルに出力したい場合もこの書き方になる。

import doctest

if __name__ == "__main__":
    doctest.testfile('output.txt')

基本的な考え方として python は「再利用されるモジュール」だけ(自動的に)キャッシュにして残す設計だと思います。スクリプトの実行は限られた使い捨ての利用なので、キャッシュにする必要が(基本的に)ない、ということ。

本質的には -m をつけたから __pycache__ フォルダができたのだ、という理解は、ちょっと違うと思う。

テストの書き方

対話モードを示す >>> のあとにテストコードを示すだけです。テストコードのインデントの深さは関係していません。

def add(a, b):
    """
    Add two numbers.

    Examples:
        >>> add(2, 3)
        5
        >>> add(-1, 1)
        0
    """
    return a + b

基本的には対話モードで示した関数の実行例の返却値を次の行で示します。

例外をテストする場合は?

def divide(a, b):
    """
    Divide a by b.

    >>> divide(4, 2)
    2.0
    >>> divide(1, 0)
    Traceback (most recent call last):
        ...
    ZeroDivisionError: division by zero
    """
    return a / b

例外をテストしたいときの書き方は決まったやり方が必要です。

Traceback (most recent call last): を例外発生を示す呪文として記述する必要があります。さらに次の行はインデントを加えたうえで ... が必要。その上、最後に発生する例外 Error +その詳細も正確に明記する必要がある。なので、通常は division by zero の部分 message もしっかり書かないとダメ。

例外の message 部分を無視したいときは?

def divide(a, b):
    """
    >>> divide(4, 2)
    2.0
    >>> divide(1, 0)  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
        ...
    ZeroDivisionError
    """
    return a / b

+IGNORE_EXCEPTION_DETAIL オプションを使用する。

このときは ... の行をなくしても OK の判定がでます。これは以下のとおりだと思います。

個人的には、例外の詳細を IGNORE_EXCEPTION_DETAIL で吸収して、その手前の ... は慣例として付けておいたらいいと思います。

例外の ... は、この書き方で特別に省略が許されているケースみたいなので。

自分のテキスト出力で、一致の省略をしたいときは?

+ELLIPSIS オプションで一致する部分を省略できる。例えば 'Result:' だけに限定することができる。(後方一致がしたいなら ... completed などになる)

def verbose_result():
    """
    >>> verbose_result()  # doctest: +ELLIPSIS
    'Result: ...'
    """
    return "Result: 123 completed"

細かいポイントだけど、返却されるテスト結果を 'Result: ...' つまり '' 文字列として評価している。この違いは次のケースで示すことができる。テスト実行中に print(つまり標準出力)に与えられたときは '' が不要です。

整理すると、戻り値として文字列を返却するときは文字列なので '' が必要。

def verbose_result():
    """
    >>> verbose_result()  # doctest: +ELLIPSIS
    Result: ...
    """
    print("Result: 123 completed")

なにも返却しない関数の場合は?

補足ですが、まったくなにも返却するデータの無い関数の場合は次のようにすることができます。

def noop():
    """
    >>> noop()
    """
    return None
Trying:
    noop()
Expecting nothing
ok
1 items had no tests:
    doctest-sample2
1 items passed all tests:
   1 tests in doctest-sample2.noop
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

参考

python docstring 学習

C# には C# のコメントの書き方があるように python には python のコメントの書き方がある。

正直、今までは主として利用するのは C# であって、私にとって python は補助的な言語だからと(まとまった学習から)逃げていたのでノウハウが整理されていないし書き方も統一感がない。すこしずつでも python という言語の習慣に従った書き方を勉強していこうと思う。

とりあえず docstring の書き方は前から気になっていたので調べた。

違うことを書いていたらすいません

docstring

一番はじめになるのは python チュートリアルの内容になると思う。

つまりこれ:

def my_function():
    """Do nothing, but document it.

    No, really, it doesn't do anything.
    """
    pass

C# のコメントとは書き方が結構異なっていて、1行目の書き方が独特に見える。なにも考えなければ """ の後はすぐ改行して、次の行からコメントを書いたほうが読みやすいように見えると思う。

これは PEP257 でも示されるように、「最初の行に簡潔な説明を書く」という習慣になっています。習慣なので「守るべき」でしかなくて、守らなかったら~のようなところはあまり考慮するものではないけど、コメントのインテリセンス表示や Sphinx などのツールの妨げになる恐れはあると思います。

では、ドキュメンテーション文字列(docstring)について、内容と書き方に関する慣習を整理しておきます。

  • 最初の行(要約)
    • 最初の行には、関数やクラスなどの「目的」を短く簡潔に書きます。この行では、対象の「名前」や「型」を書く必要はありません。これらはコードを見れば分かるためです。ただし、対象の名前が動詞で、すでに処理内容を表している場合は例外です。
    • 1行目:
      • 文のように書く(命令形でなくてもよい)
      • 文頭は大文字で始める
      • 文末はピリオド(.)で終わる(日本語なら「。」になる)
    • 2行目:
      • 2行目を空行にすること
        • 1行目(要約)と以下の説明部分を視覚的に分ける
    • 以降の行(詳細な説明)
    • 最後行:
      • 「"""」だけで終わること

C# の感覚だと、どうしてこのコメントが関数の手前ではなくて関数名のあとに書かれているのか? となる。

/// <summary>
/// コメントの例
/// </summary>
public void Sample()
{
    // ...
}

これは、言語の考え方が関わっていて、python だとこの docstring もデータとして利用可能なものだからです。以下のようにすると、プログラムからも利用、アクセスできる。

print(Sample.__doc__) # python だと「コメントの例」の部分のテキストを取得できる

C#XML コメントだと実行時には消されてしまうので、実行時にコメントを取得することは困難だと思います。なので、コメント扱いが結構違うことがわかります。

コメントの書き方の注意点は、2行目以降の書き方にあると思います。ここは、自由度が高すぎるため、いくつかの書き方がユーザーに示されています。

reST 形式だと Sphinx を使ってドキュメント生成しやすい一方で、あまり書きやすいコメント形式になっていないのが欠点。(書くのが少し手間でもある)補足すると、ドキュメント生成は Google style でもできてしまうので、ドキュメント生成が得意なフォーマットくらいの位置。

現実的には Google style がよく採用される傾向にあるようです。というのも vscode などのインテリセンスが表示されるとき、最も見た目が最適化されるのは Google style になってしまっている状況みたいです。

なので、整理すると Sphinx での自動ドキュメント化を考えると reST が標準として採用しやすく、Google style のほうが視覚的にわかりやすいので、通常はこっち。

NumPy のスタイルについては言及していないけど、これは数学系のライブラリ。ただ、他のライブラリでも利用されているケースはあるので存在感ゼロというわけでもない。

個人的に NumPy の形式は見やすいと思う。だけど、それはインテリセンスによるコメント表示を利用せずにそのまま生のテキストで見たときの見やすさのようにも思う。なので、記述がすこし手間や余白が多い気がする。他にも、---------- のようなグループを分けるだけの行があったりするので縦長になる傾向にあると思う。

いずれにしても、書き方が整理されているものがいくつかあるので、そのうち最もあなたにとって都合のよいものを選ぼう、という感じになっている。ここでは Google style を調べた。

Google style

サンプル:

def div(x, y):
    """
    Divide two numbers and return the result.

    This function performs a division and returns the quotient.
    It raises a ZeroDivisionError if the divisor is zero.

    Args:
        x (float): The numerator.
        y (float): The denominator.

    Returns:
        float: The result of division.

    Raises:
        ZeroDivisionError: If y is zero.

    Examples:
        >>> div(10, 2)
        5.0
        >>> div(5, 0)
        Traceback (most recent call last):
            ...
        ZeroDivisionError: division by zero
    """
    if y == 0:
        raise ZeroDivisionError("division by zero")
    return x / y

用意されているセクションを整理する:

  • Attributes(クラスで使用:変数の説明)
  • Args(引数の説明)
  • Returns(通常関数で使用:戻り値の型+意味)
  • Yields(ジェネレータ関数のみ使用:戻り値の型+意味)
  • Raises(例外の一覧)
  • Examples(使用例:doctest 形式が望ましい)
  • Note(補足的な情報)
  • Todo(将来的な改善、未対応のメモ)

こんな感じなので、コメントで頻出するセクションは Args, Returns で Raises, Examples が記述の推奨で Note, Todo が記述の任意になると思う。また、クラス、ジェネレータ関数に対してのみ使うセクションもある。使用の際は使い分けに注意する。

Raises を書くときの考え方は色々あると思うけど、基本的には明示的に raise したもの、つまり直接(具体的にコーディングとして)記述した例外を残す必要性は高い。そのほかの例を挙げると、ファイル I/O で open するときのようなケースだと FileNotFoundError など発生が予想できる。この場合だと、記述しておくのは「親切」であって、比較的弱い推奨になる。

Examples の doctest 形式で書いておくとテスト用に別ファイルを作成して test_***.py みたいなので実行するのはライブラリとか、複数ファイルからなる python のプロジェクトだと思う。

単純な1ファイルで完結している python のプログラムだと別ファイルを用意しなくても、テストを実行できるメリットがある。なので、1ファイルでも関数の使い方をコメントに Examples として記述しておくことは望ましい。

doctest の使い方を把握しておくのは docstring を記述するうえで必要な(推奨される)知識になっていると思う。

python -m doctest file.py

(過去に作った関数などの)ユーザー使用を「非推奨」と明示するには?

個人的に気になる欠点として、Google style は非推奨となったメソッドなどを明示する「定型の方法」が提供されていないように思います。(下記は sphinx の例)

C# でも、コメントではなくて属性で Obsolete を付与すると思います。python だと色々な示し方があるようで微妙にどうするのか困る気がする。

組み合わせることもできるのは、覚えておきたい。(例:warnings.warn も出力するし、コメントにも記述する)

  • コメントに自分仕様で明示(sphinx などを参考にする)
  • warnings.warn()
  • decorator

プロパティやメンバ変数

C# の場合は、一律で同じ /// <summary> の書き方ができた。python の場合、関数やメソッド、クラスの説明は上述で対応できるが、プロパティや変数は同じ書き方ができない。

なので、変数に関しては単純なコメントを付与することになる。

class User:
    """Represents a user."""

    name: str  # The user's name
    age: int  # The user's age

あるいは:

class User:
    """Represents a user."""

    def __init__(self, name: str, age: int):
        """
        Initialize the user.

        Args:
            name (str): The user's name.
            age (int): The user's age.
        """
        self.name = name  # The user's name
        self.age = age  # The user's age

参考

Python Zen & PEP8 の検定試験(合格)

Python Zen と PEP 8 の内容を確認する無料テスト(草テスト)を受けました。ネット上だけで受けることができる簡単なもので、一応は合格後(すぐに)それっぽい PDF ファイルが送られてきます。

結果は「合格」 95/100 点

よかったです。おそらく1問5点で20問のシンプル構成です。ささっと回答すれば10分前後で終わります。

Python Zen と PEP 8 とは

最近は、かんたんなデータ処理のために python を利用する機会が増えてきたので、基本的なコードの書き方をあらためて学習したいと思いました。

とりあえず、私は coding style に興味があるほうなので、好きなところから始めたくて PEP 8 と 20 を読みました。

どちらも、自分で意訳しています。

モチベーションを持って取り組んだわけではないのですが、しっかりやっているほうじゃないかと思いました。

受験した理由

python を使う機会が多いから。

python には「There should be one-- and preferably only one --obvious way to do it.」という考え方があるので、自由度の高い言語なんだけど、どういうコードがいいコードか、という方針や指針があります。

いい感じの書き方は、少しずつでもいいから学習したほうがいい。

勉強に使ったもの

公式の PEP を参照した。PEP = Python Enhancement Proposal で「python の(機能)拡張提案」くらいの意味だと思う。

あと、参考書も見直して slice などの仕様をテストするなどしました。

学習期間

  • PEP 20:2025/05/23 ~ 2025/05/26
  • PEP 8: 2025/05/26 ~ 2025/05/29

毎日1H~3H程度で、合計で15H前後。試験難易度からすると、かなり多いと思います。

英語が得意なわけではないので、単純に翻訳が難しかったり、よくわからない方針/指摘の内容はテストしたりサンプルコードを作ろうとすると、時間がかかりました。 とくに PEP 8 は、かなりサンプルで実行できるコードを追加しています。

費用

0 円

特に今回のために買い物をしたりはしていません。テスト費用もかかりませんでした。

参考