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