128 ー 3 章時間とお金 す boxings = [easter. easter(y) + one day for y in xrange(start. year, end. year + 1)] return [d for d in boxings if start く =d く =end] def a11 christmas(start, end) : # start と end の間のクリスマスの日付を返す = [datetime. date(), 12 , 25 ) christmases for y in xrange(start. year, end. year + 1)] return [d f0 て d in christmases if start く =d く =end] def a11 labor(start, end) : # start と end の間の労働祝日すの日付を返す = rrule. rrule(rrule. YEARLY, bymonth=9, byweekday=rrule. M0(1), labors dtstart=start, until=end) return [d. date( ) fo て d in labors] # 期間内かどうかのチェックは不要 def read holidays(start, end, holidays file= ・ holidays. txt' ) : # holidays file から読んだ休日のうち start... end にある日のリストを返す try: holidays file = open(holidays file) except 10Error, err: print 'cannot read holidays ( % て ) : ・ % (holidays file,), err return [ ] holidays = fo て line in holidays file: # 空行とコメント行をスキップする if line. isspace( ) or line. startswith('#') : continue # 書式 YYYY, M, D に沿って解釈する try: = [int(). strip( ) ) for x in line. split(' , = datetime. date(), m, d) date except ValueError: # 不正な行には警告を出して継続する print "lnvalid line %r in holidays file %r" % ( line, holidays file) continue if start<=date<=end: holidays. append(date) holidays_file. close( ) return holidays holidays by_country # カントリーコードをそれぞれ関数のシーケンスにマッピングする : ( a11 easter, a11 christmas, a11 labo て ) , 'IT' : ( a11 easter, a11 boxing, all_christmas), 訳注 : 9 月第一月曜。
2. ] 4 不可逆ストリーム入力のファイルを巻き戻す 2 ゴ 4 不可逆ストリーム入力のファイルを巻き戻す Credit: Andrew Dalke 訳 : 吉宗貞紀、鴨澤眞夫 問題 def read(self, size) : return self. start raise TypeError("RewindableFiIe can't tell except at start of file") if not self. at start: ファイルの現在位置を返す ( 先頭に巻き戻してある必要がある ) 。 def tell(self) : self. at start = True self. bu 幵 er file. seek(0) " 先頭に遡る単純なやり方。 def rewind(self) : self. rewind( ) raise ValueError()o 幵 set=%r; expecting %s" % (offset, self. start)) if 0 幵 set ! = self. start: raise ValueError("whence=%r; expecting 0 " % (whence,)) if whence ! = 0 : = 0 かっ 0 幵 set = self. start でなければならない。 whence = " 指定された位置のテータをシークする。 def seek(self, 0 幵 set, whence=o) : self. use bu 幵 er = True self. start = 0 except (IOError, AttributeError) : = input file. tell( ) self. start def try: self. at start = True self. bu 幵 er file = String10( ) self. file = input file input file に巻き戻し機能は ewind ) を付けて、ファイル的なオブジェクトにラップする。 init_(self, input file) : " ファイルハンドルを、先頭まで遡れるようにラップする。 class RewindableFiIe(object) : from cString10 import String10 ファイルオプジェクトを適切なクラスにラップする : 解法 を、先頭まで巻き戻せる形で作成し、全体を読み直せるようにしたい。 入力用のファイルオブジェクト ( データはソケットやその他の入力ファイルハンドルから得る ) 85
い .8 self. start = self. elapsedtime = self. running = Fa1se = StringVar( ) self. timestr self. makeWidgets( ) def makeWidgets(self) : " 時間のラベルを作成“ = Label(self, textvariable=self. timestr) 1 self. setTime(se1f. elapsedtime) 1. pack(fi11=X, expand=N0, pady=2, padx=2) def update(self) : 経過時間でラベルを更新” self. elapsedtime = time. time( ) self. setTime(self. elapsedtime) - self. start Tkinte 「によるストップウォッチ ー 443 = self. after(self. msec, self. update) self. timer def setTime(se1f, elap) : 一分 : 秒 : 1 / 100 秒の時間を表す文字列をセット = int(elap/60) minutes = int(elap - minutes*60. の seconds - minutes*60.0 - seconds)*100) = int((elap hseconds self. timestr. set('%02d:%02d:%02d ・ % (minutes, seconds, hseconds)) def Start(seIf) : ーストップウォッチのスタート。すでに動いていたら無視 " ・ if not self. running: self. start = time. time( ) - self. elapsedtime self. update( ) self. running = True def Stop(self) : " ストップウォッチをとめる。すでに止まっていたら無視 if self. running: self. after cancel(self. timer) self. elapsedtime = time. time( ) self. setTime(self. elapsedtime) self. running = False def Reset(self) : " ストップウォッチのリセット " self. start = time. time( ) self. elapsedtime = 0.0 sel 千 . setTime(seIf. elapsedtime) - self. start 考察 プログラムとして実行することも可能である。 main ・を使っている。そのおかげで、このモジュールをインポートして使うことも、スタンドアロンの name 以下にこのストップウォッチのウイジェットの使用例を挙げておく。 こではおなじみの if
3.6 休日の自動計算ー 127 単純な、しかし有用と目される改良点は、開始日と終了日が休日であるかどうか ( 週末シフトの人のために ) を自動的にチェックして、 if / else 文によって dayso 幵を適切に変更して週末シフトを扱えるようにするもの だ。さらに強化するのであれば、病欠の日を入力できるようにしたり、このレシピでやったように直接休日 を渡すのではなくて、自動的に休日データを参照する機能を付加したりということになろう。この目的で休 日のリストを実装する方法については「レシピ 3.6 休日の自動計算」を見てほしい。 参照 https://moin.conectiva.com/br/DateUtil?action=highlight&value=DateUtil にある dateutil に関する仮説とラ イプラリリファレンスの datetime に関する解説。 rrule. count のもう 1 つの使い方については「レシピ 3.3 2 つ の日付から期間計算」、自動的に休日をルックアップするには「レシピ 3.6 休日の自動計算」。 3.6 休日の自動計算 credit: Anna MarteIIi Ravenscroft, Alex Martelli 訳 : 吉宗貞紀、鴨澤眞夫 問題 国や地域はもとより、同じ会社の中でも組合によって休日が異なっているような場合に、与えら れた 2 つの日付の間の休日数を自動的に調べるようにしたい。 解法 2 つの日付の間には復活祭や労働祝日 ( 米国の場合 ) のように日付が変わるものも含まれる。復活祭に付随す る休暇や BoxingDay すもある。クリスマスのように日付が決まった祭日もあれば、企業の決めた休み ( CEO の誕生日 ) もある。それらの全てを、 datetime とサードバーティの dateutil モジュールで扱うことができる。 非常に柔軟性のあるアーキテクチャは、いろいろな可能性を要素ごとに分けて、適宜呼び出される独立し た関数にすることである : す import datetime 行 om dateutil import rrule, easter try: set except NameError: from sets import Set as set def a11 easter(start, end) : # start と end の間のイースタ - の日付を返す = [easter. easter(y) easters for y in xrange(start. year, end. year + 1)] return [d for d in easters if start く =d く =end] def all_boxing(start, end) : # start と end の間の Boxing Day の日付を返すすす one day = datetime. timedelta(days=l) 訳注 : クリスマス後の最初の平日。使用人などに心づけを贈る。 訳注 : なせか復活祭の翌日を計算。
] 5.7 PORT = 8992 class Ponger(pb. R00t) : def remote Pong(self, ball) : print 'CATCH ・ , ball, ball + = 1 print 'THROW ・ , ball return ball Twisted の Pe 「 spective B 「 Oke 「を使う ー 585 reactor. listenTCP(PORT, pb. BrokerFactory(Ponger( ) ) ) reactor.run( ) from twisted. internet import reactor from twisted. spread import pb 題、つまりエラーハンドリングなどをちゃんと扱ったものである : な PB クライアントを示そう。これは通常の分散処理プログラム入門のサンプルでは無視されがちな重要な問 こではもうちょっと機能リッチ クライアントの方も、同じようにごく軽いものを書いてもよいのだが、 DOG DELAY DELAY PORT = 8992 import sys self. start( ) print ping timed out, canceling and restarting def watchdog(self) : self. restart = reactor. caIILater(RESTART DELAY, self. start) = None self. ping self. ping ・ cancel( ) print ping failed, canceling and restartingl if self. ping: def remoteFai1(self, self. _ping( ) self. remote = remote remote. notify0nDisconnect(self. remoteFail) def _g0tRem0te(se1f, remote) : dfr. addCaIIbacks(se1f. _gotRemote, self. ref110teFail) dfr = pb. get0bjectAt(self. host, PORT, 30 ) self. host print ・ Waiting f0 て Server' def start(self) : self. start( ) self. ball self. host = host = None self. ping d ef init_(self, host) : class Pinger(0bject) : RESTART DELAY = 5
372 ー 9 章プロセスとスレッド、その同期 には結果のすべてやレポートされているかもしれないエラーの全てを収集するものだ。 スレッドプールの開始と停止は、繰り返し行いたい場合もそうでない場合もあると思う。一番普通なのは、 プログラム初期化の一部としてスレッドプールをスタートさせ、そのままずっと走らせておき、停止は ( する としても ) プログラムの最後のクリーンアップの一部としてのみ行うというものだ。レシピの make and start thread ー P001 関数では、デフォルトでワーカースレッドの「デーモン化」を行っているが、これが意味するの は、ワーカースレッドのみが残された状態になるとプログラムが終了するということだ。実際、メインスレッ ドが終了すると、プログラムはすぐに終了する。これもまた一般的にお薦めのアーキティクチャである。い すれにせよレシピではスレッドプールを終了してクリーンアップしたい ( そしておそらくはもう一度 make and start thread P001 をコールして走らせ直したい ) 時のために、関数 stop_and_free thread P001 を提供 レシピの機能は次のように使う . している。 fo て i in (' ba' Result: new bo Result: new be Result: new ba 普通は以下のような出力となる : show a11 e 0 巧 ( ) show a11 results( ) stop_and_free thread P001 ( ) make and start thread_pool( ) . request work(i) ただし、結果のどれかが入れ替わる可能性はある ( ほとんど起きないが ) 。 ( 結果の順序が重要な場合は、メ インスレッドからポストするワークリクエストにシリアル番号を付け、結果なりエラーなりの一部として返 普通は以下のような出力となる ( 非常に稀だが Result 行同士が入れ替わることもあり得なくはない ) : show a11 errors( ) show a11 results( ) stop_and free thread P001 ( ) make and start thread P001 ( ) for iin (' ba' ' b0' ) : request work(i) 次はエラーが出てレポートされる例である : すようにすること。 ) Error Result: Result: : exceptions. TypeError cannot concatenate ' str' and 'int' objects new bO new ba ァイテム「 7 」を貰ったワーカースレッドは、文字列 new をこのアイテムと結合しようとするが、 これは無
374 ー if 考察 9 章 プロセスとスレッド、その同期 finally: self. 10 ( k. release( ) = self. function(args) result if self. queue is not None: self. queue ・ put((args, result)) def get(self, *a, **kw) : if self. queue is not None: return self. queue. get(*a, else: 'N0t queueing results' raise ValueError, def start(self): for thread in self. threadP001: time. sleep(o) # 他のスレッドに走るチャンスを与えるため thread. start( ) def join(self, timeout=None) : fo て thread in self. threadPooI: thread. join(timeout) name maln import random def recite n times table(n) : for i in range(), 11 ) : time. sleep(). 3 + 0.3*random. random( ) ) mt = Mu1tiThread(recite n times table, range(), 11 ) ) mt. start( ) mt. join( ) print "Well done kids ! " このレシピの Mu1tiThread クラスは、関数を多数の引数セットで並列実行するシンプルな手段を、制限付き のスレッドプールにより提供する。デフォルトではキューに入れた関数コールの結果を捨てているが、取得 するオプションも用意してある。 MuItiThread クラスが取る引数は、関数とその関数への引数タブルのシーケンスである。オプション引数と して、プールで使われるスレッド数の上限値と、結果を結果キューに入れるかどうかのインジケータを取る。 コンストラクタ外部には 3 つのメソッドを露出する。プールの全スレッドを起動し、全引数タブルの並列評 価を開始する start 、プールの全スレッドに join を実行する ( プールの全スレッドの終了を待つ ) join 、そして 結果キューに入った結果を得る get である ( これはオプションフラグ queue results を True にセットし、結果を インスタンス化してある場合のみ ) 。内部的には、 MuItiTh て ead クラスのプライベートメソッド doSome を、プー ルのすべてのスレッドのターゲットコーラブルとして使っている。各スレッドは引数群のタブル ( こうしたタ プルをアイテムとする反復可能体にかかる反復子の next メソッドにより供給される。 next のコールはいつも のロッキングイディオムでガードされている ) に次々にとりかかり、作業が全て完了するまで繰り返す。
364 ー 9 章プロセスとスレッド、その同期 考察 , thon のいつもの慣例にしたがい、モジュールにメインスクリプトとして呼ばれた場合のみ実行される小 さなセルフテストを加えて完成とする。この部分はモジュールの機能がどのように使えるか示すものでもあ if name maln import threading import time class Dummy(0bject) : def foo(self) : print ' he110 行 om f00 ' time. sleep(l) def bar(self) : print ' he110 from bar' def baaz(self) : print ・ he110 from baaz' tw = Synchronized0bject(Dummy( ) , ignore=[ 'baaz' ]) threading. Thread(target=tw. f00). start( ) time. sleep(). 1) threading. Thread(target=tw. bar). start( ) time. sleep(). 1) threading. Thread(target=tw. baaz). start( ) 同期により、 bar のコールは f00 のコールが完了したときに初めて行われる。ところが baaz のコールは、 ワード引数 ignore = によって同期をバイバスして行われるので、先に完了する。このため出力は : he110 from f00 he110 from baaz he110 from bar となる。 キー 自分のプログラムの中で、オプジェクトのメソッドのほばすべてで、単一口ックによる同じロッキングコー ドを使っている場合は、オプジェクトのアプリケーション向けロジックからロッキングを分離するために のレシピを使うとよい。レシピの効果は、各メソッドを以下のコードによって効率的に置き換えることで発 揮される : self. lock. acquire( ) try: # メソッド分実際分アプリケーションコード finally: self. 10(). release( ) このイディオムは当然ながら、 ロッキングを表現するコードとして正しい。 try/finally 文は、 アプリケー
148 ー 3 章時間とお金 checksum = lambda a: ( - sum([int(y)*[7,3,1][x%3] 翫 x, y in enumerate(str(a)[::-1])])%10)%10 10 参照 本当にワンライナーのほうがよいと思うなら、腕のよいセラピストを訪ねるべきである。 3.16 為替レートの監視 Credit: VICtor Yongwei Yang 訳 : 吉宗貞紀 問題 ウェブから得られる、 2 つの通貨の間の交換レートを定期的に ( crontab によって実行される py 物 on スクリプトや、 Windows のタスクスケジューラのタスクとして ) 監視し、レートがある 解法 閾値に達したらメールによるお知らせを受け取れるようにしたい。 conn = httplib.HTTPConnection('www.bankofcanada.ca ・ ) ・ /en/markets/csv/exchange eng. csv' url = # 設定終わり your@corp.com ' toaddrs = fromaddr = ・ foo@bar.com ・ smtpServer = ' smtp.freebie.com ' thresh01dRate = 1.30 # ここでスクリプトのバラメータを設定する import smtplib import httplib な CSV (Comma-Separated Values) 形式で速報されている : をどうやって監視するのか見てみよう。レートは BankofCanada のウエプサイトで、処理しやすいシンプル は為替レートでも株価でも、寒冷予報値でも何でも構わない。具体的に、米ドルとカナダドルの為替レート この作業は、ウエプから簡単に得られる数値に対して行う可能性のある他の監視作業と同じである。それ conn. request('GET ・ , url) = conn. getresponse( ) response data = response. read( ) = data. index('U. S. D011ar ・ ) start = data[start:data. index(' \n' line = line. split(' , rate if float(rate) く threshoIdRate: # メールの送信 , start)] # 問題となる行を得る # その行の最後のフィールド 'Subject: Bank 0f Canada exchange rate alert %s ・ % rate = smtplib. SMTP(smtpServer) server server. sendmail(fromaddr, toaddrs, msg)
3.3 33 2 つの日付の間の期間は 問題 2 つの日付から、その間が何週間か計算したい。 解法 Credit: Andrea CavaIcanti 訳 : 吉宗貞紀 2 つの日付の間の期間は ー 123 こでもまた、標準の datetime モジュールに加えてサードバーティの dateutil モジュール群 ( 特に dateutil の rrule. count メソッド ) を用いることで極めて手軽に実現できる。適切なモジュールをインポートしてしま えば、実に単純な作業である : from dateutil import rrule import datetime def weeks between(start date, end date) : = rrule. rrule(rrule. WEEKLY, dtstart=start date, until=end date) weeks return weeks. count( ) 考察 説明するよりもコードを書いたほうが早いくらいだが、 week between 関数は開始と終了の日付を引数として 受け取ると、両者の間を 1 週間単位で循環するというルールをインスタンス化し、そのルールの count メソッ ドの戻り値を結果として返す。このメソッドの戻り値は整数値のみである ( 「半週」などとは返してこない ) 。 たとえば 8 日間は 2 週間と判定される。これを確かめるコードを書くのは簡単だ。以下のコードを追加して メインスクリプトとして実行すればよい・ if name maln = [datetime. date(2005, 01 , 04 ) , datetime. date(2005, 01 , 03 ) ] starts = datetime. date(2005, 01 , 1 の end f0 て S in starts: days = rrule. rrule(rrule. DAILY, dtstart=s, until=end). count( ) print "%d days shows as %d weeks " % (days, weeks between(), end)) このテストでは次のような出力が得られる : 7 days shows as 1 weeks 8 days shows as 2 weeks 循環ルールに名前を付けることは必須というわけではないので、関数のボディを、次のような 1 行の文に 変更してしまってもよい・ return rrule. rrule(rrule. WEEKLY, dtstart=start date' until=end date). count( )