式と文

k.inabaさんの「一時変数のために使用している謎のforループを取り除く」というネタに反応した自分の2/19の日記のネタをさらに研究してみる。


Pythonでは式と文が明確に分かれている。Rubyは、「a = b」は式だが、Pythonでは文である。


Pythonの文には以下のものがある。

  • 代入文
  • if文
  • for文
  • while文
  • break文、continue文
  • print文
  • def文
  • class文
  • raise文
  • など


他にもあるかもしれないがとりあえずこの程度。ここで考えたいのは代入文である。変数は初期化、代入、参照、削除の4つの操作しかない。web系の用語で言えばCRUD(Create、Read、Update、Delete)。ここで、初期化、代入は構文が同じなので同じものとして考える。すると、代入、削除は文であり、参照は式であることが分かる。


ここで、リスト内包表記やlambda式を考えてみると、定義部(BODY部と呼ぶことにする)に式を1つしかとれないという制限がある。つまり、代入文や複数の式が書けないということになる。そこで、「代入文を式にする方法」と「複数の式を1つの式にする方法」の2つを考える。


文を式にする方法は、西尾さんの記事が参考になる。


基本的な考え方は、どこかに一時的に値を入れておくということである。入れ物としてPythonではアイテム、属性、変数の3つがある。永続データはここでは無視。利用できるものをまとめると以下の通り。Python 3.0からはexec文がexec関数になるので、それも利用可能。

  • リストや辞書や集合のアイテムを利用
    • apppend()
    • __setitem__()
  • 関数やクラスやモジュールの属性を利用
    • obj.__dict__
    • setattr()
  • 変数の名前空間を利用
    • globals()
    • locals()
    • [k for k in [1] ] (但し、Python 3.0以降は k がリスト内包表記の外側に見えない)
  • exec() (但し、Python 3.0以降)


次に、ブロックを考える。ブロックとはWikipediaによれば、以下の意味である。

ブロック(block)とは、0個以上の文(ステートメント)を含むコードのまとまりの単位

Pythonの場合、この定義がそのまま当てはまる。Pythonでは式を要求するところで文を書けないが、文を要求するところで式を書くことができる。つまり、ブロックを使用すれば複数の式を1つの式に置き換えることが可能である。


ここで、ブロックの返値について考えてみよう。Rubyなどでは最後に評価された式がブロックの返値となる。Pythonでは関数によって同等なものを実現できる。但し、返値となる式はreturn文で表す。


では、関数以外でブロックを表現できないのだろうか。複数の式を列挙できるものとしてリスト(もしくはタプル)がある。また、返値は以下の例のようにすれば可能。但し関数のブロックは定義だけで呼び出しをしないとブロック内が実行されないが、リストの場合リストを書いた時点でリストのアイテムが評価される。

>>> foo = lambda: 'foo'
>>> bar = lambda: 'bar'
>>> [foo(), foo(), bar()][-1]
'bar'

すると、2/19日のネタは以下のように書くこともできる。

[locals()['v'] for line in sys.stdin
    if [locals().update(kv=line.split()),
        locals().update(k=kv[0]),
        locals().update(v=kv[1]),
        locals()['k']][-1] in tags]

一応書けたが結構汚い。最大に汚い部分は代入文を式で表している部分である。これは

で西尾さんも色々研究されているが、ベストソリューションが見つかっていない模様。


あと1つ問題あって、もしPythonRuby風のブロックを書ければ以下のように書けるが、line.split()を2回読んでいるところが問題。この問題をRubyだとどう回避できるのだろう?

[do
   k,v = line.split()
   v
end
for line in sys.stdin
if do
    k,v = line.split()
    k in tags
end]

恐らく、find_all(上記のifブロック)でしぼった後、collectで変換するしかないのではないだろうか?つまりステップが2段階必要になるのではないだろうか?ブロック間を越えて共通の変数にアクセスする仕組みが必要になるので。