2018/04/02

Python Tips:組み込みの名前を上書きしてしまったのを元に戻したい

Python で組み込み関数等の名前を上書きしてしまったときに元に戻す方法をご紹介します。

Python には、モジュールを import しなくても利用できる組み込みの関数があります。
例えば、 Python 3.6 の場合だと、その数は 60 以上にもなります。

abs() all() any() ascii() bin() bool() bytearray() bytes() callable() chr() classmethod() compile() complex() delattr()
dict() dir() divmod() enumerate() eval() exec() filter() float() format() frozenset() getattr() globals() hasattr() hash()
help() hex() id() input() int() isinstance() issubclass() iter() len() list() locals() map() max() memoryview()
min() next() object() oct() open() ord() pow() print() property() range() repr() reversed() round() set()
setattr() slice() sorted() staticmethod() str() sum() super() tuple() type() vars() zip() __import__()


これらの関数の名前を誤って上書きしてしまうと、元々あった関数の機能を使うことができません。多くの場合は変数名を変えればそれで済むのですが、 REPL や Jupyter を使っていると、 Python プロセスを起動し直す手間が大きく、稀に困ることがあります。

私の場合は、 idmapmax 等のシンプルな名前を変数名として使いたくなって、うっかり上書きしてしまうことがよくあります(他の言語のコードと Python コードを並行で触っているときによくやります)。

max = 10
max(10, 20, 5)
# => TypeError: 'int' object is not callable

結論としては、キーワード del を使って変数を削除すれば OK です。 del した後は元々の名前をまたそのまま利用できるようになります。

max = 10

del max

max(10, 20, 5)
# => 20

組み込みの名前は上書きしないように気をつけることが第一ですが、万が一上書きしてしまったときにはこういう方法があるということを覚えておくと便利です。

参考


2018/03/16

お知らせ:「 Life with Python Books 」を公開しました

サブドメイン books に Life with Python Books という名前でサイトを公開しました。


サイトは、日本語の Python 関連書籍の情報を集めたシンプルなサイトです。

一昔前 Python 本といえば、ひとりですべての本を把握できるぐらいの数だったような気がしますが、近年は Python 需要・ Python 人気の高まりを受けてか Python 関連書籍が多く出版されるようになりました。

最近の状況を見ると、 2017 年は Python 関連書籍が毎月少なくとも 1 冊はコンスタントに出版され、年間通してざっと 40 冊以上出版されたようです。そして 2018 年は 1 〜 3 月の 3 ヶ月間に私が把握しているだけでも 15 冊以上出版される(された)ので、この調子が続くと 2018 年は年間 60 冊以上、去年の 1.5 倍となる見込みです。ここ数年の勢いはすごいものがありますが、海外の Python の人気ぶりなども見るとこの流れは今後少なくとも数年は続きそうな感じがします。

(お断り: ここで「 Python 関連書籍」とは、タイトルに「 Python 」が入っているものだけでなく、何らかの形で Python に関係している「広い意味での Python 関連本」を指しています)

どんどん出版される Python 関連書籍について情報を一箇所にまとめたサイトを作りたいと思い今回作ってみました。

既存の EC サイトや出版社サイトに対する私の印象ですが、 EC サイトは Python 本だけを自分のニーズに合った切り口で検索するということがやりづらく、出版社のサイトは当然その出版社の書籍の情報しか見れず不十分、という印象です。いずれも「 Python 縛りで本を探したい」ときにいまいち使い勝手がよくないイメージでした。

このサイトを使って、私と同じように Python 本に興味がある方が、よりかんたん・スムーズに書籍を探せるようになればいいなと思います。

ただ、サイトの状態としては、まだ、 Python 関連書籍がようやく一通り登録でき(日本で出版されている主要な Python 本は 90% 以上網羅できているはずです)、各書籍の目次がひとまず揃った、というぐらいの状態です。改善の余地はまだまだあるので、タイトルの末尾に「 β 」と付けています。最初から完璧を求めるといつまでも公開できずにそのままお蔵入りになる可能性があったので、 β 版レベルで公開してしまおう、ということになりました。今後継続的に少しずつ改善をはかっていくつもりです。

Python 本に興味のある方はよろしければ使ってみてください。


永遠に β 版のままになる可能性もなきにしもあらず・・・ですが、今の時点では継続的に改善していきたいと思っているので、ご要望やお気づきの点がありましたらコメントや次のフォーム等でお気軽にご連絡ください :)

2018/03/06

Python Tips:関数全体をコンテキストマネージャでかんたんにラップしたい

Python には with 構文で使える「コンテキストマネージャ」という種類のオブジェクトがあります。

with open('log.txt', 'w') as f:
    f.write('これはログです。')

この open() の戻り値の f がコンテキストマネージャです。

このコンテキストマネージャを使って関数をまるごとラップする方法を今回はご紹介してみたいと思います。

結論は「デコレータを使う」です。以下に具体例をあげてご説明していきます。

例として、 Fabric3 ( Python 3 に対応した Fabric のフォーク版)を使った、次のようなケースを考えてみましょう。

fabfile.py:

from fabric.api import cd, run, task

PROJECT_DIR = '/path/to/the/project/root'


@task
def git_status():
    '''`git status` を実行する'''
    with cd(PROJECT_DIR):
        run('git status')


@task
def git_pull():
    '''`git pull` を `master` ブランチに対して実行する'''
    with cd(PROJECT_DIR):
        run('git pull origin master')

ここでは git_status()git_pull() という 2 つの Fabric タスクが定義されています。いずれのタスクも、 PROJECT_DIR で表されたプロジェクトのルートディレクトリで処理を実行する前提となっています。そのために、 fabric.api.cd() を使って with cd(PROJECT_DIR) でコンテキストを作ってその中で実際の処理を実行する形になっています。

この with cd(PROJECT_DIR) の部分は共通です。タスクが 2 つの場合はそれほど気になりませんが、同じ with を使うタスクが増えてくるとこれを共通化したくなることでしょう。後々タスクが増えてきたときのためにコンテキストマネージャを共通化することを考えてみましょう。

結論としては上述のとおり、「関数全体をコンテキストマネージャでラップするデコレータを作る」形がよいかと思います。

実際にデコレータを導入してみた場合のコードは次のとおりとなります。

from functools import wraps
from fabric.api import cd, run, task

PROJECT_DIR = '/path/to/the/project/root'


def decorate_cd(directory):
    '''`fabric.api.cd()` で関数をラップするデコレータを生成する'''
    def decorator(func):
        '''関数をラップするデコレータ'''
        @wraps(func)
        def wrapper(*args, **kwargs):
            with cd(directory):
                func(*args, **kwargs)

        return wrapper

    return decorator


@task
@decorate_cd(PROJECT_DIR)
def git_status():
    '''`git status` を実行する'''
    run('git status')


@task
@decorate_cd(PROJECT_DIR)
def git_pull():
    '''`git pull` を `master` ブランチに対して実行する'''
    run('git pull origin master')

ポイントはもちろん、デコレータを生成する decorate_cd() 関数です。関数が 3 重に入れ子になっていてなんだかややこしそうに見えるので、外側の関数から順にご説明します。

decorate_cd() これは、ディレクトリのパスを受け取ってデコレータとなる関数を返す関数です。この関数自体はデコレータではなく、あくまでも「デコレータ関数を生成する関数」です。


decorator() decorate_cd() によって生成され、実際に関数に対してデコレート処理を行う関数です。関数に対するデコレータは「ただひとつの引数として関数を受け取って戻り値として関数を返す」ものである必要がありますが、この関数は func を引数として受け取り、 wrapper を戻りとして返しています。

wrapper() これは、実際に fabric.api.cd() のコンテキストマネージャで関数をラップする実際にラッパー関数です。

この構造で作られた decorate_cd() を使うことで、関数をかんたんにコンテキストマネージャでラップすることができるようになります。

デコレータで装飾された関数

@decorate_cd(PROJECT_DIR)
def git_status():
    '''`git status` を実行する'''
    run('git status')

は Python では

def git_status():
    '''`git status` を実行する'''
    run('git status')

git_status = decorate_cd(PROJECT_DIR)(git_status)

と同じ意味になるので、 git_status() 関数が無事に fabric.api.cd() でラップできていることになります。

うまく使えると便利かと思います。以上、関数全体をコンテキストマネージャでかんたんにラップする方法についてでした。


参考


2018/02/20

ライブラリ: Pipenv



Python のパッケージ管理用のライブラリ「 Pipenv 」をご紹介します。

$ pipenv



Pipenv とは


Pipenv は Python のパッケージ(≒ライブラリ)をプロジェクト単位で管理するためのライブラリです。 Requests の作者として有名な Kenneth Reitz 氏 が 2017 年に始められたプロジェクトのようです。

pip や virtualenv 等を裏で組み合わせて、よりよい DX (Developer eXperience) を提供するライブラリです。

公式のドキュメントでは次のような説明がされています。

Pipenv — the officially recommended Python packaging tool from Python.org, free (as in freedom).

Pipenv is a tool that aims to bring the best of all packaging worlds (bundler, composer, npm, cargo, yarn, etc.) to the Python world. Windows is a first–class citizen, in our world.

It automatically creates and manages a virtualenv for your projects, as well as adds/removes packages from your Pipfile as you install/uninstall packages. It also generates the ever–important Pipfile.lock, which is used to produce deterministic builds.

少し長いですが、以下、意訳です。

Pipenv — Python.org で公式に推奨されている Python パッケージングツール。フリー(「自由」という意味で)。

Pipenv はパッケージングの世界( bundler / composer / npm / cargo / yarn 等)のいいとこ取りをすることを目的としたツールです。 Windows は、 Pipenv の世界では第一級市民です。

Pipenv は、あなたのプロジェクトの virtualenv を自動的に作成・管理し、あなたがパッケージをインストールまたはアンインストールしたときに Pipfile へのパッケージの追加や削除も行います。まったく共通のビルドを行うために使われる、非常に重要な Pipfile.lock の生成も行います。

イメージとしては、公式の説明のとおり、他のプログラミング言語で定番となっている各種パッケージ管理ツールと近い使用感で使えるパッケージマネージャーです。 npmyarnbundlecomposer 等のコマンドに馴染みのある方であれば抵抗なくすぐに使い始められるのではないかと思います。

Pipenv の特徴のまとめ


Pipenv の主な特徴をかんたんにまとめると次のとおりです。

  • プロジェクト単位でパッケージのインストール・アンインストールが行える
  • プロジェクト単位のパッケージは、ユーザ毎に virtualenv をまとめたディレクトリの下にインストールされ一元管理される
  • インストールされたパッケージの情報は Pipenv ファイルと Pipenv.lock ファイルに記録される
  • Pipenv にはインストールを実行した人が指定したパッケージのバージョンが記録される
  • Pipenv.lock には Pipenv が実際にインストールをしたパッケージのバージョンが記録される
  • PipenvPipenv.lock を共有すると、同じバージョンのパッケージをインストールしたプロジェクト環境がかんたんに再現できる
  • 環境変数を定義した .env ファイルが存在する場合はその中身が virtualenv 上で読み込まれる

私は個人的には「 Python のプロジェクト毎のライブラリを npm や yarn 風に管理できる、 pip と virtualenv の統合ラッパー」といったイメージを持っています。

Pipenv の基本的な使い方


すべての使い方をここで網羅するのは難しいので、基本的な使い方のみをご紹介します。

その他の基本的な使い方は pipenv--help オプションで、より詳細の使い方は公式のドキュメントで確認ができるので、詳しく知りたい方は 公式のドキュメント 等をご参照ください。

Pipenv のインストール

まずはインストール方法から見ていきましょう。前提として、 Python 3 と pip がインストールされているものとします。

$ pip install pipenv

pipenv コマンドが使えるようになれば、インストールは成功です。

$ pipenv --version
pipenv, version 9.0.3

Pipenv によるプロジェクト単位のパッケージ環境( virtualenv )の作成

Pipenv はプロジェクト単位で利用するものなので、続いて、プロジェクトのルートディレクトリを作りましょう。

$ mkdir myproject
$ cd myproject/

プロジェクトの中で Pipenv を初期化します。

$ pipenv --python 3.6
Creating a virtualenv for this project…
Using /usr/local/bin/python3.6m to create virtualenv…
⠋Running virtualenv with interpreter /usr/local/bin/python3.6m
Using base prefix '/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6'
New python executable in /Users/hayato/.local/share/virtualenvs/myproject-abX3VXqy/bin/python3.6
Also creating executable in /Users/hayato/.local/share/virtualenvs/myproject-abX3VXqy/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /Users/hayato/.local/share/virtualenvs/myproject-abX3VXqy
Creating a Pipfile for this project…

補足ですが、このコマンドを実行したときの私の環境では、 Homebrew を使ってインストールした Python 3.6 が /usr/local/bin/python3.6m に、そこから作った virtualenv が /Users/hayato/.local/bin にいて、それらを使って Pipenv を実行しています。

上の出力からわかるとおり、最初に myproject の後に - を付けてランダムな文字列(?)をつなげた virtualenv ディレクトリの下に virtualenv が作成され、 setuptoolspipwheel の 3 つのライブラリがインストールされた virtualenv が作成されました。そして最後に Pipfile が作成されました。

ちなみに、このプロジェクトのルートディレクトリ myproject はデスクトップにあり、上の ~/.local/share/virtialenvs ディレクトリとは無関係の場所にいます。

上のコマンドが成功したら、カレントディレクトリに Pipfile ファイルが作られているはずです。

$ cat Pipfile

中身は次のとおりで、最小限のプロジェクト情報を格納したファイルとなっています(このあたりは Pipenv のバージョンが上がると変わる可能性があります。参考にされる際はご注意ください)。

[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"


[packages]



[dev-packages]



[requires]

python_version = "3.6"

virtualenv の場所はコマンドで確認したりすることができます。

pipenv --venv
/Users/hayato/.local/share/virtualenvs/myproject-abX3VXqy

パッケージのインストール

プロジェクトのパッケージ環境を格納する virtualenv が用意できたら、自由にパッケージをインストールできます。方法は pip の場合と同様です。

pipenv コマンドにサブコマンド install を指定してパッケージ名を指定します。

$ pipenv install Django
Installing Django…
Collecting Django
  Using cached Django-2.0.2-py3-none-any.whl
Collecting pytz (from Django)
  Using cached pytz-2018.3-py2.py3-none-any.whl
Installing collected packages: pytz, Django
Successfully installed Django-2.0.2 pytz-2018.3

Adding Django to Pipfile's [packages]…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (374a8f)!

処理が無事に完了すると、プロジェクトのルートディレクトリにある Pipfile ファイルが更新され、同じ場所に Pipfile.lock ファイルが生成されます。

中身を確認してみましょう。 Pipfile ファイルの中の [packages] の下に django の行が追加されました。

[packages]

django = "*"

Pipfile.lock の方には、実行した環境のさまざまな情報と、実際にインストールされたパッケージの情報が記述されています。

同じ要領で他のパッケージも続けてインストールしていくとよいでしょう。

$ pipenv install requests
$ pipenv install mysqlclient
$ pipenv install uwsgi

もちろん、バージョンを指定してインストールすることも可能です。

$ pipenv install django==1.11

完全一致は == 、互換性維持は ~= といった感じで、バージョン指定の方法には pip と同じものが使えるようです(こちらもバージョンが上がると変わる可能性があるので、参考にされる際はご注意ください)。

実際のプロジェクトでは、 PipfilePipfile.lock を Git (等の VCS )の管理下に置くことで、複数人や複数の環境でコードを共用するときに同一のパッケージ環境をかんたんに共有できるようになります。

ここまで読んだときに、 Python 歴が長い方や pip に詳しい方であれば「あれ、 pip + requirements.txt と何がちがうんだろう・・・」とお思いのことかと思います。このあたりについては、説明したいのですが・・・すみません、私には正確な説明ができないので、「 pip + requirements.txt 」の課題について Kenneth Reitz 氏が語った次の記事を参照していただければと思います。


パッケージのアップデート

すでにインストールされたパッケージをアップデートしたい場合は、サブコマンド update を使用します。

$ pipenv update django

パッケージのアンインストール

インストールされたパッケージをアンインストールするには、サブコマンド uninstall を使用します。

$ pipenv uninstall django

Pipfile 入りのプロジェクトの依存関係のインストール

誰か他の人が作った、 Pipfile ファイルが含まれたプロジェクトにおいて、依存関係をインストールするには pipenv install コマンドを実行します。

$ pipenv install

virtualenv の有効化

Pipenv でインストールされたパッケージは virtualenv の中にいるので、そのままでは利用できません。利用するには virtualenv を有効化する必要があります。

Pipenv が作成した virtualenv を有効にするには pipenv shell コマンドを利用します。

$ pipenv shell
Spawning environment shell (/usr/local/bin/bash). Use 'exit' to leave.
Loading init files ........... done!
source /Users/hayato/.local/share/virtualenvs/myproject-abX3VXqy/bin/activate

無事に virtualenv が有効化できたら、 Python のパスが virtualenv のものになり、インストールしたパッケージが利用できることが確認できるでしょう。

(myproject-abX3VXqy) $ which python
/Users/hayato/.local/share/virtualenvs/myproject-abX3VXqy/bin/python

(myproject-abX3VXqy) $ pip freeze | grep requests
requests==2.18.4

(myproject-abX3VXqy) $ django-admin startproject projectX .

virtualenv を有効化しているうちはコマンドプロンプトの前に ( virtualenv 名) が付くので、 virtualenv が有効になっているかどうかはそこで確認することができます。

有効になった virtualenv を無効にして元に戻るには、 exit コマンドを使います。もちろん、 exit のショートカット、たとえば MacOS なら ctrl + d を使うこともできます。

(myproject-abX3VXqy) $ exit
$

virtualenv を有効にしてコマンドを実行

特定のコマンドを実行するときだけ virtualenv を利用したい場合はサブコマンド run を使います。

$ # Pipenv で管理されている virtualenv を利用して `django-admin` コマンドを実行する
$ pipenv run django-admin startproject projectX .

$ # Pipenv で管理されている virtualenv を利用して `manage.py` ファイルを実行する
$ pipenv run app/manage.py runserver

他の言語のパッケージマネージャではそれぞれ次のコマンドに相当するイメージです(間違えていたらすみません・・・)。

Bundler (Ruby):

$ bundle exec rails ...

Npm (Node):

$ ./node_modules/.bin/ava

Composer (PHP):

$ composer run phpunit ...

virtualenv の削除

Pipenv で作成した virtualenv が不要になったら、 virtualenv を削除しましょう。

削除は他のサブコマンドとは少しちがって、 pipenv コマンドに --rm オプションを渡して実行する形になっています。

$ pipenv --rm
Removing virtualenv (/Users/hayato/.local/share/virtualenvs/myproject-abX3VXqy)…

個人的には、 virtualenv は削除し忘れることがよくあるので、一元管理されているディレクトリをときどきチェックして不要になったものがないか確認するようにしています。

Pipenv でのオートコンプリートの有効化

Pipenv にはサブコマンドのオートコンプリート(自動補完)機能が付いています。

pipenv --completion を実行するとオートコンプリートを有効にするスクリプトが出力されるので、もしオートコンプリートを利用したいなら .bashrc などで用意するようにしておくとよいでしょう。

ちなみに、私の Bash で実行すると次のように出力されました。

$ pipenv --completion

_pipenv_completion() {
    local IFS=$'\t'
    COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \
                   COMP_CWORD=$COMP_CWORD \
                   _PIPENV_COMPLETE=complete-bash $1 ) )
    return 0
}

complete -F _pipenv_completion -o default pipenv

以上です。

Pipenv には、今回は取り上げなかった機能として次のようなもの等もあります。

  • 既知の脆弱性のチェック( safety を利用)
  • Flake8 のスタイル違反のチェック
  • 使われていない可能性のあるパッケージのチェック

より詳しく知りたい方は公式ドキュメントをご覧になってみてください。




参考


日本語

こちらの記事はとてもわかりやすいです。


英語

いずれも Kenneth Reitz 氏本人の公式ドキュメント / 公式ページですが、これがいちばんわかりやすい気がします。

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() についてでした。