Pythonワンライナー：階乗の定義を３通りで

# ワンライナー

Pythonistaの嗜み。

# 環境

```
% python --version
Python 3.5.2
% uname
Linux
```

Ubuntu 16.04です。

# 基本

ワンライナーでは、いかに文を式で書くかにかかっています。
セミコロンに頼っているようでは「;」のキートップを引き抜かれる盗難にあった時に困ります。

`if`は３項演算子、`for`はリスト内包表記でがんばります。
それ以外について見ていきます。

## 代入

```python
x = 10
```
は

```python
globals().update({"x": 10})
```
となります。あとはlambda式の仮引数で名前を使い、引数で右辺を与える感じでしょうか。

## 関数定義

```python
def f(x):
   return x*2
```
は

```python
globals().update({"f": lambda x: x*2})
```
`globals()`はグローバル変数の定義を管理するdictを返す関数です。
他にもローカル変数用の`locals()`もあります。
`update`はdictの要素を更新・追加するメソッドです。

## モジュール

```python
import numpy
x = numpy.ones(10)
```
これは、

```python
__import__("numpy").ones(10)
```
とすると`import numpy`してから`numpy.ones`を実行したことに相当しますので、これを`x`に代入してやります。

```python
globals().update({"x": __import__("numpy").ones(10)})
```

`__import__("hoge")`関数は`hoge`モジュールオブジェクトを返す関数です。
このモジュール内の関数を１回だけ使うならそのままメソッド呼び出しでいいですが、
複数回使うようなら`globals()`や`lambda`を使いこなすと良いと思います。

## クラス

https://coreblog.org/ats/how-to-never-use-lambdas/
を参照。ポイントは

```python
lambda(new): new.classobj("Foo", (), dict(メソッド群をlambdaで))
```
とすると
に`__import__("new")`を渡してやる感じです。

(追記)

python 3以降は`new`モジュールが廃止され、`types`モジュールに統合されています。
https://docs.python.jp/2/library/new.html
https://docs.python.jp/3.6/library/types.html#module-types

こちらを使ってみますと、

```python
class Foo:
    def __init__(self, a):
        self.a = a
f = Foo(2)
print(f.a)
```

としたとき、クラス定義は

```python
types.new_class("Foo", exec_body=
   lambda Foo: Foo.update({
       "__init__":
            lambda self, a:
                self.__dict__.update({"a": a})
   })
)
```
となります。`exec_body`にはコールバック関数を与えるのですが、その引数`Foo`は`new_class`で追加されるクラスの名前空間dictですので、これの`update`を使ってやればクラスにメンバやメソッドを登録できます。
ただしメソッド中でメンバを追加する（上の例では`a`）場合は、そのメソッド内でインスタンスの名前空間（`self.__dict__`）を`update`する必要があります。

`types.new_class`はクラスオブジェクト（クラスを定義した何か）を返します。例えば今回の場合は `<class 'types.Foo'>` が返ってきます。
インスタンス生成　`Foo(2)` に相当することをするには `types.new_class(...)(2)`とします。

```python
types.new_class("Foo", exec_body=lambda Foo: Foo.update({"__init__": lambda self, a: self.__dict__.update({"a": a}) }))(2)
```

これでインスタンスオブジェクトを得ることができますので、メンバ変数`a`の出力は`print(types.new_class(...)(2).a)`とすればよいです。

```python
print(types.new_class("Foo", exec_body=lambda Foo: Foo.update({"__init__": lambda self, a: self.__dict__.update({"a": a}) }))(2).a)
```

なお`new_class`はクラスをグローバル変数に追加しません。

```python
types.new_class("Foo", exec_body=lambda Foo: Foo.update({"__init__": lambda self, a: self.__dict__.update({"a": a}) })) # <class 'types.Foo'>
Foo(2) # NameError: name 'Foo' is not defined
types.Foo(2) # AttributeError: module 'types' has no attribute 'Foo'
```

## 例外

イテレータを定義していると`StopIteration`を`raise`する必要になるのですが、
`raise`文は、とりあえず適当な長さ０のイテレータを作って`next`を呼び出して投げたりしたことがあります。

# ワンライナーにするターゲット

今回は素直な階乗の定義と評価を１行で書いてみます。

```python
def factorial(n):
    return factorial(n - 1)*n if n > 1 else n
print(factorial(5))
```
当然`print`までカバーします。

## １．`globals().update()`

まずは素直に関数定義を`globals().update()`で行います。

```python
globals().update({"f": lambda n: n*f(n - 1) if n > 2 else n })
print(f(5))
```
`f`の定義をしてから`f(5)`の評価というフローがあるため、素直に書くとこのように2行になりました。
これを1行にしていきます。`update()`が引数に与えた何かを返してくれたらそれを`print`に渡すだけで済むところですが、`update()`は`None`を返しますので、

```python
globals().update({"f": lambda n: n*f(n - 1) if n > 2 else n })(5) #error
```
は`None(5)`という形になってしまい、無理です。いろいろ解決法はあると思いますが、とりあえず

```python
print((lambda x:f(5))( globals().update({"f": lambda n: n*f(n - 1) if n > 2 else n })))
```
とlambdaの引数中で定義して、lambdaの本体で評価してみました。`x`には`None`が入り、無視されます。

## ２．不動点定理（Y-combinator）

https://stackoverflow.com/a/10144992
に提示されているように、

```python
Y = (lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))
print(Y(lambda f: lambda n: n*f(n-1) if n > 1 else 1)(5))
```
とすれば、無名関数のみを使って再帰関数を定義できます。
一行にまとめるなら、

```python
print((lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))(lambda f: (lambda i: 1 if (i == 0) else i * f(i - 1)))(5))
```

なお、先のstackoverflowのコメントの

```
 Nice job, you just turned Python into Lisp :)
```
はお気に入りです。

## ３．reduction

階乗は[1, 2, ..., n]のリストを積でreductionすることに相当します。
`reduce`は`functools`に、積の関数`mul`は`operator`で定義されています。

```python
from functools import reduce
from operator import mul
print(reduce(mul, range(1,6)))
```
これを１行にして、

```python
print(__import__("functools").reduce(__import__("operator").mul, range(1, 6)))
```

なお、`__import__("operator").mul` は `lambda x, y: x*y` でも大丈夫です。


# その他

ここまで読んでくださりありがとうございます。お疲れ様でした。