LoginSignup
12

More than 5 years have passed since last update.

シェル芸勉強会 meets Python

Last updated at Posted at 2017-10-30

イントロダクション

シェル芸勉強会 meets バイオインフォマティクス vol.1 - connpass
## 背景 NGS現場の会MLで突如盛り上がったawkでデータを処理する手法についての議論を受けたツイートが起源である。...

先日、こんなイカレたした勉強会がありました。
で、主催者の一人が返信をくれました。

お題はこちらから

バイオシェル芸 問題のみ - 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に色々突っ込んでいます。

00_startup.py
import sys,os
import pandas as pd
import numpy as np
import seaborn as sns
sns.set(font=["Arial Unicode MS"])

ただ、これがワンライナーかというと、若干疑問が生じます。
(jupyterとかatom-hydrogenとかではかなり便利ですが。)

更に...

ということで、若干追加の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')

公式Document

この関数は 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!

改行してインデントつけようよ!!

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
12