coLinux日記

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

Raspberry Pi に最新の Python3 をインストールする

2024-07-14 08:32:34 | Python
連休なのでちょっと寄り道して、今使っている Raspberry Pi に、最新の Python3 をインストールしてみました。

使っている OS は、

$ cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 12 (bookworm)"
NAME="Raspbian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
$

です。

https://www.python.org/
から、最新の Python を調べると、現時点では、 3.12.4 でした。

早速、インストールしてみました。終わったら、 /usr/local/python の下にインストールすることにします。
$ cd
$ wget https://www.python.org/ftp/python/3.12.4/Python-3.12.4.tgz
$ tar xvf Python-3.12.4.tgz
$ cd Python-3.12.4
$ ./configure --prefix=/usr/local/python --enable-optimizations
$ make


(このマシンですと50分から1時間ぐらいかかりました。
 以下、途中で気になった make 時の出力を示します。 .... は途中省略を表します。
 例によって、空白文字が正しく表示されていません。)

......

The following modules are *disabled* in configure script:
_sqlite3

The necessary bits to build these optional modules were not found:
_bz2 _ctypes _ctypes_test
_curses _curses_panel _dbm
_gdbm _hashlib _lzma
_ssl _tkinter _uuid
readline
To find the necessary bits, look in configure.ac and config.log.

Could not build the ssl module!
Python requires a OpenSSL 1.1.1 or newer

(これらのソフトは、必要に応じてあらかじめインストールしておいた方がよいようです。
 特に OpenSSL 1.1.1 以上が必要みたいですが、
 Python3 で SSL は、どのように使うのか興味深いですね。)

.......

# Next, run the profile task to generate the profile information.
./python -m test --pgo --timeout=1200 || true
Using random seed: 1449334811
0:00:00 load avg: 1.00 Run 44 tests sequentially (timeout: 20 min)
0:00:00 load avg: 1.00 [ 1/44] test_array
0:00:08 load avg: 1.00 [ 2/44] test_base64
0:00:11 load avg: 1.00 [ 3/44] test_binascii
0:00:11 load avg: 1.00 [ 4/44] test_binop
0:00:12 load avg: 1.00 [ 5/44] test_bisect
0:00:13 load avg: 1.00 [ 6/44] test_bytes
0:00:50 load avg: 1.00 [ 7/44] test_bz2 -- test_bytes passed in 37.6 sec
0:00:51 load avg: 1.00 [ 8/44] test_cmath -- test_bz2 skipped
0:00:52 load avg: 1.00 [ 9/44] test_codecs
0:01:02 load avg: 1.00 [10/44] test_collections
0:01:10 load avg: 1.00 [11/44] test_complex
0:01:12 load avg: 1.00 [12/44] test_dataclasses
0:01:16 load avg: 1.00 [13/44] test_datetime
0:01:40 load avg: 0.94 [14/44] test_decimal
0:02:44 load avg: 0.98 [15/44] test_difflib -- test_decimal passed in 1 min 3 sec
0:02:52 load avg: 0.98 [16/44] test_embed
0:03:38 load avg: 0.99 [17/44] test_float -- test_embed passed in 45.5 sec
0:03:40 load avg: 0.99 [18/44] test_fstring
0:04:01 load avg: 1.00 [19/44] test_functools
0:04:06 load avg: 1.00 [20/44] test_generators
0:04:09 load avg: 1.00 [21/44] test_hashlib
0:04:12 load avg: 1.00 [22/44] test_heapq
0:04:18 load avg: 1.00 [23/44] test_int
0:04:21 load avg: 1.00 [24/44] test_itertools
0:05:04 load avg: 1.00 [25/44] test_json -- test_itertools passed in 42.2 sec
0:09:35 load avg: 1.00 [26/44] test_long -- test_json passed in 4 min 31 sec
0:10:16 load avg: 1.00 [27/44] test_lzma -- test_long passed in 41.5 sec
0:10:17 load avg: 1.00 [28/44] test_math -- test_lzma skipped
0:10:44 load avg: 1.00 [29/44] test_memoryview
0:10:48 load avg: 1.00 [30/44] test_operator
0:10:49 load avg: 1.00 [31/44] test_ordered_dict
0:10:58 load avg: 1.00 [32/44] test_patma
0:10:59 load avg: 1.00 [33/44] test_pickle
0:11:56 load avg: 1.00 [34/44] test_pprint -- test_pickle passed in 56.6 sec
0:11:58 load avg: 1.00 [35/44] test_re
0:12:05 load avg: 1.00 [36/44] test_set
0:12:46 load avg: 1.00 [37/44] test_sqlite3 -- test_set passed in 40.7 sec
0:12:47 load avg: 1.00 [38/44] test_statistics -- test_sqlite3 skipped
0:13:05 load avg: 1.00 [39/44] test_struct
0:13:11 load avg: 1.00 [40/44] test_tabnanny
0:13:14 load avg: 1.00 [41/44] test_time
0:13:17 load avg: 0.92 [42/44] test_unicode
0:13:37 load avg: 0.94 [43/44] test_xml_etree
0:13:41 load avg: 0.95 [44/44] test_xml_etree_c

Total duration: 13 min 49 sec
Total tests: run=8,314 skipped=175
Total test files: run=44/44 skipped=3
Result: SUCCESS
( Python3 は、make 時にテストをするみたいです。
 make check とかしたいところですが、時間がかかりそうなのでこれで良しとします。)
$

make できたので、さっそくインストールします。

$ su
# mkdir /usr/local/python
# make install
# exit

インストールできたか確認してみます。

$ ls /usr/local/python
bin include lib share
$
$ ls /usr/local/python/bin
2to3 idle3 pip3 pydoc3 python3 python3.12
2to3-3.12 idle3.12 pip3.12 pydoc3.12 python3-config python3.12-config
$
$ ls /usr/local/python/lib
libpython3.12.a pkgconfig python3.12
$


OpenSSL とか使えないので、今回インストールした Python3 は標準では使えないようにしているので、
環境変数 PATH と LD_LIBRARY_PATH を再設定します。

$ export PATH=/usr/local/python/bin:$PATH
$ export LD_LIBRARY_PATH=/usr/local/python/lib

実行してみます。

$ python3
Python 3.12.4 (main, Jul 13 2024, 23:01:23) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>


ちゃんと、3.12.4 になっています。
Raspberry Pi でも最新の Python3 が一応使えるので、Raspberry Pi の有効性があらためて確認できました。

追記:使用した Raspberry Pi はちょっと古い、
$ cat /proc/cpuinfo
.............
Model : Raspberry Pi 4 Model B Rev 1.2
$
です。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ + 代入文について

2024-07-09 22:27:49 | Python
SimplePrograms の 17番目のプログラムで、気になる代入の形が出てきたので少し調べてみました。

Python の場合、代入は「代入文 (Assignmet statement) 」で規定されているようです。
https://docs.python.org/ja/3/reference/simple_stmts.html#assignment-statements

「文」だとすると

left = right = col

は出来ないと思えます。実際 JavaScript などは、代入は「式」となっています。

そこで、もう一度定義をじっくり見てみました。(インデントがいい加減ですのでご容赦ください。)

assignment_stmt ::= (target_list "=")+ (starred_expression | yield_expression)
target_list    ::= target ("," target)* [","]
target        ::= identifier
          | "(" [target_list] ")"
          | "[" [target_list] "]"
          | attributeref
          | subscription
          | slicing
          | "*" target

starred_expression ::= expression | (starred_item ",")* [starred_item]
starred_item ::= assignment_expression | "*" or_expr


ちゃんと見てみたら、assignment_stmt に定義されていました。
(target_list "=")+
なので、
target_list1 = target_list2 = .....
となって、代入文として連鎖代入ができるのですね。

さらに、target_list が、
target ("," target)* [","]
となっているので、
target1 , target2 , target3 .... = .......
が可能なので、

left, right = left - 1, right + 1

も出来るのですね。これは 4 lines プログラムでも出てきたのですが、右側がタプルだったのでそんなものかと勘違いしていました。

改めて、試して見ましょう。
連鎖代入は、
>>> a = b = c = 4
>>> print(a, b, c)
4 4 4


target が複数の場合は、
>>> a , b = 5 , 6
>>> print(a, b, c)
5 6 4
>>>


target は、丸括弧や、角括弧で囲めるので、
>>> (a , b ) = 7, 8
>>> print(a,b,c)
7 8 4
>>> [a , b ] = 9, 10
>>> print(a,b,c)
9 10 4
>>>


更に、定義どおり最後に "," があっても大丈夫です。
>>> a, b, c, = 10, 20, 30
>>> print(a,b,c)
10 20 30
>>> a, b, c, = 100, 200, 300,
>>> print(a,b,c)
100 200 300
>>>


target_list は、左から右へ順番に代入されるようで、target が添字表記(subscription) の時、良くわかります。
>>> print(d)
[1, 2, 3, 4, 5]
>>> i, d[ i ] = 2 , 30
>>> print(d)
[1, 2, 30, 4, 5]
>>>


"=" の右側は、4 lines プログラムのようにタプルでも良いので、リストも良いみたいです。
>>> a , b, c = 1, 2, 3
>>> a, b = (10, 20)
>>> print(a,b,c)
10 20 3
>>> a, b = [100, 200]
>>> print(a,b,c)
100 200 3

この代入するタプルやリストは、予め変数に代入しておくことが可能です。
>>> print(a, b, c)
100 200 3
>>> d = [1, 2]
>>> print(d)
[1, 2]
>>> a, b = d
>>> print(a, b, c)
1 2 3
>>>

ついでに、target がスライス表記(slicing) の場合を見てみます。
>>> d = [1, 2, 3, 4, 5, 6]
>>> d[1:3]
[2, 3]
>>> d[2:4]
[3, 4]
>>> d[2:4] = 30, 40
>>> print(d)
[1, 2, 30, 40, 5, 6]
>>> d[0:2]
[1, 2]
>>>

通常は、「最初の添字:最後の添字」となりそうですが、Python の場合は違いますね。
これは、文字列のインデックス表記と同じで、
https://docs.python.org/ja/3/tutorial/introduction.html#text
から引用すると、

----------
インデクス表記に加え、 スライス もサポートされています。インデクス表記は個々の文字を取得するのに使いますが、 スライス を使うと部分文字列を取得することができます:
>>> word = 'Python' # これは追記しました。
>>> word[0:2] # characters from position 0 (included) to 2 (excluded)
'Py'
>>> word[2:5] # characters from position 2 (included) to 5 (excluded)
'tho'

スライスの使い方をおぼえる良い方法は、インデックスが文字と文字の あいだ (between) を指しており、最初の文字の左端が 0 になっていると考えることです。そうすると、 n 文字からなる文字列中の最後の文字の右端はインデックス n となります。例えばこうです:

+---+---+---+---+---+---+
| P  |  y  |  t  |  h |  o |  n |
+---+---+---+---+---+---+
0    1     2     3    4    5    6
-----------

つまり、インデクスは要素の間を表し、この図で言えば、
[a:b]なら[インデクスの右側の文字:インデクスの左側の文字]なので、リストなら、
a の右側の要素から、b の手前の要素までを表すようです。

そのため、d[2:4] なら、先頭要素を0番目として、2番目の要素から3番目の要素までを表すのですね。

定義の最後によると、 target の頭に "*" を付けることができるようです。しかし、

>>> *c = [1, 2, 3]
File "<stdin>", line 1
SyntaxError: starred assignment target must be in a list or tuple
>>>


なので、色々試したことろ、次のようにできることが分かりました。

>>> a, b, *c = [1, 2, 3, 4, 5, 6]
>>> print(a, b, c)
1 2 [3, 4, 5, 6]
>>>
>>> a, b, *c = 1, 2, 3, 4, 5
>>> print(a, b, c)
1 2 [3, 4, 5]
>>>
>>> a, b, *c = (1, 2, 3, 4, 5)
>>> print(a, b, c)
1 2 [3, 4, 5]
>>>


興味深いです。ちなみに、代入文で詳しそうなのを探したら、以下のものを見つけました。

Python's Assignment Operator: Write Robust Assignments
https://realpython.com/python-assignment-operator/

(中学生や高校生の方は、英語なので昔だったら諦めるところですが、現在はブラウザで翻訳できるので敷居が低くなりました。)

これからも分かるように、Python の「代入」は奥が深そうですね。

今回は、「代入文」だけになりました。次回は「リスト内包表記」に触れてみたいと思います。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ その17

2024-07-07 09:44:30 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 17番目のプログラムは、 18行の再帰呼び出し(エイト・クイーン問題)です。

BOARD_SIZE = 8

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

def solve(n):
    if n == 0:
       return [[]]

    smaller_solutions = solve(n - 1)

    return [solution+[(n,i+1)]
       for i in range(BOARD_SIZE)
          for solution in smaller_solutions
             if not under_attack(i+1, solution)]
for answer in solve(BOARD_SIZE):
    print (answer)



ファイルにして実行してみます。出力は92行(解の総数)あるのでほとんど省略します。

$ ./prog-18.py
[(1, 4), (2, 2), (3, 7), (4, 3), (5, 6), (6, 8), (7, 5), (8, 1)]
.......... 省略 ....................
[(1, 4), (2, 7), (3, 5), (4, 2), (5, 6), (6, 1), (7, 3), (8, 8)]
[(1, 5), (2, 7), (3, 2), (4, 6), (5, 3), (6, 1), (7, 4), (8, 8)]
$

17番目のプログラムなのに18行で、これ以降のサンプルプログラムは行が飛び飛びになっており、これまでよりより具体的なプログラムになっているようです。今後どうするかは、考えてみます。

これは、だいぶ前に Microsoft の Bing に質問した解説があるので紹介します。現在どうなっているかは試して見てください。
まずは、最初の8行の部分です。
ここから ----------------------
こんにちは、これはBingです。??

8-queens の問題とは、8×8のチェス盤に8つのクイーンを互いに攻撃しないように配置する問題です12。一般に、n-queens の問題として、n×nのチェス盤にn個のクイーンを配置する問題も考えられます1。

Python で書かれたサンプルプログラムは、再帰的な関数とリスト内包表記を使って、n-queens の問題のすべての解を求めるものです3。コードの各部分について説明します。

(1)まず、BOARD_SIZE = 8 という行は、チェス盤のサイズを定義しています。この値を変えると、n-queens の問題に対応できます。

(2)次に、under_attack(col, queens) という関数は、ある列番号 col と既に配置されたクイーンのリスト queens を引数にとり、その列にクイーンを置くことができるかどうかを判定する関数です。この関数では、以下の処理を行っています。

(3)・ left と right という変数に col を代入します。これらは、左斜め上方向と右斜め上方向にあるクイーンの列番号を表します。
(4)・ for r, c in reversed(queens): というループで、queens のリストを逆順に取り出しています。r は行番号、c は列番号です。
(5)・ left, right = left - 1, right + 1 という行で、left と right をそれぞれ1ずつ減らし、増やしています。これは、斜め上方向に移動することを表しています。
(6)・ if c in (left, col, right): という条件文で、c が left, col, right のいずれかと等しいかどうかを判定しています。これは、同じ列か同じ斜め方向にクイーンがあるかどうかを判定しています。
(7)・ もし条件が真ならば、True を返します。これは、その列にクイーンを置くことができないことを表しています。
(8)・ もしループが終了したら、False を返します。これは、その列にクイーンを置くことができることを表しています。

この関数は、クイーンの配置が可能かどうかを高速に判定するためのものです。
---------------------- ここまで (対応する行を () で追記してあります。

次に solve() の部分(9行目から16行目まで)です。

ここから ----------------------
( 9)次に、solve(n) という関数は、n 個のクイーンを配置する問題のすべての解を求める関数です。この関数では、以下の処理を行っています。

(10)・ if n == 0: という条件文で、n が0かどうかを判定しています。これは、すべてのクイーンが配置されたかどうかを判定しています。
(11)・ もし n == 0 ならば、[[]] を返します。これは、空の解のリストを表しています。

(12)・ もし n != 0 ならば、smaller_solutions = solve(n - 1) という行で、smaller_solutions という変数に、n-1 個のクイーンを配置する問題のすべての解のリストを代入しています。これは、再帰的に関数を呼び出すことで、問題を小さく分割して解いていることを表しています。

(13)・ return [solution+[(n,i+1)] for i in range(BOARD_SIZE) for solution in small_solutions if not under_attack(i+1, solution)] という行(つまり、13,14,15,16行)で、リスト内包表記を使って、
n 個目のクイーンを配置する可能性のあるすべての解を生成しています。この行では、以下の処理を行っています。

(14) ・ for i in range(BOARD_SIZE): というループで、i を0から7まで変化させています。これは、列番号を表しています。
(15) ・ for solution in smaller_solutions: というループで、smaller_solutions のリストから要素を取り出しています。これは、n-1 個のクイーンを配置する問題の解を表しています。
(16) ・ if not under_attack(i+1, solution): という条件文で、i+1 番目の列にクイーンを置くことができるかどうかを判定しています。これは、under_attack 関数を呼び出して、クイーンが互いに攻撃しないかどうかを判定しています。

・ もし条件が真ならば、solution+[(n,i+1)] をリストに追加します。これは、n 個目のクイーンを i+1 番目の列に置いた場合の解を表しています。

・ このようにして、n 個のクイーンを配置する問題のすべての解のリストを返します。
---------------------- ここまで (対応する行を () で追記してあります。

最後に答えを出力する部分です。
ここから ----------------------
(17)(18)最後に、for answer in solve(BOARD_SIZE): print (answer) という行で、solve(BOARD_SIZE) 関数を呼び出して、8-queens の問題のすべての解を求めています。そして、for ループでそれらの解を表示しています。

このプログラムは、再帰的な関数とリスト内包表記というPython の特徴的な機能を使って、簡潔に書かれています。しかし、初心者にとっては難しいかもしれません。もっと詳しく説明が必要な場合は、教えてください。
---------------------- ここまで (対応する行を () で追記してあります。

もっともらしく解説文を作ってくれました。便利ですね。

このプログラムから、Python は再帰呼び出しができる言語ということが分かりました。

(4)行目の reversed() は組み込み関数で、引数を逆順に取り出すイテレータを返すもののようです。

>>> b
[(1, 2), (3, 4), (5, 6), (7, 8)]
>>> reversed(b)
<list_reverseiterator object at 0xf798f730>
>>>
>>> for i,j in reversed(b):
...        print(i,j)
...
7 8
5 6
3 4
1 2
>>>

(6)行目の if 文の中の in は、
in の後ろのタプルの中に含まれるかどうかの判定に使うのですね。ここは、Copilot に聞いてみました。

ここから ----------------------
`in` は、リストや辞書の中に特定の値が含まれているかどうかを判定するために使います。
(途中省略)
要するに、`in` は特定の値がコンテナ(リストや辞書など)に含まれているかどうかを確認するのに便利なキーワードです。
---------------------- ここまで

タプルが使えるかどうかを確認すると、

ここから ----------------------
はい、`if` 文の中で `in`を使う際に、リストの代わりにタプルを使用することもできます。
タプルはリストと似ていますが、要素を変更できない不変のコレクションです。
---------------------- ここまで

Python のプログラムで分からない所の確認に生成AIは有効ですね。例も出てくるので実際に試せますから。
ためしてみると、次のようになりました。

>> print(a)
(1, 2, 3)
>>> print(b)
(4, 5, 6)
>>> print(c)
2
>>> if c in a:
...        print('True')
...    else:
...        print('False')
...
True
>>> if c in b:
...        print('True')
...    else:
...        print('False')
...
False
>>>
タプルでなくてリストでも大丈夫です。

>>> a = [1, 2, 3]
>>> if c in a:
...        print('True')
...    else:
...        print('False')
...
True
>>>

これは、よく使われそうです。

ここで、気になったのが
(3)行目のいわゆる連鎖代入
(5)行目のいっぺんに2つの変数に代入できる?表記と、
(13)~(15) の return にある、「リスト内包表記」です。

長くなったので、次回はこれに付いて調べて見たいと思います。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

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でシェアする

SimplePrograms で Python を学ぶ その13

2024-05-24 22:29:16 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 13番目のプログラムは、 unittest を使った ユニットテスト です。

import unittest
def median(pool):
    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
class TestMedian(unittest.TestCase):
    def testMedian(self):
       self.assertEqual(median([2, 9, 9, 7, 9, 2, 4, 5, 8]), 7)
if __name__ == '__main__':
    unittest.main()


13番目に出てくるということは、unittest って、 Python では有用なモジュールみたいですね。
しかも利用するためにクラスを使っているので、12番目がクラスだったのですね。

プログラムの見た目だけでは動きの想像が付かないので、例によってファイルにして実行してみます。

$ ./prog-013.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
$

マニュアル、
https://docs.python.org/ja/3/library/unittest.html#module-unittest
を参考に unittest を調べましょう。最も参考になるのは、
https://docs.python.org/ja/3/library/unittest.html#basic-example
の基本的な例の部分ですね。これと、13番目のプログラムを対比すると、関数 median()
の定義の後のクラス TestMedian の定義は、object の代わりに unittest.TestCase を指定してるので、
「テストケースは、 unittest.TestCase のサブクラスとして作成します。メソッド名が test で始まる三つのメソッドがテストです。」
から、

テストケース TestMedian は、unittest.TestCase のサブクラスとして作成され、
テストランナーは、TestMedian の メソッドで test で始まる、 メソッド testMedian を検索してテストするようです。

「これらのテスト内では、予定の結果が得られていることを確かめるために assertEqual() を、条件のチェックに assertTrue() や assertFalse() を、例外が発生する事を確認するために assertRaises() をそれぞれ呼び出しています。」
なので、
assertEqual() の、第一引数 media関数の実行結果が、第二引数となっているかどうかを確かめるテストを実施するようです。

「最後のブロックは簡単なテストの実行方法を示しています。 unittest.main() は、テストスクリプトのコマンドライン用インターフェースを提供します。コマンドラインから起動された場合、上記のスクリプトは以下のような結果を出力します」
なので、以下のテストの実施方法は丸ごと覚えるのが正解のようです。

if __name__ == '__main__':
    unittest.main()

それで、unittest によって、プログラムの実行結果のような結果が表示されるのですね。

ちなみに定義したmedian()関数は、

>>> a = len([1, 2, 3, 4, 5])
>>> a
5
>>> a % 2
1
>>> a % 2 == 1
True
>>> b = len([1, 2, 3, 4])
>>> b
4
>>> b % 2
0
>>> b % 2 == 1
False
>>>

なので、関数 median() に渡される引数として数字要素のリストを渡すと、
それを sorted() でソートして、len() でリストの要素数を求めて、
条件式 size % 2 == 1 ( % はあまりを求める算術演算子で、そのあまりが 1 に等しいどうかを調べる)から、
要素数が奇数の場合は真ん中の要素を return で返し、
要素数が偶数の場合は真ん中に近い2つの要素を2 で割った平均を return で返す、
関数であることが分かります。

unittest で、 testMedian というテストを作成し、median([2, 9, 9, 7, 9, 2, 4, 5, 8])
が正しく 7 を返すかどうかをテストするのですね。

こんな単純なテストでは分かりませんが、かなり複雑な関数を作成し、色々な引数での返却値が正しいかどうかのテストを
unittest で実施しておいて、
その後、関数に機能を追加したり、バグがあったりした場合、その結果を更に unittest のクラスのメソッドに追加していけば、
作成した unittest プログラムを実行するだけで修正が正しい事を保証できるレポートが手に入るので、
unittest の利用価値は高そうです。

Python を学んでいる者としては、Python でどのくらい unittest が利用されているか興味深いです。
コメント (2)
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ その12

2024-05-16 20:54:00 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 12番目のプログラムは、クラス です。

class BankAccount(object):
   def __init__(self, initial_balance=0):
      self.balance = initial_balance
   def deposit(self, amount):
      self.balance += amount
   def withdraw(self, amount):
      self.balance -= amount
   def overdrawn(self):
      return self.balance < 0
my_account = BankAccount(15)
my_account.withdraw(50)
print (my_account.balance, my_account.overdrawn())


Python のクラス定義ですね。
https://docs.python.org/ja/3/reference/compound_stmts.html#class-definitions
クラス定義は、複合文「複合文には、他の文 (のグループ) が入ります」の中で説明されているので、インデントによって中身を記述するわけです。説明によれば、

class classname(argment_list):

なので、 (argment_list) が継承リストで
「継承リストは通常、基底クラスリストを与えます」
だそうです。プログラムでは classname が 「BankAccount」 で、argment_list が 「object」 ですね。
「継承リストのないクラスは、デフォルトで、基底クラス object を継承する」
なので、(object) は省略できるようです。

さらに、クラス定義の中身の先頭にある __init__() は、
https://docs.python.org/ja/3/reference/datamodel.html#object.__init__
によると、

object.__init__(self[....])

なので、このクラスのインスタンス(クラスから作られたオブジェクト)を作るときに初期化するものを指定するための基底クラス object のメソッドですね。
この部分は丸ごとまねするのが良さそうです。

プログラムの見た目から、Python では、クラス定義内の self の部分を生成したインスタンスの名前に置き換えて使うようです。 

クラスを使うときにはオブジェクト指向を理解しておくのが前提なので、
オブジェクト指向超入門
http://www.rsch.tuis.ac.jp/~ohmi/software-intro/objectoriented.html
あたりで復習するとして、ここでは、クラスのインスタンスを生成すると、クラスで定義されているデータとメソッドが使えるようになるので、データとメソッドという用語を使います。(ということは、SimplePrograms が示すように、Pythonを学ぶ場合、同時にオブジェクト指向を理解する必要があるということらしいです。import でそれを感じましたが常識なんですね。)

ここで言っているデータは、Python では、
「インスタンス属性は、メソッドの中で self.name = value とすることで設定できます」
なので、「インスタンス属性(instance attribute)」と言うみたいです。

まず、クラスのデータから確かめて見ましょう。

>>> class Atest(object):
...      def __init__(self,initial_adata=0):
...         self.adata = initial_adata
...
>>>

クラス Atest を定義して、__init__ でデータを設定するのがねらいです。
このクラスのインスタンスを生成するには他の言語で一般的な new とか使わずに Python では classname そのもの(「( )」は必要?)
を変数に代入すれば良いみたいです。

>>> obj_01 = Atest() # クラス Atest のインスタンスを生成
>>> obj_01.adata
0

引数を指定しないと生成されたインスタンス obj_01 の データ adata が 0 となりますね。
つまり、__init__() の initial_adata=0 は、デフォルトで、0 を initial_adata に代入するということです。

>>> obj_02 = Atest(100)
>>> obj_02.adata
100
>>>

引数 100 を指定してインスタンス obj_02 を生成すると、そのデータ adata が 100 になりました。
つまり、__init__() の initial_adata に 指定した引数がデフォルト値の代わりに代入されて使用されるわけです。 ちなみに存在しないデータに対しては、
>>> obj_02.bdata
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Atest' object has no attribute 'bdata'. Did you mean: 'adata'?
>>>
「bdata なんて無いよ、adata じゃないの」と言ってくれるんですね。

また、インスタンスだけなら、
>>> obj_02
<__main__.Atest object at 0xf788eb10>
>>>
なので、「クラスAtest の object」と理解されているようです。

ついでに、2つの引数を指定してインスタンスを生成してみます。

>>> class Btest(object):
...      def __init__(self,initial_adata=0,initial_bdata=0):
...         self.adata = initial_adata
...         self.bdata = initial_bdata
...         self.tdata = self.adata + self.bdata
...
>>> obj_03 = Btest(10,100)
>>> obj_03.adata
10
>>> obj_03.bdata
100
>>> obj_03.tdata
110
>>>

複数引数も大丈夫ですね。

続いて、メソッドの定義を見てみましょう。プログラムをまねしてメソッドを定義してみます。
Plus というメソッドを作って、そのメソッドに引数を渡してみます。
対話型の Pyton は、矢印キーで入力履歴が再生できるので積極的に利用するとチェックも簡単です。

>>> class Ctest(object):
...      def __init__(self,initial_adata=0,initial_bdata=0):
...         self.adata = initial_adata
...         self.bdata = initial_bdata
...      def Plus(self, amount):
...         self.pdata1 = self.adata + self.bdata
...         self.pdata2 = self.pdata1 + amount
...
>>> obj_04 = Ctest(10,100)
>>> obj_04.pdata1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Ctest' object has no attribute 'pdata1'. Did you mean: 'adata'?
>>> obj_04.Plus(10000)
>>> obj_04.pdata1
110
>>> obj_04.pdata2
10110
>>>

インスタンス obj_04 を作成し、最初のエラーは、まだ Plus メソッドを実行していなかったので、データ pdata1 が作成されていなかったからですね。
obj_04.Plus(10000)
で、引数に 10000 を与えて Plus を実行すると、pdata1 と pdata2 が生成されて、
obj_04 のデータとして参照できるようになることが分かりました。メソッドの引数は、
def Plus(self, amount): の amount に代入されてその中で使えるようになるのですね。
インスタンスのデータは、そのメソッド等で値が代わったりすると、その後それが参照できるわけです。

クラスの簡単なデータ生成はこれの応用でできそうなので、この形式は丸ごと覚えた方がよさそうです。

いままで試してみたことで、このプログラムが理解できるようになりました。
BankAccount クラスのインスタンス my_account を生成するときに引数 15 を与えたので、
my_account のデータ balance は 15 なります。
my_account のメソッド withdraw に引数 50 を与えて実行したので、
そのデータ balance は -35 (15 - 50 だから) になります。
最後に print 文で、そのデータ balance と メソッド overdrawn() の実行結果が表示されるのですね。
ということは、メソッド定義内で、
reture 戻り値
を使うと メソッドの戻り値とできるわけです。
overdrawn() の戻り値は、条件式で、 balance が 負なら True 、そうでなければ False? ですね。
確認のため、プログラムに最後の3行を複製して修正し、条件式が偽になるものを追加して実行してみます。

$ ./prog-012-02.py
-35 True
10 False
$

(10 False は追加した3行で出力されました。) やはり条件が正しくないと、 False でした。

おまけで、チュートリアルのクラスの説明の中で、クラスのデータがリストの場合があったので追記しておきます。

class Dog:

   def __init__(self, name):
      self.name = name
      self.tricks = [] # creates a new empty list for each dog

   def add_trick(self, trick):
      self.tricks.append(trick)

__init__() でリストを生成しておかないと、複数のインスタンスで共通のリストになっちゃうようです。注意したいところですね。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする