Pythonの不思議（そしてもう一つのFizzBuzz）


# Pythonの不思議（そしてもう一つのFizzBuzz）

本記事では、以下の不思議を取り上げます。

- インスタンスメソッドがクラスメソッド
- クラスメソッドが関数
- rangeがrange
- 定数がない
- 関数合成する関数がない

道中、[ファンファンのファン問題](https://qiita.com/yamamoto_hiroya/items/2633491dd4c7f6550c1d)を解きます。

最後に、FizzBuzzを関数型言語っぽく、解きます。

## インスタンスメソッドがクラスメソッド

あ…ありのまま、今起こった事を話すぜ！

「おれは、そいつをインスタンスメソッドだと思ったら、いつのまにかクラスメソッドだった」


```python
class Hoge:
    def __init__(self, j):
        self.j = j
    
    def fuga(self, i):
        return i + self.j

# インスタンスメソッドだと思ったら
h = Hoge(1)
print(h.fuga(2))

# クラスメソッドだった
print(Hoge.fuga(h, 2))
```

    3
    3


`h.fuga(2)`は、`Hoge.fuga(h, 2)`の構文糖衣なんですね…。

## クラスメソッドが関数

あ…ありのまま、今起こった事を話すぜ！

「おれは、そいつをクラスメソッドだと思ったら、いつのまにかファンクションだった」


```python
class Hoge:
    def __init__(self, j):
        self.j = j
    
    def fuga(self, i):
        return i + self.j

# クラスメソッドだと思ったら
h = Hoge(3)
print(Hoge.fuga(h, 2))

# ファンクションだった
print(type(Hoge.fuga))

# 後付けメソッド
def piyo(myself, i):
    return i * myself.j
Hoge.piyo = piyo
print(h.piyo(2))
```

    5
    <class 'function'>
    6


インスタンスメソッド → クラスメソッド → ファンクション（関数）とつながりました。

[参考サイト1](http://www.shido.info/py/python7.html)によれば、こういう事情があるようです。

>Pyhton は基本的には（広い意味での）関数型言語で、オブジェクト指向はハッシュ表を使って後付したものです。 この点、もともとオブジェクト指向言語として設計された C++, Java, Ruby などとは違います。
>
>Python は手続きの定義を、関数定義とメソッド定義に分ける代わりに、 全てを関数定義にし、メソッド定義として使う場合は第一引数をインスタンスに割り当てるという 約束事を導入しました。
>
>このようにしたのは、関数型言語は、オブジェクト指向言語より抽象性が高いという信念でしょう。 Python は関数型言語 Haskell から多大な影響を受けているいわれています。 実際、[code 2] で示したように関数型言語を使えばオブジェクト指向は簡単に実装できます。

**Pythonは関数型言語だった**のか。一時期Haskellにハマっていた私にとっては、胸熱なことです。

以下、ここまでの参考サイトです。

- [Python のクラスシステム](http://www.shido.info/py/python7.html)
- [和訳 なぜPythonのメソッド引数に明示的にselfと書くのか](https://coreblog.org/ats/translation-of-why-explicit-self-has-to-stay/)
- [Python言語リファレンス/3.データモデル](https://docs.python.org/ja/3/reference/datamodel.html#index-35)

なお、デコレータ`@classmethod`についても、ふれておかねばなるまい。


```python
class Hoge:
    j = 2
    
    def fuga(cls, i):
        return i + cls.j
    
    @classmethod
    def piyo(cls, i):
        return i + cls.j

print(Hoge.fuga(Hoge, 3))

# 第一引数を省略可能となる
print(Hoge.piyo(3))
```

    5
    5


## rangeがrange

あ…ありのまま、今起こった事を話すぜ！

「おれは、rangeをリストだと思ったら、いつのまにかrangeだった」


```python
print(range(10))

import numpy as np
print(np.arange(10))
```

    range(0, 10)
    [0 1 2 3 4 5 6 7 8 9]


rangeをprintしても、つれない結果を返してくれます。numpyのarangeのような結果を返してくれれば良いのにね。

実は、これはPython3からの仕様です。Googleアカウントをお持ちの方は、[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb)でランタイムをPython2に設定して試してみれば分かりますが、Python2では、rangeはarangeと同様の結果を返してくれます。

これについては、[Pythonチュートリアル](https://docs.python.jp/3/tutorial/controlflow.html#the-range-function)に記載があります。

>range() が返すオブジェクトは、いろいろな点でリストであるかのように振る舞いますが、本当はリストではありません。これは、イテレートした時に望んだ数列の連続した要素を返すオブジェクトです。しかし実際にリストを作るわけではないので、スペースの節約になります。
>
>このようなオブジェクトは イテラブル (iterable) と呼ばれます。これらは関数やコンストラクタのターゲットとして、あるだけの項目を逐次与えるのに適しています。 for 文がそのような イテレータ であることはすでに見てきました。関数 list() もまた一つの例です。これはイテラブルからリストを生成します:

そ、そうだったのか！

イテラブルは面白いです。「実際にリストを作るわけではない」ため、「文脈が要求する回数だけ評価される潜在的な無限リストのようなもの」も作成できます。なんだか、すごくHaskellっぽいですね。

例えば、以下は、[ファンファンのファン問題](https://qiita.com/yamamoto_hiroya/items/2633491dd4c7f6550c1d)に対する一解答となります。


```python
def fanfun():
    while True:
        yield 'ファンファンファン ファンファンのファン'

import itertools as it
list(it.islice(fanfun(), 10))
```




    ['ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン',
     'ファンファンファン ファンファンのファン']



関数fanfun()は、何度でも「ファンファンファン ファンファンのファン」と歌い続けます。そのような**ジェネレータ**を生成します。

fanfun()の定義内に、制御を表すiやnが出てこないのが、スマートですね。Haskellっぽいなぁ。

## 定数がない

[ポルナレフ](http://dic.nicovideo.jp/a/%E3%81%82...%E3%81%82%E3%82%8A%E3%81%AE%E3%81%BE%E3%81%BE%20%E4%BB%8A%20%E8%B5%B7%E3%81%93%E3%81%A3%E3%81%9F%E4%BA%8B%E3%82%92%E8%A9%B1%E3%81%99%E3%81%9C%21)にも飽きてきたので、普通に書きますが、Pythonにはユーザ定義の定数を宣言する方法がありません。[Pythonチュートリアル](http://iss.ndl.go.jp/books/R100000002-I027164236-00)の索引を65536回見直しましたが（嘘）、定数のての字も出てきません。

[Python Cookbook, 2nd Edition](http://barbra-coco.dyndns.org/yuri/Python/python-cookbook-2nd-edition.pdf)のp238を参考に、定数クラスを自作することもできます。


```python
import sys

class Const():
    class ConstError(TypeError):
        pass
    def __setattr__(self, name, value):
        if name in self.__dict__:
            raise self.ConstError("Can't rebind const(%s)" % name)
        self.__dict__[name] = value
    def __delattr__(self, name):
        if name in self.__dict__:
            raise self.ConstError("Can't unbind const(%s)" % name)
        raise NameError("Doesn't exist const(%s)" % name)

c = Const()
c.x = 1

# 実験1
try:
    c.x = 1
except:
    print("An Error Occured:", sys.exc_info()[0])

# 実験2
try:
    del c.x
except:
    print("An Error Occured:", sys.exc_info()[0])

# 実験3
c.y = [1, 2, 3]
try:
    c.y[0] = 0
except:
    print("An Error Occured:", sys.exc_info()[0])

print(c.y)
```

    An Error Occured: <class '__main__.Const.ConstError'>
    An Error Occured: <class '__main__.Const.ConstError'>
    [0, 2, 3]


実験1、実験2には成功していますね。

しかし、実験3で値が変更できたように、定数化は浅いレベルに留まります。Pythonは**代入（substitution）**はできても、**束縛（binding）**はできないのです。代入と束縛の違いについては、以下記事を参照。

- [関数型言語メモ (WIP)](https://qiita.com/japboy/items/5c8578a44023f249d922)

関数型言語としてはちょいと残念。

## 関数合成する関数がない

functoolsには、関数に引数を部分適用する[関数](https://docs.python.jp/3/library/functools.html#functools.partial)なんてのもあって、オサレなのに、なぜか関数合成する関数がない。なんでなーん。


```python
from operator import add
from functools import partial

increment = partial(add, 1)
print(increment(1))
```

    2


ないなら作るまでのことです。以下サイトを参考にさせて頂きました。

- [Python Tips：関数の合成をしたい](https://www.lifewithpython.com/2015/03/python-compose-functions.html)

そして、FizzBuzzを関数型言語っぽく、解きます。リスト内包表記は使わない。


```python
import itertools as it

# 自然数を定義（無限！）
natural = it.count(1)

# fizzbuzzを定義
def fizzbuzz1(n):
    if n % 15 == 0:
        return 'fizzbuzz'
    elif n % 3 == 0:
        return 'fizz'
    elif n % 5 == 0:
        return 'buzz'
    else:
        return str(n)

# fizzbuzz数列（無限！）
fizzbuzz = map(fizzbuzz1, natural)

# 関数合成
def compose(outer_func, inner_func):
    def composed(*args, **kwds):
        return outer_func(inner_func(*args, **kwds))
    return composed

# ジェネレータの先頭からリスト化
take = compose(list, it.islice)

# fizzbuzz実行！
print(take(fizzbuzz, 100))
```

    ['1', '2', 'fizz', '4', 'buzz', 'fizz', '7', '8', 'fizz', 'buzz', '11', 'fizz', '13', '14', 'fizzbuzz', '16', '17', 'fizz', '19', 'buzz', 'fizz', '22', '23', 'fizz', 'buzz', '26', 'fizz', '28', '29', 'fizzbuzz', '31', '32', 'fizz', '34', 'buzz', 'fizz', '37', '38', 'fizz', 'buzz', '41', 'fizz', '43', '44', 'fizzbuzz', '46', '47', 'fizz', '49', 'buzz', 'fizz', '52', '53', 'fizz', 'buzz', '56', 'fizz', '58', '59', 'fizzbuzz', '61', '62', 'fizz', '64', 'buzz', 'fizz', '67', '68', 'fizz', 'buzz', '71', 'fizz', '73', '74', 'fizzbuzz', '76', '77', 'fizz', '79', 'buzz', 'fizz', '82', '83', 'fizz', 'buzz', '86', 'fizz', '88', '89', 'fizzbuzz', '91', '92', 'fizz', '94', 'buzz', 'fizz', '97', '98', 'fizz', 'buzz']


## まとめ

いろいろ書いたけど、私は今まで触った言語の中で、Pythonが一番好きです。

（2018/9/10追記）
[こちら](https://qiita.com/tanuk1647/items/f1a0e416efdb799a89c9)に続編を書いたので、よろしければ見てやってください。