FizzBuzzで学ぶIterator、Generator、Decorator

はじめに
===
pythonドキュメントを読んで、Iterator, Generator, Decoratorなどを学んだので、練習のためこれらでFizzBuzzを書いてみた。

Iteratorとは(引用)
===
http://docs.python.jp/2/tutorial/classes.html#iterator
(引用開始)
こうしたアクセス方法は明確で、簡潔で、かつ便利なものです。イテレータの使用は Python 全体に普及していて、統一性をもたらしています。背後では、 `for` 文はコンテナオブジェクトの `iter()` を呼び出しています。この関数は `next()` メソッドの定義されたイテレータオブジェクトを返します。 `next()` メソッドは一度コンテナ内の要素に一度に一つづつアクセスします。コンテナ内にアクセスすべき要素がなくなると、 `next()` は `StopIteration` 例外を送出し、 for ループを終了させます。実際にどのように動作するかを以下の例に示します。
(中略)
イテレータプロトコルの背後にあるメカニズムを一度目にすれば、自作のクラスにイテレータとしての振る舞いを追加するのは簡単です。 `__iter__()` メソッドを定義して、 `next() `メソッドを持つオブジェクトを返すようにしてください。クラス自体で `next()` を定義している場合、 `__iter__()` では単に `self` を返すようにできます。
(引用終了)

簡単に作るなら、`__iter__()`メソッドと`next()`メソッドとを持つクラスを定義し、終了条件を満たしたら`StopIteration`の例外を投げるようにすればいいらしい。
Iteratorのインスタンスは`for`文にそのまま使える模様である。

コード
---
range()関数を参考にして、似たような仕様のFizzBuzzIteratorを書いてみた。

```py
def fizzbuzz(n):
  n = int(n)
  if (n%15) == 0: return "FizzBuzz"
  elif (n%5) == 0: return "Buzz"
  elif (n%3) == 0: return "Fizz"
  else: return n

class FizzBuzzIterator:
  def __init__(self,start, stop, step=1):
    self.count = start
    self.stop = stop
    self.step = step

  def __iter__(self):
    return self

  def next(self):
    if (self.step > 0 and self.count >= self.stop) or (self.step < 0 and self.count <= self.stop):
      raise StopIteration
    else:
      result = fizzbuzz(self.count)
      self.count += self.step
      return result
```

1から100までのFizzBuzzを出力

```py
fbiter1 =  FizzBuzzIterator(1,101)
for fzbz in fbiter1: print fzbz
```

1から99まで奇数のみFizzBuzzを出力

```py
fbiter2 =  FizzBuzzIterator(1,101,2)
for fzbz in fbiter2: print fzbz
```

100から1まで逆順でFizzBuzzを出力

```py
fbiter3 =  FizzBuzzIterator(100,0,-1)
for fzbz in fbiter3: print fzbz
```

Generatorとは（引用）
===
http://docs.python.jp/2/tutorial/classes.html#generator
(引用開始)
ジェネレータ(generator)は、イテレータを作成するための簡潔で強力なツールです。ジェネレータは通常の関数のように書かれますが、何らかのデータを返すときには yield 文を使います。 next() が呼び出されるたびに、ジェネレータは以前に中断した処理を再開します (ジェネレータは、全てのデータ値と最後にどの文が実行されたかを記憶しています)。以下の例を見れば、ジェネレータがとても簡単に作成できることがわかります。
(引用終了)


コード
---
Iteratorはなんかとても頑張った感があったのだけど、generatorは単にfor文書けばよくないか？などと疑問を持たせてくれた。

```py
def fizzbuzz(n):
  n = int(n)
  if (n%15) == 0: return "FizzBuzz"
  elif (n%5) == 0: return "Buzz"
  elif (n%3) == 0: return "Fizz"
  else: return n

def fizzbuzz_generator(start,finish,diff=1):
   for n in range(start,finish,diff):
     yield fizzbuzz(n)

for fzbz in fizzbuzz_generator(1,100):
  print fzbz
```

Decoratorとは（引用）
===
http://docs.python.jp/2/glossary.html#term-decorator
（引用開始）
(デコレータ) 関数を返す関数。通常、 `@wrapper` という文法によって関数を変換するのに利用されます。デコレータの一般的な利用レとして、 `classmethod()` と` staticmethod()` があります。

デコレータの文法はシンタックスシュガーです。次の2つの関数定義は意味的に同じものです。

```py
def f(...):
    ...
f = staticmethod(f)

@staticmethod
def f(...):
    ...
```
（引用終了）

コード
---
よくわからなかったので書いてみた。

```py
def fizzbuzz(n):
  n = int(n)
  if (n%15) == 0: return "FizzBuzz"
  elif (n%5) == 0: return "Buzz"
  elif (n%3) == 0: return "Fizz"
  else: return n

def fizzbuzz_decorator(func):
  def wrapper(*args, **kwargs):
    func(fizzbuzz(*args))
  return wrapper

@fizzbuzz_decorator
def f(n):
  print n

f(1)
f(2)
f(3)
f(4)
f(5)
```

上記だと、fは、デコレータ式`@fizzbuzz_decorator`によって、fは、`fizzbuzz_decorator(f)`、つまり、以下のwrapper()関数に書き換わっているらしい。

```py
def wrapper(*args, **kwargs):
  f(fizzbuzz(args[0]))
```

なので、fの引数がfizzbuzz()関数によって処理されてからもともとのfに渡されるので、fizzbuzz文字列が出力されることとなるようである。

実行結果

```
1
2
Fizz
3
Buzz
```

Decorator式のネスト（引用）
---
デコレータ式はネストすることもできるらしい。
http://docs.python.jp/2/reference/compound_stmts.html#function
（引用開始）
関数定義は一つ以上のデコレータ (decorator) 式でラップできます。デコレータ式は関数を定義するとき、関数定義の入っているスコープで評価されます。その結果は、関数オブジェクトを唯一の引数にとる呼び出し可能オブジェクトでなければなりません。関数オブジェクトの代わりに、返された値が関数名に束縛されます。複数のデコレータはネストして適用されます。
（引用終了）

```py
def fizzbuzz(n):
  n = int(n)
  if (n%15) == 0: return "FizzBuzz"
  elif (n%5) == 0: return "Buzz"
  elif (n%3) == 0: return "Fizz"
  else: return n

def fizzbuzz_decorator(func):
  def wrapper(*args, **kwargs):
    func(fizzbuzz(*args))
  return wrapper

def fizzbuzz_decorator2(func):
  def wrapper(*args, **kwargs):
    for n in range(1,args[0]):
      func(n)
  return wrapper

@fizzbuzz_decorator2
@fizzbuzz_decorator
def f(n):
  print n

f(101)
```

これは、以下と等価な模様である。

```
f = fizzbuzz_decorator2(fizzbuzz_decorator(f))
```

実行結果

```
1
2
Fizz
3
Buzz
（中略）
98
Fizz
Buzz
```
デコレータに引数を渡すこともできるらしいので、調査次第今後追記予定。