2010-08-01
*1280651063*抜粋翻訳 PEP3104: Access to Names in Outer Scopes
** 概要
ネストしたスコープをサポートする多くの言語では、コードは最も近いスコープの名前を参照したり再束縛(代入)したりできる。現在のところPythonは外側のスコープの任意の名前を参照できるが再束縛は2つのスコープの中でしかできない。ローカルスコープ(単なる代入を使う)と、モジュールグローバルのスコープ( global 宣言を使う)だ。
この制限はなんどもPython-Devメーリングリストやその他で話題にあがり、多くの議論とこの制限を取り除く方法の提案につながった。このPEPでは提案された数多くの選択肢を総括し、それぞれに指摘された長所と他所を付記する。
** 理由
バージョン2.1以前、Pythonのスコープの扱いは標準的なCに似たものであった。ファイルの中には2つのスコープレベルしか存在しない。グローバルとローカルである。Cでは、これは関数の定義がネストできないことによる自然な結果である。しかしPythonでは、関数は通常トップレベルで定義されるとはいえ、関数定義はどこでも実行できる。これは構文上はPythonにネストしたスコープがあるように見せる。しかしその機能はない。この不一致はプログラマを驚かせる。
たとえばトップレベルで動いていた再帰関数は他の関数の中に入れると動かなくなるだろう。なぜなら再帰関数自体の名前が再帰関数の本体のスコープからは見えなくなるからだ。これは関数はどこに置かれているかにかかわらず同じように振舞うべきだ、という直感に反する。下に例を挙げる::
|python|
def enclosing_function():
def factorial(n):
if n 2:
return 1
return n * factorial(n - 1) # fails with NameError
print factorial(5)
||
Python 2.1では外側のスコープすべてで束縛された名前が見えるようになり、より静的にネストしたスコープに近づいた (PEP 227を参照)この変更によって上で挙げたコード例は期待通りに動くようになった。しかし、全ての名前への代入は暗黙的にその名前をローカルとして宣言するので外側のスコープの名前を再束縛することは不可能である(global 宣言によって名前をグローバルスコープに強制することをのぞく) なので下記のコードは、ボタンをクリックすると表示された数値が増えたり減ったりすることを期待したコードだが、レキシカルスコープに慣れた人が期待するようには動かない。::
|python|
def make_scoreboard(frame, score=0):
label = Label(frame)
label.pack()
def increment(step=i):
score = score + step # fails with UnboundLocalError
button = Button(frame, text='%+d' % i, command=increment)
button.pack()
return label
||
**他の言語での例
*** JavaScript, Perl, Scheme, Smalltalk, GNU C, C# 2.0
これらの言語はスコープを示すために変数の宣言を用いる。JavaScriptでは、字句的スコープの変数はキーワード var をつけて宣言される。宣言されていない変数名はグローバルと解釈される。Perlでは字句的スコープの変数はキーワード my をつけて宣言される。宣言されていない変数名はグローバルと解釈される。Schemeではすべての変数は(define や let をつけるか、仮引数として)宣言が必要である。Smalltalkではブロックはすべて、縦棒で挟まれた、ローカル変数名を宣言するリストで始められる。CとC#ではすべての変数に型宣言が必要である。これらすべてにおいて変数はその宣言が含まれているスコープに属する。
***Ruby1.8
Rubyは参考になる例だ。なぜなら他の現在有名な言語で唯一、Pythonのように、変数宣言を要求することなく静的にネストしたスコープをサポートすることに挑戦しているように見える。そしてそれゆえに一般的でない解決方法に到達したからだ。Rubyの関数は他の関数定義をもつことができる。そして波カッコで囲われたブロックを持つことができる。ブロックは外のスコープにアクセスすることが出来る。しかしネストした関数はそうではない。ブロックの中での名前への代入は、外のスコープですでに束縛された名前を隠さない場合に限りローカル変数の宣言と解釈される。そうでない場合は代入は外の名前の再束縛と解釈される。Rubyのスコープに関する構文とルールも長きに渡って議論されており、Ruby 2.0で変更される見込みである。 28_ *1280656890*抜粋翻訳 PEP 342: Coroutines via Enhanced Generators
** 概要
このPEPはジェネレータのAPIと構文を改善して、シンプルなコルーチンとして使えるようにすることを提案する。
** 動機
コルーチンはいろいろなアルゴリズムを表現する自然な方法である。たとえば、シミュレーション、ゲーム、非同期I/O、そしてイベントドリブンプログラミングや協調的マルチタスクの他の形だ。 Pythonのジェネレータ関数はほとんどコルーチンだ、しかし完全にではない、なぜなら値を作って処理を一時停止することは許すが、処理を再開するときに値や例外を渡す方法がないからだ。
** 実装例
1. ジェネレータ関数が最初に呼ばれたときに自動的に最初のyieldまで処理が進むようにする “consumer” デコレータ:
|python|
def consumer(func):
def wrapper(*args,**kw):
gen = func(*args, **kw)
gen.next()
return gen
wrapper.__name__ = func.__name__
wrapper.__dict__ = func.__dict__
wrapper.__doc__ = func.__doc__
return wrapper
||
2. “consumer”デコレータを使って”reverse generator”を作る。これは、複数の画像を受け取ってサムネイルの並んだページを作り、それを他のcomsumerに送る。このような関数は、それぞれは複雑な内部状態を持ちうるような”consumers”を、効率的な処理パイプラインを作るためにつなぎあわせることができる:
|python|
@consumer
def thumbnail_pager(pagesize, thumbsize, destination):
while True:
page = new_image(pagesize)
rows, columns = pagesize / thumbsize
pending = False
try:
for row in xrange(rows):
for column in xrange(columns):
thumb = create_thumbnail((yield), thumbsize)
page.write(
thumb, col*thumbsize.x, row*thumbsize.y
)
pending = True
except GeneratorExit:
# close() was called, so flush any pending output
if pending:
destination.send(page)
# then close the downstream consumer, and exit
destination.close()
return
else:
# we finished a page full of thumbnails, so send it
# downstream and keep on looping
destination.send(page)
@consumer
def jpeg_writer(dirname):
fileno = 1
while True:
filename = os.path.join(dirname,page%04d.jpg % fileno)
write_jpeg((yield), filename)
fileno += 1
# Put them together to make a function that makes thumbnail
# pages from a list of images and other parameters.
#
def write_thumbnails(pagesize, thumbsize, images, output_dir):
pipeline = thumbnail_pager(
pagesize, thumbsize, jpeg_writer(output_dir)
)
for image in images:
pipeline.send(image)
pipeline.close()
||
|python|
import collections
class Trampoline:
Manage communications between coroutines
running = False
def __init__(self):
self.queue = collections.deque()
def add(self, coroutine):
Request that a coroutine be executed
self.schedule(coroutine)
def run(self):
result = None
self.running = True
try:
while self.running and self.queue:
func = self.queue.popleft()
result = func()
return result
finally:
self.running = False
def stop(self):
self.running = False
def schedule(self, coroutine, stack=(), value=None, *exc):
def resume():
try:
if exc:
value = coroutine.throw(value,*exc)
else:
value = coroutine.send(value)
except:
if stack:
# send the error back to the caller
self.schedule(
stack0, stack1, *sys.exc_info() )
else:
# Nothing left in this pseudothread to
# handle it, let it propagate to the
# run loop
raise
if isinstance(value, types.GeneratorType):
# Yielded to a specific coroutine, push the
# current one on the stack, and call the new
# one with no args
self.schedule(value, (coroutine,stack))
elif stack:
# Yielded a result, pop the stack and send the
# value to the caller
self.schedule(stack0, stack1, value) # else: this pseudothread has ended
self.queue.append(resume)
||
4. シンプルなechoサーバとトランポリンを使ってそれを走らせるコード (“nonblocking_read”, “nonblocking_write” と、たとえば接続が閉じられたときにはConnectionLostを投げるようなその他のI/Oコルーチンの存在を仮定している):
|python|
# coroutine function that echos data back on a connected
# socket
#
def echo_handler(sock):
while True:
try:
data = yield nonblocking_read(sock)
yield nonblocking_write(sock, data)
except ConnectionLost:
pass # exit normally if connection lost
# coroutine function that listens for connections on a
# socket, and then launches a service handler coroutine
# to service the connection
#
def listen_on(trampoline, sock, handler):
while True:
# get the next incoming connection
connected_socket = yield nonblocking_accept(sock)
# start another coroutine to handle the connection
trampoline.add( handler(connected_socket) )
# Create a scheduler to manage all our coroutines
t = Trampoline()
# Create a coroutine instance to run the echo_handler on
# incoming connections
#
server = listen_on(
t, listening_socket(localhost,echo), echo_handler
)
# Add the coroutine to the scheduler
t.add(server)
# loop forever, accepting connections and servicing them
# in parallel
#
t.run()
||
*1280678844*ぼへー
今日翻訳したPEPを講義資料に追加して提出するつもりだったのをすっかり忘れてそのまま提出してしまった、まあいいけど。入れるとしたらOuter Scopeの方だけだから実例が1個増えるだけだし。