pathlib.Pathを調べる

pathlib.Pathを調べる

最近はPython3.4以降の環境でスクリプトを作っているため、osモジュールからpathlibモジュールを利用するように方針を変えている。
そこで、pathlib.Pathの使えそうなメソッドを調べることにした。

まずはimport

1
from pathlib import Path

メインクラスをimportする。

ディレクトリ内のファイル・ディレクトリを取得する。

1
2
3
p = Path('C:/Windows')
for path in p.iterdir():
    print(path)

iterdir()で返却されるのはPathのイテレータ。なので、さまざまなpathのメソッドを呼び出すことができる。

ディレクトリかファイルかを判定する。

1
2
3
4
5
6
7
8
p = Path('C:\', 'Windows')
for path in p.iterdir():
    if path.is_dir():
        print('
Directory:{}'.format(path))
    elif path.is_file():
        print('
File     :{}'.format(path))
    else:
        print('
?        :{}'.format(path))

pathはPathクラスのインスタンスなので、そのメソッドを呼び出せばよい。
なお、Path()の引数は、os.path.joinのように、カンマ区切りで指定しても良い。

パスの追加

1
2
3
4
p = Path('C:\', 'Windows')
q = p / '
System32'
for path in q.iterdir():
    print(path)

演算子’/’がオーバーロードされている。カッコいい。ただ、あまり使うことはない気がする。

ファイルを開く

1
2
3
4
p = Path('C:\', 'Windows', 'System32', 'drivers', 'etc', 'hosts')
with p.open(encoding='
UTF-8') as r:
    for line in r:
        print(line)

pathインスタンスからそのままファイルオープンすることができる。エンコーディングも指定できる。便利。

ドライブ名の取得、パスの分解

1
2
3
p = Path('C:\', 'Windows', 'System32', 'drivers', 'etc', 'hosts')
print(p.drive)
print(p.parts)

ドライブ名はWindows独自。
partsも何かに使えるかな?partsに対してinして、フィルタする?

parentsとparent

1
2
3
4
5
p = Path('C:\', 'Windows', 'System32', 'drivers', 'etc', 'hosts')
print(p.parent)
print(p.parents[0])
print(p.parents[1])
print(p.parents[2])

parentは親、parentsは指定階層上の親。2階層上や3階層上の親が欲しい場合もあるかも。

名前、拡張子、拡張子なしの名前

1
2
3
4
5
p = Path('C:\', 'var', 'log', 'accesslog.tar.gz')
print(p.name)
print(p.suffix)
print(p.suffixes)
print(p.stem)

nameはファイル名またはディレクトリ名(accesslog.tar.gz)。
suffixは末端の拡張子(.gz)。
suffixesは拡張子のリスト([.tar, .gz])。
stemは拡張子(suffix)を除いたファイル名(accesslog.tar)。

カレントディレクトリ、ホームディレクトリ

1
2
print(Path.cwd())
print(Path.home())

地味だけど便利そう。
※ home()はPython3.5以降。

ファイルステータス

1
2
3
4
p = Path('C:\', 'Windows', 'System32', 'drivers', 'etc', 'hosts')
stat = p.stat()
print(stat.st_size)
print(stat.st_mtime)

os.stat()と同様の結果を得られる。ファイルサイズや作成日、更新日など。

パスが存在するかどうか

1
2
3
4
5
p = Path('C:\', 'Windows', 'System32')
if p.exists():
    print('
exists')
else:
    print('
not exists')

ファイル出力前に使えそう。
ファイル入力前はis_file()がよさそう。

再帰的なディレクトリ走査

1
2
3
4
for path in Path('C:\', 'Windows', 'System32').glob('**/*.dll'):
    print(path)
for path in Path('
C:\', 'Windows', 'System32').rglob('*.dll'):
    print(path)

glob()の引数の’**/’が再帰を表す。単一階層であれば’*.dll’を指定する。これは使えそう。
rglob()は常に再帰する。どちらでも覚えやすい方を使えばよいと思う。

ディレクトリ作成

1
2
p = Path('F:/work1/work2/work3')
p.mkdir(parents=True)

ディレクトリを作成する。parent=Trueを指定することで、再帰的にディレクトリを作成してくれる。osモジュールのos.makedirs()の様に動く。
これは便利。

単純なファイルの読み書き ※Python3.5以降

1
2
3
4
p = Path('C:\', 'Windows', 'System32', 'drivers', 'etc', 'hosts')
text = p.read_text(encoding='
UTF-8')
p = Path('
F:\', 'temp.txt')
p.write_text(text, encoding='
UTF-8')

read_text()でファイル全体を読み込み、write_text()で引数の文字列を一気に書き出す。ファイルサイズによっては使えそう。
※ Python3.5以降

名前変更

1
2
p = Path('F:\\temp.txt')
p.replace('F:\\work\new.txt')

replace()でファイル名を変更できる。途中のディレクトリを勝手に作ってくれることはないので、そこはあらかじめ準備しておく。

touch

1
2
p = Path('F:\\temp2.txt')
p.touch()

シェルコマンドのtouchと同様。現在日時で0バイトファイルを作成する。

まとめ

なかなか良いモジュールだ。
再帰走査ができるし、そのままエンコードを指定して読み込むこともできるし、足りないことの方が見つからない。これからはどんどん使っていこう。もうPython2.7には戻れない。

同じタグの記事
同じカテゴリの記事

macOSをMojaveに更新した。スクリプト実行環境のバージョンは変わったか

macOSをMojaveに更新した。スクリプト実行環境のバージョンは変わったか

Python

Mojave

1
2
3
Python 2.7.10 (default, Aug 17 2018, 17:41:52)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

High Sierra

1
2
3
Python 2.7.10 (default, Oct  6 2017, 22:29:07)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

Pythonのバージョンは変更なし。
コンパイラのバージョンは上がっている。

perl

Mojave

1
2
3
4
This is perl 5, version 18, subversion 2 (v5.18.2) built for darwin-thread-multi-2level
(with 2 registered patches, see perl -V for more detail)

Copyright 1987-2013, Larry Wall

High Sierra

1
2
3
4
This is perl 5, version 18, subversion 2 (v5.18.2) built for darwin-thread-multi-2level
(with 2 registered patches, see perl -V for more detail)

Copyright 1987-2013, Larry Wall

perlのバージョンは変わらない。

PHP

Mojave

1
2
3
PHP 7.1.19 (cli) (built: Aug 17 2018 18:03:17) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies

High Sierra

1
2
3
PHP 7.1.16 (cli) (built: Mar 31 2018 02:59:59) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies

細かいバージョンが上がっている。

Ruby

Mojave

1
ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18]

High Sierra

1
ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin17]

Rubyのバージョンは変わらない。

sqlite3

Mojave

1
SQLite version 3.24.0 2018-06-04 14:10:15

High Sierra

1
SQLite version 3.19.3 2017-06-27 16:48:08

細かいバージョンが上がっている。

まとめ

今回もPythonのバージョンアップはなかった。
macOSの何らかのプログラムがPython2に依存しているのだろうか。

同じタグの記事
同じカテゴリの記事

Pythonでリスト、タプルの重複除去

Pythonでリスト、タプルの重複除去

特殊なデータストアからデータを抽出し、返却するバッチ処理がある。そのバッチ処理は定期実行され、キューイングはXMLファイル行われる。
同じ時間の処理で、返却先が異なるが同じデータへのリクエストが来ることがある。
同じデータをデータストアに何度もアクセスしたくない。リクエストをListにため込んでから、重複を除去して問い合わせを行う。
Listの重複をどのように行うか。

setを利用する

1
2
3
4
5
6
7
work_list = []
for x in range(10):
    work_list.append(x % 3)
print(work_list)

target_set = set(work_list)
print(target_set)

このプログラムを実行すと、work_listは

1
[0, 1, 2, 0, 1, 2, 0, 1, 2, 0]

target_setは

1
{0, 1, 2}

と出力される。

まとめ

リストの重複除去が簡単に行えた。
しかし、順序は保証されないようなので、順序が重要な場合は自身でイテレートして重複を除去する必要がある。
または、そもそもwork_listにappendする前に、if xx not in work_list:でリストに含まれないことを確認してからappendすればよい。ここはお好みで。

同じタグの記事
同じカテゴリの記事

Pythonで文字列が含まれているかどうかを調べる

Pythonで文字列が含まれているかどうかを調べる

あいかわらず、ログファイルを集計している。
ログファイルに特定の文字が現れる行の行数を数えたい。

文字が含まれているかどうか

strのfindを使うと、他の言語のindexOfの様に位置を出現個所の位置を返してくれるようだ。

1
2
3
line = 'test message'
print(line.find('mes'))
print(line.find('mec'))

上記の例だと、最初が5、次が-1が返る。
-1より大きいかどうかを判断すれば文字が含まれるかどうかをチェックできる。
しかし、今回は位置は必要ない。
そういう場合は、配列に含まれているかどうかと同様に in を使えばよい。

1
2
3
line = 'test message'
print('mes' in line)
print('mec' in line)

上記の例だと、最初がTrue、次がFalseが返る。
スマート。
そして、統一感がある。

まとめ

inで含まれているかどうかが、配列でもディクショナリでも文字列でも扱えるところは統一感があって素晴らしい。
ちなみに、Kotlinであればcontainsがそれにあたるのだろう。

同じタグの記事
同じカテゴリの記事

Pythonでディレクトリ走査

Pythonでディレクトリ走査

相変わらず、ログファイルを集計している。
特定のディレクトリのファイルをすべて検査するのだが、今まではosモジュールを利用していた。

os.listdir

1
2
3
4
5
6
7
import os

top_dir = '/logs'
for f in os.listdir(top_dir):
    path = os.path.join(top_dir, f)
    if os.path.isfile(path):
        print(path)

今までは、この方法を実行していた。
しかしこの方法は、os.listdirの戻り値がファイル名のみなので、そのファイルにアクセスしようと思うと、top_dirとos.path.join()しなくてはならない。
Python3.4からはpathlib.Pathが利用できる。

pathlib.Path

1
2
3
4
5
6
from pathlib import Path

top_dir = '/logs'
for f in Path(top_dir).iterdir():
    if f.is_file():
        print(f)

iterdirの戻り値はPathなので、そのメソッドを利用しファイルかどうかを確認し、Pathなので__str__でフルパスを取得できるし、nameではファイル名のみの取得もできる。suffixで拡張子のみの取得もできる。とても便利。

まとめ

これからはPathを率先して利用しよう。

同じタグの記事
同じカテゴリの記事

Pythonで日付、時刻の比較

Pythonで日付、時刻の比較を行う

急に性能が落ちてきた処理がある。しかし、それがいつからなのか、体感的なもので本当はたいして性能劣化していないのか。
よくわからなかったので、ログファイルを集計して処理時間を計測してみることにした。

簡単に作りたかったのでPythonを利用する。
文字列の日時をdatetimeに変換して、datetimeとdatetimeを引き算し、返ってきたtimedeltaから差を得ればよいようだ。

文字列からdatetimeへの変換

1
2
3
4
import datetime

start_date_time = datetime.datetime.strptime('2018-09-14:14:49:31', '%Y-%m-%d:%H:%M:%S')
end_date_time = datetime.datetime.strptime('2018-09-14:14:51:13', '%Y-%m-%d:%H:%M:%S')

日付のフォーマットを指定すれば、ログファイルの日付フォーマットはいろいろ対応できそうだ。

処理時間を算出する

1
2
3
dist_delta = end_date_time - start_date_time
print(dist_delta.seconds)
print(dist_delta.days)

secondsで差の秒数を取得できる。
何日も差があるようであれば、daysで差の日付を取得できる。

まとめ

数十万行のログファイルから特定のキーで開始時間と終了時間を取得しつつ、処理時間の平均を日ごとに集計したが、1分未満で処理が終わった。Python優秀だ。

同じタグの記事
同じカテゴリの記事

PythonでCSVファイルを読む

PythonでCSVファイルを読む

テキストデータを読むバッチを度々作る。
昔からの習慣でカラムの区切りはタブかバックタブを利用し、プログラムでsplitして読んでいた。特にPythonは簡単に使えるので、.strip()してから.split(‘\t’)という習慣があった。
しかし、CSVファイルを出力するプログラムが、先方の仕様で出力されてしまう場合、それに合わせて読み込む必要がある。
区切り文字だったり、各カラムの囲み文字があったり、自前のプログラム対応するのは面倒だ。そんな時csvモジュールを使う。

CSVファイルを読む

1
2
3
4
5
6
import csv

with open('sample.csv', 'r') as r:
    reader = csv.reader(r, delimiter=',', quotechar='"', skipinitialspace=True)
    for row in reader:
        print(row)

delimiterは区切り文字。デフォルトは’,’。
quotecharはカラムの囲み文字。デフォルトは'”‘。
skipinitialspace=Trueは、区切り文字の前後のスペースを無視してくれる。CSVファイルではあまりないだろうが、下記のようなデータの場合に、都合よく処理してくれる。

1
"ABC", "DEF", 30

skipinitialspace=Trueが指定されないと、2カラム目は’ “DEF”‘になってしまう。
扱うデータによってはスペースにも意味があるかもしれないので、理解して利用する。

まとめ

Python2の頃は、strとunicodeの違いで、日本語が含まれるファイルを読むのに苦労したが、Python3なら何も気にせず読み込むことができる。
これからはcsvモジュールを利用するようにしてみよう。

同じタグの記事
同じカテゴリの記事

pythonでデータクラス

pythonでデータクラス

python3.7の新機能を読んでいた。
dataclassアノテーションが使えるようになったようだ。
これで、Kotlinの様に簡単にDtoを定義できるようになる。

dataclassの作り方

1
2
3
4
5
6
7
8
import dataclasses

@dataclasses.dataclass
class Dto37:
    id: int
    name: str

dto = Dto37(1, 'One')

簡単だ。かつては普通のクラスを作って、コンストラクタですべてのプロパティを受け取れるようにしたり、
__str__や__repr__を実装したりしていたのに。

しかし、NamedTupleというのを使えば、python3.6でも似たようなことができるようだ。

typing.NamedTuple

1
2
3
4
5
6
7
from typing import NamedTuple

class Dto36(NamedTuple):
    id: int
    name: str

dto = Dto36(2, 'Two')

なんてことだ。簡単だ。

さらに調べると、collections.namedtupleというのを使えば、python3.1でも似たようなことができていた。

collections.namedtuple

1
2
3
4
5
6
7
from collections import namedtuple

Dto31 = namedtuple('Dto31', ['id', 'name'])
dto = Dto31(3, 'Three')

Dto31_2 = namedtuple('Dto31_2', 'id,name')
dto = Dto31_2(4, 'Four')

これは少し癖がある。データタイプも指定できないし。
でも、パラメータを配列で渡しても良いし、カンマ区切りの文字列や、スペース区切りの文字列で渡しても良い。__repr__も実装されている。結構便利だ。

改めてdataclassアノテーション

今までも、やり方はあったのになぜ今回dataclassを追加したか。
dataclassの説明を読むと、適用されるメソッド(__repr__や__eq__など)のコントロールができたり、公開するフィールドを選んだりすることができるのがdataclassのようだ。読み取り専用のクラスも作成できたり、自由度が高くなっている。
今後、python3.7がメインの環境になった暁には、利用してみよう。

同じタグの記事
同じカテゴリの記事

手軽なのに強力なsqlite3

手軽なのに強力なsqlite3

適当なツールにsqlite3をよく使う。
pythonを使っていると、最初からpython3ライブラリが使えるので、テキストファイルの集計なども :memory: データベースに投入して出力すると、簡単に実装できる。

データベースを:memory:ではなく、ファイルにした場合、データの再利用が可能だ。
そのデータをちょっと見たいとき、わざわざpythonを経由して見るのは面倒なので、sqlite3コマンドでアクセスしている。

SELECTの結果

デフォルトの出力形式は見ずらい。
効率的といえば効率的かもしれないが、すべてのカラムがパイプ区切りでべったりくっついて表示されてしまう。
この設定は.modeで行う。

.mode csv

カンマ区切りで出力される。特定のカラムに絞ったデータをCSVで出力して、次の処理を行うのも良いかもしれない。

.mode tabs

タブ区切りで出力される。データにカンマが含まれることも多いと思うので、こちらで出力しておいた方が、次の利用に問題が出ないかもしれない。

.mode ascii

カラムの区切り文字が0x1F、行の区切り文字が0x1Eで出力される。
データにカンマもタブも含まれている場合は、この形式が良いと思う。

.mode html

html形式で出力される。使えないこともない。

.mode insert TABLENAME

insert文として出力される。これをファイルに出力して、特定のデータのみを移行するというのにも使える。
TABLENAMEはINSERT文のINSERT先のテーブル名として利用される。
指定しないとINSERT先がTABLEになってしまう。

.mode column

コンソール出力で見栄えが良いように出力してくれる。
OracleのSQL*Plusのような感じか。
しかし、.widthと組み合わせて使わないと、カラムのデータが途中で区切れてしまうことがある。要注意。
.widthはcolumnの先頭から文字数をスペース区切りで指定していく。
.width 10 10 10 20 20 20

日本語が登録されたカラムがあると、文字数でカウントされ縦位置がずれてしまうので注意。
数値とコードのみの、トランザクションテーブルなら使いやすい。

.header on

1行目にカラム名が表示されるようになる。
これはよく使う。

.output FILENAME

SELECT結果の出力先をファイルにする。
.modeをasciiやinsert、htmlに設定した場合は、.outputをファイルにしておく。
終わったら、「.output stdout」として、出力先をコンソールに戻しておく。

その他、たまに利用する設定

.timer on

処理時間が表示されるようになる。
SQL*PlusのSET TIMING ONのような感じ。
sqlite3で性能問題に当たるような、大作SQLを作ったことがないので、あまり気にしないが。

.show

変更した設定値の一覧が確認できる。

.table

テーブル一覧の表示。

.read FILENAME

外部ファイルを読み込んで実行する。
.mode insertで出力した結果を取り込むのに使える。

普段、何も考えずによく使う使い方

1
2
3
4
sqlite> .header on
sqlite> .mode column
sqlite> select * from test_table limit 10;
sqlite> ...

まとめ

簡単に使えてとても便利。それにINSERTとSELECTはとても速い。
INSERTは1件コミットにするととても遅いので注意。
SELECTはINDEXを効かせれば、数万レコードのテーブルでも一瞬で返ってくる。
ツールもなかなか使えるし、手放せないデータベースになってきた。

最後に、sqlite3コマンド抜けるのは.quit(または.exit)。

同じタグの記事
同じカテゴリの記事

pythonからchromeを操作する

pythonからchromeを操作する。

pythonでseleniumライブラリを利用することで、
ChromeやFirefoxを操作できるようだ。

Windows環境で試してみた。

pipでseleniumドライバーをインストール

1
python -m pip install selenium

ChromeのWebDriverをダウンロード

https://sites.google.com/a/chromium.org/chromedriver/downloads

zipファイルがダウンロードされ、解凍すると「chromedriver.exe」というファイルが1つ入っていた。
これを経由してChromeへアクセスするようだ。

pythonから呼び出し

準備(必須ではない)

chromedriver.exeが存在する場所にPATHを通す。
またはPATHが通っているディレクトリにchromedriver.exeを置く。

参考:この方法は必須ではない。
ドライバーのインスタンス生成時に、chromedriver.exeのフルパスを指定できるので、その方法を用いても良い。

pythonからchromeを利用する。

まずは、seleniumのwebdriverをインポート。

1
from selenium import webdriver

webdriverのインスタンスを生成する。

1
2
3
4
5
6
7
<h1>chromedriver.exeにPATHが通っている場合。</h1>
driver = webdriver.Chrome()
<h1>chromedriver.exeにPATHが通っていない場合。</h1>
path_to_chromedriver = 'C:/usr/chromedriver.exe'
driver = webdriver.Chrome(path_to_chromederiver)
<h1>または</h1>
driver = webdriver.Chrome(executable_path = path_to_chromedriver)

ここまで実行するとchromeが立ち上がる。
画面を非表示にしたい場合は、Optionを定義しヘッドレスモードで起動する。

1
2
3
4
5
6
7
8
9
<h1>ヘッドレスオプションの準備</h1>
options = webdriver.ChromeOptions()
options.add_argument('--headless')
<h1>インスタンス生成</h1>
driver = webdriver.Chrome(options = options)
<h1>または</h1>
driver = webdriver.Chrome(
    executable_path = path_to_chromedriver,
    options = options)

URLを開く

1
driver.get('https://www.yahoo.co.jp/')

HTMLを取得する

1
html = driver.page_source

スクリーンショットを保存する。

1
driver.save_screenshot('screenshot.png')

javascriptを実行して、結果を得る。

1
page_height = driver.execure_script('return document.body.scrollHeight')

ウインドウサイズを設定する。

1
driver.set_window_size(1280, 800)

終了処理

1
driver.quit()

これでChromeが閉じる。

まとめ

ひたすらスクリーンショットを取らなくてはならないときなど便利そうだ。

同じタグの記事
同じカテゴリの記事