coLinux日記

coLinuxはフリーソフトを種として、よろずのシステムとぞなれりける。

SimplePrograms で Python を学ぶ その16

2024-06-29 09:43:52 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 16番目のプログラムは、 csv 、 タプル・アンパック 、 cmp() です。

import csv

# need to define cmp function in Python 3
def cmp(a, b):
    return (a > b) - (a < b)

# write stocks data as comma-separated values
with open('stocks.csv', 'w', newline='') as stocksFileW:
    writer = csv.writer(stocksFileW)
    writer.writerows([
       ['GOOG', 'Google, Inc.', 505.24, 0.47, 0.09],
       ['YHOO', 'Yahoo! Inc.', 27.38, 0.33, 1.22],
       ['CNET', 'CNET Networks, Inc.', 8.62, -0.13, -1.4901]
    ])

# read stocks data, print status messages
with open('stocks.csv', 'r') as stocksFile:
    stocks = csv.reader(stocksFile)

    status_labels = {-1: 'down', 0: 'unchanged', 1: 'up'}
    for ticker, name, price, change, pct in stocks:
       status = status_labels[cmp(float(change), 0.0)]
       print ('%s is %s (%.2f)' % (name, status, float(pct)))

prog-16.py というファイルにして実行してみます。

$ ls
prog-16.py
$ ./prog-16.py
Google, Inc. is up (0.09)
Yahoo! Inc. is up (1.22)
CNET Networks, Inc. is down (-1.49)
$ ls
prog-16.py stocks.csv
$ cat stocks.csv
GOOG,"Google, Inc.",505.24,0.47,0.09
YHOO,Yahoo! Inc.,27.38,0.33,1.22
CNET,"CNET Networks, Inc.",8.62,-0.13,-1.4901
$

実行すると、前半で stocks.csv という CVS 形式のファイルが作成され、後半でそのファイルを読み込んで、
定義した cmp() 関数の結果を含めて表示するプログラムですね。

まず、cmp() 関数の定義ですが、複数引数を持つ場合ですね。 カンマで引数を分けており、
条件式が True なら 1、 False なら 0 になる事を利用して、
cmp(a, b) で、 a>b なら 1、 a=b なら 0、 a<b なら -1 を返す関数です。

じつは、この関数 python2 ではあったのに python3 から無くなったようです。
別の古い Raspberry Pi で試して見ます。
$ python
Python 2.7.13 (default, Feb 6 2022, 20:16:18)
[GCC 6.3.0 20170516] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print "cmp(80,100) : ", cmp(80,100)
cmp(80,100) : -1
>>> # print は、カッコがなくてもエラーにならない!

$ python3
Python 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print "cmp(80,100) :", cmp(80,100)
File "<stdin>", line 1
print "cmp(80,100) :", cmp(80,100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
>>> # print は、カッコがないとエラー
>>>
>>> print("cmp(80,100) :", cmp(80,100))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'cmp' is not defined
>>>

これは、古いPythonのプログラムを Python3 で使うときの非互換になるので対処が必要という話なので、サンプルプログラムに取り上げたのが興味深いです。

前半のファイル書き込み用の open() 関数は、次の説明から理解できます。
https://docs.python.org/ja/3/library/functions.html?highlight=open#open

ファイル名の引数の次の引数が、'w' なので、ファイルへの書き出し用 に開くのですね。
newline='' は、
「ユニバーサル改行モードは有効になりますが、行末は変換されずに呼び出し元に返されます。」
ということで、普通はこの指定で良さそうです。

次に CSV ファイルの読み書きです。
https://docs.python.org/ja/3/library/csv.html

CSVファイルはよく使う可能性があるので、とりあえず読み込みと書き込みはこのサンプルを丸ごと使うのが良さそうです。
例えば、
a01,b01,c01
a02,b02,c02
というCSVファイルは、1行づつ配列(正確には「変更可能なシーケンス型であるリスト型」のようです。)配列にして、
それらを個々の要素にして更に配列を作るわけですね。

そのオブジェクトを 'w' でファイルをオープンして、 csv.writer() と writerows() で書き出すと理解すれば良さそうです。

一方 CSVファイルの読み込みは、 'r' でファイルをオープンして csv.reader() で読み込むのですね。

ここで、for 文でファイルから読み込んだオブジェクトを1行づつ読み込むために、

for ticker, name, price, change, pct in stocks:

を使って、CSVファイルの1行を表すタプルをアンパックして各変数に代入し、各行の処理を行うようです。

各変数の値は文字列として受け取るらしいので、 change は float(change)で浮動小数点にするわけです。
もし整数なら、int(change) ですね。

status_labels は、お馴染みの辞書型ですね。最初に定義したcmp()関数を使って、change の値を判定して、
その判定値によって辞書を引いて、'down', 'unchanged', 'up' を出力するプログラムでした。

サンプルプログラムをそのまま利用するのが、とりあえず CSV ファイル操作の Python プログラムを作るときは近道ですね。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ その15

2024-06-21 18:53:51 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 15番目のプログラムは、 itertools です。

from itertools import groupby
lines = '''
This is the
first paragraph.

This is the second.
'''.splitlines()
# Use itertools.groupby and bool to return groups of
# consecutive lines that either have content or don't.
for has_chars, frags in groupby(lines, bool):
    if has_chars:
       print (' '.join(frags))
# PRINTS:
# This is the first paragraph.
# This is the second.

15行なのにコメント行が5行ですね。ファイルにして実行してみます。

$ ./prog-015.py
This is the first paragraph.
This is the second.
$

最後のコメント行通りの出力です。

まず、splitlines() メソッドの動きを見てみます。3重クオートによって生成された lines 変数は、

>>> lines
'\nThis is the\nfirst paragraph.\n\nThis is the second.\n'
>>>

で、splitlines() メソッドを使うと、

>>> lines.splitlines()
['', 'This is the', 'first paragraph.', '', 'This is the second.']
>>>

改行文字が区切りであり、空行が空の要素になるのですね。

また、join()は、

>>> print('X'.join('abcde'))
aXbXcXdXe
>>>
>>> print('X'.join(['ab','cd','ef']))
abXcdXef
>>>

なので試した例では、join()の引数が、文字列なら1文字づつ、配列なら要素毎に、'X'オブジェクトを挟んで結合するようです。

これは、
https://docs.python.org/ja/3/library/stdtypes.html?highlight=join#bytes.join
から、
「要素間のセパレータは、このメソッドを提供する bytes または bytearray オブジェクトとなります。」
だからですね。

各メソッドが分かったので、itertools を調べましょう。
https://docs.python.org/ja/3/library/itertools.html?highlight=itertools

このモジュールは APL を参考にしているので、複数要素からなるオブジェクトに対して複雑な繰り返しの操作を行うことのできるもののようです。

APLは古い記憶をたどると、確か
ι 12
で 1 , 2, 3, ... , 12 のベクトルが生成できて(つまりイテレータ)、さらに、
4 3 ρ ι 12
とすると、このベクトルを4行3列の2次元配列にしてくれる、という変わった言語だったと思います。
ちなみに、APLは特殊文字を多様するため現在使用するのは難しそうですが、
この言語、文字列配列変数の中身を変更して実行できる、のような特殊な機能満載なので、機会があればに深淵?を覗いてみるのも良いかもしれませんね。

今回は、そんなイテレータの中から、groupby()を使った例ですね。
https://docs.python.org/ja/3/library/itertools.html?highlight=itertools#itertools.groupby

groupby()の実際の動きは複雑そうですが、今回は
itertools.groupby(iterable, key=None)
の、 key が bool なので、 True または False を返すようです。試してみます。

>>> a
['', 'This is the', 'first paragraph.', '', 'This is the second.']
>>> for b, c in groupby(a, bool):
...        print(b, c)
...
False <itertools._grouper object at 0xb6a5bbb0>
True <itertools._grouper object at 0xb6a5bad8>
False <itertools._grouper object at 0xb6a5bbb0>
True <itertools._grouper object at 0xb6a5bad8>
>>>

groupby(a, bool) によって、配列 a の要素が 空文字なら False, 文字列なら True になるようです。
a の要素が 5 なのに、for ループは4回なのは、a の要素 1 と 2 が、連続して空文字でなかったので、
「key 関数の値が変わるたびに休止または新しいグループを生成します」から、一つの itertools._grouper オブジェクト内に 2つの要素をグループ化したようです。

それで、そのグループがjoin()によって' 'で連結されて、

'This is the first paragraph.'

になったのですね。例えばこんな感じです。

>>> aa
['ab', '', 'cde', '', '', 'fg', 'hij', 'klm']
>>> for b, c in groupby(aa, bool):
...        print(b,c)
...
True <itertools._grouper object at 0xb6971bc8>
False <itertools._grouper object at 0xb6a5bad8>
True <itertools._grouper object at 0xb6a5b9e8>
False <itertools._grouper object at 0xb6a5bad8>
True <itertools._grouper object at 0xb6a5b9e8>
>>> for b, c in groupby(aa, bool):
...        if b:
...           print(' '.join(c))
...
ab
cde
fg hij klm
>>>

連続した空行と連続した文字列はグループ化されて8要素が5グループに分類され、空行は False なので除かれ、最後の連続した3要素が ' ' 文字(空白文字)で連結されました。

itertools の奥は深そうですが、SamplePrograms にでてくるのが凄いですね。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ + docstring 関連とPEPについて

2024-06-11 16:41:30 | Python
引き続き14番目のプログラムに関連した投稿をします。

前回使っていた LINE AI アシスタントは、3日使うと「有料」になったので、今後生成AIを使う時は Microsoft Copilot (1日30問無料)を使います。

「Python の入門時に docstring を使うことは非常に一般的であり、推奨されるベストプラクティスです。」
とお勧めされたので、 docstring の重要性からもう少し調べてみようと思いました。

前回の生成AIの回答の中で、「PEP 257]というドキュメントが言及されています。早速 PEP 257 を探すと、アブストラクトにあるように、Pythonのdocstringに関連するセマンティクスと規則のドキュメント、みたいです。
英文ですが、現在はブラウザが翻訳してくれるので、すべての入門者に優しくなっていますね。 
その文書の中で、PEP 8 を参考にしているみたいなのでそれも探してみると、表題が、

PEP 8 - Style Guide for Python Code

となっていて、入門者にとって重要な文書(しかも著者は Python の生みの親として知られる人!)でした。全然知りませんでした。

もしかして PEP って RFC みたいなものかと思い、Copilot に聞いてみました。

ここから -------
Q1:
python における PEP文書の位置付けを教えてください。もしかしてネットワーク技術におけるRFC文書と同じものですか?
A1:
PythonにおけるPEP(Python Enhancement Proposal)文書は、Pythonコミュニティによって提案され、議論され、採択されたPythonプログラミング言語の変更や新機能に関する設計文書です3。PEPはPythonの設計と開発プロセスの一部として使用され、新しい機能の提案や言語の改善を進めるための枠組みを提供します3。

一方で、RFC(Request For Comments)文書は、インターネット技術の標準化を行うIETF(Internet Engineering Task Force)が発行している技術仕様などについての文書群です?。これにはTCP/IP関連のプロトコルの標準仕様などが記されており、インターネット上で公開されています?。

つまり、PEPとRFCは似た役割を持っていますが、対象とする技術領域が異なります。PEPはPython言語の改善に特化しているのに対し、RFCはインターネット全般の技術標準に関するものです。どちらもその分野の開発者や研究者にとって重要なガイドラインとなっています。
----- ここまで

Pythonでは、PEP が重要なのですね。

PEP 8 は長い文書なのでここでは触れませんが、既に日本語訳も公開されています。その最初のところで PEP 20 という文章があることがわかりました。
https://peps.python.org/pep-0020/

早速 Copilot に聞いてみました。

ここから -------
Q2:
PEP20 にある19の格言を、Python初心者向けに分かりやすく解説してください。
A2:
もちろんです。PEP 20、別名「Pythonの禅」には、Pythonの設計哲学を表す19の格言があります。これらはPythonのコードがどのように書かれるべきか、そしてPythonコミュニティが価値を置く原則を示しています。ここでは、それぞれの格言を初心者向けに簡単に解説します。

1. **美しい方が醜いより良い** - コードは読みやすく、整理されているべきです。
2. **明示的な方が暗黙的なより良い** - コードは自己説明的であるべきで、その動作が明確であるべきです。
3. **単純な方が複雑なより良い** - シンプルな解決策を目指しましょう。
4. **複雑な方が複雑化したより良い** - 必要ならば複雑さを受け入れますが、無駄に複雑にしないでください。
5. **フラットな方がネストしたより良い** - 深いネストは避け、フラットな構造を目指しましょう。
6. **疎な方が密なより良い** - 情報は適度に配置し、過密状態を避けましょう。
7. **可読性は重要** - コードは他の人にとっても理解しやすいものであるべきです。
8. **特別な例外は特別扱いに値しない** - 一貫性を保ち、例外を作らないようにしましょう。
9. **実用性は純粋さに勝る** - 完璧を求めるよりも、実用的なコードを書きましょう。
10. **エラーは黙って通過させるな** - エラーは明確にし、適切に処理する必要があります。
11. **曖昧さに直面したら、推測する誘惑に負けるな** - 明確な解決策を見つけましょう。
12. **明白なやり方が一つあるべき** - ある問題に対しては、一つの明確な解決策があるはずです。
13. **今やる方が、やらないより良い** - 行動を起こしましょうが、急ぐ必要はありません。
14. **実装が説明しにくいなら、それは悪いアイデア** - シンプルで説明しやすいコードを書きましょう。
15. **名前空間は素晴らしいアイデア** - 名前空間を活用して、コードの整理をしましょう。

これらの格言は、Pythonのコードを書く際の指針となります。コードを書くときは、これらの原則を心に留めておくと良いでしょう。
----- ここまで

19 の格言のはずが、15しかないのは、いくつかの2つの格言を一つにして紹介しているからみたいです。
PEP 20 の最後の方に 「Easter Egg」として、

>>> import this

とあるので、実際にPythonで実行してみます。

>>> import this
he Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
>>>

やっぱり、Python は面白いですね。

こうして生成AIを使ってきたわけですが、 Python でののプログラム作成時に生成AIを使うと、Python言語入門者として技術が身につかない可能性があります。
しかし、今回 SimplePrograms の14番目で出てきた docstring はどうでしょう。
自分でプログラムを作って、そこに説明を入れるのは入門者には難しいと感じて、ついついそこを素通りしていつの間にか作成したプログラムに説明をいれるのを怠ってしまいがちになることが考えられます。Pythonではdocstring が用意されているので、なれないうちはとりあえず生成AIに docstring を作ってもらった方が良いのでは、と感じました。

そこで、SimpleProgramsの 18番目のプログラム内の関数 under_attack() の docstring を Copilot に作ってもらいましょう。

ここから -------
Q3:
次の Python で書かれた関数 under_attack()のために、最適な docstring を作成してください。
def under_attack(col, queens):
    left = right = col
    for r, c in reversed(queens):
       left, right = left - 1, right + 1

    if c in (left, col, right):
       return True
    return False


A3:
もちろんです!以下に、`under_attack` 関数のための適切なドックストリングを提供します。

```python
def under_attack(col, queens):
    """
    Checks if placing a queen in the specified column (`col`) would result in an attack
    from any of the previously placed queens (`queens`).

    Args:
       col (int): The column index where the new queen is to be placed.
       queens (list): A list of (row, column) tuples representing the positions of
          previously placed queens.

    Returns:
       bool: True if the new queen would be under attack, False otherwise.
    """

    left = right = col
    for r, c in reversed(queens):
       left, right = left - 1, right + 1

       if c in (left, col, right):
          return True
    return False
```

このドックストリングは、関数の目的、引数、および戻り値を明確に説明しています。関数を使用する際に参考にしてくださいね!ーーーーーーーーーーーーー
----- ここまで

だいたい狙い通りですね。

英語しか出てこないと思う場合は、DeepL とかを使って翻訳してから、最後に自分なりに直すというのが良いかもしれません。
参考までにDeepLの翻訳例を示します。

    指定された列(`col`)にクイーンを置くことで、それまでに置かれたクイーン(`queens`)のいずれかが攻撃されるかどうかをチェックする。
    引数:
       col (int): 新女王が配置される列のインデックス
       queens (list): 以前に配置されたクイーンの位置を表す(row, column)タプルのリスト。
    戻り値:
       新女王が攻撃を受けていれば真、そうでなければ偽。

質問では 8 クイーンでの関数とは書いていないのですが、検索結果から Copilot が判断したようです。

この回答では、doctest 用の部分がないみたいなので、それはまた別の質問方法が必要そうです。

Python入門時に、
Pythonプログラムで関数やクラスを作成するとき、とりあえず生成AIを使って docstring を追加しておこう。
という流れになっているのか気になります。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ + LINE AI アシスタントで doctest を調べる

2024-06-07 22:17:08 | Python
前回の14番目のプログラムで出てきた doctest をもう少し知りたいと思いました。そのため、以前も試した LINE の機能でその後正式に公開されたサービス「LINE AI アシスタント」を利用してみます。
質疑応答は以下の通りです。(Q:質問、A:回答です。)
------- ここから
Q1:
Python の doctest の使い方を初心者に説明するにはどのようにすればよいのでしょうか。
A1:
Python の doctest
モジュールは、コード内のコメントとして書かれたテストケースを実行するためのツールです。初心者向けに説明するために、以下のステップに従ってください。

### 1. doctest の基本概念
doctest は、Python のドキュメンテーション文字列(docstring)に書かれた例を実行して、その出力が期待される結果と一致するかどうかを確認します。これにより、コードの動作を簡単にテストできます。

### 2. 簡単な例
まず、簡単な関数とその docstring にテストケースを追加してみましょう。

```  python
def add(a, b):
    """
    2つの数値を加算します。

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

### 3. doctest の実行
次に、このテストを実行する方法を説明します。以下のコードを追加して、スクリプトを実行します。

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

これにより、doctest は docstring 内のテストケースを実行し、結果を確認します。

### 4. 実行結果の確認
スクリプトを実行すると、テストが成功した場合は何も表示されません。エラーがある場合は、どのテストが失敗したかが表示されます。

### 5. 追加のヒント
- 複数行の出力をテストする場合は、`>>>` の後に改行を入れて続けて書きます。
- 例外が発生することをテストする場合は、`>>>` の後に例外を記述します。


```  python
def divide(a, b):
    """
    2つの数値を除算します。

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

### まとめ
doctest は、コードのドキュメンテーションとテストを一体化する便利なツールです。初心者でも簡単に使えるので、ぜひ試してみてください。
ここまで----------

この場合、三重クオートは docstring というのですね。生成された例を実行してみます。最初が test00.py 2番目が test01.py の各ファイルにしました。

$ python3 ./test00.py
$

docstring 通りに動作するので何も表示されないのですね。そこで、docstring の中をわざと次のように変更して、test00-2.py を作成します。
    >>> add(2, 3)
    4
    >>> add(-1, 1)
    1

$ python3 ./test00-2.py
**********************************************************************
File "./test00-2.py", line 5, in __main__.add
Failed example:
    add(2, 3)
Expected:
    4
Got:
    5
**********************************************************************
File "./test00-2.py", line 7, in __main__.add
Failed example:
    add(-1, 1)
Expected:
    1
Got:
    0
**********************************************************************
1 items had failures:
    2 of 2 in __main__.add
***Test Failed*** 2 failures.
$

テストとなる部分以外は日本語が使えるようです。

生成された2番目の例を実行すると、

$ python3 ./test01.py
$

docstring 通りで、ちゃんとエラーになる場合も確認できるようですね。

さらに docstring について質問してみます。
------- ここから
Q2:
この回答に出てくる docstring は、初心者に馴染みが無いように思います。docstring を分かりやすく説明してください。
A2:
もちろんです。docstring(ドックストリング)は、Python の関数、クラス、モジュールなどに対する説明文を記述するための特別な文字列です。初心者向けに分かりやすく説明しますね。

### 1. docstring とは?
docstring は、関数やクラスの最初に書かれる文字列で、その機能や使い方を説明します。これにより、コードを読む人がその関数やクラスが何をするものかを理解しやすくなります。
### 2. docstring の書き方
( 省略 )
### 3. docstring の利点
- **コードの可読性向上**: 他の開発者がコードを理解しやすくなります。
- **自動ドキュメント生成**: Sphinx などのツールを使って、自動的にドキュメントを生成できます。
- **インタラクティブシェルでのヘルプ**: `help()` 関数を使って、docstring を簡単に表示できます。
( 以下省略 )
ここまで----------

つまり、
>>> help(関数名)
と help()関数を使うと、docstring の内容を表示してくれるようです。

更に次の質問をしてみました。
------- ここから
Q3:
この説明によると、python で関数を作るときには、通常のプログラミング言語のコメントの代わりにdocstringで説明するコメントをいれるべきだと思いますが、python 入門時に一般的な話でしょうか?
A3:
Python の入門時に docstring を使うことは非常に一般的であり、推奨されるベストプラクティスの一つです。以下にその理由を詳しく説明します。

### 1. Python の哲学
Python には「明示的は暗黙的に勝る」という哲学があります。これは、コードが何をしているのかを明確にすることが重要であるという考え方です。docstring はこの哲学に沿ったもので、関数やクラスの動作を明示的に説明する手段となります。

### 2. 初心者にとっての利点
- **理解しやすさ**: docstring を使うことで、関数やクラスの目的や使い方が明確になります。これにより、初心者でもコードを理解しやすくなります。
- **学習の一環**: docstring を書く習慣を早い段階で身につけることで、後々の開発においても役立ちます。

### 3. コメントとの違い
通常のコメント(`#` で始まる行)は、コードの特定の部分についての短い説明を提供するために使われます。一方、docstring は関数やクラス全体の説明を提供するために使われます。
( 省略 )
### 4. docstring の標準
Python には、docstring の書き方に関する標準的なガイドラインがあります。例えば、PEP 257 というドキュメントがあり、docstring の書き方について詳しく説明しています。これに従うことで、統一感のあるドキュメントを作成できます。

### 5. 実際の開発での利用
実際の開発現場では、docstring は非常に重要です。特に大規模なプロジェクトやチーム開発においては、docstring があることで他の開発者がコードを理解しやすくなります。また、自動ドキュメント生成ツールを使う際にも docstring は必須です。

### まとめ
Python の入門時に docstring を使うことは非常に一般的であり、推奨されるベストプラクティスです。docstring を使うことで、コードの可読性が向上し、他の開発者や将来の自分にとっても理解しやすいコードを書くことができます。ぜひ、関数やクラスを書く際には docstring を活用してみてください。
ここまで----------

Python では、関数やクラスを作成するときに docstring を使う事がとても大切だと思いました。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ その14

2024-06-01 09:31:11 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 14番目のプログラムは、 doctest ベースのテストです。

def median(pool):
    '''Statistical median to demonstrate doctest.
    >>> median([2, 9, 9, 7, 9, 2, 4, 5, 8])
    6 #change to 7 in order to pass the test
    '''
    copy = sorted(pool)
    size = len(copy)
    if size % 2 == 1:
       return copy[int((size - 1) / 2)]
    else:
       return (copy[int(size/2 - 1)] + copy[int(size/2)]) / 2
if __name__ == '__main__':
    import doctest
    doctest.testmod()


よくわからないので、とりあえずファイルにして実行してみましょう。

$ ./prog-014.py
**********************************************************************
File "./prog-014.py", line 4, in __main__.median
Failed example:
    median([2, 9, 9, 7, 9, 2, 4, 5, 8])
Expected:
    6 #change to 7 in order to pass the test
Got:
    7
**********************************************************************
1 items had failures:
   1 of 1 in __main__.median
***Test Failed*** 1 failures.
$

非常に不思議な動きです。3重クォートで作成した関数 media の動作を説明するような文字列から、テストを作成して実行結果が一致するかどうかをテストするようです。
それで、doctest というのですね。

最後の、3行は、doctest を使う場合よく使う書き方でしょう。

https://docs.python.org/ja/3/library/doctest.html?highlight=doctest#module-doctest
には、
「doctest モジュールは、対話的 Python セッションのように見えるテキストを探し出し、セッションの内容を実行して、そこに書かれている通りに振舞うかを調べます。」
具体的に実行例が挙げられているので、実際に使用する場合はこちらの例を参考にした方が良さそうです。

プログラムで、 6 #change to 7 ....
となっているのは、# 以降は Python のコメントとして解釈されて、median()の結果が 6 になるかどうかのテストで、
7 になるので、Failed になるのですね。

ちなみに対話型 Python では、

>>> '''
... line 1
... line 2
... '''
'\nline 1\nline 2\n'
>>>

となるのに、これがファイルの中にあるとあたかもコメントの様に何も表示されません。
$ cat prog-014-02.py
'''
This is a test.
'''
'abc'
123

$ python3 prog-014-02.py
$

不思議ですね。
doctest は、次回もう少し続けてみたいと思います。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする