イントロダクション
シェル芸勉強会 meets バイオインフォマティクス vol.1 - connpass
## 背景 NGS現場の会MLで突如盛り上がったawkでデータを処理する手法についての議論を受けたツイートが起源である。... |
|
先日、こんなイカレたした勉強会がありました。
で、主催者の一人が返信をくれました。
ワンライナーなら割と何でも良いと思ってます。
— kubor@創薬ちゃんP (@kubor_) 2017年9月8日
お題はこちらから
バイオシェル芸 問題のみ - SSSSLIDE
2017.10.18 @ 株式会社ディー・エヌ・エー(渋谷オフィス) シェル芸勉強会 meets バイオインフォマティクス vol.1 https://bio-shell.connpass.co... |
|
Air_LT
東京まで行けなかったのでAir_LTと称して、勝手にpythonのワンライナーについてtweetしてみました。
bioinfoシェル芸 meets IPython - slideship.com
https://bio-shell.connpass.com/event/66089/ Air_LT |
|
僕はある程度、データ解析で使うライブラリが固定されているので、ipython startupに色々突っ込んでいます。
import sys,os
import pandas as pd
import numpy as np
import seaborn as sns
sns.set(font=["Arial Unicode MS"])
ただ、これがワンライナーかというと、若干疑問が生じます。
(jupyterとかatom-hydrogenとかではかなり便利ですが。)
更に...
セミコロンを使わないのが真のワンライナー #バイオシェル芸
— ことね (ktnyt) (@kotone_nyt) 2017年10月18日
ということで、若干追加のTIPSを。
importのワンライナー
通常使うpythonのimportは文です。
しかし、importするモジュールを動的に変化させるために組み込み関数__import__
が用意されています。
# これらは等価
import pandas
pandas = __import__('pandas')
# これらも等価
import pandas as pd
pd = __import__('pandas')
# こんなのも等価
from pandas import DataFrame as df
df = getattr(__import__("pandas"),'DataFrame')
この関数は
import
文により呼び出されます。 (builtins モジュールをインポートしてbuiltins.__import__
に代入することで) この関数を置き換えて import 文のセマンティクスを変更することができますが、同様のことをするのに通常はインポートフック (PEP 302 参照) を利用する方が簡単で、かつデフォルトのインポート実装が使用されていることを仮定するコードとの間で問題が起きないので、このやり方は強く推奨されません。__import__()
を直接使用することも推奨されず、importlib.import_module()
の方が好まれます。
あいたー
ワンライナー化の例
"拡張子ゲシュタルト崩壊"を例にします。
バイオシェル芸 問題のみ - SSSSLIDE
バイオシェル芸 問題のみ - SSSSLIDE... |
|
GTH.tar.gz を解凍し、GTHフォルダ内のファイル名を
次のルールで変更してください
- 拡張子が.fa、.mfa、.fas、fasta →拡張子.faに統一
- 拡張子が.fq、.fastq →拡張子.fqに統一
- ファイル名が重複した場合は上書き
https://github.com/bio-shell/study1/blob/master/data/GTH.tar.gz
pythonicに書くならこんな感じだと思います。
import os
import re
import tarfile
tarfile.open('GTH.tar.gz').extractall()
fa = re.compile('.m?fa(s|sta)?$')
fq = re.compile('.f(ast)?q$')
files = list()
for file in os.listdir('GTH/'):
files.append('./GTH/' + file)
for file in files:
if re.search(fa, file):
os.replace(file, re.sub(fa, '.fa', file))
else:
os.replace(file, re.sub(fq, '.fq', file))
1) 条件分岐は条件演算子に置きかえます。
for file in files:
if re.search(fa, file):
os.replace(file, re.sub(fa, '.fa', file))
else:
os.replace(file, re.sub(fq, '.fq', file))
os.replace(file, re.sub(fa, '.fa', file)) if re.search(fa, file) else os.replace(file, re.sub(fq, '.fq', file))
ifとforの順序を入れ替えて少しリファクタリングします。
os.replace(file, re.sub(fa, '.fa', file) if re.search(fa, file) else re.sub(fq, '.fq', file))
2) for文は内包表記に置き換えます
for file in files:
os.replace(file, re.sub(fa, '.fa', file) if re.search(fa, file) else re.sub(fq, '.fq', file))
[os.replace(file, re.sub(fa, '.fa', file) if re.search(fa, file) else re.sub(fq, '.fq', file)) for file in files]
3) ./GTH/を付与する処理を入れ込む
内包表記でもlambdaでもいいです。
今回はなんとなくlambdaを使います。
files = list()
for file in os.listdir('GTH/'):
files.append('./GTH/' + file)
- lambda
files = map(lambda file: './GTH/' + file, os.listdir('GTH/'))
- 内包表記
files = ['./GTH/' + file for file in os.listdir('GTH/')]
入れ込むとこんな感じです。
[os.replace(file, re.sub(fa, '.fa', file) if re.search(fa, file) else re.sub(fq, '.fq', file)) for file in map(lambda file: './GTH/' + file, os.listdir('GTH/'))]
4) 正規表現を組み込む
パターンをコンパイルする必要もないのでsearch,matchの引数に渡します。
[os.replace(file, re.sub('.m?fa(s|sta)?$', '.fa', file) if re.search('.m?fa(s|sta)?$', file) else re.sub('.f(ast)?q$', '.fq', file)) for file in map(lambda file: './GTH/' + file, os.listdir('GTH/'))]
5) モジュール読み込みの関数化
__import__
に置き換えます。冗長になるけどワンライナーのためには仕方ありません。
import os
import re
import tarfile
tarfile.open('GTH.tar.gz').extractall()
[os.replace(file, re.sub('.m?fa(s|sta)?$', '.fa', file) if re.search('.m?fa(s|sta)?$', file) else re.sub('.f(ast)?q$', '.fq', file)) for file in map(lambda file: './GTH/' + file, os.listdir('GTH/'))]
__import__('tarfile').open('GTH.tar.gz').extractall()
[__import__('os').replace(file, __import__('re').sub('.m?fa(s|sta)?$', '.fa', file) if __import__('re').search('.m?fa(s|sta)?$', file) else __import__('re').sub('.f(ast)?q$', '.fq', file)) for file in map(lambda file: './GTH/' + file, __import__('os').listdir('GTH/'))]
5) タプルでくくる
2行をタプルでくくってワンライナーの完成です。
(__import__('tarfile').open('GTH.tar.gz').extractall(),[__import__('os').replace(file, __import__('re').sub('.m?fa(s|sta)?$', '.fa', file) if __import__('re').search('.m?fa(s|sta)?$', file) else __import__('re').sub('.f(ast)?q$', '.fq', file)) for file in map(lambda file: './GTH/' + file, __import__('os').listdir('GTH/'))])
6) 別法
まるっとlambdaでくくっておくとか。
(lambda gz,i,tar,os,re,fa,fq:(i(tar).open('{}.tar.gz'.format(gz)).extractall(),[i(os).replace(f,i(re).sub(fa,'.fa',f)if i(re).search(fa,f)else i(re).sub(fq,'.fq',f))for f in map(lambda f:'./{}/{}'.format(gz,f),i(os).listdir('{}/'.format(gz)))]))('GTH',__import__,'tarfile','os','re','.m?fa(s|sta)?$','.f(ast)?q$')
内包表記を排除してlambdaに統一するとか
(lambda gz,i,tar,os,re,fa,fq:(i(tar).open('{}.tar.gz'.format(gz)).extractall(),map(lambda f:i(os).replace(f,i(re).sub(fa,'.fa',f)if i(re).search(fa,f)else i(re).sub(fq,'.fq',f)),map(lambda f:'./{}/{}'.format(gz,f),i(os).listdir('{}/'.format(gz))))))('GTH',__import__,'tarfile','os','re','.m?fa(s|sta)?$','.f(ast)?q$')
こんな感じで受け渡しも可能です。
echo GTH| python -c"(lambda gz,i,tar,os,re,fa,fq:(i(tar).open('{}.tar.gz'.format(gz)).extractall(),[i(os).replace(f,i(re).sub(fa,'.fa',f)if i(re).search(fa,f)else i(re).sub(fq,'.fq',f))for f in map(lambda f:'./{}/{}'.format(gz,f),i(os).listdir('{}/'.format(gz)))]))(input(),__import__,'tarfile','os','re','.m?fa(s|sta)?$','.f(ast)?q$')"
最後に
pythonのワンライナーを紹介
import this
The Zen of Python
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
改行してインデントつけようよ!!