2018/01/30

Python Tips: 標準出力の出力先がターミナルかそれ以外かによって処理を切り替えたい

Python で標準出力の出力先がターミナルの場合とそうでない場合とで処理を切り替える方法をご紹介します。

イメージは次のような感じです。

標準出力の出力先がターミナルである:

$ 出力先がターミナル
$ python sample.py

標準出力の出力先がターミナルではない:

$ # パイプで他のコマンドに渡されている
$ python sample.py | cat -

$ # ファイルに出力されている
$ python sample.py > result.txt

この方法を使うと、出力先によってフォーマットを自動で切り替えることができます。これによって、例えば、標準出力がターミナルの場合は視覚的に見やすく縦のラインを揃えて出力して、ファイルや別のコマンドの場合は余計な整形を行わず機械的に処理しやすい形で出力する、といった形が可能となります。

結論としては、次の記事の方法をそのまま標準出力にあてはめるだけで、とてもかんたんです。


sys.stdoutisatty() メソッドを実行すると、出力先がターミナルの場合は True 、そうでない場合は False が返ってくるようになっているので、これを利用しましょう。

サンプルで確認してみます。

check_stdout.py:

# coding: utf-8

'''Python で出力がパイプされているかどうかをチェックする
'''

import sys


def main(): 
    if sys.stdout.isatty():
        print('もしや、出力先はターミナルですね。')
    else: 
        print('出力先はターミナルではありませんね。')


if __name__ == '__main__': 
    main()

このコードを check_stdout.py という名前で保存して実行してみます。

$ python check_stdout.py
もしや、出力先はターミナルですね。

$ python check_stdout.py | cat -
出力先はターミナルではありませんね。

$ python check_stdout.py > result.txt
$ cat result.txt
出力先はターミナルではありませんね。

出力先によって処理(この場合は出力内容)が切り替わっているのが確認できました。

以上です。

シンプルでわかりやすいですねー。

2018/01/24

Python Tips: Python のバージョンを確認したい

Python で、 Python 本体や利用ライブラリのバージョンを確認する方法をご紹介します。

お断り: 以下のコードの動作は MacOS 環境で確認しています。おそらく Ubuntu や CentOS 、 Windows 等でも同様ではないかと思いますが、 Mac 以外をお使いの方は「違いがあるかもしれない」という前提で参考にしてみてください。

以下、「ターミナルのコマンドで確認する方法」と「コードの中で確認する方法」の 2 つに分けてご説明していきます。

まずは目次です。

  • Python 本体のバージョンを確認する
    • ターミナルで確認する
    • コード内で確認する
  • ライブラリのバージョンを確認する
    • ターミナルで確認する
    • コード内で確認する

Python 本体のバージョンを確認する


ターミナルで確認する

Python のバージョンをターミナル上で確認するには、 python コマンドの --version オプションを利用します。

$ python --version 
Python 3.6.1

大文字の -V オプションも同じ意味になります。

$ python -V
Python 3.6.1

このオプションは 2 回重ねて指定することで、より詳細のビルド情報を確認することができます。

$ python --version --version
Python 3.6.1 (default, Apr  4 2017, 09:40:51)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]

$ python -VV
Python 3.6.1 (default, Apr  4 2017, 09:40:51)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]

ちなみに、少し紛らわしいですが、小文字の -v オプションも存在します。こちらは verbose (冗長な)を意味するオプションで、インポート処理等のトレース情報を出力してくれるモードを有効にしたいときに指定するものなので、バージョン表示とは無関係です。

コード内で確認する

続いて、コード内で確認する方法です。

コード内で確認する場合は sys.version_info を使うとよいでしょう。

import sys

print('Major version: {}'.format(sys.version_info.major))  # => 3
print('Minor version: {}'.format(sys.version_info.minor))  # => 6
print('Micro version: {}'.format(sys.version_info.micro))  # => 1

sys.version_infomajor minor micro などのアトリビュートにバージョン情報を格納したオブジェクトです(タプル型を継承した独自のクラスのオブジェクトのようです)。

また、 sys.version には、 Python のバージョンを表す文字列が格納されています。私の環境では、これはターミナルで python --version と打ったときに出力されるものと同じ文字列となっています。

import sys

print(sys.version)
# =>
# 3.6.1 (default, Apr  4 2017, 09:40:51)
# [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]

この sys.version_infosys.version の使い分けですが、プログラムでバージョン情報を利用したい場合(メジャーバージョンによって分岐させたい場合など)には sys.version_info を、単純に人が確認するのに使いたい場合には sys.version の方を使うとよいかと思います。


ライブラリのバージョンを確認する


続いて、 Python 本体ではなくライブラリのバージョンを確認する方法についてです。

ターミナルで確認する

ターミナルでライブラリのバージョンを確認する方法としては、 Python3 で pip コマンドを使っている場合は、大きく分けて 2 通りの方法ーー「一覧でまとめて確認する方法」と「個別に確認する方法」があります( Anaconda などを使っている場合は未確認です)。

まず前者の「一覧でまとめて確認する方法」についてですが、こちらは pip のサブコマンド freeze または list を使用します。

pip freeze:

$ pip freeze 
alabaster==0.7.10
appnope==0.1.0
argh==0.26.2
astroid==1.4.5
attrs==17.2.0
...

pip list:

$ pip list --format=columns
Package           Version
----------------- ----------
alabaster         0.7.10
appnope           0.1.0
argh              0.26.2
astroid           1.4.5
attrs             17.2.0
...

特定のパターンにマッチするライブラリだけ知りたい場合は grep などで抽出するとよいでしょう。

$ pip list --format=columns | grep beautifulsoup
beautifulsoup4    4.6.0

list はさまざまなフォーマットをサポートしています。

$ pip list --format=json
[{"name": "alabaster", "version": "0.7.10"}, {"name": "appnope", "version": "0.1.0"}, {"name": "argh", "version": "0.26.2"}, {"name": "astroid", "version": "1.4.5"}, {"name": "attrs", "version": "17.2.0"}, ...

freezelist はよく似ていますが、両者にはおおよそ次のような違いがあります。

  • freeze は、 pip install -r で使える requrements.txt の形式で出力する。
  • list は、 --format オプションを使って legacy columns freeze json などさまざまなフォーマットで出力できる。
  • freeze は、デフォルトでは pipsetuptools は含まない。一方の listpipsetuptools も含む(厳密にいうと、 list は editable モードでインストールされたパッケージも含む、という違いもあるようです)。

私の素人的なざっくりした印象では、 freeze は requirements.txt ファイル生成専用のもので、 list はその他いろんな用途のための(人間に見せたり pip 以外のプログラムに渡したりするための)一覧を生成するもの、というイメージです。

後者の「個別に確認する方法」は、 pip コマンドのサブコマンド show を使うというものです。 pip show [パッケージ名] と入力します。

Requests ライブラリで試してみると、私の環境では次のように表示されました。

$ pip show requests
Name: requests
Version: 2.18.4
Summary: Python HTTP for Humans.
Home-page: http://python-requests.org
Author: Kenneth Reitz
Author-email: me@kennethreitz.org
License: Apache 2.0
Location: /[Python がインストールされているパス]/lib/python3.6/site-packages
Requires: idna, chardet, urllib3, certifi

コード内で確認する

続いて、 Python ライブラリのバージョンをコード内で確認する方法についてです。

私はこれをする必要に迫られたことがないのでこの方法がよいかとどうかの確信は無いのですが、おそらく pkg_resources.working_set に格納されているパッケージ情報を見る方法がよいのではないかと思います。

import pkg_resources

def get_package_versions(name_fragment):
    '''指定された文字列を名前に含むパッケージのバージョン情報を取得する

    Args:
        name_fragment (str): 情報を確認したいパッケージに含まれる文字列。

    Returns:
        list: 'name' と 'version' というキーを持った辞書のリスト。
    '''
    return [
        {
            'name': package.project_name,
            'version': package.version,
        }
        for package in pkg_resources.working_set
        if name_fragment in package.project_name
    ]

get_package_versions('req')
# => [{'name': 'requests', 'version': '2.18.4'}]

get_package_versions('jupyter')
# => [{'name': 'jupyter', 'version': '1.0.0'},
#  {'name': 'jupyter-core', 'version': '4.0.6'},
#  {'name': 'jupyter-console', 'version': '4.1.1'},
#  {'name': 'jupyter-client', 'version': '4.1.1'}]

詳細を知りたい方は setuptools の公式ドキュメントの pkg_resources のところをご覧ください


ライブラリのバージョンを調べる別の方法として、「モジュールオブジェクトの __version__ アトリビュートを見る」というのがありますが、ライブラリの作者が入れていなければこのアトリビュートは無いこともあるようなので、あまりここに依存すべきではないのかと思います。

Python で本体やライブラリのバージョンを確認する方法については以上です。


参考

2018/01/19

Python Tips: split() を活用したい

Python での文字列操作に便利な split() の使い方についてご説明してみます。

'Hello, world'.split()

split()str 型と正規表現ライブラリにあるので、これらを順番に見ていきましょう。

str 型の split() メソッド


str 型の split() メソッドのドキュメントは次のようになっています。

print(''.split.__doc__)
# =>
# S.split(sep=None, maxsplit=-1) -> list of strings
# 
# Return a list of the words in S, using sep as the
# delimiter string.  If maxsplit is given, at most maxsplit
# splits are done. If sep is not specified or is None, any
# whitespace string is a separator and empty strings are
# removed from the result.

str.split() — Python 公式ドキュメント

日本語翻訳では次のように書かれています。

文字列を sep をデリミタ文字列として区切った単語のリストを返します。 maxsplit が与えられていれば、最大で maxsplit 回分割されます (つまり、リストは最大 maxsplit+1 要素になります)。 maxsplit が与えられないか -1 なら、分割の回数に制限はありません (可能なだけ分割されます)。

sep が与えられた場合、連続した区切り文字はまとめられず、空の文字列を区切っていると判断されます(例えば '1,,2'.split(',')['1', '', '2'] を返します)。引数 sep は複数の文字にもできます (例えば '1<>2<>3'.split('<>')['1', '2', '3'] を返します)。区切り文字を指定して空の文字列を分割すると、 [''] を返します。

str.split() — Python ドキュメント(日本語)

つまり、 split() はオプション引数(非必須な引数)を 2 つ受け取ることができる。その引数を使って、利用者は「区切り文字に何を使うのか」と「最大分割回数」を指定することができる、とのことです。戻り値の型は list です。

いくつかサンプルを見てみましょう。

引数なしで利用する

# 引数を指定しなければ、スペースで分割されます
'Guardians of the Galaxy'.split()
# => ['Guardians', 'of', 'the', 'Galaxy']

# スペースはどれだけ長くてもまとめられます
'Guardians   of   the        Galaxy'.split()
# => ['Guardians', 'of', 'the', 'Galaxy']

# 半角スペースの他にも、改行やタブもスペースとみなされます
'Guardians\tof   \t   the   \n   Galaxy'.split()
# => ['Guardians', 'of', 'the', 'Galaxy']

# その他 Unicode で「空白文字」とされているものもスペースとみなされるので
# 日本語で使われる全角空白( ideographic space / U+3000 )も区切り対象になります
'ガーディアンズ オブ ギャラクシー'.split()
# => ['ガーディアンズ', 'オブ', 'ギャラクシー']

引数を指定して利用する

# 第 1 引数に文字を渡せばそれが区切り文字となります
'文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。'.split('、')
# => ['文壇の', '或老大家が亡くなって', 'その告別式の終り頃から', '雨が降りはじめた。']

# 第 1 引数に文字列を渡した場合、その文字列がまるごと区切りとして使用されます
# 各文字が区切りになるわけではありません
'文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。'.split('、。')
# => ['文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。']

# 半角空白を渡した場合の挙動は、引数を渡さなかった場合と大幅に異なります

# 「区切り対象文字が半角空白だけで、なおかつ、半角空白が連続しない場合」にかぎり結果は同じになりますが・・・
'Guardians of the Galaxy'.split(' ')
# => ['Guardians', 'of', 'the', 'Galaxy']

# 連続する空白は 1 つずつ区切られてしまいます
'Guardians   of   the        Galaxy'.split(' ')
# => ['Guardians', '', '', 'of', '', '', 'the', '', '', '', '', '', '', '', 'Galaxy']

# また、タブや全角などの空白文字では区切られません
'ガーディアンズ オブ ギャラクシー'.split(' ')
# => ['ガーディアンズ\u3000オブ\u3000ギャラクシー']

# 第 2 引数は最大区切り回数です
'文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。'.split('、', 2)
# => ['文壇の', '或老大家が亡くなって', 'その告別式の終り頃から、雨が降りはじめた。']

# 第 2 引数を渡す必要があるけれど、区切り回数は指定したくない場合には `-1` を渡せば OK です
'文壇の、或老大家が亡くなって、その告別式の終り頃から、雨が降りはじめた。'.split('、', -1)
# => ['文壇の', '或老大家が亡くなって', 'その告別式の終り頃から', '雨が降りはじめた。']

以上です。

以下、よくあるニーズ別に使い方を見てみましょう。

複数の文字で区切りたい

str 型の split() メソッドは複数の区切り文字に対応していません。複数の文字で区切りたい場合は、 2 つのアプローチがあります。

  • split() 前に区切り文字を置換しておく
  • 正規表現ライブラリの split()を使う

split() 前に区切り文字を置換しておく:

('愛のままにわがままに僕は君だけを傷つけない'
  .replace('の', '\n')
  .replace('に', '\n')
  .replace('を', '\n')
  .split('\n'))
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

文字列に含まれないことが保証されている文字がもしあるなら、対象文字をすべてそれに置換した後に split() をかけることで、複数の文字で区切ることができます。

ただ、ほとんどの場面では次の「正規表現ライブラリの split() 」を使った方法を使うのがよいでしょう。

正規表現ライブラリの split()を使う:

正規表現の関数 split() を使えば複数の区切り文字での分割がシンプルにできます。

import re

re.split('[のにを]', '愛のままにわがままに僕は君だけを傷つけない')
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

あらゆる空白を区切り文字にしたいが、最大回数は指定したい

str 型の split() に第 1 引数を渡さなかったときの挙動を維持しつつ、最大分割回数は指定したい場合は、第 1 引数に None を指定して第 2 引数を指定します。

'Guardians   of   the        Galaxy'.split(None, 1)
# => ['Guardians', 'of   the        Galaxy']

あるいは、最大分割回数の引数を名前付きで渡しても OK です。

'Guardians   of   the        Galaxy'.split(maxsplit=1)
# => ['Guardians', 'of   the        Galaxy']

文字列を 1 文字ずつ分割したリストを取得したい

文字列を 1 文字ずつ分割したい場合に split() を使うことはできません。 split() を使うと、区切り文字が文字列から除外されてしまうためです。

その用途には、コンストラクタ list() などが使えます。

list('ガーディアンズ オブ ギャラクシー')
# =>
['ガ', 'ー', 'デ', 'ィ', 'ア', 'ン', 'ズ', '\u3000', 'オ', 'ブ', '\u3000', 'ギ', 'ャ', 'ラ', 'ク', 'シ', 'ー']

文字列を改行文字で分割したい

文字列を改行文字で分割したい場合は、多くの場合、 split() よりも、専用のメソッド splitlines() を使うとよいでしょう。

haiku = '''朝顔に
我は飯食う


男哉'''

haiku.splitlines()
# => ['朝顔に', '我は飯食う', '', '', '男哉']


split() だと、連続した改行(空行)がまとめられてしまいます。

haiku = '''朝顔に
我は飯食う


男哉'''

haiku.split()
# => ['朝顔に', '我は飯食う', '男哉']

このあたりはどちらの挙動が望ましいものなのかを先に把握して使い分けるとよいでしょう。

リスト全体を一気に生成せずにイテレータなどを介して各行を取得したい場合などは io.StringIO などを使ってもよいかもしれません。

import io
  
haiku = '''朝顔に
我は飯食う


男哉'''

for line in io.StringIO(haiku):
    print(repr(line))
# =>
# '朝顔に\n'
# '我は飯食う\n'
# '\n'
# '\n'
# '男哉'

str 型の split() メソッドについては以上です。続いて正規表現ライブラリの split() について見ていきましょう。


正規表現ライブラリの split()


正規表現ライブラリにも str 型と似た split() があります。

厳密にいうと、「 split() 関数」と「正規表現オブジェクトの split() メソッド」の 2 つがあります。

import re

# split() 関数
re.split('[のにを]', '愛のままにわがままに僕は君だけを傷つけない')
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

# 正規表現オブジェクトの split() メソッド
pattern = re.compile('[のにを]')
pattern.split('愛のままにわがままに僕は君だけを傷つけない')
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

ここでは、 split() 関数の方を使って使い方を見てみましょう。

まず、宣言文は次のようになっています。

def split(pattern, string, maxsplit=0, flags=0):

各引数について説明します。

  • pattern: 区切りに使いたい文字の正規表現パターン。
  • string: 分割対象の文字列。
  • maxsplit: 最大分割回数。 str 型の同名の引数と同じイメージ。
  • flags: 正規表現フラグ。

正規表現フラグには次のものが利用可能です。

  • re.ASCII (re.A)
  • re.DEBUG
  • re.IGNORECASE (re.I)
  • re.LOCALE (re.L)
  • re.MULTILINE (re.M)
  • re.DOTALL (re.S)
  • re.VERBOSE (re.X)

このフラグは Python 3.6 以降では Enum 型の enum.IntFlag のサブクラスである re.RegexFlag のインスタンスとなっているようです。

このフラグは正規表現のオプションを指定するためのものです。正規表現に馴染みのある方はどういうものを意味するのかピンと来るのではないかと思います。詳しく知りたい方は公式のドキュメントにあたってみてください。

re — Python 公式ドキュメント

正規表現ライブラリの split() 関数の docstring は次のようになっています。

import re

print(re.split.__doc__)
# =>
# Split the source string by the occurrences of the pattern,
#     returning a list containing the resulting substrings.  If
#     capturing parentheses are used in pattern, then the text of all
#     groups in the pattern are also returned as part of the resulting
#     list.  If maxsplit is nonzero, at most maxsplit splits occur,
#     and the remainder of the string is returned as the final element
#     of the list.

re.split() — Python 公式ドキュメント

この関数は、文字列を正規表現パターンで分割したリストを返す。正規表現の中でかっこ( () )が使われた場合、それは戻り値のリストに含まれる、とのことです。

サンプルを見てみましょう。

import re

# 指定された正規表現で分割する
re.split('[のにを]', '愛のままにわがままに僕は君だけを傷つけない')
# => ['愛', 'まま', 'わがまま', '僕は君だけ', '傷つけない']

# 改行文字で分割する
haiku = '''朝顔に
我は飯食う


男哉'''

re.split('\n', haiku)
# => ['朝顔に', '我は飯食う', '', '', '男哉']

re.split('\n+', haiku)
# => ['朝顔に', '我は飯食う', '男哉']

# () を使って、区切り文字そのものを戻り値のリストに含める
re.split('(\s+)', 'ガーディアンズ  オブ  ギャラクシー')
# => ['ガーディアンズ', '\u3000\u3000', 'オブ', '\u3000\u3000', 'ギャラクシー']

正規表現オブジェクトの split() メソッドの方は引数が少し異なりますが、使い方はこの split() 関数とほぼ同じです。

宣言部は次のようになっているようです。

regex.split(string, maxsplit=0)

正規表現ライブラリの split() については以上です。

...

ここまで来ると押さえておきたいのが「 str 型の split() と正規表現ライブラリの split() はどのように使い分ければよいのか」という点についてですが、こちらは、次のケースでのみ str 型の split() を使って、その他のケースでは正規表現ライブラリの split() を使う、というのがよいのではないかと思います。

  • Unicode のすべての空白文字で区切りたい。空白が連続する場合はひとつにまとめたい。 → str 型の split() を区切り文字指定なしで利用する。
  • 区切り文字が 1 つだけ。区切り文字が連続する場合は、まとめずに空文字で分割してほしい。 → str 型の split() を区切り文字を指定して利用する。

レアケースな気もしますが、実行時パフォーマンス向上を追求しないといけない場合は、無理やりにでも str 型の split() を使った方がいいケースもあるかもしれません。


以上、文字列操作に便利な Python の split() についてでした。