vim / fzf + rg で .gitignore以外のファイルも検索したい場合

fzf使ってます? fuzzy searchをvim でインテグレーションするにはfzf + fzf.vimがおすすめ

ただfzfの問題点は、.gitignoreファイルを標準では無視することです。

FZFとは

FZFは、汎用のコマンドラインファジーファインダーです。あなたのシステムにインストールするのはとても簡単です。Homebrewを使って、brew install fzfを実行するだけで、Macにインストールできます。

fzf optionの基本

見た目

export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'

ここで問題になるのが、プロジェクト内にたくさんのファイルがあり、バージョンコントロールシステムで追跡されていないファイルを無視したい場合です。もしgitでコードを管理しているならたとえば .gitignoreに記述されるようなファイルのことです。

rgも使おう

この問題を解決するには、デフォルトのfindではなく別のコマンドを使ってシステムを横断しなければなりません。findコマンドは.gitignoreファイルを無視しません。これは単なるダミーの finder ユーティリティプログラムです。find の代わりに使うプログラムにはいくつかの選択肢があります。 私はripgrepを選びました。ここでの比較で説明したように、ripgrepは最速の検索ツールです。 ripgrepのインストールはとても簡単です。termnalでrunbrew install ripgrepを実行すると、Macにインストールできます。異なるプラットフォームにインストールする場合は、ここで説明する手順に従ってください。 インストール後、bashrcファイルでFZF_DEFAULT_COMMANDを以下のように設定する必要があります。

export FZF_DEFAULT_COMMAND='rg --files'

ここで--filesのオプションは

ripgrepが検索するであろうファイルをprintしますが、実際には検索しない

という意味です。

.gitignoreを利用しないで.rgignoreを使う

大きなプロジェクトになると、別の問題が出てきます。 gitignore に登録されている別のファイルがあっても、そのファイルを検索したい場合だ。

たとえば、Railsにはapplication.ymlというファイルがあります。これは通常はgitリポジトリにプッシュしませんが、それでもfzfで検索したいことはあるでしょう。

Ripgrep のドキュメントを見ると、検索するファイルの .gitignore を無視するオプション --no-ignore があります。 しかし、これでは先ほどまでやっていたことが無駄になってしまいます。

この問題を解決するために、プロジェクト内に .rgignore という特殊なファイルを作ることができることがわかりました。Ripgrepの公式ドキュメントには次のように書かれています。

.ignore (アプリケーションに依存しない) または .rgignore (ripgrep 特有の) ファイルで、追加の無視ルールやオーバーライドを指定することができます。 プロジェクトのルートフォルダに.rgignoreファイルを作成し、検索したいファイルを管理することができます。

たとえば.gitignoreでは無視してるが検索したいファイル名が wanttosearch.dat だった場合

!wamtosearch.dat

と書いておくことで検索対象に含めることができるわけです。

Pythonのlogging二種類: RotatingFileHandlerとTimedRotatingFileHandler

Pythonのloggingモジュールにはたくさんのオプションがある。

この記事では、logging モジュールのログローテションの機能について解説していきます。Pythonには2種類のログローテションをサポートしています。

ログローテーションとは

ログローテーションとは、システムが残すログが際限なく増えることを防ぐために、一定の容量や期間ごとに古いログを削除したり新しいログで上書きする操作のこと。 

  • サイズに基づいてログローテションさせる(RotatingFileHandler)
  • ある時間間隔に基づいてログを回転させる(TimedRotatingFileHandler)

これらの2種類のloggerがどのように実装され、使用されているか、いかに深掘ります。

RotatingFileHandler

LogingモジュールのRotatingFileHandlerクラスは、開発者がログのサイズに基づいてログローテションさせる機能を提供するlogging handler objectです。maxBytesパラメータを使って、ログを回転させるタイミングを指定することができます。つまり、ログが特定のバイト数に達すると、「rollover」されます。

これは、ファイルサイズを超えそうになったときに発生します。ハンドラーはファイルを閉じ、新しいファイルを静かに開きます。backupCountパラメータに数字を渡すと、ログファイルの最後に「.1」、「.2」などを追加してくれる。

handler = RotatingFileHandler(path, maxBytes=20,
                                  backupCount=5)

このコードを実行すると、オリジナルのtest.logと5つのバックアップ・ログの計6つのファイルができあがるはずです。

docs.python.org

TimedRotatingFileHandler

TimedRotatingFileHandlerは、開発者が時間の経過に基づいてログローテションを作成することができます。以下の時間条件でログローテションさせるように設定できます。

秒 (s) 分 (m) 時間 (h) 日 (d) w0~w6(平日、0=月曜 midnight

これらの条件を設定するには、2番目の引数であるwhenパラメータに渡すだけです。

handler = TimedRotatingFileHandler(
                                     path,
                                     when="m",
                                     interval=1,
                                     backupCount=5
                                     )

この例では、1分ごとにログを回転させ、バックアップカウントを5に設定しています。より現実的なローテーションは1時間ごとでしょうから、間隔を60にしたり、whenを「h」にしたりします。このコードが実行されると、6つのファイルが作成されますが、ログファイル名に整数を付加するのではなく、strftimeフォーマットの%Y-%m-%d_%H-%M-%Sを使ってタイムスタンプを付加します。

DashとZealとは?

DashとZealは、何百もの一般的なプログラミング言語フレームワーク、ライブラリのオフラインドキュメントを提供する。 どちらもスタンドアロンのアプリケーションで、各種OSのネイティブアプリとして動作します。

DashはMacOS用(有料)で、Zealはオープンソースで、すべての主要なプラットフォームで動作します。

なぜpath.joinでなくpathlibを使うべきか

pathlibをos.pathモジュールを不必要にオブジェクト指向にしたものとおもってませんか?

そんなあなたにpathlibがいかに便利か布教したい!

この記事を読んで、Pythonでファイルを扱うときはいつでもPythonのpathlibモジュールを使うようになってもらえればと思います。

os.path

os.pathモジュールは、Pythonでパスを扱うために昔から利用されてきました。なので必要なものはほとんど揃っています。

ただ書き方が複雑なんです。

たとえばos.path.joinを使った場合、

import os.path

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates')
from os.path import abspath, dirname, join

BASE_DIR = dirname(dirname(abspath(__file__)))
TEMPLATES_DIR = join(BASE_DIR, 'templates')

なんて書き方をすることになります。

見にくくありませんか?

あとos.pathでのやり方はpathがあくまでstringであり、 たまたまpathのフォーマットを持っているに過ぎません。

Path専用のオブジェクトではないのです。

os.pathの文字列を入力したり出力したりする関数は、コードを内側から読まなければならないため、入れ子になっていると実に面倒です。 このような入れ子になった関数呼び出しを、連鎖したメソッド呼び出しなんて嫌じゃないですか?

その問題を解決する鍵がpathlibモジュールです。pathlibを使えば以下のように書けます

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATES_DIR = BASE_DIR.joinpath('templates')

os.pathモジュールでは、関数のネストが必要でしたが、pathlibモジュールのPathクラスでは、Pathオブジェクトのメソッドや属性を連鎖させて、同等のパス表現を得ることができる。

osモジュールは機能が多くてややこしい

Python の os.path モジュールは、パスを扱う他にも様々な機能がある。パスを使って実際に何かをしたい(例えば、ディレクトリを作成したい)と思ったら、他のPythonモジュール、多くの場合osモジュールを使う必要があるんです。

osモジュールには、ファイルやディレクトリを操作するための多くのユーティリティがあります: mkdir, getcwd, chmod, stat, remove, rename, rmdir. また、chdir, link, walk, listdir, makedirs, renames, removedirs, unlink (remove と同じ), symlink までもあります。また、ファイルシステムとは全く関係のないものとして、fork、getenv、putenv、environ、getlogin、systemなど、ここでは説明できないほどたくさんあります。

pathlibモジュールは、これらのファイルシステム関連のosユーティリティの多くを、Pathオブジェクトのメソッドに置き換えることですっきり整理されてるのも魅力の一つです。

以下は、src/pypackagesディレクトリを作成し、.editorconfigファイルの名前をsrc/.editorconfigに変更するコードです。

import os
import os.path

os.makedirs(os.path.join('src', '__pypackages__'), exist_ok=True)
os.rename('.editorconfig', os.path.join('src', '.editorconfig'))

pathlibを使えば以下のようにオブジェクトのメソッドで表現することができます

from pathlib import Path

Path('src/__pypackages__').mkdir(parents=True, exist_ok=True)
Path('.editorconfig').rename('src/.editorconfig')

pathlibのコードでは、メソッドを連鎖させるためにパスが最初に置かれていることに注目し欲しい。

Zen of Pythonが言うように、「名前空間は素晴らしいアイデアのひとつであり、もっと多くのことをしよう」。osモジュールは非常に大きな名前空間で、その中にはたくさんのものが入っています。pathlib.Pathクラスは、osモジュールよりもはるかに小さく、より具体的な名前空間です。さらに、このPath名前空間のメソッドはPathオブジェクトを返すので、ネストした文字列の多い関数呼び出しの代わりに、メソッドの連鎖が可能になります。

https://inventwithpython.com/blog/2018/08/17/the-zen-of-python-explained/

globもシンプルに

Python標準ライブラリのファイルパス/ファイルシステム関連のユーティリティは、osモジュールとos.pathモジュールだけではありません。globという便利なパス関連モジュールもあります。

glob.glob関数を使って、あるパターンにマッチするファイルを見つけることができます。

toplevel = glob('*.csv')
all_csv_files = glob('**/*.csv', recursive=True)

さてこれをpathlibで表現するとこんな感じになります。

from pathlib import Path

toplevel = Path.cwd().glob('*.csv')
all_csv_files = Path.cwd().rglob('*.csv')

pathlibでファイルを開く

pathlibモジュールは、多くの複雑なケースをやや単純化しますが、単純なケースをさらに単純化するものもあります。

1つまたは複数のファイルに含まれるすべてのテキストを読む必要がありますか?

ファイルを開いて、その内容を読み、withブロックを使ってファイルを閉じることができます。

from glob import glob

file_contents= [].
for filename in glob('**/*.py', recursive=True):
    with open(filename) as python_file:
        file_contents.append(python_file.read())

あるいは、Pathオブジェクトのread_textメソッドとリスト内包を使って、ファイルの内容を1行で新しいリストに読み込むこともできます。

from pathlib import Path

file_contents = [
    path.read_text()
    for path in Path.cwd().rglob('*.py')
]

ファイルに書き込む必要がある場合は open context managerを使います

with open('.editorconfig') as config:
    config.write('# config goes here')

あるいは、write_textメソッドを使うこともできます。

Path('.editorconfig').write_text('# config goes here')

コンテキストマネージャーなどでopenを使用したい場合は、代わりにPathオブジェクトのopenメソッドを使用することができます。

from pathlib import Path

path = Path('.editorconfig')
with path.open(mode='wt') as config:
    config.write('# config goes here')

ちなみに、Python3.6では、組み込みのopen関数にPathオブジェクトを渡すこともで来ます

from pathlib import Path

path = Path('.editorconfig')
with open(path, mode='wt') as config:
    config.write('# config goes here')

Object志向でコードをより明確に

オブジェクト指向的にはJSONオブジェクトはディクショナリーにデシリアライズされ、日付はdatetime.date オブジェクトを使ってネイティブに表現され、ファイルシステムのパスはpathlib.Pathオブジェクトを使ってジェネリックに表現されるようになるととても美しです。

Pathオブジェクトを使用すると、コードがより明確になります。日付を表現しようとしているのであれば、日付オブジェクトを使うことができます。ファイルパスを表現しようとすれば、Pathオブジェクトを使うことができます。

オブジェクト指向プログラミングはクラスは別の抽象化のレイヤーを追加しますし、抽象化は時にシンプルさよりも複雑さを増すことがあります。しかし、pathlib.Pathクラスは便利な抽象化です。また、すぐに普遍的な抽象化として認識されるようになっています。

PEP 519 のおかげで、ファイルパスオブジェクトはパスを扱うための標準となりつつあります。Python 3.6 では、組み込みの open 関数や os, shutil, os.path モジュールの様々な関数が pathlib.Path オブジェクトで適切に動作します。パスを扱うコードのほとんどを変更することなく、今すぐpathlibを使い始めることができます。

pathlibには何が足りないの?

pathlibは素晴らしいものですが、完璧ではない。

例えば、pathlib.Pathメソッドの中にshutilと同等のものがない。

ファイルやディレクトリをコピー/削除/移動するための高レベルのshutil関数にPathオブジェクト(およびpath-likeオブジェクト)を渡すことはできますが、Pathオブジェクトにはこれらの関数に相当するものがありません。

そのため、ファイルをコピーするには、次のようにしなければなりません。

from pathlib import Path
from shutil import copyfile


from= Path('old_file.txt')
tofile = Path('new_file.txt')
copyfile(from, tofile)

また、pathlibにはos.chdirに相当するものがないのも残念だ。

つまり、現在の作業ディレクトリを変更する必要がある場合には、chdirをインポートする必要があります。

from pathlib import Path
from os import chdir

parent = Path('..')
chdir(parent)

os.walk関数もpathlibに相当するものはありません。しかし、pathlibを使って独自のwalkのような関数を簡単に作ることができます。

pathlib.Pathオブジェクトに、これらの欠落している操作のいくつかのメソッドが含まれるようになることを期待しています。しかし、これらの欠落した機能があったとしても、私は「os.path and friends」よりも「pathlib and friends」を使う方がはるかに扱いやすいと感じています。

常にpathlibを使うべきか? Python 3.6以降、pathlib.Pathオブジェクトは、すでにパス文字列を使用しているほぼすべての場所で動作します。ですから、もしあなたが Python 3.6 (またはそれ以上) を使っているなら、pathlib を使わない理由はありません。

もしあなたがPython3の以前のバージョンを使っているなら、文字列の世界に戻るためのエスケープハッチが必要なときに、いつでもPathオブジェクトをstrコールで包んで文字列を取り出すことができる。少々面倒ですが、以下のようになります。

from os import chdir
from Pathlib import Path

chdir(Path('/home/sasaki'))  # Python 3.6+ で動作
chdir(str(Path('/home/sasaki')) # それ以前のバージョンでも動作します。

Python 3のどのバージョンを使っているかに関わらず、pathlibを試してみることをお勧めします。

私は、pathlibを使うことでコードがより読みやすくなると感じています。ファイルを扱う私のコードのほとんどは、デフォルトでpathlibを使うようになっていますし、あなたもそうすることをお勧めします。もしpathlibを使えるなら、使うべきです。

hashコマンドとは?bashでコマンド有無を確認する

Linuxシステムにおけるhashコマンドは,bashの組み込みコマンドであり,最近実行されたプログラムのハッシュテーブルを管理するために使用される。プログラムの位置を記憶し、表示します。各コマンド名のフルパス名が表示されます。

hash [-lr] [-p pathname] [-dt] [name ...]

オプション:

-d:各NAMEの記憶された場所を忘れるために使用します。

-l: 入力として再利用可能な形式で表示します。

-p:パス名 NAMEのフルパス名としてPATHNAMEを使用します。

-r: 記憶した場所をすべて忘れます。

-t: 各NAMEの記憶された場所を表示します。複数のNAMEが与えられた場合は、各場所の前に対応するNAMEを付けます。

  if  ! hash $1 2>&1  ; then
        brew install $1
    else
        echo  $1' is already installed'
    fi

モノリシックなデータレイクから分散型データメッシュへの移行方法とは

多くの企業は、次世代のデータレイクに投資しており、大規模なデータを民主化してビジネスインサイトを提供し、最終的には自動化されたインテリジェントな意思決定を行うことを期待しています。データレイクアーキテクチャに基づくデータプラットフォームには、大規模化しても約束が果たされないという共通の失敗モードがあります。これらの問題に対処するためには、データレイクやその前身であるデータウェアハウスの中央集権的なパラダイムからシフトする必要があります。すなわち、ドメインを第一級の関心事とし、プラットフォーム思考を適用してセルフサービスのデータインフラを構築し、データを製品として扱うという、最新の分散型アーキテクチャを採用したパラダイムに移行する必要があるのです。

Dict[]に複数の型を指定する

Python

h = {'a': [], 'b': {})

のような辞書にたいして型ヒントを設定したい場合どうすればいいだろうか?

これは「混合辞書(Heterogeneous dictionaries)」と呼ばれるもので、特定のキーに対して特定の型の値を定義する必要がある。この問題は、文字列キーを持つ混合辞書のTypeで議論されていますが、最近になってようやくPythonで実装された(3.8以降)。 https://github.com/python/typing/issues/28

docs.python.org