Oneline
Pythonistaの嗜み。
セミコロンは甘え。
環境
- Ubuntu 16.04
- Python 3.5.2
ターゲット
イテレータクラスをワンライナーで定義して呼び出します。
例題としてFizzbuzzをやってみます。
- イテレータクラス
FizzBuzz
-
fb = FizzBuzz(n)
とすると1番目からn
番目までのFizzBuzzを順番に返すイテレータfb
を得る -
fb
をn + 1
回以上__next__
するとStopIteration
を送出
必要になるテクニック
特に今回必要になるテクニックです。
以下を他の処理と接続できるように文ではなく関数またはオブジェクトで表現できるようにしなければなりません。
- 変数の代入
- import
- クラス定義
- 条件分岐
- 複数の文を順番に実行するような処理(代入、if、return)など
- 例外の送出
普通の実装
class FizzBuzz(object):
def __init__(self, M):
self.M = M
self.i = -1
def __iter__(self):
return self
def __next__(self):
self.i += 1
if self.i >= self.M:
raise StopIteration()
return "{}".format(self.i%3//2*'Fizz' + self.i%5//4*'Buzz' or self.i+1)
fb = FizzBuzz(16)
print("\n".join(fb))
このように、__iter__
と__next__
(python2だとnext
)を持つクラスのインスタンスはイテレータとなります。
そしてイテレータの終了条件に来ると StopIteration
を送出すればイテレータの終わりと判断されます。
そしてjoin
は文字列を返すイテレータを渡してやると、終了までの要素を指定された文字列でつなげてくれます。
イテレータの例:list
__next__
関数のreturn
文にあるformat
の引数の中身が、self.i
番目のFizzBuzzの処理となります。どこかのコードゴルフのものをパクり参考にしました。
join
に渡すために、"{}".format()
で文字列化しました。str()
でもいいのかもしれない。
カウンタself.i
の初期値が-1
なのが少々不格好ですが、ご容赦ください。
あと、15番目までやればFizzBuzzはとりあえず一周しますので、ターミナルの見やすさのためにfb = FizzBuzz(16)
と16番目で終了させました。
クラス定義のみ関数化
FB = __import__("types").new_class(
"FB",
exec_body=lambda FB: FB.update({
"__init__": lambda self, M: self.__dict__.update({"M": M, "i": -1}),
"__iter__": lambda self: self,
"__next__": (lambda self:
self.__dict__.update({"i": self.i + 1}) or (
iter([]).__next__() if self.i >= self.M else "{}".format(self.i%3//2*'Fizz' + self.i%5//4*'Buzz' or self.i+1)
)
)
})
)
fb = FB(16)
print("\n".join(fb))
クラス定義
types
モジュール中のnew_class
関数を使います。import
文も関数にして1行化しました。
python2ではnew
モジュールが使えます。
代入
"__init__"
に渡されているlambdaの中身が、python onlinerにおける典型的な代入文になります。
複数文の実行
__next__
関数では2つの文(インクリメント、return)を順番に実行しなければなりませんが、これをlambdaの中で書こうとすると一気に難しくなります。
None
がFalse
として扱われることを使うと、None
を返す関数a
、b
、c
がある場合、
a() or b() or c()
とするとそれぞれが順番に実行されます。
update
関数がNoneを返すのでこのテクニックが使え、"__next__"
の次の行のような書き方となり、これがself.i += 1
の操作に該当します。
例外
続いての困難はraise
文です。今回は長さ0
のイテレータの__next__
を呼びだせば即StopIteration
となることを使いました。
条件
if
文は普通に3項演算子的なあれです。
これに収めるために、Fizzbuzzの処理本体をコードゴルフのものにしました。
すべてを一行に
クラス定義のみ関数化したものを単純に改行を消して、print
関数の中に押し込めただけです。
print("\n".join(__import__("types").new_class("FB", exec_body=lambda FB: FB.update({"__init__": lambda self, M: self.__dict__.update({"M": M, "i": -1}),"__iter__": lambda self: self, "__next__": (lambda self: self.__dict__.update({"i": self.i + 1}) or ( iter([]).__next__() if self.i >= self.M else "{}".format(self.i%3//2*'Fizz' + self.i%5//4*'Buzz' or self.i+1)))}))(16)))
終わりに
「誰得」、この一言に尽きます。