今さらPython3 (35) - パッケージ、標準ライブラリとか

この本の第5章をやってます。

入門 Python 3

入門 Python 3

パッケージ

モジュールをファイル階層にスケールしたものをパッケージ(Packages)と呼びますと。

例に出ているものをそのまま作ってみよう。まずはboxes/weather_new.py (さっきweather.py作ってしまったので名前変えてます)

from sources import daily, weekly

print("Daily forecast:", daily.forecast())
print("Weekly forecaset")
for number, outlook in enumerate(weekly.forecast(),1):
    print(number, outlook)

boxes/sources/daily.py

def forecast():
    'fake daily forecast'
    return 'like yesterday'

随分手を抜いたコードだなw。つづいては、boxes/sources/weekly.py

def forecast():
    """Fake wekely forecast"""
    return ['snow', 'more snow', 'sleet', 'freezing rain', 'rain', 'fog', 'hail']

実行してみる。

C:\Users\xxxxxxx\PycharmProjects\IntroducingPython3\boxes>python weather_new.py
Daily forecast: like yesterday
Weekly forecaset
1 snow
2 more snow
3 sleet
4 freezing rain
5 rain
6 fog
7 hail

最後にサラッと、__init__.pyを空でもいいから作っておけとありますね。自分はツールを使ったので自動でできていましたが、こんな感じ。

f:id:deutschina:20151218132453p:plain

sourceのスペル間違えたまま作ってしまったのはここだけの話。

標準ライブラリ

Pythonの標準ライブラリにはたくさんの便利なものが含まれてますよって話ですね。標準文書も用意されているし、

The Python Standard Library — Python 3.5.1 documentation

チュートリアルの中にも標準ライブラリを使うセクションがあります。

10. Brief Tour of the Standard Library — Python 3.3.6 documentation

Doug Hellmannさんは、標準ライブラリを丁寧に紹介するwebサイトや書籍を出されている。

Python Module of the Week - Python Module of the Week

Python Standard Library by Example, The (Developer's Library)

Python Standard Library by Example, The (Developer's Library)

日本人の悪い癖で、英語だと読み飛ばしがちですけど、良い情報が無料でもゴロゴロ転がっている訳ですね。

setdefault()とdefaultdict()

このタイミングで出てくると言うことは、標準ライブラリの1つなんでしょうか。とりあえず読み進めてみましょう。

>>> periodic_table = {'Hydrogen': 1, 'Helium':2}
>>> print(periodic_table)
{'Hydrogen': 1, 'Helium': 2}
>>> carbon = periodic_table.setdefault('Carbon', 12)
>>> carbon
12
>>> periodic_table
{'Hydrogen': 1, 'Helium': 2, 'Carbon': 12}
>>> helium = periodic_table.setdefault('Helium', 947)
>>> helium
2

なんでperiodicで元素番号の話なんだろうと思ったら、周期表っていいますもんね。という、どうでも良い確認はさておき、元素名がキー、元素番号を値として持つ辞書(dict)があります。既にヘリウム(Helium)というキーに対して2番という値が割り当たっているところに、947で登録しようとしたらsetdefault()くんは賢く対応して、「ヘリウム、お前は2番だろ」と値が上書きされるのを防いでくれているわけですね。

ここで、defaultdic()の話。

>>> from collections import defaultdict
>>> periodic_table = defaultdict(int)
>>> defaultdict
<class 'collections.defaultdict'>
>>> periodic_table['Hydrogen']
0
>>> periodic_table['Hydrogen'] = 1
>>> periodic_table['lead']
0
>>> periodic_table['Hydrogen']
1
>>> periodic_table
defaultdict(<class 'int'>, {'Hydrogen': 1, 'lead': 0})
>>> periodic_table['Oxygen']
0
>>> periodic_table
defaultdict(<class 'int'>, {'Hydrogen': 1, 'lead': 0, 'Oxygen': 0})
>>>

何が嬉しいのかがよく見えないけど、まずは理解してみよう。

defaultdictは標準ライブラリcollectionsの中に入っている関数なんだよね。元素の周期表が入るであろうperiodic_tableにdefaultdict(int)という値を渡している。水素(Hydrogen)に1を渡せば、1という値が割り当たるし、存在しないキーを入れると、勝手に0が割り当てられると。

>>> a = defaultdict(str)
>>> a['a'] = 'A'
>>> a['a']
'A'
>>> a['b']
''
>>> a
defaultdict(<class 'str'>, {'a': 'A', 'b': ''})
>>>

defaultdict(int)があるからには、strもイケるんじゃね?という想像でやってみたらこうなったの図。空白が入る訳ですね。

・・・仕組みは分かる。でも、ゴミが増えるだけのような?get()の方が幾分スマートに感じるんだけど。。。

で、他の型でもイケるという話が次なのね。

>>> from collections import defaultdict
>>>
>>> def no_idea():
...     return 'Huh?'
...
>>> bestiary = defaultdict(no_idea)
>>> bestiary['A'] = 'Abominable Snowman'
>>> bestiary['B'] = 'Basilisk'
>>> bestiary['A']
'Abominable Snowman'
>>> bestiary['B']
'Basilisk'
>>> bestiary['C']
'Huh?'
>>> bestiary
defaultdict(<function no_idea at 0x00563ED0>, {'B': 'Basilisk', 'C': 'Huh?', 'A'
: 'Abominable Snowman'})
>>>

strとかintではなくて、任意の関数を入れても大丈夫ということだよね。

>>> bestiary = defaultdict(lambda: 'Huh?')
>>> bestiary['C']
'Huh?'
>>> bestiary = defaultdict('Huh?')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: first argument must be callable
>>>

関数の代わりにlambdaでもイケるけど、かといって値を直接入れるのはダメですと。ゼロとか空白とか'Huh?'が入っているからゴミが入るイメージだけど、なんか他に使い道があるかもという気になってきた。

>>> from collections import defaultdict
>>> food_counter = defaultdict(int)
>>> for food in ['spam', 'spam', 'eggs', 'spam']:
...     food_counter[food] += 1
...
>>> for food, count in food_counter.items():
...     print(food, count)
...
eggs 1
spam 3
>>>

これは、spamが3個あって、eggsが1つあるよというのをカウントしているんだよね。

>>> dict_counter = {}
>>> for food in ['spam', 'spam', 'eggs', 'spam']:
...     if not food in dict_counter:
...          dict_counter[food] = 0
...     dict_counter[food] += 1
...
>>> for food, count in dict_counter.items():
...     print(food, count)
...
eggs 1
spam 3
>>>

これをやるより簡単だと言ってるんだよね。

(つづく)