リスト内包表記で始める超"実用的"なPythonワンライナー入門

#概要

Pythonはホワイトスペースを有効活用した言語ですが、その結果、ワンライナーをすることが非常に困難でもあります。

しかし、[以前](https://qiita.com/KTakahiro1729/items/c9cb757473de50652374)書いたように、リスト内法表記は素晴らしい表現力を持っています。また、[リスト内法表記でLISPを実装した猛者](https://qiita.com/t-sin/items/662b055447ec87476384#_reference-66eb1dc40cfb03481dbd)もいます。

この記事では、リスト内包表記を主武器に「Pythonでワンライナー」を目指します。
なお、可読性のために改行やインデントをしていますが、全て省くことも可能です。

現在、[自動生成器（変換器？）](https://github.com/KTakahiro1729/JunkPy)を作ってます。

#免責
著者は、本記事の情報を用いることで生じたいかなる不都合に対しても責任を負いません。

#第一目標
以下のコードをリスト内包表記で実装してみましょう。

```py3
a = 7
b = 2
a = 3
c = a + b
print(c)

#=> 5
```

とりあえず答え

```py3
[ns
for ns in [dict()] if not(
ns.update({"a" : 7) or
ns.update({"b" : 2}) or
ns.update({"a" : 3}) or
ns.update({"c" : ns["a"] + ns["b"]}) or
print(ns["c"]) or
None)]
```

リスト内包表記オバケでありながら、「何がしたいコードか」は何となく分かるのではないでしょうか？
具体的に中身を見ていきましょう。

##名前空間
この作業の上で、最も重要な概念は「名前空間」です。[^3]
名前空間、というのは一般的なプログラミングにも使われる用語ですが、端的に言うと「変数とそれが保持する値の一覧」です。
したがって、これは辞書として実装することができます。[^1]
つまり、変数の操作は「辞書への操作」によって行えることになります。

[^3]: 名前空間の活用はZen of Pythonに書かれているくらいなので、ベストプラクティスです。

このコードでは名前空間は`ns`という辞書で表現されています。
`ns`は最初の行の`[dict()]`というところで初期化されます。
ようするに、この`ns`を操作することが、変数操作の肝になるわけです。

辞書の操作は関数を実行することが多いので、まず先に関数の実行についてみてみましょう。

##関数の実行
[ここ](https://qiita.com/KTakahiro1729/items/c9cb757473de50652374#%E3%83%96%E3%83%BC%E3%83%AB%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%AE%E7%9F%AD%E7%B5%A1%E8%A9%95%E4%BE%A1%E3%82%92%E4%BD%BF%E3%81%86)に書いたように、`[i for i in list if hoge]`のhoge部分に関数をぶち込むと、関数が実行できます。`or`でつなぐと、短絡評価の仕組みに基づいて、複数実行してくれます。[^2]

[^2]: このorはC言語のセミコロンに非常によく似ています。どういうことかというと、orを抜かすと、Cでセミコロンを落とした時みたいなエラーを返してきます。初心者にありがちです。あと、`hoge object is not callable`みたいに言われた時も、たいていはorが抜けてます。

`if not`と`not`を入れているのは、次の段階への布石です。現段階では、`not`を省いても出力は変わりません。


##変数の操作
変数の操作は「`ns`という辞書への操作」」と同義である、という説明をしました。
関数が実行できるようになったので、辞書のメソッドを使ってみましょう。
上で用いているのは、`ns.update({key:value})`という操作です。これは「辞書への追加」操作なので、「変数の追加」ということになります。

##目標1のまとめ
リスト内包表記によるプログラミングでは、「名前空間」という「変数名と値をつなぐ辞書」を使います。
名前空間は関数により操作され、収録する変数の状態が変化します。

関数の実行には`if not (func1() or func2())`を使います。
これは返値のない関数は`None`を返す性質を利用しています。

#目標2
以下のコードをリスト内包表記で実装してみましょう。

```py3
num = int(input())
if num % 2:
    print("odd")
else:
    print("even")

#<= 3
#=> odd
```

答え

```py3
[ns
for ns in [dict()] if not(
ns.update({"num" : int(input())}) or
(( #if
    print("odd") or
    None)
if ns["num"] % 2
    else (
    print("even") or
    None) or
None) or       
None)]
```

[3項演算子](https://qiita.com/KTakahiro1729/items/c9cb757473de50652374#if-else%E3%81%A8%E4%B8%80%E7%B7%92%E3%81%AB%E4%BD%BF%E3%81%86)を使ってます。
私は3項演算子の利用を「純粋なリスト内包表記」とは認めていません。（何者）
しかし、この記事の目的は「pythonワンライナー」なので許容します。

#目標3

```py3
num = 5
for i in list(range(num)):
    print(i)
    i = i + 1
    print(i)

#=> 0
#=> 1
#=> 1
#=> 2
#=> 2
#=> 3
#=> 3
#=> 4
#=> 4
#=> 5
```

答え

```py3
[ns
for ns in [ns
for ns in [dict()] if not(
ns.update({"num" : 5})
)]
for l in [list(range(ns["num"]))] for _ in l if not (ns.update({"i":_}) or
    print(ns["i"]) or
    ns.update({"i": ns["i"] + 1}) or
None)]
```

##for文
この中で`for i in list(range(num)):`に相当するのは

```py3
for l in [list(range(ns["num"]))] for _ in l if not (ns.update({"i":_}) or
```

です。
1. `l`にイテレーターを格納
2. 一つだけ値を取り出し、`_`に一時的に格納
3. `if not`以降の関数操作で名前空間に登録

という処理をしています。
もっと短くも書けるのですが、このように書くと`l.clear()`を使って、`break`ができるようになります。




より注目してほしいのは、`for ns in [ns`と書いてある二行目です。
このコードでは、for文を実装するために`num`への代入操作をしたリストを包含したリストを作っています。
構造としては、

```py3
[... [nsの操作] for文]
```

という入れ子構造をしているわけです。
`for ns in [ns`は、内側で作成した名前空間を一つ外側の入れ子構造に継承する役割を持っています。

#目標4
```py3
num = 0
while True:
    print("#=>",num)
    if num == 5:
        break
    else:
        num = num + 1
        continue

#=> 0
#=> 1
#=> 2
#=> 3
#=> 4
#=> 5
```

答え

```py3
[ns
for ns in [ns
for ns in [dict()] if not(
ns.update({"num" : 0})
)]
for l in [[None]] for _ in l if not (
    print(ns["num"]) or
    (( #if
        l.clear() or
    None) 
    if ns["num"] == 5
    else ( 
        ns.update({"num":ns["num"] + 1}) or
        l.append(None) or
    None)) or
None)]
```

基本的に、目標3の改変です。無限ループを扱うために[for文を有効活用](https://qiita.com/KTakahiro1729/items/d58b93b8a41f939a0fad)しています。


#目標5
モジュールとか関数とかクラスとかを使った、より本格的なプログラミングもしてみましょう。

```py3
import random

class LiveEntity(object):
    def __init__(self, name,HP, ATK, DEF = 0):
        self.name = name
        print("Hi, I'm {0}.".format(self.name))
        self.HP = HP
        self.ATK = ATK
        self.DEF = DEF
        self.is_alive = True
    def attack(self, target):
        attack_v = random.randint(0,self.ATK)
        print("{0} attacked {1} : {2}".format(self.name, target.name, attack_v))
        target.attacked(attack_v)
    def attacked(self, attack_v):
        defence = random.uniform(0,self.DEF)
        damage = int(attack_v * (1 - defence))
        print("{0} lost HP : {1} - {2}".format(self.name, self.HP, damage))
        self.HP -= damage
        if self.HP <= 0:
            self.HP = 0
            self.die()
    def die(self):
        self.is_alive = False
        print("{0} is gone".format(self.name))

Player = LiveEntity("KTakahiro", 10, 2)
Enemy  = LiveEntity("Exam", 100, 100, 0.5)
print("-----\nFIGHT!\n-----")
while True:
    if Enemy.is_alive and Player.is_alive:
        break
    Player.attack(Enemy)
    Enemy.attack(Player)
```

出力の一例です。

```console
Hi, I'm KTakahiro.
Hi, I'm Exam.
-----
FIGHT!
-----
KTakahiro attacked Exam : 0
Exam lost HP : 100 - 0
Exam attacked KTakahiro : 9
KTakahiro lost HP : 10 - 9
KTakahiro attacked Exam : 1
Exam lost HP : 100 - 0
Exam attacked KTakahiro : 47
KTakahiro lost HP : 1 - 47
KTakahiro is gone
```

モジュールの利用には`__import__`、関数定義には`lambda`、クラス定義には`type`を使います。詳細は[リスト内包表記の活用と悪用](https://qiita.com/KTakahiro1729/items/c9cb757473de50652374)を読んでください。

最後の例になるので、上から順に少しずつやっていきましょう。

まず、ライブラリのインポート

```py3
[ns
for ns in [dict()] if not(
ns.update({"random" : __import__("random")}) or
None)]
```

次にクラスの定義

```py3
[ns
for ns in [dict()] if not(
ns.update({"random" : __import__("random")}) or
ns.update({"LiveEntity": type("LiveEntity",(
),{
    "__init__": lambda self, name, HP, ATK, DEF = 0:(setattr(self, "local0", dict([])) or
        setattr(self, "name", name) or
        print("Hi, I'm {0}.".format(self.name)) or
        setattr(self, "HP", HP) or
        setattr(self, "ATK", ATK) or
        setattr(self, "DEF", DEF) or
        setattr(self, "is_alive", True) or
    delattr(self, "local0") or None),
    "attack": lambda self, target:(setattr(self, "local1", dict([])) or
        self.local1.update({"attack_v": int(ns["random"].uniform(0, self.ATK))}) or
        print("{0} attacked {1} : {2}".format(self.name, target.name, self.local1["attack_v"])) or
       target.attacked(self.local1["attack_v"]) or
    delattr(self, "local1") or None),
    "attacked" : lambda self, attack_v:(setattr(self, "local2", dict([])) or
        self.local2.update({"defence":ns["random"].uniform(0,self.DEF)}) or
        self.local2.update({"damage" : int(attack_v * (1-self.local2["defence"]))}) or
        print("{0} lost HP : {1} - {2}".format(self.name, self.HP, self.local2["damage"])) or
        setattr(self,"HP", self.HP - self.local2["damage"]) or
        (( #if
            setattr(self, "HP", 0) or
            self.die() or
        None) 
        if self.HP <= 0 
        else (None)) or
    delattr(self, "local2") or None),
    "die" : lambda self:(setattr(self, "local3", dict([])) or
        setattr(self, "is_alive", False) or
        print("{0} is gone".format(self.name)) or
    delattr(self, "local3") or None), 
    })}) or
None)]
```

メソッド内のための名前空間は`self.local`という辞書を作ることで実装しました。寿命は短いです。

次に、PlayerとEnemyのインスタンスを作ります。

```py3
[ns
for ns in [dict()] if not(
ns.update({"random" : __import__("random")}) or
ns.update({"LiveEntity": type("LiveEntity",(
),{
... #中略
    })}) or
ns.update({"Player" : ns["LiveEntity"]("KTakahiro", 10, 2)}) or
ns.update({"Enemy"  : ns["LiveEntity"]("Exam", 100, 100, 0.5)}) or
None)]
```

最後に、Whileの中で戦ってもらいます。

```py3
[ns
for ns in [ns
for ns in [dict()] if not(
ns.update({"random" : __import__("random")}) or
ns.update({"LiveEntity": type("LiveEntity",(
),{
    "__init__": lambda self, name, HP, ATK, DEF = 0:(setattr(self, "local0", dict([])) or
        setattr(self, "name", name) or
        print("Hi, I'm {0}.".format(self.name)) or
        setattr(self, "HP", HP) or
        setattr(self, "ATK", ATK) or
        setattr(self, "DEF", DEF) or
        setattr(self, "is_alive", True) or
    delattr(self, "local0") or None),
    "attack": lambda self, target:(setattr(self, "local1", dict([])) or
        self.local1.update({"attack_v": int(ns["random"].uniform(0, self.ATK))}) or
        print("{0} attacked {1} : {2}".format(self.name, target.name, self.local1["attack_v"])) or
       target.attacked(self.local1["attack_v"]) or
    delattr(self, "local1") or None),
    "attacked" : lambda self, attack_v:(setattr(self, "local2", dict([])) or
        self.local2.update({"defence":ns["random"].uniform(0,self.DEF)}) or
        self.local2.update({"damage" : int(attack_v * (1-self.local2["defence"]))}) or
        print("{0} lost HP : {1} - {2}".format(self.name, self.HP, self.local2["damage"])) or
        setattr(self,"HP", self.HP - self.local2["damage"]) or
        (( #if
            setattr(self, "HP", 0) or
            self.die() or
        None) 
        if self.HP <= 0 
        else (None)) or
    delattr(self, "local2") or None),
    "die" : lambda self:(setattr(self, "local3", dict([])) or
        setattr(self, "is_alive", False) or
        print("{0} is gone".format(self.name)) or
    delattr(self, "local3") or None), 
    })}) or
ns.update({"Player" : ns["LiveEntity"]("KTakahiro", 10, 2)}) or
ns.update({"Enemy"  : ns["LiveEntity"]("Exam", 100, 100, 0.5)}) or
print("-----\nFIGHT!\n-----") or
None)]
for l in [[None]] for _ in l if not (
    (( #if
        l.clear() or
    None) 
    if (ns["Player"].is_alive and ns["Enemy"].is_alive)
    else ( 
        ns.update({"num":ns["num"] + 1}) or
        l.append(None) or
    None)) or 
    ns["Player"].attack(ns["Enemy"]) or
    ns["Enemy"].attack(ns["Player"]) or
                
None)]
```

あとはこれを文字列として処理し、改行や余計なスペースをなくしてください。

```py3
[ns for ns in [ns for ns in [dict()] if not(ns.update({"random" : __import__("random")}) or ns.update({"LiveEntity": type("LiveEntity",( ),{ "__init__": lambda self, name, HP, ATK, DEF = 0:(setattr(self, "local0", dict([])) or setattr(self, "name", name) or print("Hi, I\'m {0}.".format(self.name)) or setattr(self, "HP", HP) or setattr(self, "ATK", ATK) or setattr(self, "DEF", DEF) or setattr(self, "is_alive", True) or delattr(self, "local0") or None), "attack": lambda self, target:(setattr(self, "local1", dict([])) or self.local1.update({"attack_v": int(ns["random"].uniform(0, self.ATK))}) or print("{0} attacked {1} : {2}".format(self.name, target.name, self.local1["attack_v"])) or target.attacked(self.local1["attack_v"]) or delattr(self, "local1") or None), "attacked" : lambda self, attack_v:(setattr(self, "local2", dict([])) or self.local2.update({"defence":ns["random"].uniform(0,self.DEF)}) or self.local2.update({"damage" : int(attack_v *(1-self.local2["defence"]))}) or print("{0} lost HP : {1} - {2}".format(self.name, self.HP, self.local2["damage"])) or setattr(self,"HP", self.HP - self.local2["damage"]) or(( setattr(self, "HP", 0) or self.die() or None) if self.HP <= 0 else(None)) or delattr(self, "local2") or None), "die" : lambda self:(setattr(self, "local3", dict([])) or setattr(self, "is_alive", False) or print("{0} is gone".format(self.name)) or delattr(self, "local3") or None), })}) or ns.update({"Player" : ns["LiveEntity"]("KTakahiro", 10, 2)}) or ns.update({"Enemy" : ns["LiveEntity"]("Exam", 100, 100, 0.5)}) or print("-----\nFIGHT!\n-----") or None)] for l in [[None]] for _ in l if not(((l.clear() or None) if(ns["Player"].is_alive and ns["Enemy"].is_alive) else( ns.update({"num":ns["num"] + 1}) or l.append(None) or None)) or ns["Player"].attack(ns["Enemy"]) or ns["Enemy"].attack(ns["Player"]) or None)]
```

出力

```console
Hi, I'm KTakahiro.
Hi, I'm Exam.
-----
FIGHT!
-----
KTakahiro attacked Exam : 1
Exam lost HP : 100 - 0
Exam attacked KTakahiro : 83
KTakahiro lost HP : 10 - 83
KTakahiro is gone
```

お疲れさまでした。

#感想
なにやってんだおれ






[^1]: 実際、pythonの名前空間を`globals()`で出力すると、辞書型です。