付録

9. その他諸々

9.1. zip, enumerate, range and other iterator/generaotor

pythonのfor文は 反復可能なオブジェクト(iterable oject)に対して続くスイートの中身を実行します。 iterableオブジェクトとしてはリスト(list)やタプル(tuple)がよく知られているが、その他にもイテラブルな オブジェクトが存在します。 python3では、zip, range, enumerate, mapはクラスとなりその呼び出しはイテラブルな オブジェクトを返すようになりました。python2ではzip, enumerate, map()はリストを返していましたので、 結果のリストが必要な場所では、これらの呼び出しをlist()でラップしておく必要があります。

python3のitertoolsには、for文でよく使われるパターンを簡略するためにさまざまなイテラブルなオブジェクト を生成するためのメソッドが提供されています。

9.1.1. iterator.count() の使い方

例えば、ある条件を満たすまで、繰り返しを行いつつ、実行回数の把握が必要な場合には、

i=0
while True:
  do_something()
  if check_condition(i):
    break
  i +=1

のようなパターンがよく使われていました。このパターンをitertoolsのcount()を使って書き換えると

import itertools

for i in itertools.count():
  do_something()
  if check_condition(i):
    break

と見易い形に書き換えることができます。

9.1.2. map()zip()

python2ではmap(None, list1,lisn2,..)zip(list1,list2,..)の代わりに使うことができました。 python3では, map()のこの形での利用は不可能になっています. Python2でもzip()を使うように書き換えておくべきでしょう。

9.2. functools module and cmp_to_key() method

リストのソートメソッドの使い方はpython2からpython3で大きく変わったことのひとつです。

python2では listのsort()のプロトタイプは、

sort(...)
L.sort(cmp=None, key=None, reverse=False) -- stable sort IN PLACE;
cmp(x, y) -> -1, 0, 1

となっています。通常は文字、数字などの自然な並びに従って整列させる場合には、とくに引数は必要ありませんが、 特別なデータ型や、標準的でない順番に整列させrたい場合には、cmp引数に、ふたつの要素を比較するための関数を指定します。 この関数はふたつの引数の大小に従って、-1,0,1のいずれかを返すことを期待されています。 (python2.4以降ではpython3と同じく、key関数を使うこともできますが、それはまた別のお話。)

例えば、

1s=list("python")
2s.sort(lambda x,y: 0 if ord(x) == ord(y) else 1 if ord(x) > ord(y) else -1)
3print(s)

をpython2で実行すると

s=list("python")
s.sort(lambda x,y: 0 if ord(x) == ord(y) else 1 if ord(x) > ord(y) else -1)
print(s)
['h', 'n', 'o', 'p', 't', 'y']

が出力されます。

一方、python3ではsort()のインタフェースは

sort(*, key=None, reverse=False) method of builtins.list instance
Sort the list in ascending order and return None.

The sort is in-place (i.e. the list itself is modified) and stable (i.e. the
order of two equal elements is maintained).

If a key function is given, apply it once to each list item and sort them,
ascending or descending, according to their function values.

The reverse flag can be set to sort in descending order.

となっています。ソート順を制御するためのcmpに代えて、keyと呼ばれる関数を用意することになっています。 このkey()関数は、ソート対象の各要素毎に評価され、その結果に基づいてソートが実行されます。

click to download python script

click to download jupyter notebook

このcmpとkeyの違いを吸収するための関数cmp_to_key()が モジュールfunctoolsの中に用意されています。これを使って、先ほどのpython2でのソートを 実行するには、次の様に書き換えます。

1import functools
2s=list("python")
3s.sort(key=functools.cmp_to_key(lambda x,y: 0 if ord(x) == ord(y) else 1 if ord(x) > ord(y) else -1))
4print(s)
['h', 'n', 'o', 'p', 't', 'y']

この様に、python2で使っていたcmp()関数を、pytyon3で動作するようにすることができます。

click to download python script

click to download jupyter notebook

ちなみに、python3での sort() を key パラメータを使って、同様のソートを行うには

1s=list("python")
2s.sort(key=lambda x:ord(x))
3print(s)
['h', 'n', 'o', 'p', 't', 'y']

とすれば十分です。 この様に、 cmp() から key() への切り替えは、 それほど困難なことではありません。 cmp_to_key() はあくまで、python2で使っていた cmp() 関数を どうしてもそのまま使いたい場合(例えば大量のソースコードをpython2からpython3に機械的に変換したい時など)に限定しておくのが良さそうです。

9.3. reload 問題

Python2とPython3では、reloadの取り扱いが変わっています。 Python2ではreload関数は組み込み関数で、globalなネームスペースに存在していますが、 Python3においては、reload関数は、importlibモジュールの中で定義されています。 アプリケーションの中で、モジュールのリロードを行うことはほとんど考えられないので、 問題になることは無いかと思いますが、プログラム開発中にはしばしば 修正済みのモジュールを reloadすることになるでしょう。

python3では、 reload関数の利用に先立ち、

Python3でのreload関数の利用
>>> from importlib import reload
reload
<function reload at 0x10275e158>

としてreload関数をimportします。この後、

mymoduleをリロードする。
reload(mymodule)

としてやることで、python2と同じく、モジュールをreloadしてやることができます。

9.4. print 問題

print はpython2では , python3では 関数 となっています。 2to3はこの違いを適切に取り扱って、プログラムを更新してくれます。 ただし、この場合変更後のコードはpython2では動作しなくなります。

このため、同じコードベースをpython2/python3のどちらでも動作させるには、 ちょっと工夫が必要です。

9.4.1. print()関数

まずは、python2では__future__モジュールを使ってpython2でも python3のprint関数が使えるようにすることができます。

from __future__ import print_function

__future__ モジュールのインポートはファイル中の最初の実行文である 必要があるので、注意してください。

9.4.2. sys.stdout/stderr

もともと、print文は、標準出力に指定された文字列を出力します。 標準出力および標準エラー出力はsys モジュールの sys.stdoutおよび stderrとしてアクセス 可能ですから、print文を使う代わりに、sys.stdoutあるいは sys.sterrに文字列を送り出すことで、print文を使用しないようにも できます。

import sys

sys.stdout.write("Hello World")

9.4.3. logging モジュール

そもそも、デバグメッセージを表示するためにprint文を使っているなら、 loggingなどのモジュールを使いメッセージを表示させる のが良いでしょう。loggingを使えば、開発のフェーズ毎に "Debug", "Info","Warnig"などのメッセージレベルを使って出力される メッセージを制御することができます。 使い方は、次の:py:mod:logging モジュールの使用例を ご覧ください。

import logging
logging.getLogger().setLevel(logging.WARNING)
...

logging.warning("これは警告です。")
出力例:

WARNING:root:これは警告です。

logging モジュールには、 {'DEBUG':10, 'INFO':20, 'WARNING':30, 'ERROR':40, 'CRITICAL':50, 'FATAL':50} のレベルが定義されています。また、このレベルに対応した出力関数 debuf, info, warning, error, critical, fatal() が提供されています。 実行時の設定レベル以上のメッセージが出力されます。 メッセージの出力先は、端末だけでなく、ファイル、syslogなどに変更/追加することも可能です。

9.5. exceptions モジュール

python3では exceptions モジュールは廃止されています。 python2で exceptions モジュール経由で呼び出すException のクラスは全て __builtins__ の中に入っていますので、 moduleを import する必要は有りません。 exceptions.xxxException の様な使い方をしていた場合には、直接 xxxException を使う様にすれば良いかと思われます。

9.6. maxint

python3では取り扱える整数の最大値が 存在しません 。 従って、 sys.maxint も廃止されています。 取り扱える上限を決めているものが何かによって、対応が変わるかと思いますが、 sys.maxint ではない別のものにしておくべきでしょう [1]

9.7. time.mktimeの2000年問題(2023/12/15追記)

>>> time.mktime((2023,12,15,0,0,0,0,0,0))
1702566000.0
>>> time.mktime((23,12,15,0,0,0,0,0,0))
time.mktime((23,12,15,0,0,0,0,0,0))
1702566000.0

となりますが、python3.6では、

>>> time.mktime((2023,12,15,0,0,0,0,0,0))
1702566000.0
>>> time.mktime((23,12,15,0,0,0,0,0,0))
-61411339139.0

python3.9 では、

>>> time.mktime((2023,12,15,0,0,0,0,0,0))
1702566000.0
>>> time.mktime((23,12,15,0,0,0,0,0,0))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: mktime argument out of range

となっています。ともあれ、 time.mktime() で二桁の年号をお使いの場合は、気をつけましょう。 :py:`python2 の :py:func:`mktime`では, yearが 69 より小さい場合は year+2000 が、69以上で100より小さい場合には、:py:year:`year+1900`がUnix time換算時に年号として使われます。

python3.9では, mktime() のyearの引数が1900より小さい場合には、 OverFlowError を返すようです。

>>> time.mktime ((1900, 1, 1,0,0,0,0,0,0 ))
-2209021200.0

なので、単に「Unix時間が負になる」ことは問題では無いようにも見えます。