2015/06/24

Python Tips:Python のバージョンによって処理を変えたい

Python において、 Python のバージョンによって処理を変更する方法をご紹介します。

結論としては sys.version オブジェクトを利用する方法が最も Pythonic でシンプルかと思います。

# Python 2.x でなければ現在の Python のバージョンを表示して処理を終了する

import sys

if sys.version_info.major != 2:
    sys.stderr.write('Please use python 2.7. Your python version is as following:\n')
    sys.stderr.write('{}\n'.format(sys.version))
    exit()

# 以下メインの処理

上のコード内でも使用していますが、 sys.version にはバージョン情報を表す文字列が入っています。また、 sys.version_info にはバージョン情報に関係するさまざまな情報が含まれています。

import sys

# 2.7.6 の場合
prtin(sys.version_info)
# => '2.7.6 (default, Sep  9 2014, 15:04:36) \n[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)]'

print(sys.version.major)  # => 2
print(sys.version.minor)  # => 7
print(sys.version.micro)  # => 6

以上です。

2015/06/17

ライブラリ:Fabric

Python のパッケージである Fabric をご紹介したいと思います。

import fabric

Fabric とは


Fabric はいわゆる「デプロイツール」と言われるもののひとつで、 ssh での一連の処理を自動化するためのライブラリです。
Python らしいかんたんなインタフェースとなっており、比較的スムーズに使いはじめることができるのが特徴です。
同種のツールとしては Ruby の Capistrano があります。

公式ページには次のように紹介されています。

Fabric is a Python (2.5-2.7) library and command-line tool for streamlining the use of SSH for application deployment or systems administration tasks.

It provides a basic suite of operations for executing local or remote shell commands (normally or via sudo) and uploading/downloading files, as well as auxiliary functionality such as prompting the running user for input, or aborting execution.

意訳すると次のような感じになるでしょうか。

Fabric は Python (2.5-2.7) のライブラリで SSH を使いやすくするしてくれるコマンドラインツールです。アプリケーションのデプロイやシステム管理のために使うものです。

ローカル・リモートでのシェルコマンドの実行(通常のものと sudo 付きのもの)やファイルのアップロード・ダウンロードのための基本的な処理のセットを提供します。ユーザからの入力受付のためのプロンプトや処理の中断などの補助的な機能もあわせて提供します。

私が思う Fabric を使うことのメリットは次のとおりです。

- 基本的なシェルコマンドを知っていれば全体の流れは Python 上で書ける
- 分岐やループ処理がかんたん
- 例外・エラーの処理がかんたん
- 準備がかんたん: ローカルで fabric をインストールすればすぐに使い始められる

では基本的な使い方をかんたんにご紹介していきます。

インストール


まずはパッケージをインストールする必要があります。記事執筆時点の私の Mac 環境では pip だけでかんたんにインストールできました。

$ pip install fabric

Fabric で Hello World


では最初の Hello World をしてみましょう。
基本的な流れとしては、まずは fabfile.py をカレントディレクトリに作成して、その中の処理を fab コマンド呼び出す、という形になります。

まずは以下のコードを fabfile.py に保存しましょう。

# coding: utf-8
from fabric.api import local, run

# ホスト名を指定 
# 複数個渡せばそのそれぞれに対して同じ処理が実行される
env.hosts = ['your_host_name']

def hello_world():
    # ローカル環境で echo を実行
    local('echo "Hello, world"')
    # リモート環境で echo を実行
    run('echo "Hello, world"')

つづいて fab コマンドでこちらを実行します。

$ fab hello_world

fab コマンドはこの fabric が提供するコマンドで、カレントディレクトリの fabfile.py の中の関数をコマンドラインから呼び出す機能を提供します。

上記のコマンドを実行するとまず最初にローカル環境で Hello, world がエコーされ、つづいてリモートホスト your_host_name 上で Hello, world がエコーされます。リモートに接続する際には通常の SSH が開始されるときの処理が自動的に走ります。パスワードで入る設定の場合にはパスワード入力のプロンプトが現れ
公開鍵で入る設定の場合にはそのまま SSH 接続が確立されます。

fabfile.py 内の関数は引数を受け取ることもできます。

def hello(name):
    # ローカル環境で echo を実行
    local('echo "Hello, {}"'.format(name))
    # リモート環境で echo を実行
    run('echo "Hello, {}"'.format(name))

引数に渡す値(文字列)を指定するには fab コマンドで関数名の後に「 : 」を付けてその後に渡します。

$ fab hello:japan

Fabric の基本的な関数


基本的な関数を覚えておけば、大体の処理は行うことができるかと思います。

- local
- run
- lcd
- cd
- get
- put

from fabric.api import local, run, lcd, cd, get, put

# ローカルでコマンドを実行
local('コマンド')  

# リモートでコマンドを実行
result = run('コマンド')  

# ローカルの特定のディレクトリでコマンドを実行
with lcd('ディレクトリ'):  
    local('コマンド')

# リモートの特定のディレクトリでコマンドを実行
with cd('ディレクトリ'):  
    result = run('コマンド')

# SFTP でリモートからローカルにファイルをコピー
get('リモートのパス', 'ローカルのパス')

# SFTP でローカルからリモートにファイルをコピー
put('リモートのパス', 'ローカルのパス')

## 処理を終了
abort('これ以上何もできません')

ファイルの存在チェックなども行えます。

from fabric.contrib import files
from fabric.api import abort

# filename ファイルがすでに存在すれば処理を中断して終了する
if files.exists(filename):
    abort('File {} already exists'.format(filename))

以上です。


参考
Welcome to Fabric! — Fabric documentation

2015/06/11

Python Tips:複数の辞書を統合したい

Python で 2 つの辞書を統合する方法をご紹介します。

基本的には 辞書型の update メソッドを使う形が最もシンプルかつかんたんな方法です。

land_animal = {'uma': 3, 'ushi': 5, 'kame': 7}
sea_animal = {'kujira': 10, 'kame': 12}

land_animal.update(sea_animal)

print(land_animal) # => {'kame': 12, 'kujira': 10, 'uma': 3, 'ushi': 5}

update は Ruby でいうところの破壊的メソッドで、レシーバそのものを変更してしまう点に注意が必要です。上の例では land_animal が書き換えられます。 land_animal を残しておきたい場合は copy してから update するのがよいでしょう。

all_animal = land_animal.copy()
all_animal.update(sea_animal)

ちなみに update 関数は他の辞書を受け取る形だけでなくキーワード引数を渡す形で使うことも可能です。

land_animal = {'uma': 3, 'ushi': 5, 'kame': 7}

land_animal.update(uma=10)
print(land_animal)  # => land_animal = {'uma': 10, 'ushi': 5, 'kame': 7}

辞書型を同じキーを持つ要素の値を足し合わせる形で使うには collections.Counter を使うのが便利です。 collections.Counter クラスのインスタンスでは辞書型にはない「 + 」演算子がサポートされています。

from collections import Counter    

land_animal = {'uma': 3, 'ushi': 5, 'kame': 7}
sea_animal = {'kujira': 10, 'kame': 12}

land_animal_counter = Counter(land_animal)
sea_animal_counter = Counter(sea_animal)
all_animal_counter = land_animal_counter + sea_animal_counter

all_animal = dict(all_animal_counter)


collections — High-performance container datatypes — Python 公式ドキュメント

2015/06/03

Python Tips:type() と isinstance() をうまく使い分けたい

Python には type() と isinstance() というよく似た関数が存在します。今回はそれらの使い分け方法についてご紹介します。

結論からいうと、次のようなちがいがあります。

- type() そのインスタンスを生成したクラスを返す
- isinstance() ふたつの引数がオブジェクトとクラスまたはそのスーパークラスの関係にあれば True を返す

class Onitsuka(object):
    pass

class Puma(Onitsuka):
    pass


p1 = Puma()

# type は直接そのインスタンスのクラスを返す
print(type(p1) == Puma)      # => True
print(type(p1) == Onitsuka)  # => False

# isinstance 継承元も含めてチェックする
print(isinstance(p1, Puma))      # => True
print(isinstance(p1, Onitsuka))  # => True

以上です。

ちなみに、クラス間の継承関係をチェックするには issubclass() 関数やクラスメソッド mro() を使うことができます。

Python Tips:クラスの継承関係をチェックしたい

Differences between isinstance() and type() in python
- StackOverflow