coLinux日記

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

SimplePrograms で Python を学ぶ その19

2024-08-23 22:54:36 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 19番目のプログラムは、 21行の XML,HTML パーサです。
(このブログではうまく表示できないので、XML/HTMLデータは大文字のカギ括弧を使っています。)

dinner_recipe = '''<html><body><table>
<tr><th>amt</th><th>unit</th><th>item</th></tr>
<tr><td>24</td><td>slices</td><td>baguette</td></tr>
<tr><td>2+</td><td>tbsp</td><td>olive oil</td></tr>
<tr><td>1</td><td>cup</td><td>tomatoes</td></tr>
<tr><td>1</td><td>jar</td><td>pesto</td></tr>
</table></body></html>'''

# From http://effbot.org/zone/element-index.htm
import xml.etree.ElementTree as etree
tree = etree.fromstring(dinner_recipe)

# For invalid HTML use http://effbot.org/zone/element-soup.htm
# import ElementSoup, StringIO
# tree = ElementSoup.parse(StringIO.StringIO(dinner_recipe))

pantry = set(['olive oil', 'pesto'])
for ingredient in tree.getiterator('tr'):
     amt, unit, item = ingredient
     if item.tag == "td" and item.text not in pantry:
         print ("%s: %s %s" % (item.text, amt.text, unit.text))


これを、prog-21.py というファイルにして実行してみます。

$ python3 prog-21.py
e "/home/espiya/test/python/samples/prog-21.py", line 18, in <module>
     for ingredient in tree.getiterator('tr'):
                 ^^^^^^^^^^^^^^^^
AttributeError: 'xml.etree.ElementTree.Element' object has no attribute 'getiterator'
$ (空白がずれていると思うので ^^ は tree.getiterator を表しています。)

SimpoePrograms 初めての実行できない事例ですね。ちなみに、コメントのURLももはや存在しません。

原因は、getiterator() ですね。

https://docs.python.org/ja/3.6/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element.getiterator

バージョン 3.2 で非推奨: 代わりに Element.iter() メソッドを使用してください。

なので、 

iter(tag=None)

現在の要素を根とする木の イテレータを作成します。
イテレータは現在の要素とそれ以下のすべての要素を、文書内での出現順 (深さ優先順) でイテレートします。 tag が None または '*' でない場合、与えられたタグに等しいものについてのみイテレータから返されます。イテレート中に木構造が変更された場合の結果は未定義です。

ということで、 tree.iter('tr') で tree の中で、タグが tr のものを抽出できます。指示に従って、
for ingredient in tree.iter('tr'):
と変更して prog-21-01.py を作成して、2つのファイルの違いを表示した後に、実行してみます。

$ diff prog-21.py prog-21-01.py
18c18
< for ingredient in tree.getiterator('tr'):
---
> for ingredient in tree.iter('tr'):
$
$ python3 prog-21-01.py
baguette: 24 slices
tomatoes: 1 cup
$

これは、HTML 形式のデータ dinner_recipe を取り扱うプログラムですね。
つまり、開始タグ + コンテンツ + 終了タグ からなる要素(element)の集まりで、コンテンツに別の要素を含んでいること(入れ子)を許すようなデータの扱いです。

プログラムを見ていきましょう。

import 文から、使用するモジュールは xml.etree.ElementTree で、それを etree としています。

tree = etree.fromstring(dinner_recipe) は、

文字列定数で与えられた XML 断片を解析します。 XML() と同じです。 text には XML データを含む文字列を指定します。 parser はオプションで、パーザのインスタンスを指定します。指定されなかった場合、標準の XMLParser パーザを使用します。 Element インスタンスを返します。

なので、試して見ます。

>>> d = '''<html><body><table>
... <tr><td>aaa</td><td>001</td></tr>
... <tr><td>bbb</td><td>002</td></tr>
... </table></body></html>
... '''
>>>
>>> d
'<html><body><table>\n<tr><td>aaa</td><td>001</td></tr>\n<tr><td>bbb</td><td>002</td></tr>\n</table></body></html>\n'
>>> t = etree.fromstring(d)
>>> t
<Element 'html' at 0xf6d83690>
>>>

作成された Element インスタンスですが、

>>> print(tree[0].tag)
body
>>> print(tree[0][0].tag)
table
>>> for trTag in tree[0][0]:
...        print(trTag.tag, trTag.text)
...        for child in trTag:
...            print(' ',child.tag, child.text)
...
tr None
     td aaa
     td 001
tr None
     td bbb
     td 002
>>>
のように、ツリー構造になっているそうです。
ちなみに、HTMLが間違っていると、mismatched tag というエラーが出るようです。

この例の Element インスタンス t は、for 文でこのプログラムのように iter('tr')を使えば、
tr タグで囲まれた2つの td タグの部分を2つの要素として取り出せるので、

>>> for i in t.iter('tr'):
...        a, b = i
...        print(a.tag, a.text, b.tag, b.text)
...
td aaa td 001
td bbb td 002
>>>

それぞれ、タグ名は .tag 、値は .text として参照できるようになるのですね。

次に set が出てきました。集合型ですね。
https://docs.python.org/ja/3/tutorial/datastructures.html#sets

Python には、 集合 (set) を扱うためのデータ型もあります。集合とは、重複する要素をもたない、順序づけられていない要素の集まりです。 Set オブジェクトは、和 (union)、積 (intersection)、差 (difference)、対称差 (symmetric difference)といった数学的な演算もサポートしています。

中括弧、または set() 関数は set を生成するために使用することができます。注: 空集合を作成するためには set() を使用しなければなりません ({} ではなく)。後者は空の辞書を作成します。辞書は次のセクションで議論するデータ構造です。


ということで、set()以外に中括弧も使えるのですね。リストと似てますが、「順序づけられない要素の集まり」が集合の要点?だと思います。しかも「重複する要素をもたない」のですね。

チュートリアルにあるデモを修正して 3.12.5 で試してみました。

>>> number = set(['one', 'two', 'three', 'four', 'five'])
>>> print(number)
{'five', 'three', 'four', 'two', 'one'}
>>> 'four' in number
True
>>> 'seven' in number
False
>>> number = {'one', 'two', 'one', 'three', 'seven', 'three'}
>>> print(number)
{'two', 'one', 'three', 'seven'}
>>> 'four' in number
False
>>> 'seven' in number
True
>>>

ちゃんと、重複要素は除去されていますね。この例から 集合 number に対して、
a in number が、
True なら a は集合number の要素であり、
False なら a は集合number の要素ではない
となるようですね。更に、
a not in number も可能で、上の最後の例から、

>>> 'seven' not in number
False
>>>

となりました。

プログラムは、

tree = etree.fromstring()で、Elementインスタンス tree を得て、

集合 pantry を定義して、

for文で、tree.iter('tr') から1つずつ tr の中身を ingredient に代入して、

ingredientから3つの要素(タグがth または td)を、amt, unit, item に代入して、

if文で 要素item のタグ item.tag が td でかつ、 そのタグで囲まれたコンテンツ item.text が 集合 pantry に属さなければ、

要素 item: amt unit として出力する

ですね。

今回は、XML/HTML データを扱うプログラムでしたが、
これらのタイプデータを取り扱うときは Python を使おう!ということも、入門者に伝わってくる?ものでした。
特に、fromstring()で生成される、木構造のElementインスタンスが有益そうに思いました。
使うためには、様々なこのタイプのデータがどのような構造になるのかを自分で調べることになるので、
プログラミングで意外と重要な、実際にプログラミングする前に使おうとする機能を色々「試して」確認しておく、
ということが実感できるかもしれませんね。

また、Python も色々なモジュールが公開されているので、これらを活用してプログラムするというのが SimpleProgram の考え方かもしれませんね。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Raspberry Pi に Python 3.12.5 をインストールする

2024-08-16 17:04:26 | Python
2024年8月6日(日本時間8月7日)に Python 3.12.5 がリリースされました。

https://www.python.org/downloads/release/python-3125/
「Python 3.12 は Python プログラミング言語の最新メジャー リリースであり、多くの新機能と最適化が含まれています。 3.12.5 は最新のメンテナンス リリースで、3.12.4 以降の 250 を超えるバグ修正、ビルドの改善、ドキュメントの変更が含まれています。

このバージョンの Python 3.12 には、デフォルトで pip 24.2 も付属しています。ただし、古い macOS バージョンとの互換性がないため、macOS 10.9 ~ 10.12 では、インストール プロセス中 (証明書のインストール手順)、pip のバージョンが 24.1.2 にダウングレードされます。詳細については、インストーラの ReadMe とこの件に関する pip の問題を参照してください。 10.13 より古いバージョンの macOS は 2019 年以降 Apple によってサポートされておらず、それらのサポートを維持することがますます困難になっています。このリリースの 3.12 では引き続きこれらをサポートしていますが、将来の 3.12 リリースでは macOS 10.12 以前のサポートを中止せざるを得なくなる可能性があります。 (Python 3.13 ではすでにサポートが終了しています。)」
(DeepL 翻訳)

最後の、Python 3.13 ですが、10月頃に公開されるようです。楽しみですね。

早速、こちらの Raspberry Pi にインストールしてみました。

入手したファイルは、
Python-3.12.5.tar.xz
Python-3.12.5.tar.xz.asc

です。 "Public key servers" で検索して、
7169605f62c751356d054a26a821e680e5fa6305.asc
を得て、

$ gpg --import 7169605f62c751356d054a26a821e680e5fa6305.asc
$
$ gpg --verify Python-3.12.5.tar.xz.asc
gpg: 署名されたデータが'Python-3.12.5.tar.xz'にあると想定します
gpg: 2024年08月07日 05時32分27秒 JSTに施された署名
gpg: RSA鍵7169605F62C751356D054A26A821E680E5FA6305を使用
gpg: "Thomas Wouters <thomas@python.org>"からの正しい署名 [不明の]
gpg: 別名"Thomas Wouters <thomas@xs4all.nl>" [不明の]
gpg: 別名"Thomas Wouters <twouters@google.com>" [不明の]
gpg: 別名"Thomas Wouters <thomas.wouters.prive@gmail.com>" [不明の]
gpg: 別名"Thomas Wouters <thomaswout@gmail.com>" [不明の]
gpg: *警告*: この鍵は信用できる署名で証明されていません!
gpg: この署名が所有者のものかどうかの検証手段がありません。
主鍵フィンガープリント: 7169 605F 62C7 5135 6D05 4A26 A821 E680 E5FA 6305
$


キーサーバから入手した鍵を証明するものを入手していないのでこのように表示されますが、この結果とMD5 Sum が、

$ md5sum Python-3.12.5.tar.xz;echo 02c7d269e077f4034963bba6befdc715
02c7d269e077f4034963bba6befdc715 Python-3.12.5.tar.xz
02c7d269e077f4034963bba6befdc715
$


と、一致しているので問題なしとしました。

前回のように make します。例によって1時間くらいかかるので気長に待ちます。

$ tar xvf Python-3.12.5.tgz
$ cd Python-3.12.5
$ ./configure --prefix=/usr/local/python --enable-optimization --with-readline --enable-shared
$ make

# make install


以上で、インストールは完了です。実際に動作させてみます。

$ which python3
/usr/local/python/bin/python3

$ python3
Python 3.12.5 (main, Aug 16 2024, 12:49:15) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

$ pip3 --version
pip 24.2 from /usr/local/python/lib/python3.12/site-packages/pip (python 3.12)
$


無事に、Python3 3.12.5 になりました。

ついでにインストールしたばかりの 3.12.5 で、ちょっと気になった yield でリストを返す事を試してみました。

>>> a = [1]
>>> a
[1]
>>> a.append(2)
>>> a
[1, 2]
>>> a.append(3)
>>> a
[1, 2, 3]
>>>


を利用して、無限にリストの要素数を増やしていくジェネレータ関数を作ってみます。

>>> def test_list():
...       number = 1
...       a = [number]
...       while True:
...           yield a
...           number += 1
...           a.append(number)
...
>>>


この関数で生成されたジェネレータイテレータを変数 data に代入します。

>>> data = test_list()
>>> data
<generator object test_list at 0xf7573570>


作成したイテレータ data から next() でメンバーを一つずつ得てみます。

>>> next(data)
[1]
>>> next(data)
[1, 2]
>>> next(data)
[1, 2, 3]
>>> next(data)
[1, 2, 3, 4]
>>>


yield はリストも返せる事が分かったので、これは色々使い道がありそうですね。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ + yield

2024-08-12 23:53:32 | Python
今回は、SimplePrograms の 20lines プログラムの続きで、yield 関連について見てみます。

その前に、プログラミング言語学習で役立つと思っているチートシート (Cheat Sheet) を探してみました。

Python 3 Cheat Sheet
https://perso.limsi.fr/pointal/_media/python:cours:mementopython3-english.pdf

チートシートは、昔はC言語とかに対してコンピュータメーカーが紙で作って配っていたりしてたようですが、今ではインターネットから得られるので Python3 関連も用意されています。チートシートで重要なのは印刷したときに一枚の紙に収まることだと思いますが、現在のプリンタではA4 用紙の両面印刷で収まるかどうかで判断してこれをご紹介しました。

さて、yield 文に戻りますが、
https://docs.python.org/ja/3/reference/simple_stmts.html#the-yield-statement

yield 文は意味的に yield 式 と同じです。yield 文を用いると yield 式文で必要な括弧を省略することが出来ます。

yield 式及び文は generator を定義するときに、その本体内でのみ使うことが出来ます。関数定義内で yield を使用することで、その定義は通常の関数でなくジェネレータ関数になります。


なので、具体的な説明は yield式に書いてあるそうです。
https://docs.python.org/ja/3/reference/expressions.html#yield-expressions

関数の本体で yield 式 を使用するとその関数はジェネレータ関数になり、


であり、ジェネレータ関数の説明は、

ジェネレータ関数が呼び出された時、ジェネレータとしてのイテレータを返します。ジェネレータはその後ジェネレータ関数の実行を制御します。ジェネレータのメソッドが呼び出されると実行が開始されます。開始されると、最初の yield 式まで処理して一時停止し、呼び出し元へ expression_list の値を、または expression_list が省略されていれば None を返します。ここで言う一時停止とは、ローカル変数の束縛、命令ポインタや内部の評価スタック、そして例外処理のを含むすべてのローカル状態が保持されることを意味します。再度、ジェネレータのメソッドが呼び出されて実行を再開した時、ジェネレータは yield 式がただの外部呼び出しであったかのように処理を継続します。再開後の yield 式の値は実行を再開するメソッドに依存します。__next__() を使用した場合 (一般に for 文や組み込み関数 next() など) の結果は None となり、send() を使用した場合はそのメソッドに渡された値が結果になります。

なので、yield文による動きも前回通りですね。最後の方にある next()の記述が気になったので試してみると、

>>> a = iter_primes()
>>> a
<generator object iter_primes at 0xf72a7db8>
>>> b = next(a)
>>> print(b)
2
>>> b = next(a)
>>> print(b)
3
>>> b = next(a)
>>> print(b)
5
>>> b = next(a)
>>> print(b)
7
>>>

という感じで、next()は問題なく使えるようです。これは、
https://docs.python.org/ja/3/glossary.html#term-generator

(ジェネレータ) generator iterator を返す関数です。 通常の関数に似ていますが、 yield 式を持つ点で異なります。 yield 式は、 for ループで使用できたり、next() 関数で値を 1 つずつ取り出したりできる、値の並びを生成するのに使用されます。

(ジェネレータイテレータ) generator 関数で生成されるオブジェクトです。


で説明されていました。同様に glossary から、
iterable(イテラブル)が、

メンバーを一度に 1 つずつ返すことができるオブジェクト。

iterator(イテレータ)が、

データの流れを表現するオブジェクトです。イテレータの __next__() メソッドを繰り返し呼び出す (または組み込み関数 next() に渡す) と、流れの中の要素を一つずつ返します。データがなくなると、代わりに StopIteration 例外を送出します。

と、なっていました。

今回の 20lines のプログラムでは、

関数 iter_primes() 定義内で、yield 文が使われているので、その関数は「ジェネレータ関数」になり、「ジェネレータ関数が呼び出された時、ジェネレータとしてのイテレータを返」すので、そのオブジェクトを変数に代入して、next()とかで要素をひとつづつ取り出せるようになり、

それをfor 文で1000まで出力するわけですね。

さらに、組み込み関数filter() ですが、
https://docs.python.org/ja/3/library/functions.html#filter

filter(function, iterable)
iterable の要素のうち、 function が真であるものからイテレータを構築します。 iterable にはシーケンスか、イテレーションをサポートするコンテナか、イテレータを渡せます。 function が None のときは恒等関数が指定されたものとして扱われ、 iterable のうち偽であるものがすべて取り除かれます。



となるので、
filter(prime.__rmod__, numbers)
は、イテレータ numbers から、prime で割り切れる(0なので偽になる)ものを除いて、新たなイテレータを構築するわけですね。

yield も filter() も面白い動きをしますね。これが、SimplePrograms に出てきたということで、入門時に是非知っておいてほしい Python の一押しの機能ということかもしれません。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

SimplePrograms で Python を学ぶ その18

2024-08-02 22:41:20 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 18番目のプログラムは、 20行の素数のふるいです。

import itertools

def iter_primes():
    # an iterator of all numbers between 2 and +infinity
    numbers = itertools.count(2)

    # generate primes forever
    while True:
        # get the first number from the iterator (always a prime)
        prime = next(numbers)
        yield prime

        # this code iteratively builds up a chain of
        # filters...slightly tricky, but ponder it a bit
        numbers = filter(prime.__rmod__, numbers)

for p in iter_primes():
    if p > 1000:
        break
    print (p)


15 lines プログラムで使った itertools モジュールのイテレータ count() を使うのですね。
https://docs.python.org/ja/3/library/itertools.html
によれば、

count()、 引数:[start,[step]]、 結果:start,start+step,start+2*step,...

となる、無限イテレータ(Infinite iterators)だそうです。
つまり、itertools.count(2) は 2以上の 1づつ増加する数列を生成するのですね。
そして、この数列は、next()で1つづつ取り出せるわけです。

>>> n = itertools.count(2)
>>> p = next(n)
>>> print(p)
2
>>> p = next(n)
>>> print(p)
3
>>> p = next(n)
>>> print(p)
4
>>>

iter_primes()関数の動きを見てみます。
メインのwhile ループの中で、 yield を無視して動作を調べると、

>>> import itertools
>>> numbers = itertools.count(2)
>>> # ここから while ループ
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
2
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
3
....................
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
31
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
37
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
41
>>>
....................

このように、毎回素数が得られるわけですね。filter() は numbers からprime で割り切れるものを除いて、新しい numbers を生成するメソッドのようです。

その後、for 文を使って、iter_primes() を1回だけ呼び出して、生成された素数のイテレータオブジェクトから1つづつ取り出して(つまり next() のように動作する?) p に代入し、その素数が1000 を越えるまで print(p) で出力するのがこのプログラムです。

しかし、普通のプログラミング言語だと、このような関数を作り出すのは難しいように思います。それを Python では、 yield 文(式?)を使ってこれを実現するようです。

関数 iter_prime() は、中で yield prime つまり、 yield 文を使っているのでジェネレータ関数になります。

最初にこのジェネレータ関数を呼び出すと、
yield 式まで実行して prime の値を返して処理を「一時停止」し、
次にこの関数の一時停止を解除すると、yield の次を実行して while ループで繰り返して yield 式の処理をして、
また一時停止します、
のようになるらしいです。

つまり、
>>> import itertools
>>>
>>> # 1回目
>>> numbers = itertools.count(2)
>>>
>>> prime = next(numbers)
>>> print(prime)
2
>>>
>>> # 2回目
>>> numbers = filter(prime.__rmod__, numbers)
>>> prime = next(numbers)
>>> print(prime)
3
>>>
>>> # 3回目
>>> numbers = filter(prime.__rmod__, numbers)
>>> prime = next(numbers)
>>> print(prime)
5
>>>
===== 以下省略 ====

となるので、ここで生成される関数iter_primes()は、素数の無限イテレータ・オブジェクトを生成し、 for 文の in で指定され最初に1回だけこの関数が実行され、生成されたジェネレータオブジェクトから次々と素数が p に代入されるみたいです。

これは for文の復習として、以下のリファレンスを参考に説明してみました。

------------------------
for 文は、シーケンス (文字列、タプルまたはリスト) や、その他の反復可能なオブジェクト (iterable object) 内の要素に渡って反復処理を行うために使われます:

for_stmt ::= "for" target_list "in" starred_list ":" suite
            ["else" ":" suite]

starred_list 式は一度だけ評価され、評価結果として イテラブル オブジェクトを返す必要があります。そのイテラブルから イテレータ が作られます。

------------------------

つまり、
>>> a = iter_primes() # 一回しか関数を呼び出さないことを強調します。
>>> a
<generator object iter_primes at 0xf7027db8>
>>> for p in a:
...       if p > 6:
...          break
...       print(p)
...
2
3
5
>>>

です。 この動きはとても興味深いです。

ちょっと理解が怪しくなっているように感じているので、次回以降に yield や filter() を調べて見たいと思います。
コメント (3)
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする