投稿者「admin」のアーカイブ

ジェネレータ関数とyield

イテレータを生成するための構文。
yieldで値を返却することで、関数が繰り返し処理を行う状態を保持できるようになる。

>>> def gentest():    #3つの文字列を返すgenerator関数
...   yield "hello"
...   yield "world"
...   yield "finish"
...
>>> [s for s in gentest()]   #繰り返し処理を行う
['hello', 'world', 'finish']

yieldを実行すると、値を返却しつつ、現在の処理を中断したまま保持するようなイメージ。

リスト内包表記

リストを生成するための表記方法。リストに対して繰り返し処理を行う場合にも使用出来る。従来のmap, filterの代わりになる。だいたいの場合for文でループするよりも高速に動作する。

繰り返し処理でリスト生成する時の構文

>>> aray = [1, 2, 3]
>>> [x*2 for x in ary]    #aryの各要素を*2してリストを生成する。
[2, 4, 6]
>> ary
[1, 2, 3]        #aryに変更はない

 

リスト内包表記をfor文を使わない同等の記述

記述すると以下のようになる。(ただしこの処理の場合Pythonインタプリタで処理するのでリスト内包表記よりも低速。リスト内包表記は構文からcライブラリで処理するのでより高速に生成することができる。)

>>> ary = [1,2,3]
>>> newary = []
>>> for x in ary:
...   newary.append(x*2)
...
>>> newary
[2, 4, 6]

mapとlamda式での同等の記述

mapとlambda式でも同様の処理が簡潔に記述できるが、リスト内包表記よりもおおむね低速。(forよりは速い)

>>> ary = [1,2,3]
>>> map(lambda x: x*2, ary)
[2, 4, 6]

リスト内包表記でのif

リスト内包表記ではif文で条件にマッチしたものだけ抽出することもできる。(filter相当)

>>> ary = [1,2,3]
>>> [x for x in ary if x % 2 == 1]    # 奇数である要素(x % 2 == 1)を抽出したリストを作る。
[1, 3]

#filterを使った同等のコード
>>> ary = [1,2,3]
>>> filter(lambda x:x%2==1, ary) #lambda式がTrueを返す要素を抽出したリストを作る
[1, 3]

#リスト内包表記の方が、抽出した上で演算できるので応用範囲が広い
>>> ary = [1,2,3]
>>> [x*2 for x in ary if x % 2 == 1]    # 奇数を抽出し、*2したリストを作る。
[2, 6]
#filterとmapを使った同等のコード(分かりにくい)
>>> map(lambda x:x*2, filter(lambda x: x%2==1, ary))
[2, 6]

ネストした表記

>>> [(x,y) for x in range(3) for y in range(3,6)]
[(0, 3), (0, 4), (0, 5), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)]
>>> [x*y for x in range(3) for y in range(3,6)]
[0, 0, 0, 3, 4, 5, 6, 8, 10]

# ※for文での同等のコード
>>> newary = []
>>> for x in range(3):
...   for y in range(3,6):
...     newary.append(x*y)
...
>>> newary
[0, 0, 0, 3, 4, 5, 6, 8, 10]

while, forループのelse

Pythonではwhile, forのループにelseを使えます。
これは他の言語には珍しい機能で、ありがちなケースでは「ループ処理で何かを探索して見つけたらbreakする、breakしなかったら見つからなかった」といったケースでフラグ変数を使う必要がなくなります。

elseを使わないループ

elseを使わない、ありがちなループ探索処理の場合は、見つかった場合にフラグを更新してbreakし、ループの外側でフラグの状態をチェックしたif文を記述します。

ary = [1, 3, 9, 2, 1]
exist = False
for n in ary:
    if n == 9:
        exist = True
        break
if exist:
    print "Found! 9"
else:
    print "Not found 9.."

elseを使うループ

elseを使った場合は、existフラグを使用する必要がなくなります。

ary = [1, 3, 9, 2, 1]
for n in ary:
    if n == 9:
        print "Found! 9"
        break
else:
    print "Not found 9.."

elseブロックが処理されるのは、「breakでループを抜けなかった時」です。厳密に言うと「ループを一度も実行しなかった時」も含まれます。
たとえば以下のケースは、aryが長さ0のリストなのでループ内は一度も処理しません。したがってelseブロックが実行されます。

elseブロックは、breakせずにループが回りきったとき実行される」と覚えておきましょう

ary = []
for n in ary:
    if n == 9:
        print "Found! 9"
        break
else:
    print "Not found 9.."

文字列の正規表現マッチング re.match re.search

完全マッチするか(match)

import re
m = re.match(r"\d-\d\d", "12-34")   #raw文字列で指定することで、エスケープ不要
print(m)        #None 完全マッチしない

m = re.match(r"\d\d-\d\d", "12-34") #Match Objectを返す
print(m.group(0))       # "12-34"

m = re.match(r"(\d\d)-(\d\d)", "12-34")  #group取得
print(m.group(0))      # "12-34"
print(m.group(1))      # "12"
print(m.group(2))      # "34"

部分一致(search)

matchとは違い文字列の一部分にマッチしても取得できる。

m = re.search(r"\d-\d\d", "12-34") 
print(m.group(0))       #2-34

m = re.search(r"\d\d", "12-34") 
print(m.group(0))       #12
m2 = m.re.search("12-34", m.end())) #最初のsearch時に作成された正規表現オブジェクトを再利用
print(m.group(0))       #34

正規表現オブジェクト

re.match()や、re.search()は正規表現オブジェクトの生成を省略した簡易な利用法。使い捨てのマッチングに便利だが、繰り返しマッチングを行う場合は正規表現オブジェクトを生成してからマッチングを行うほうが効率が良い。

ptn = re.compile(r"\d\d")   #正規表現オブジェクトをコンパイル
print(ptn.search("12345678").group(0))   #作成した正規表現オブジェクトを利用
print(ptn.search("12345678", 2).group(0))   #作成した正規表現オブジェクトを利用

ファイル読み書き file open read write

ファイル読み込み

レガシーな書き方

file = open("filename", "r")
try:
    whole_str = file.read()
finally:
   file.close()  # finallyでcloseすべき

withブロックを使用してcloseを自動的に行う方法。(こちらを推奨)

with open("filename", "r") as file:
  whole_str = file.read()

file.read([size])で指定したバイト数を読み込み。size未指定の場合はすべて読み込み。
file.readline()で1行読み込み。文字列に改行文字は残る。最後の行を読み込んだ後に””(空文字)がかえる。
file.readlines()で全行を読み込んでリストを返す。

ファイル書き込み

レガシーな書き方

file = open("filename", "w")
try:
    file.write("some string")
finally:
    file.close()

withブロック

with open("filename", "w") as file:
    file.write("some string")

file.write(str)でファイルに書き込み
file.writelines(sequence)でファイルに複数行の文字列を書き込み。改行文字は付加しないのでシーケンス内の文字列につけておくこと。

文字コードを指定して読み書き

codecsモジュールを使う。

import codecs

with codecs.open("filename", "w", "shift_jis") as file:
    file.write(u"あいうえお")

with codecs.open("filename", "r", "shift_jis") as file:
    whole_str = file.read()
    print whole_str

pythonのgeventをmacでインストールした

easy_install geventでエラーになった

error: Setup script exited with error: command 'gcc' failed with exit status 1

検索したらlibeventが必要だということで、インストールした。homebrewからインストールできた。

brew install libevent
easy_install gevent

Ubuntuでやったときも同様に

gevent/libevent.h:9:19 

なるエラーが出てきたが、apt-get install libevent-dev を導入したらOKだった。

ログ設定ファイル fileConfig logging

設定ファイルを利用することで、コード中にログ設定を書かなくて済む。

import logging
import logging.config

logging.config.fileConfig("log.conf")

logging.debug("debug message")
logging.info("info message")
logging.warn("warn message")
logging.error("error message")

log.conf
rootLoggerにStreamHandlerとRotatingFileHandlerの2つを設定し、
RotatingFileHanlderの方はwarn以上のみ出力する(エラーログ)


[loggers]
keys=root

[handlers]
keys=consoleHandler,errorHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler,errorHandler

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[handler_errorHandler]
class=handlers.RotatingFileHandler
level=WARN
formatter=simpleFormatter
args=("error.log", 'a', 200*1024, 3)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s %(levelname)-7s %(message)s

設定ファイルの記述方法は、こちらを参照。

ファイルへのログ出力、Logger生成

basicConfigでファイルへ出力

basicConfigでfilename引数を指定。

logging.basicConfig(level=logging.DEBUG, 
    filename="test.log",
    format="%(asctime)s %(levelname)-7s %(message)s")

basicConfigで設定する場合は、logger全体(rootLogger)への一括設定となり簡単ではあるが細かい設定はできない。

注意
basicConfigはすでにrootLoggerにハンドラが設定されているとなにも処理をしない。したがってbasicConfigで簡易設定できるのだが、2回目はなにも変更されないので注意。levelなどを変更しようと思ってもすでにhandlerが設定されているとなにも処理されない。他人が使用するライブラリなどではbasicConfigは使用しないほうがよい。
force=Trueというパラメータ設定することで強制的に現在設定ずみのハンドラを削除して再設定することができるが、これは3.8で追加された機能。また3.7以前で使用してもエラーにはならないので注意。

Loggerを設定してファイルへログ出力

basicConfigからのrootLoggerではなく、自分でLoggerを取得して詳細に設定する。

以下の例では、Loggerに2つのHandlerを設定して、1回の出力処理で2種類Handler(StreamとFile)への出力をする例。Handlerについては、こちらを参照。

logger(level=DEBUG) --+-- handler1(StreamHandler) 
                     +-- handler2(FileHandler)   level=WARN
import logging

logger = logging.getLogger("logger")    #logger名loggerを取得
logger.setLevel(logging.DEBUG)  #loggerとしてはDEBUGで

#handler1を作成
handler1 = logging.StreamHandler()
handler1.setFormatter(logging.Formatter("%(asctime)s %(levelname)8s %(message)s"))

#handler2を作成
handler2 = logging.FileHandler(filename="test.log")  #handler2はファイル出力
handler2.setLevel(logging.WARN)     #handler2はLevel.WARN以上
handler2.setFormatter(logging.Formatter("%(asctime)s %(levelname)8s %(message)s"))

#loggerに2つのハンドラを設定
logger.addHandler(handler1)
logger.addHandler(handler2)

#出力処理
logger.debug("debug message")
logger.info("info message")
logger.warn("warn message")
logger.error("error message")

実行結果

$ ./logger.py
2012-04-20 12:16:36,234    DEBUG debug message
2012-04-20 12:16:36,236     INFO info message
2012-04-20 12:16:36,236  WARNING warn message 
2012-04-20 12:16:36,236    ERROR error message

$ cat test.log
2017-06-13 10:36:11,231  WARNING warn message
2017-06-13 10:36:11,231    ERROR error message

オススメのPythonログ出力 基本設定

とりあえず、最初はこの設定をしてコンソールにログ出力するのが、後々カテゴリ分けしたくなったり、ファイルに出力したくなったときにもなんとかなる。

import logging

# basicConfigでフォーマットとレベル指定
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)7s %(message)s")
# ロガーを必ず生成して使用(ロガー名は完全修飾名を使用)
log = logging.getLogger(__name__)

log.debug("debug message")
log.info("info message")
log.warning("warning")
log.error("error")
# 実行結果
2012-04-20 09:17:34,857 DEBUG   debug message
2012-04-20 09:17:34,857 INFO    info message
2012-04-20 09:17:34,858 WARNING warning message
2012-04-20 09:17:34,858 ERROR   error message

とりあえずファイルに出力したくなったらbasicConfigでfilenameを指定

logging.basicConfig(level=logging.DEBUG, filename="debug.log",
    format="%(asctime)s %(levelname)7s %(message)s")

設定ファイルに分けたくなったり、カテゴリ分けしたくなったらfileConfigを使用

logging.config.fileConfig("log.conf")

ロガーを生成せずに、logging.debugみたいにして出力していると、全部rootLoggerからの出力になってしまうのでカテゴリわけができなくなる。
モジュールの完全修飾名でロガー生成生成していれば、パッケージ毎に分けることが可能になる。

フォーマットの書式文字列については以下を参照。

LogRecord 属性※抜粋

属性名 フォーマット 説明
asctime %(asctime)s LogRecord が生成された時刻を人間が読める書式で表したもの。デフォルトでは “2003-07-08 16:49:45,896” 形式
(コンマ以降の数字は時刻のミリ秒部分) です。
filename %(filename)s pathname のファイル名部分。
funcName %(funcName)s ロギングの呼び出しを含む関数の名前。
levelname %(levelname)s メッセージのための文字のロギングレベル
('DEBUG', 'INFO', 'WARNING',
'ERROR', 'CRITICAL')。
lineno %(lineno)d ロギングの呼び出しが発せられたソース行番号
(利用できる場合のみ)。
module %(module)s モジュール (filename の名前部分)。
msecs %(msecs)d LogRecord が生成された時刻のミリ秒部分。
message %(message)s msg % args として求められた、ログメッセージ。 Formatter.format() が呼び出されたときに設定されます。
name %(name)s ロギングに使われたロガーの名前。
pathname %(pathname)s ロギングの呼び出しが発せられたファイルの完全なパス名 (利用できる場合のみ)。
process %(process)d プロセス ID (利用可能な場合のみ)。
thread %(thread)d スレッド ID (利用可能な場合のみ)。
threadName %(threadName)s スレッド名 (利用可能な場合のみ)。

日付の加算 減算 timedelta

日付の加算・減算を行うには、datetime.timedeltaを使用する。

>>> import datetime
>>> d = datetime.datetime.now()
>>> d
datetime.datetime(2012, 4, 18, 06, 29, 28, 538000)

>>> d + datetime.timedelta(days=3) #3日加算
datetime.datetime(2012, 4, 21, 06, 29, 28, 538000)

>>> d - datetime.timedelta(hours=3) #3時間減算
datetime.datetime(2012, 4, 18, 03, 29, 28, 538000)

引数に使用可能なのは以下のとおり
timedelta([days[, seconds[, microseconds[, milliseconds[, minutes[, hours[, weeks]]]]]]])

timedeltaでは月の加算減算はできません。サードパーティ製ライブラリのpython-dateutil.relativedeltaを使うとできます。
日付に月単位で加算減算する (relativedelta) | Python Snippets