闇Pythonista入門(Pythonワンライナーのテクニック集)
世界には1行でプログラムを書くワンライナーという技巧的プログラミングの世界があります。 ワンライナーと言われる言語の多くはPerlやRubyなのですが、委員長キャラのPythonでもワンライナーができます。
PEP8とZen of Pythonで綺麗になっているPythonicな世界に
Pythonでも1行で書いたよ!楽しい!!
✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
などと技巧プログラミングをする闇Pythonista(私)がテクニックなどもろもろをまとめたものがこの記事になってます。
まだPython力を鍛えている途中のわたしなのでなにか指摘などありましたらコメントをいただければです。
対象読者
テクニック1:代入文を式にする
Pythonでの代入は基本改行が必要です。改行はワンライナーの敵です。なんとか代入したい。 多言語でもよくあるセミコロンで改行代わりがPythonでもできるのですが。。。
それでは闇度が薄いのでグローバル変数辞書を書き換えてしまうのが私は好きです。そうすると代入含めすべての処理をorとandでつなぐことができるます。。。
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をよく使う方法でdict
のvalue
にlambdaを登録しておいてkey
で分岐させたりしてます。
d={1:lambda x:x**2,2:lambda x:x*2} d[1](10) # = 100 d[2](10) # = 20
テクニック3:三項演算子を使う
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:ループを実装する
ループの実装方法としてはいくつか方法があります。
再帰
例えば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になる要素がくるまで要素をジェネレータを進めます。
なので、無限ジェネレータと常に真を返す関数をあげれば無限ループが作れます。
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もイテレータで、関数引数の*で展開は基本的にイテレータであるobj.__next__()
を呼び出しています。
generatorもイテレータなのでこんなふうに無限ジェネレータを渡すと無限に関数引数を展開して関数が呼び出されません。
以下のように可変長引数lambda
を用いれば次の処理につなげることが出来ます。
gen = (print(x) for x in range(3)) after_progress = lambda *x: print("finished!!!") after_progress(*gen) # 0 # 1 # 2 # finished!!!
テクニック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を書いてみた昔の記事があるので、これらを使ってどうやってるのかなと思っていただければです。
さらに闇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