ラベル 標準ライブラリ の投稿を表示しています。 すべての投稿を表示
ラベル 標準ライブラリ の投稿を表示しています。 すべての投稿を表示

2019年11月2日

文字列 'ABC...Z' の作り方

Python においても、'A', 'B', 'C', ... 'Z' という文字列が欲しい場合があります。

文字型を持っている C言語であれば、以下のような書き方になると思います。

for (char c = 'A'; c <= 'Z'; c++) {
    printf("%c", c); // => A B C ... Z
}
Pythonでも同様の書き方は可能です。
for i in range(ord('A'), ord('Z') + 1):
    print(chr(i))  # => A B C ... Z



しかし、Python は文字型を持たない故、Python らしくない書き方です。より Pythonic な方法は無いものでしょうか?
これを解決するのが string モジュールです。

string モジュールには、よく使われると思われる制御文字列が定義されています。
'ABC...Z' の文字列も定義されています。

import string
print(string.ascii_uppercase)
# => ABCDEFGHIJKLMNOPQRSTUVWXYZ

for i in string.ascii_uppercase:
    print(i)  # => A B C ... Z


string には他にも以下の固定文字列が定義されています。なかなか気の利くモジュールですね。
>>> import string

>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

>>> string.ascii_lowercase
'abcdefghijklmnopqrstuvwxyz'

>>> string.ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

>>> string.digits
'0123456789'

>>> string.hexdigits
'0123456789abcdefABCDEF'

>>> string.octdigits
'01234567'

>>> string.printable
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

>>> string.punctuation
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

>>> string.whitespace
' \t\n\r\x0b\x0c'

2019年8月15日

webbrowser

あまり有名でないと思われる標準ライブラリの一つに、webbrowser があります。webbrowser は文字通り、ブラウザを開くためのライブラリです。

最も典型的な使い方は以下のようになります。

>>> import webbrowser

>>> webbrowser.open('https://www.python.org/')
# 標準ブラウザで https://www.python.org/ が開かれる

ブラウザを開くというのは一般的な動作ですが、ブラウザの立ち上げ方は OS やその設定に依存します。この辺りをうまく吸収することを意図したライブラリではないかと思われます。

ちなみに、webbrowser.open() の引数に URL ではなくローカルファイルパスを渡した場合の動作は、環境依存になるとのことです。実際、Windows と Mac で試してみましたが、異なる結果となりました。
webbrowser.open() の引数には URL のみを指定するのが無難なようです。

2019年8月3日

演算子の話⑤ functools.total_ordering

前回、__lt__() や __eq__() が定義されていても <= や >= はフォールバックしてくれない、という話をしました。
少し気の利かない仕様のような気がします。
これをフォローしてくれるのが functools の total_ordering です。

total_ordering はデコレータで、__lt__()、__le__()、__gt__()、__ge__() の4つの不等式系関数のうち最低1つと __eq__() を持っているクラスを修飾し、全6つの不等号・等号演算子に対応するようになります。

@total_ordering  # デコレータ
class Stored(object):
    def __init__(self, value):
        self.value = value

    def __lt__(self, rhs):
        return self.value < rhs.value

    def __eq__(self, rhs):
        return self.value == rhs.value

# 全6つの不等号・等号に対応
>>> Stored(1) < Stored(2)
True

>>> Stored(1) <= Stored(2)
True

>>> Stored(1) > Stored(2)
False

>>> Stored(1) >= Stored(2)
False

>>> Stored(1) == Stored(2)
False

>>> Stored(1) != Stored(2)
True
__le__() や __ge__() が定義されていなくても、<= や >= が呼ばれています。

と、一見便利そうな total_ordering ですが、実際にはあまり使う機会がありません。
第一の理由は、わざわざ total_ordering で修飾するのが煩わしい、ためです。__lt__() を定義した時点で < とそのフォールバックの > に対応でき、さらに __eq__() を定義することで == とそのフォールバックの != に対応できます。つまり、total_ordering の前提条件の時点で6つの不等号・等号のうち、4つはカバーできることになります。なので、total_ordering を使うよりも、あと1つ __le__() あたりを定義してした方が手っ取り早い、というわけです。
もう一つの理由は、total_ordering を使うと動作が遅くなる、ためです。不等式は数学的クラスでよく使われますが、数学的クラスでは動作速度が重要となります。そのような使用目的では、total_ordering を使うよりも、愚直に全6つの不等式・等式系関数を定義した方が、動作は速くなります。

2019年5月2日

operator.itemgetter

operatorライブラリで定義されている operator.itemgetter を紹介します。


operator.itemgetter は、list や dict の要素を取得するための関数を提供します。
具体的な使用例は以下のようになります。

>>> from operator import itemgetter

>>> t = ('Python', 1990, 'Guido')
>>> itemgetter(0)(t)
'Python'
>>> itemgetter(1)(t)
1990
>>> itemgetter(2)(t)
'Guido'

>>> d = {'name': 'Python', 'release': 1990, 'designed by': 'Guido'}
>>> itemgetter('name')(d)
'Python'
>>> itemgetter('release')(d)
1990
>>> itemgetter('designed by')(d)
'Guido'
上の例だけ見ても特に使い道がないように思えますが、実際に operator.itemgetter の使い所となるのは、ずばり lambda の代わりです。


例として、sorted() に key を指定する方法を示します。
tuple の2番目の要素でソートをする、という場合、代表的なやり方として、operator.itemgetter を使うやり方と、lambda を使うやり方があります。

>>> from operator import itemgetter

>>> t = (('Python', 1990, 'Guido'), ('Ruby', 1995, 'Matz'), ('Perl', 1987, 'Larry'))

# sorted の key を itemgetter で指定
>>> sorted(t, key=itemgetter(1))
[('Perl', 1987, 'Larry'), ('Python', 1990, 'Guido'), ('Ruby', 1995, 'Matz')]

# sorted の key を lambda で指定
>>> sorted(t, key=lambda x: x[1])
[('Perl', 1987, 'Larry'), ('Python', 1990, 'Guido'), ('Ruby', 1995, 'Matz')]
どちらのやり方でも、タイプ量、可読性ともに大差はありませんが、動作速度は operator.itemgetter の方が若干早いので、operator.itemgetter の使用が推奨されます。




なお、operatorライブラリには、itemgetter の兄弟版とでも言うべき、attrgetter も用意されています。

2016年9月17日

itertools.groupby の紹介

itertools.groupby を紹介します。itertools.groupby は指定したキーに従って、連続要素をグループ分けします。

実際の動作例は以下のようになります。
指定したキー(ここでは lambda x: x % 2)の値が同じであれば、グループとされます。連続要素の順番は変わりません。

>>> from itertools import groupby

>>> ints = (1, 3, 2, 4, 1, 5, 3, 2, 2)
>>> for key, group in groupby(ints, key=lambda x: x % 2):
...     print(key)
...     print(list(group))
...     print('')
...
1
[1, 3]

0
[2, 4]

1
[1, 5, 3]

0
[2, 2]


具体的な使い方として、文字列のリストを頭文字を強調した形で文字出力する例を示す。
>>> from itertools import groupby

>>> names = ('ABC', 'Ada', 'ALGOL', 'C', 'C#', 'C++', 'CoffeeScript', 'Pascal', 'PHP', 'Prolog', 'Python')
>>> for k, g in groupby(names, key=lambda x: x[0]):
...     print(k + ' __________')
...     for i in g:
...         print(i)
... 
A _____________
ABC
Ada
ALGOL
C _____________
C
C#
C++
CoffeeScript
P _____________
Pascal
PHP
Prolog
Python

2016年9月9日

collections.namedtuple の注意点

collections.namedtuple のちょっとした注意点を紹介します。

namedtuple は tuple を継承したものです。

>>> from collections import namedtuple
>>> Point = namedtuple('Point', 'x y z')

# namedtuple は tuple のサブクラス
>>> issubclass(Point, tuple)
True

当然、tuple のアトリビュートは namedtuple にも引き継がれます。
# tuple のアトリビュート
>>> dir(tuple)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '_
_eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs
__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__'
, '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__
rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'i
ndex']

# namedtuple のアトリビュート
>>> dir(Point)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '_
_eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs
__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__'
, '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '
__repr__', '__rmul__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__s
ubclasshook__', '_asdict', '_fields', '_make', '_replace', '_source', 'count', '
index', 'x', 'y', 'z']

ここで注目すべきは、tuple は count と index という _(アンダーバー)で始まらないアトリビュートを2つ持っている点です。即ち、namedtuple の要素名として count か index を指定した場合、その namedtuple は親クラスである tuple のインターフェースを変えてしまった良くないサブクラス、ということになります。
結論として、namedtuple の要素名として count と index は使うべきではありません。
>>> WrongNames = namedtuple('WrongNames', 'count index')
>>> wrong_names = WrongNames(1, 2)

# count は要素名になってしまった
>>> wrong_names.count
1
# tuple の count() を期待するとエラー
>>> wrong_names.count(1)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'int' object is not callable

# index は要素名になってしまった
>>> wrong_names.index
2
# tuple の index() を期待するとエラー
>>> wrong_names.index(1)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'int' object is not callable

2016年4月11日

itertools.takewhile() と itertools.dropwhile()

itertoolsライブラリに含まれている itertools.takewhile() と itertools.dropwhile() を紹介します。


itertools.takewhile() と itertools.dropwhile() は似た動作をします。どちらも引数として、条件式と iterableオブジェクトを受け、itertools.takewhile()は「オブジェクトの先頭から条件を満たす要素を取り出す」、itertools.dropwhile()は「オブジェクトの先頭から条件を満たす要素を取り除く」という動作をします。
実際の動作は以下のようになります。

>>> from itertools import takewhile, dropwhile
>>> data = [1, 4, 6, 4, 1]

# takewhileは先頭から条件
# を満たす要素を取り出す
>>> for i in takewhile(lambda x: x < 5, data):
...     print(i)
...
1
4

# dropwhileは先頭から条件
# を満たす要素を取り除く
>>> for i in dropwhile(lambda x: x < 5, data):
...     print(i)
...
6
4
1


以下は str.lstrip() 相当の関数を、itertools.dropwhile() を用いて実装した例です。
>>> def lstrip(s, chars):
...     return ''.join(dropwhile(chars.__contains__, s))
...

>>> lstrip("abcde", "ba")
'cde'

2016年3月14日

コマンドラインアプリにはargparse

アプリケーションの引数を解析する argparse を紹介します。

Python で起動したアプリケーションは、sys.argv に引数が格納されています。型は文字列のリストで、[0]がモジュール名、[1]以降が引数となります。

# sample.py
# 引数を出力するだけのソースコード
import sys
print(sys.argv)


# 引数を与えてsample.pyを実行
> python sample.py -s src.txt --dst dst.xml
['sample.py', '-s', 'src.txt', '--dst', 'dst.xml']

argparse はこの引数を解析するためのライブラリです。
以下は argparse を用いたプログラムの実装例です。このプログラムでは、2つのファイル名を引数として取得します。

# convert.py
import argparse

__version__ = '1.0.0'

def main():
    # parserを作成
    parser = argparse.ArgumentParser(description='Convert file')
    parser.add_argument('-v', '--version', action='version',
                        version=('%(prog)s ' + __version__))
    parser.add_argument('-s', '--src', metavar='src',
                        type=str, help='source file name')
    parser.add_argument('-d', '--dst', metavar='dst',
                        type=str, help='output file name')

    # sys.argvをparseし、引数を取得
    args = parser.parse_args()

    # 引数を使った何らかの動作
    print(args.src)
    print(args.dst)

if __name__ == '__main__':
    main()

実行例です。
> python convert.py -s src.txt --dst dst.xml
src.txt
dst.xml

また、バージョン表示やヘルプ表示も可能です。
# バージョン表示
> python convert.py -v
convert.py 1.0.0


# ヘルプ表示
> python convert.py -h
usage: module1 [-h] [-v] [-s src] [-d dst]

Convert file

optional arguments:
  -h, --help         show this help message and exit
  -v, --version      show program's version number and exit
  -s src, --src src  source file name
  -d dst, --dst dst  output file name
argparse は豊富な機能を備えているので、引数解析部分の車輪の再発明を回避することができます。
また、argparse を使うことで、ヘルプなどの基本コマンドの統一にもつながります。
引数を用いるコマンドラインアプリでは、argparse を使うことが推奨されます。



以下余談です。
Python には元々、getopt という C言語の同名ライブラリを模したモジュールと、よりモダンな optparser がありました。
しかし、optparser よりも更に高機能な引数解析が必要となり、argparse が作成されました。標準モジュールが似た機能のモジュールを複数持ったことにより、混乱もあったものと思われます(https://www.python.org/dev/peps/pep-0389/)。
argparse がリリースされた 3.2 以降(Python 2 では 2.7以降)は、optparser は非推奨となっています。

2016年3月7日

abc.abstractmethod で抽象クラス

Python は組み込みレベルでは抽象クラス/抽象メソッドという概念を持っていません。
abc というライブラリに、抽象クラスを定義する abc.ABCMeta と、デコレータで抽象メソッドを定義する abc.abstractmethod があります。
本項では、抽象クラスの定義方法について解説します。


以下は、Base という抽象クラスの定義です。Base は抽象メソッドである func() を持っています。

>>> from abc import ABCMeta
>>> from abc import abstractmethod

# Baseを抽象クラスとして定義
>>> class Base(metaclass=ABCMeta):

...     # func()を抽象メソッドとして定義
...     @abstractmethod
...     func(self):
...         pass
...

Base は抽象クラスですので、そのまま実体化しようとすると、エラーになります。
>>> base = Base()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Base with abstract methods func

また、Base を継承したクラスで、抽象メソッドを具体化していない場合も、エラーとなります。
# Baseを継承するクラス。
# 抽象メソッドを具体化していない。
>>> class Derived(Base):
...     pass
...

# 実体化できない
>>> derived = Derived()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Derived with abstract methods func

Base を継承した非抽象クラスを定義するには、抽象メソッドを具体化する必要があります。
# Baseを継承するクラス。
# 抽象メソッドを具体化している。
>>> class Derived(Base):
...     def func(self):
...         print('not abstract')
...

# 実体化可能
>>> derived = Derived()
>>> derived.func()
not abstract

2016年2月29日

itertools.chain() で連結

iterable なオブジェクトをまとめる itertools.chain() について紹介します。


itertools.chain() を使うことで、複数の iterableオブジェクトを連結することができ、一つの for文で複数のオブジェクトを扱えます。
以下は、文字列とリストを一つの for文 でまとめて走査する例です。

>>> from itertools import chain

# chain()の引数は文字列とリスト
>>> for i in chain('ABC', [1, 2, 3]):
...     print(i, end=(' '))
...
A B C 1 2 3


以下はもう少し実践的な、2つのフォルダの glob の結果をまとめて表示するプログラムです。
>>> from itertools import chain
>>> from glob import glob

# 複数のパス
>>> path1 = './doc/*.txt'
>>> path2 = './tool/*.txt'

# glob(path1)とglob(path2)をchain()で連結
>>> for i in chain(glob(path1), glob(path2)):
...     print(i)
...
./doc/readme.txt
./doc/changelog.txt
./tool/readme.txt




itertools.chain() には弟にあたる itertools.chain.from_iterable() もあります。
itertools.chain() は複数の引数を連結するものでしたが、chain.from_iterable()は引数を1つのみ取ります。
以下の chain() と chain.from_iterable() は同じ内容になります。
>>> chain('ABC', [1, 2, 3])

>>> chain.from_iterable(['ABC', [1, 2, 3]])


itertools.chain.from_iterable() の方が便利な場面があります。
以下は、先ほどの glob を使う例を改良したものです。
>>> from itertools import chain
>>> from glob import glob

# 複数のパスをリストで持つようにする
>>> paths = ['./doc/*.txt', './tool/*.txt']

# chain.from_iterable()の方がスッキリ書ける
>>> for i in chain.from_iterable(glob(path) for path in paths):
...     print(i)
...
./doc/readme.txt
./doc/changelog.txt
./tool/readme.txt

2016年2月8日

functools.reduce()

高階関数の1つ、functools.reduce() を紹介します。

reduce() の第一引数は引数を2つ取る関数、第二引数はコンテナ、となります。
以下は具体的な例です。

>>> from functools import reduce
>>> from operator import mul

>>> c = [1, 2, 3, 4, 5]
>>> reduce(mul, c)
120
上記 reduce() の中で実際に行われている処理は、以下のようになります。
コンテナの各要素を関数でまとめ上げる、といった感じですね。
mul(mul(mul(mul(1, 2), 3), 4), 5)

reduce() を使わない一般的なループ文は以下のようになります。
>>> m = 1
>>> for i in c:
>>>     m *= i

>>> m
120
同じ処理の実装に3行必要となっていますので、reduce() の方がスッキリとした実装であると言えます。

書き方以外にも、reduce() は c[0] から計算を始めているのに対し、一般的なループ文の例では1行目で m の初期化を行っている点も相違点です。
reduce() の第二引数のサイズが0の時、および1の時には、それぞれ以下のように動作します。特にサイズが0の場合は例外発生するので要注意です。

# サイズが0ならエラー
>>> reduce(int.mul, [])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: reduce() of empty sequence with no initial value

# サイズが1なら[0]
>>> reduce(int.mul, [10])
10

以下は reduce() のもう少し実践的な使用例です。文字列の XORチェックサムを求めています。
>>> from functools import reduce
>>> from operator import xor

>>> s = "ABC"
>>> reduce(xor, (ord(i) for i in s))
64




以下余談です。

Python 2 では高階関数として map(), filter(), reduce() の3つがビルトイン関数として用意されていました。
しかし、Python 3 では reduce() のみが functools へと移されました。ビルトインから別モジュールへの移動ですから、事実上の降格です。

私にはこの決定が良い物とは思えません。
と言うのも、map() および filter() は内包表記を使うことで、同等の処理を実現できます。仮に map() と filter() が降格になるならば、それは「Python は内包表記を推している」という風に受け取られると思います。
しかし reduce() にはこれと言った代替手段は無く、reduce() を使わないとなると普通にループ文を書くしかありません。代替手段の無い reduce() こそ、ビルトインに残る方が良いと思うのですが、、、。
降格の一番の理由は、Guido氏(Python の開発者)が reduce() を「直感的でない」と否定的に見ていることのようです。

2015年12月15日

with文の話3

with文専用のオブジェクトをより簡単に定義する contextlib.contextmanager を紹介します。

こちら で、print をハックする with文専用クラスを紹介しました。
このクラスを中身を見てみると、__enter__() と __exit__() という2つのメソッドが定義されているのみです。このようなクラスと同じ動作をする関数を作れるようにするのが contextlib.contextmanager です。 contextlib.contextmanager を使った実装例は以下のようになります。

import sys
import io
from contextlib import contextmanager

@contextmanager
def print_hack():
    sio = io.StringIO()
    tmp, sys.stdout = sys.stdout, sio
    yield sio
    sys.stdout = tmp
行なっていることは以前のクラスと同じです。変わった点は
  • クラスから関数になった
  • contextmanager のデコレータを使用する
  • __enter__() と __exit__() に相当する部分が yield で区切られる。yield までが __enter__()、yield より後ろが __exit__()、となる
となります。


関数の使い方は以下の通りで、以前のクラスと全く同じです。

# with文でprint_hack()を呼び出す
>>> with print_hack() as hack:
...     print('Hello hack')
...
>>>   # ここではprintされない

# with内のprintの内容を確認
>>> hack.getvalue()
'Hello hack\n'

また、ここで定義した関数は通常の関数呼び出しからは呼び出すことはできません。誤使用を防ぐという意味で、大事な点です。
>>> print_hack()
<contextlib._GeneratorContextManager object at 0x01780470>


個人的には、クラスを定義すると self をたくさん書かなければならない点が苦痛です。関数で済むなら関数で済ます方が楽で良いのではないでしょうか。

2015年12月12日

doctestを有効に使おう

Python の標準ライブラリに含まれるテストツール doctest を紹介します。

doctest は、関数ヘッダ(又はファイルヘッダ)にテスト内容を記載することで、関数単体テストを実施できるようにするモジュールです。
実際の関数単体テストの記述は以下のようになります。

def count_if(target, predicate=bool):
    """ Count items in target which satisfies condition
    ... 省略 ...

    >>> count_if([0, 1, 2, 3, 0, 1, 2, 3, 0])
    6
    >>> count_if([0, 1, 2, 3, 0, 1, 2, 3, 0],
    ...          lambda x: x % 2 == 0)
    5
    >>> count_if(['', 'a', '', 'b', ''])
    2
    """
    return sum(predicate(i) for i in target)


doctest の長所と短所として、以下が挙げられます。
 長所1: テストを別ファイルにしなくてよい
 長所2: テスト内容はドキュメントとしても使える
 長所3: unittestからdoctestを取り込み可能
 短所: 大掛かりなテストには不向き

手軽に関数単体テストを実施できる点は、実に Pythonic なモジュールと言えます。
しかし、大掛かりなテスト(準備だけの専用関数が必要、テスト項目が数十もある、等)となると、やはりテスト専用のファイルを用意してやる必要があります。

2015年12月11日

calendar.timegm()

timeモジュールには時間に関する様々な関数・型が定義されています。

最も基本的な現在時刻を取得する関数の使用例を示します。

>>> import time

# エポックタイムを取得
>>> time.time()
1449268841.95064

# struct_time(UTC)を取得
>>> time.gmtime()
time.struct_time(tm_year=2015, tm_mon=12, tm_mday=4, tm_hour=22, 
tm_min=40, tm_sec=48, tm_wday=4, tm_yday=338, tm_isdst=0)

time.gmtime() は、エボックタイムを引数として受けることも出来ます。この場合、現在時刻ではなく引数のエポックタイムを struct_time(UTC) に変換します。
# エポックタイム → struct_time(UTC)
>>> time.gmtime(1449200000)
time.struct_time(tm_year=2015, tm_mon=12, tm_mday=4, tm_hour=3, 
tm_min=33, tm_sec=20, tm_wday=4, tm_yday=338, tm_isdst=0)

それではこの逆、即ち struct_time(UTC) をエポックタイムに変換する関数はあるのでしょうか?
あるにはあるのですが、何故か timeモジュールではなく calendarモジュールに含まれています。calendar.timegm() がその関数です。
>>> given_utc = time.gmtime(1449200000)

# struct_time(UTC) → エポックタイム
>>> import calendar
>>> epoch = calendar.timegm(given_utc)

>>> time.gmtime(epoch + 24 * 60 * 60)
time.struct_time(tm_year=2015, tm_mon=12, tm_mday=5, tm_hour=3, 
tm_min=33, tm_sec=20, tm_wday=5, tm_yday=339, tm_isdst=0)

「struct_time(local time) → エポックタイム」の変換は timeモジュールの time.mktime() でできるというのに、何だかアンバランスですね。

2015年12月10日

pydocにprivate属性も表示させる

pydoc に private属性も表示させる方法を紹介します。

pydoc は言わずと知れたドキュメント生成のための標準ライブラリです。
この pydoc で生成されるドキュメントには、protected や private 属性の変数/関数は含まれません。モジュールの使用者にとってはこの仕様でもいいのですが、モジュールを読んで解析 or 変更しようとしている人にとっては、この仕様は望ましくありません。

この部分の処理を行っているのは、pydoc の以下の関数です(pydoc のバージョンは 3.4)。

def visiblename(name, all=None, obj=None):
    """Decide whether to show documentation on a variable."""
    # Certain special names are redundant or internal.
    # XXX Remove __initializing__?
    if name in {'__author__', '__builtins__', '__cached__', '__credits__',
                '__date__', '__doc__', '__file__', '__spec__',
                '__loader__', '__module__', '__name__', '__package__',
                '__path__', '__qualname__', '__slots__', '__version__'}:
        return 0
    # Private names are hidden, but special names are displayed.
    if name.startswith('__') and name.endswith('__'): return 1
    # Namedtuples have public fields and methods with a single leading underscore
    if name.startswith('_') and hasattr(obj, '_fields'):
        return True
    if all is not None:
        # only document that which the programmer exported in __all__
        return name in all
    else:
        return not name.startswith('_')

visiblename() の最後の行で、_ で始まる変数/関数が弾かれています。この最後の行を変更した関数を作り、visiblename() と置き換えることで、protected や private 属性でもドキュメントに表示させることができます。
以下は visiblename() を置き換える具体的なファイルの例です。
### mypydoc.py
# -*- coding:utf-8 -*-

import pydoc

def my_visiblename(name, all=None, obj=None):
    """Decide whether to show documentation on a variable."""
    # Certain special names are redundant or internal.
    # XXX Remove __initializing__?
    if name in ['__author__', '__builtins__', '__cached__', '__credits__',
                '__date__', '__doc__', '__file__', '__spec__',
                '__loader__', '__module__', '__name__', '__package__',
                '__path__', '__qualname__', '__slots__', '__version__']:
        return 0
    # Private names are hidden, but special names are displayed.
    if name.startswith('__') and name.endswith('__'): return 1
    # Namedtuples have public fields and methods with a single leading underscore
    if name.startswith('_') and hasattr(obj, '_fields'):
        return True
    if all is not None:
        # only document that which the programmer exported in __all__
        return name in all
    else:
        # change to make protected/private variable visible
        return True
#        return not name.startswith('_')

def main():
    pydoc.visiblename = my_visiblename
    pydoc.cli()

if __name__ == '__main__':
    main()

ここで示した mypydoc.py は、pydoc.py と同等の使い方(e.g. python mypydoc.py -w modulename)が可能です。

2015年12月7日

collections.OrderedDict

collectionsライブラリで定義されている collections.OrderedDict を紹介します。
(OrderedDict は Python 3.1 で追加されました。さらに 3.2 と 3.5 で若干の機能追加が行われています。)

dict の key の順序はハッシュ値により決定されます。つまり使う側から見ると、順序は制御不可能です。
OrderedDict は dict を継承したクラスで、key の順序が登録した順になります。
dict と OrderedDict の動作を比較します。

# ビルトインのdict。順序はハッシュ値依存
>>> d = dict()
>>> d['A'] = 10
>>> d['B'] = 11
>>> d['C'] = 12
>>> d
{'C': 12, 'B': 11, 'A': 10}

# OrderedDict。順序は登録順
>>> from collections import OrderedDict
>>> od = OrderedDict()
>>> od['A'] = 10
>>> od['B'] = 11
>>> od['C'] = 12
>>> od
OrderedDict([('A', 10), ('B', 11), ('C', 12)])


OrderedDict は dict を継承して作られています。このため、dict でできることは全て OrderedDict でもできるようになっています。この辺り、コンテナ型を自作する場合には参考にしたい所ですね。
>>> issubclass(OrderedDict, dict)
True

>>> isinstance(od, dict)
True


OrderedDict の使い所は当然ながら、データの順序を保ちつつ dict でデータを管理するようなケースです。設定ファイルの読み込み等で最もよく使われると思います。
以下は、tuple の順序を変えずに OrderedDict へ代入する例です。
# 順序を維持したいデータ
>>> settings = (
...     ('protcol', 'TCP'),
...     ('IP', '192.168.0.1'),
...     ('port', 60000),
... )

# OrderedDictへ代入
>>> od = OrderedDict(settings)
>>> od
OrderedDict([('protcol', 'TCP'), ('IP', '192.168.0.1'), ('port', 60000)])


また、通常の dict から key の大小で並び替えた OrderedDict を作るには以下のようにします。(当然、dict の key は大小比較が可能である必要があります。)
# 通常のdict。順序はハッシュ値依存
>>> d = dict()
>>> d['A'] = 10
>>> d['B'] = 11
>>> d['C'] = 12

# key順でOrderedDictへ代入
>>> od = OrderedDict(sorted(d.items()))
>>> od
OrderedDict([('A', 10), ('B', 11), ('C', 12)])

2015年12月6日

functools.partial()

引数の部分適用を実現する functools.partial() を紹介します。
部分適用とは、一部引数の値を固定にし、別の関数として扱うことです。


リトルエンディアンで2バイト、4バイト、2バイトの整数が格納されているバイト列を考えます。

>>> import binascii
>>> b = binascii.unhexlify('0100020000000300')

このバイト列から2バイト、4バイト、2バイトの順で3つの整数を取り出すソースコードは以下のようになります。
>>> i1 = int.from_bytes(b[0:2], 'little')
>>> i2 = int.from_bytes(b[2:6], 'little')
>>> i3 = int.from_bytes(b[6:8], 'little')
>>> i1, i2, i3
(1, 2, 3)

上のソースコードでは、int.from_bytes() を呼ぶ度に 引数'little' を指定しているのが、あまり格好良い感じではないですね。省略できればなぁ~、と思えてきます。
ここで、functools.partial() を使って引数の部分適用をすると、ソースコードが一気に見やすくなります。
以下は functools.partial() の具体的な使用例です。
>>> from functools import partial
>>> to_int = partial(int.from_bytes, byteorder='little')

>>> i1 = to_int(b[0:2])
>>> i2 = to_int(b[2:6])
>>> i3 = to_int(b[6:8])
>>> i1, i2, i3
(1, 2, 3)
バイト列から整数への変換部分がスッキリしましたね。


上例は functools.partial() の便利さを示すものですが、functools.partial() のより大きな恩恵として「部分適用した関数を変数に代入可能」が挙げられます。

つまり、、、

>>> def other_func(to_int):
...     print(to_int(b[0:2]))
...     print(to_int(b[2:6]))
...     print(to_int(b[6:8]))
...

# partialの戻り値は変数に代入可能
>>> other_func(partial(int.from_bytes, byteorder='little'))
1
2
3

# partial無しでは変数に代入できない
>>> other_func(int.from_bytes(byteorder='little'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Required argument 'bytes' (pos 1) not found
上記のように、functools.partial() ならば部分適用した関数を変数(ここでは引数)へ代入可能です。
functools.partial() 無しで実現するには、別途関数を定義する、lambdaを使う、等の方法がありますが、簡潔さと可読性の両観点から、部分適用には functools.partial() を使うのが最適であると言えます。

2015年12月5日

collections.Counter

コンテナの要素数を数える時に便利な collections.Counter を紹介します。
(※ collections.Counter は Python 3.1 からの機能です。)


'refrigerator'という単語に含まれている文字をカウントするプログラムを考えます。
dict内包表記を使うと、以下のように実装できます。

>>> s = 'refrigerator'

>>> d = {i:s.count(i) for i in set(s)}
>>> d
{'o': 1, 'a': 1, 'i': 1, 'r': 4, 't': 1, 'g': 1, 'f': 1, 'e': 2}

同様の処理を collections.Counter では以下のように実現できます。
>>> from collections import Counter

>>> c = Counter(s)
>>> c
Counter({'r': 4, 'e': 2, 'o': 1, 'g': 1, 'i': 1, 't': 1, 'a': 1, 'f': 1})

collections.Counter はいくつかの専用メソッドを備えています。要素数に関する何らかの値を取得したい場合に、これらのメソッドは有用です。
# 最多の要素を表示
>>> c.most_common(1)
[('r', 4)]

# 要素数 0 でもKeyErrorではない
>>> c['b']
0

頻繁には使わないけど、目的とガッチリ噛み合った時には非常に強力、そんなツールだと思います。

2015年11月29日

test.pyはimportできない!

何かをテストするソースコードを書き、そのファイル名を test.py として保存したとします。
この test.py を import しようとしても、意図通りには import できません。これは、Python の標準ライブラリに test という名前のライブラリがあり、こちらを優先して import してしまうためです。
import test  # ←標準ライブラリをimportする!

この test というライブラリは、Python の開発者(Python を作っている人達)のためのテスト支援ツールであり、多くの Python プログラマーは使用しません。自称・中級者の私も一度も使ったことがありません。
このようなライブラリに test という極めて一般的な名前を付けてしまったのは、明らかに失敗です。今更言っても仕方のないことですが、、、。


結論として、test.py というファイルは作るべきではありません。

2015年11月28日

JSONを使おう

JSON は JavaScript Object Notation の略で、データ記述言語の一つです。名前の通り元々は JavaScript 用に開発されたのですが、その使い勝手の良さから多くの言語がサポートしています。

JSON の長所は以下のようになります。

  • 仕様がシンプル!
  • テキストベースでヒューマンリーダブル
  • 書式がソースコードに近い
  • Python をはじめ多くの言語が対応している


Python には json というライブラリが標準に含まれています。
使う関数は以下の4つになります。
No.関数変換処理
1json.dumpsオブジェクト → JSON文字列
2json.dumpオブジェクト → JSONファイル
3json.loadsJSON文字列 → オブジェクト
4json.loadJSONファイル → オブジェクト


json.dumps と json.loads の使い方をヘルプより抜粋します。
>>> import json

# オブジェクト → JSON文字列
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'

# JSON文字列 → オブジェクト
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
['foo', {'bar': ['baz', None, 1.0, 2]}]

json.dumps(とjson.dump)はインデントの調整も可能です。
>>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4))
{
    "4": 5,
    "6": 7
}
json ライブラリの各関数は、無駄がなく洗練されています。JSON の思想ともよく合致していますね。


JSON の主な使い所は、他言語とのデータのやり取り、デバッグや内部情報の出力、等になります。中でも他言語とのやり取りを手軽にできるという点は、他のデータ記述言語よりも抜きん出ています。

一方、コメントが書けない、書き方がソースコード的である、という点から設定ファイルとして使うのにはあまり向いていません(特に非エンジニアも編集する場合)。