闇Pythonista入門(Pythonワンライナーのテクニック集)

  • 261
    Like
  • 0
    Comment
More than 1 year has passed since last update.

はじめに

世界には1行でプログラムを書くワンライナーという技巧的プログラミングの世界があります。
ワンライナーと言われる言語の多くはPerlやRubyなのですが、比較的委員長キャラのPythonでもワンライナーができます。

PEP8とZen of Pythonで綺麗になっている白Pythonの世界に
Pythonでも1行で書いたよ!楽しい!!
✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
などと技巧的プログラミングをする闇Pythonista(私)がテクニックなどもろもろをまとめたものがこの記事になってます。

まだPython力を鍛えている途中のわたしなのでなにか指摘などありましたらコメントをいただければです。

対象読者

  • Pythonをある程度かける人
  • Pythonの細かい挙動に興味のある人
  • 白Pythonに飽きてしまった人

テクニック1:代入文を式にする

Pythonでの代入は改行が基本必要です。改行はワンライナーの敵です。なんとか代入したい。
多言語でもよくあるセミコロンで改行代わりがPythonでもできるのですが。。。

それではラムダ式などの中には書けないのでグローバル変数辞書を書き換えてしまうのをよくやります。そうすると代入含めすべての処理をorとandでつなぐことができ、lambda内に代入をかけるようになります。

Pythonのグローバル変数辞書は 組み込み関数globals()dict型で取得できます。このdictをいじることによって代入したようにみせることができます。

globals()['spam'] = 'goood'
# spam = 'goood'と同義

print(spam)
# 'goood'と表示される

dict型の隠蔽されたメソッドであるdict.__setitem__(name,value)を使って関数化できます。
またdict型なのでdict.update(other)も使えます

globals().__setitem__('spam','goood')
# spam = 'goood'
globals().update({'ohuton':'suyasuya'})
# ohuton = 'suyasuya'

print(spam)
# 'goood'と表示
print(ohuton)
# 'suyasuya'と表示

テクニック2:lambdaを使う

無名関数であるlambdaはいろいろと楽しいことが出来ます。
普通に関数宣言っぽくつかったり、無名関数っぽく使ったりいろいろできます。デフォルト引数可変長引数も使えます。やった!

pow = lambda x,p=1:x**p # x^pを返す関数、pが指定されていなければp=1を用いる

spam = (lambda x,p:x**p)(3,2) # 3^2 = 9が代入される

破壊的lambdaを使って処理をしたりも出来ます。
先ほどの代入関数をlambdaで宣言してみましょう。
私はいつもc(name,value)で代入関数を作ってます。

globals().update({'c':lambda x,y:globals().__setitem__(x,y)})

c('spam','goood')
# spam = 'goood'

私がlambdaをよく使う方法でdictvalueにlambdaを登録しておいてkeyで分岐させたりしてます。

d={1:lambda x:x**2,2:lambda x:x*2}
d[1](10) # = 100
d[2](10) # = 20

テクニック3:三項演算子を使う

Pythonも三項演算子が存在して以下のように書きます。

flag = True

res = 'Trueeeeee :-)' if flag else 'Falseeeeee :-<'

ちょっとながくなると読みにくいので使うときは注意しましょう。

abcd = lambda x:('A'if x=='a'else 'B')if x in'ab'else('C'if x=='C'else'D')

テクニック4:暗黙の型変換・短絡評価を理解する

Pythonも多少暗黙の型変換があります。基本的にboolへの変換を短絡評価でよく使います。

有名な短絡評価ifも楽しいです。
記号の前後はスペースを消しても動くので、短絡評価ifで書いたほうが三項演算子より短い場合があります。ゴルフるときはぜひ使いましょう。

i = 1
print(i%2 and'odd!!!!'or'>EVEN<')

テクニック5:リスト内包表現とgeneratorをマスターする

Pythonには素晴らしいリスト内包表現という文法があります。
たとえば、1~9の奇数だけのリストを作りたいと思った時に

l = [x for x in range(10) if x%2]

とすれば作ることができます。
ちなみに非常に読みにくいですが入れ子もできます。

l = [y for x in range(10) for y in range(x)]

これではすべての要素を評価するのでイテレータを使って必要なものときだけ呼び出すようにします。
イテレータの実現方法のひとつとしてジェネレータ(generator)を使います。

イテレータは簡単にいえば途中で処理を止めてまた必要なときに処理の続きをすることができる書き方で、無限ループの途中に挟んだりすることができます。

def count():
    c = 0
    while True:
        yield c
        c += 1

gen = count()

print(next(gen)) # 0と表示
print(next(gen)) # 1と表示
print(next(gen)) # 2と表示
print(next(gen)) # 3と表示

ちなみに、Python3.xでは組み込み関数のnext(gen)ですが、
Python2.xではgen.next()でイテレータを次に進めることが出来ます。

これを使って先ほどの奇数を無限に返すジェネレータを作ってみるとこんな感じになります。

def count():
    c = 0
    while True:
        yield c
        c += 1

odd_gen = (x for x in count() if x%2)

print(next(odd_gen)) # 1と表示
print(next(odd_gen)) # 3と表示
print(next(odd_gen)) # 5と表示

今回宣言したcount()とほぼ同じものが標準ライブラリのitertoolsに含まれているを使いましょう。

import itertools

odd_gen = (x for x in itertools.count() if x%2)

テクニック6:ループを実装する

ループの実装方法としてはいくつか方法があります。

  1. 再帰
  2. itertools.dropwhile()など
  3. 関数引数のイテレータ展開

再帰

例えばgcdを実装するときにたぶん普通に書くとこんな感じになると思います。

gcd = lambda x,y: gcd(y,x%y) if x%y else y

Pythonには再帰上限が存在して、デフォルトで1000回までです。

import sys
sys.getrecursionlimit() # 標準で1000
sys.setrecursionlimit(100000000) # で弄る

あまり深くならない場合は大丈夫ですが、深くなる場合は使えません。

itertools.dropwhile()

先ほどのitertoolsですが非常に便利な関数がたくさんあります。リファレンス

そのなかの一つがdropwhileで関数がFalseになる要素がくるまで要素をジェネレータを進めます。

i = itertools.dropwhile(
    lambda x: x!=5,
    [1,6,5,4,5,1]
)
# [5,4,5,1]

なので、無限ジェネレータと常に真を返す関数をあげれば無限ループが作れます。

gen = itertools.dropwhile(
    lambda x:True, # 常にTrue
    (print(x) for x in itertools.count())
)

next(gen)
# 1から順に無限に数字がprintされる

こんな感じに処理をするジェネレータをlambdaで書いてitertoolsで無限ループにするという方法があります。

関数引数のイテレータ展開

つい最近発見した方法で、関数引数にリストなどを展開するspam(*l)を使います。

spam = lambda :0
# 呼び出される関数(なんでもいい

gen = (print(x) for x in itertools.count())
#主処理generator

spam(*gen)

Pythonではlistもイテレータを返します。list().__iter__()でイテレータを取得でき、関数引数の*で展開は基本的にイテレータであるiter().__next__()を呼び出しています。
generatorもイテレータなのでこんなふうに無限ジェネレータを渡すと無限に関数引数を展開して関数が呼び出されません。

以下のように可変長引数lambdaを用いれば次の処理につなげることが出来ます。

gen = (print(x) for x in range(3))
after_progress = lambda *x: print("finished!!!")
after_progress(*gen)

# 0
# 1
# 2
# finished!!!

str.join()を使って亜種も作れます

''.join(print(x) for x in itertools.count())

但しこの方法は今までの実行結果をメモリー上に保持するためメモリーを食います。

テクニック7:組み込み関数・標準ライブラリを理解する

手間もかからずうまくワンライナーを書くには組み込み関数を使って コードの長さを減らすとゴルフもできます。

組み込み関数

私がよく使う組み込み関数は

  • all(iterable)
  • any(iterable)
  • sorted(iterable[,key][,reversed])
  • max(iterable,*[,key])
  • min(iterable,*[,key])
  • zip(*iterable)
  • map(function,iterable,...)
  • filter(function, iterable)
  • __import__(name)

などです。
keyにlambdaで書いた処理をわたして欲しい物を選ぶなど出来ます。

知ってる方も多いと思いますが、出てきた文字列を頻度順にソートもsorted()だけでできます。標準関数を使えるかは発想次第だと思います。

sorted(set(string), key=lambda x:-string.count(x))

例えば、最高頻出の文字をリスト(イテレータオブジェクト)で取得したいとき

get_most_char = lambda s:filter(lambda x:s.count(x)==max(map(s.count,s)),set(s))

とforなしで書けたりできます。(もう少し短く出来そうではあります。。。
これでは毎回max(map(s.count,s))を評価してると思うのでちょっとコストは大きそうなので、少し長くなりますがlambdaでくるんで引数として先に計算しておけば毎回評価されず一度だけの評価でできます。

get_most_char = lambda s:(lambda p:filter(lambda x:s.count(x)==p,set(s)))(max(map(s.count,s)))

リファレンスを読むと
next(iterable[, default])StopIterationの時はdefaultを返す、や
sum(iterable[,start])でsumの最初を選べるなど
しらないオプションが意外とあって楽しいです。

標準ライブラリ

Pythonの豊富な標準ライブラリを使っていくとよりスマートです。

  • collections
  • types
  • math
  • itertools
  • functools
  • operator

あたりは結構好きなのでよく使います。
興味のある方はリファレンスを読むと楽しめると思います。

これらを使って…

逆ポーランド記法演算fizzbuzzを書いてみた昔の記事があるので、これらを使ってどうやってるのかなと思っていただければです。

解説は無いですがEsolang AmeWallpaperChangerも書いてたりします。

さらに闇Pythonを知りたいという方へ

CheckIOというPythonのSNSをご存知でしょうか。
ある問題をとくPythonコードを書いたり他人のコードを読んで議論したりするSNSで、Puzzleのカテゴリを読むと海外の闇Pythonを読むことができます。

わたしもそれで勉強(?)してるのでおすすめです。

普通にPythonicな書き方を議論してるのをみてPythonを勉強するというのもできるので、闇でない方にもおすすめです。

さいごに注意書き

私はPythonはZen of Pythonに従っているのでPythonであると思うので、業務でのコードや他人(もしくは未来の自分)が読むと想定されるコードでは書かないようにしたほうがいいと思います。。。
変な書き方を実用してるとあとで自分のSAN値を捨てることになります(なりました)

参考URLと文献

闇Pythonistaとしての私の原点

闇Pythonistaになるための暗黒Tips -- Pythonによるワンライナーのためのメモ
http://bugrammer.g.hatena.ne.jp/nisemono_san/20111208/1323310507

そのほか参考にさせていただいたページ

「逆に凄いわ」って感心するPythonのlambda活用法
http://coreblog.org/ats/how-to-never-use-lambdas/

エキスパートPythonプログラミング(1) イテレータ、ジェネレータ、ジェネレータ式
http://d.hatena.ne.jp/nihohi/20120831/1346376992

Python 標準ライブラリ
http://docs.python.jp/3.3/library/index.html
http://docs.python.jp/2.7/library/index.html

Python 言語リファレンス
http://docs.python.jp/3.3/reference/index.html
http://docs.python.jp/2.7/reference/index.html

書籍:エキスパートPython ASCII

ちなみに

この記事は私のブログと同時投稿されています