LoginSignup
33
36

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-03-27

概要

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

しかし、以前書いたように、リスト内法表記は素晴らしい表現力を持っています。また、リスト内法表記でLISPを実装した猛者もいます。

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

現在、自動生成器(変換器?)を作ってます。

免責

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

第一目標

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

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

#=> 5

とりあえず答え

[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)]

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

名前空間

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

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

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

関数の実行

ここに書いたように、[i for i in list if hoge]のhoge部分に関数をぶち込むと、関数が実行できます。orでつなぐと、短絡評価の仕組みに基づいて、複数実行してくれます。3

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

変数の操作

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

目標1のまとめ

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

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

目標2

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

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

#<= 3
#=> odd

答え

[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項演算子を使ってます。
私は3項演算子の利用を「純粋なリスト内包表記」とは認めていません。(何者)
しかし、この記事の目的は「pythonワンライナー」なので許容します。

目標3

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

答え

[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)):に相当するのは

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への代入操作をしたリストを包含したリストを作っています。
構造としては、

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

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

目標4

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

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

答え

[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文を有効活用しています。

目標5

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

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)

出力の一例です。

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を使います。詳細はリスト内包表記の活用と悪用を読んでください。

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

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

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

次にクラスの定義

[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のインスタンスを作ります。

[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の中で戦ってもらいます。

[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)]

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

[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)]

出力

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. 名前空間の活用はZen of Pythonに書かれているくらいなので、ベストプラクティスです。 

  2. 実際、pythonの名前空間をglobals()で出力すると、辞書型です。 

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

33
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
36