2016/09/27

Python Tips:デコレータに引数を渡したい

Python のデコレータに引数を渡す方法について見てみます。

具体的には「引数を取ることができるデコレータの作り方」を見ていきます。

まずはかんたんにおさらいから。 Python では `@デコレータ` という形でデコレータを使うことができます。

# デコレータ関数 add_print() の定義
def add_print(func):
    def wrapper(*args):
        print("{} is invoked.".format(func.__name__))
        func(*args)

    # ラップした関数そのものを返す
    return wrapper

# デコレータ関数の利用
@add_print
def myfunc():
    print("Hello!")

# 動作確認
myfunc()
# =>
# myfunc is invoked.
# Hello!

myfunc() を add_print() でデコレートできていることが確認できます。

デコレータ機能をシンプルに使うだけの場合はこれでよいのですが、デコレータを使ってちょっと凝ったことをやろうとするとデコレータにパラメータを渡したくなってきます。今回はその方法を見ていきます。

作り方の前に使い方を先に見てみましょう。引数を受け取れるデコレータは次のように使います。

@デコレータ関数を生成する関数(引数)
def デコレート対象の関数(引数):
    関数の中身

引数を取らないデコレータの場合と見比べてみましょう。引数を取らないデコレータは次のように書きます。

@デコレータ関数
def デコレート対象の関数(引数):
    関数の中身

両者のちがいは、「 @ 」の後ろが「デコレータ関数を生成する関数(引数)」となっているか「デコレータ関数」となっているかです。

「デコレータ関数を生成する関数」と書いているのはまちがいではありません。引数を受け取れるデコレータというのは、実は「引数を受け取ってデコレータ関数を生成する関数」にほかなりません。

・・・概念的な説明だけだと何を言ってるのかわけわからないので具体例を見てみましょう。

sample.py:

# coding: utf-8

import os


def main():
    """main 関数
    """
    print("CSV generator.")
    names = None
    while not names:
        print("Enter file names with comma delimiter: ", end="")
        names = input().strip().split(",")

    create_files(*[name + ".csv" for name in names])


def add_print(pattern):
    """ファイルの作成前後に表示を行うデコレータ
    """
    def _add_print(func):
        def wrapper(*args):
            print("Creating {} {} ... ".format(pattern, ",".join(args)), end="")
            func(*args)
            print("Done!")
        return wrapper

    return _add_print


@add_print("files")
def create_files(*paths):
    """空のファイルを作成する
    """
    for p in paths:
        with open(p, "a"):
            os.utime(p, None)


if __name__ == "__main__":
    main()

動作イメージ:

python sample.py
CSV generator.
Enter file names with comma delimiter: a,b
Creating files a.csv,b.csv ... Done!

少し長いですが、ポイントになるのはごく一部です。

デコレータの使用場所は以下の部分です。

@add_print("files")
def create_files(*paths):
    """空のファイルを作成する
    """
    for p in paths:
        with open(p, "a"):
            os.utime(p, None)

この add_print() というのが上述の「引数を受け取ってデコレータ関数を生成する関数」になります。これはデコレータ関数ではなく、引数 files を受け取ってデコレータ関数 _add_print() を生成しています。そして、戻り値である _add_print() が create_files() を実際にデコレートし、 wrapper() という関数に置き換えてデコレート処理が完了するという流れです。

ということで、「引数を受け取れるデコレート関数」というものは実は見た目上のものであって、実際には「引数を受け取ってデコレート関数を生成する関数」を作成する必要があります。

元々デコレータ関数というもの自体が「関数を返す関数」なので、「デコレータを返す関数」となると自然「関数を返す関数を返す関数」という入れ子構造となります。見た目はとてもややこしいのですが、自分で実際に書いてみると構造がよくわかってすっきり見通せるようになるので、興味があってまだ書いたことのない方はよろしければぜひ自分の手でひとつ書いてみてください。

参考:

- Pythonのデコレータの使い方

2016/09/01

Python Tips: Mac で ValueError: unknown locale: UTF-8 のエラーを解決したい

Mac OS X のターミナルで Python を利用していると次のようなエラーが出ることがあります。

ValueError: unknown locale: UTF-8

エラーの長いバージョンは次のような形です。

Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/locale.py", line 560, in getdefaultlocale
    return _parse_localename(localename)
  File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/locale.py", line 487, in _parse_localename
    raise ValueError('unknown locale: %s' % localename)
ValueError: unknown locale: UTF-8

今回はこの問題の解決方法を見ていきたいと思います。

この問題は私の場合は matplotlib を使っているときに遭遇しましたが、検索してみると結構多くの方が遭遇されていて、 matplotlib にかぎらずいろんな状況で発生するようです。 Python 以外でも多く発生しているようです。

まずは問題の原因から見ていきます(自分なりに調べて書いていますが、間違っていたらすみません・・・)。

原因


問題の原因は、ターミナルの環境変数の locale 周りの設定がまずいことのようです。

標準ライブラリ locale の中に getdefaultlocale() という関数があるのですが、これが呼び出されたときに、デフォルトのロケール情報を取得するために次の環境変数の値を見にいきます。

- LC_ALL
- LC_CTYPE
- LANG
- LANGUAGE

その際に、これらの変数の値として「 ja_JP.UTF-8 」といったフォーマットの文字列が入っていることが期待されているようです。

しかし、 Mac のターミナルで環境変数を確認してみると、次のようになっていたりします。

$ locale
LANG="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_CTYPE="UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_ALL=

この場合に次のようなことが起こります。

上のリストの上から順に値がチェックされますが、 Python は LC_ALL は空なのでスルーします。次に LC_CTYPE をチェックするとこれには値が入っているのでこれをロケール情報として利用しようとします。ただ、格納されている値が「 UTF-8 」という予想しないフォーマットなので、うまく処理できず例外を上げる形になります。

対策


対策の方向性は 2 通りあるようです。

- 対策 A: 環境変数をセットする
- 対策 B: ターミナルの設定を変える

対策 A: 環境変数をセットする

上述のとおり、最初に LC_ALL がチェックされるので、これに変数をセットしておけば OK のようです。例えば Bash を使っている場合は .bashrc の中に次の 1 行を追加します。

export LC_ALL=ja_JP.UTF-8

エラーが発生するかどうかは次のコマンドでかんたんにチェックできます。

$ python -c 'import locale; print(locale.getdefaultlocale());'

エラーが出なくなれば OK です。

ちなみに上のとおりに LC_ALL をセットしてもよいですが、 LANG の値をそのまま流用するなら次のとおりとなるでしょうか。こちらの方がスマートなような気がします。

export LC_ALL=$LANG


対策 B: ターミナルの設定を変える

もうひとつの対策方法はターミナルの設定を変える方法です。

Terminal の場合と iTerm の場合について説明します。

Terminal の場合は、設定の「 Preferences → Profiles → Advanced 」を開きます。「 Set locale environment variables on startup 」というチェックボックスを見つけて、そのチェックを外します。


iTerm の場合も Terminal と同様です。設定の「 Preferences → Profiles → Terminal 」を開きます。「 Set locale variables automatically 」というチェックボックスを見つけて、そのチェックを外します。


いずれも、設定を変更した後新しいセッションを開くと設定が反映された状態になるので、次のコマンドなどで確認します。

$ python -c 'import locale; print(locale.getdefaultlocale());'

私の場合は例外が出なくなりました。

ちなみにこの場合の locale コマンドの出力結果は次のとおりでした。

$ locale
LANG="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_CTYPE="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_ALL=

LC_CTYPE が他と同じ「 ja_JP.UTF-8 」のフォーマットになっていることがわかりますね。

この「 locale の変数を自動セットする機能」については他の部分にも影響する可能性があるので副作用には注意が必要ですが、個人的には A の対策よりも本質的でシンプルでよい感じがします。

以上です。

ご参考になれば幸いです。

参考
- sphinx で ValueError: unknown locale: UTF-8 というエラーが出た - Please Sleep
- python - Pelican 3.3 pelican-quickstart error "ValueError: unknown locale: UTF-8" - Stack Overflow
- Fix "ValueError: unknown locale: UTF-8" under Mac OS X 10.7 - Lion
- encoding - In OSX Lion, LANG is not set to utf8, how fix? - Stack Overflow
- Setting locales correctly on Mac OSX Terminal application « Remi Bergsma's blog

2016/08/08

ライブラリ:csvtomd

Python のライブラリ「 csvtomd 」をご紹介します。

- mplewis/csvtomd: Convert your CSV files into Markdown tables.

$ pip3 install csvtomd

csvtomd は「 CSV を Markdown のテーブルに変換」するツールです。名前そのままですね。プログラムの中で import して使用するものではなく、ターミナル上で利用できるコマンドを提供してくれます。 Python のコードを書かなくても日頃 Markdown を使う方であれば利用することができます。

コマンド名はそのまま「 csvtomd 」です。

インストール


インストール方法は pip でそのままインストールします。 Python 3 にのみ対応しているとのことで、公式では `pip3` でのインストールコマンドが紹介されています。

$ pip3 install csvtomd

使い方


使い方はシンプルで、対象となる CSV のファイル名を引数に渡すだけです。 CSV を解釈し Markdown 化したテーブルを出力してくれます。日本語にもばっちり対応しています。

$ # 対象の CSV を確認
$ cat analysis.csv
名前,鼻,爪
犬,◯,出しっぱなし
猫,△,収納可
人間,×,出しっぱなし
$ # csvtomd で CSV を Markdown に変換
$ csvtomd analysis.csv
名前  |  鼻  |  爪
----|-----|--------
犬   |  ◯  |  出しっぱなし
猫   |  △  |  収納可
人間  |  ×  |  出しっぱなし

変換対象のファイルは複数受け取ることも可能です。

利用可能なオプションは次のとおりです。

- -h --help: ヘルプを表示する。
- -n --no-filenames: 複数の Markdown テーブルを出力する際にファイル名を表示しない。
- -p --padding: パディングの半角スペースの数を整数で指定する。デフォルトは 2 。
- -d --delimiter: 区切り文字を指定。デフォルトは , 。

以上です。

私はここ数年、仕事をしている日はほぼ毎日 Markdown を触りますが、 Markdown ではテーブルの編集が大変で・・・なかなかテーブルを使いたくありません。テーブルについてはスプレッドシートツールを使う方がずっとかんたんなので、テーブルについてはあえてひと手間入れて「スプレッドシートで編集 → csvtomd などで Markdown 化 → Markdown に貼り付け」の形がよいなぁと思います。

2016/07/28

Python Tips:リストの tail を取得したい

Python でリストの tail 部分(先頭要素を除くすべての要素)を取得する方法をご紹介します。

いくつものアプローチが考えられるかと思いますが、代表的なものを 5 つほどあげてみます。

1. ふつうにループを回す方法


まず最初にふつうにループを回す方法を見てみましょう。

# 対象のリスト
l1 = ['mouse', 'cow', 'tiger', 'rabbit']

def tail1(list_original):
    """リストの tail を取得する( for ループを使用)。
    """
    list_tail = []
    for i, e in enumerate(list_original):
        if i > 0:
            list_tail.append(e)

    return list_tail

print(tail1(l1))
# => ['cow', 'tiger', 'rabbit']

どうということはないですね。「 Pythonic 」「関数型」なんていう考え方はさておいて、とりあえず書くとしたらこうなるでしょうか。もちろん Python らしくないのでこのパターンを使うのはやめましょう。

変数 l1 については以下の 2 つめ以降のサンプルでも使用します。

2. pop() メソッドを使う方法


続いて、リストの pop() メソッドを使って先頭の要素を削除する方法です。

def tail2(list_original):
    """リストの tail を取得する( pop() メソッドを使用)。

    オリジナルのリストの先頭要素が削除されるので要注意。
    """
    list_original.pop(0)
    return list_original

print(tail2(l1[:]))
# => ['cow', 'tiger', 'rabbit']

こちらはもともとのリストが破壊されてもよい(最初の要素がなくなってもよい)場合にのみ使用することができるパターンです。

3. スライスシンタックス [] を使用する方法


続いてスライスシンタックスを使う方法です。 target_list[1:] と書くことで、先頭以外のすべての要素をつかむことができます。

def tail3(list_original):
    """リストの tail を取得する(スライスシンタックスを使用)。
    """
    return list_original[1:]

print(tail3(l1))
# => ['cow', 'tiger', 'rabbit']

4. アンパック演算子を使用する方法


4 つめは * 演算子を使用する方法です。

def tail4(list_original):
    """リストの tail を取得する(アンパックのための * 演算子を使用)。
    """
    head, *tail = list_original
    return tail

print(tail4(l1))
# => ['cow', 'tiger', 'rabbit']

* を使えばきれいに head と tail を分割することができます。シンプルでわかりやすいですね。ちなみに head と tail に代入している行は次のように書くことも可能です。

[head, *tail] = list_original

ただしこちらは Python 3 限定の書き方であり、 Python 2 では使用することができません。 PEP 3132 で導入されました。

ちなみに、この「 * 」の演算子、 Ruby などでは splat (スプラット)演算子という名前が一般的ですが、 Python では公式な名前がついていないようです。。。 Ruby などにならって通称 splat 演算子で呼ぶのがわかりやすいでしょうか。

5. itertools.islice() を使用する方法


最後に itertools の islice() 関数を使う方法を見てみましょう。

import itertools

def tail5(list_original):
    """リストの tail を取得する( itertools.islice() を使用)。
    """
    return itertools.islice(list_original, 1, None)

print(list(tail5(l1)))
# => ['cow', 'tiger', 'rabbit']

islice() は iterable なオブジェクトを受け取り、その特定の区間を返すイテレータを返す関数です。

3 つの引数は前から順に「 iterable オブジェクト」「 start インデックス」「 stop インデックス」となります。 stop に None を渡すと無制限という意味になるので、この場合は「最初の 1 要素のみスキップして全要素取得する」という処理になります(結果をリストとして表示するために print() の前に list() でリスト化していますが、戻り値はイテレータです)。

もともとのリストが長い場合などにはこの islice() パターンを使うのがよいでしょう。

以上です。

パターン 1 2 はあくまでも比較用としてあげたものなので、通常使用するのはパターン 3 4 5 のいずれかがよいかと思います。


参考:

- PEP 3132 -- Extended Iterable Unpacking | Python.org
- proper name for python * operator? - Stack Overflow

2016/07/18

Python Tips: URL エンコード・デコードがしたい

Python で URL エンコード/デコードをする方法をご紹介します。

Python にも PHP の rawurlencode() や JavaScript の encodeURI() のような URI(URL) に関わる処理を行うためのライブラリが用意されています。 urllib.parse です( Python 3 の場合。 Python 2 の場合は urllib 直下に関数があります)。

import urllib.parse

urllib は標準ライブラリなので pip などで別途インストールする必要はありません。 Python をインストールするとすぐに使うことができます。

主な使い方を以下にかんたんに見ていきます。

URL をパース/デコードする


urllib.parse 内の urldecode() や parse_qs() 、 unquote() などが役立ちます。

import urllib.parse

parsed = urllib.parse.urldecode("http://www.lifewithpython.com/2015/01/02?m=1&tag=goto%27s")

parsed.scheme  => 'http'
parsed.hostname  => 'www.lifewithpython.com'
parsed.path  => '/2015/01/02'
parsed.query  => 'm=1&tag=goto%27s'

query = urllib.parse.parse_qs(parsed.query)
query  => {'m': ['1'], 'tag': ["goto's"]}

tag_value = urllib.parse.unquote('goto%27s')
tag_value  => "goto's"

ルールは RFC 1808 、 RFC 3986 などに基づくとされています。正確な処理が求められるところでは、ルールについて詳細を確認しておくとよいでしょう。

URL をエンコードする


パースとは逆にエンコードする場合です。

こちらは urllib.parse の urlencode() や quote() 、 urljoin() などを使用します。

import urllib.parse

urllib.parse.urlencode({'a': "goto's", 'b': '?foo bar'})
# => 'a=goto%27s&b=%3Ffoo+bar'

urllib.parse.quote('goto's')
# => 'goto%27s'

urllib.parse.urljoin('http://www.lifewithpython.com/abc/ruby.html', 'python.html')
# => 'http://www.lifewithpython.com/abc/python.html'

以上です。便利ですねー。

このあたりを自分で実装するとなるとかなり大変かと思うですが、ひととおり揃っているので助かります。ちょっとしたときにサッと使えるようになっておくと重宝するかと思います。


参考
- urllib.parse — Parse URLs into components — Python 公式ドキュメント

2016/07/14

Python Tips: Mac の辞書アプリを Python から利用したい

Mac に備え付けの辞書アプリを Python から利用する方法についてです。

import DictionaryServices

Mac には Python が同梱されており、その Python を使うと辞書アプリにアクセスすることができます。実際には

- 辞書アプリ API が Objective C 用に用意されており、
- さらに、その Objective C には PyObjC を使って Python からアクセスできる

ということで、 Python から辞書アプリへのアクセスが可能となっています。 Python → Objective C → 辞書アプリという流れですね。

早速実際のサンプルコードを見てみたいと思います。

mac_dict_app_short.py:

#!/usr/bin/python2.7
# coding: utf-8

# 辞書アプリにアクセスするためのパッケージ
from DictionaryServices import DCSGetTermRangeInString, DCSCopyTextDefinition

# 辞書で検索したい単語
word = u'平成'

# 辞書で単語を検索する
# 最初に DCSGetTermRangeInString で入力された文字列の中から有効な単語の範囲を割り出す
word_range = DCSGetTermRangeInString(None, word, 0)
# 続いて DCSCopyTextDefinition で辞書での検索結果を取得する
word_definition = DCSCopyTextDefinition(None, word, word_range)

print(word_definition)

こちらを mac_dict_app_short.py などのファイル名で保存し、 Mac OS X に同梱されている Python ( El Capitan の場合は /usr/bin/python2.7 )で実行します。無事に処理が成功すれば次のような結果が返ってくるでしょう。

$ /usr/bin/python2.7 mac_dict_app_short.py
へいせい【平成】Heisei.▸ 私は平成元年卒業だI graduated in 1989 [in the first year of Heisei]. (!特に日本の元号を明記する必要がある場合を除いて, 西暦を用いる)

かんたんに解説します。

DictionaryServices は Mac OS X に備え付けの辞書アプリを利用するためのパッケージ(ライブラリ)です。私の環境では実体は以下のディレクトリでした。

/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC/DictionaryServices

公式のドキュメントによると、このパッケージには大きく 2 つの機能があるようです。

1. 辞書アプリを使う機能。結果は辞書アプリを立ち上げて表示する。
2. 辞書アプリを使う機能。結果はプログラム内で返す。

辞書アプリを立ち上げる 1 の使い方でもいいのですが、せっかくターミナルから利用するのであればターミナル内で完結する 2 の形を使いたいものです。実際、今回のコードでは 2 の方を利用しています。

2 を実現するための具体的な関数がコード内で使用している 2 つの関数です。

- DCSGetTermRangeInString
- DCSCopyTextDefinition

前者の DCSGetTermRangeInString は渡された文字列の中から単語として有効な範囲(インデックス)を検索する関数です。後者の DCSCopyTextDefinition は実際に辞書を検索する関数です。

いずれも引数は、第 1 引数には None を、第 2 引数に検索したい単語を、第 3 引数にインデックスを渡す形になっています。詳しくは公式のドキュメントに書いてあるので興味のある方はそちらを参照してみてください。

- DCSGetTermRangeInString - Dictionary Services Reference
- DCSCopyTextDefinition - Dictionary Services Reference

GitHub Gist に任意の単語を検索できる完全版を置きました。こちらを使うと、標準入力の各行に対して辞書を引いた結果を返してくれます。調べたい単語がたくさんあって、単語をひとつずつ調べるのが面倒なときなんかには重宝するかと思います。

mac_dict_app.py:



使い方のイメージは次のとおりです。

$ cat words.txt
Mac
転勤族
IoT
$ chmod u+x mac_dict_app.py
$ ./mac_dict_app.py < words.txt
Mac
Mac 1 |mak| ▶noun trademark a type of personal computer. ORIGIN 1980s: from Macintosh, the brand name of a range of computers manufactured by Apple Inc.; the range was named after a variety of dessert apple (see McIntosh).
転勤族
てんきん【転勤】名詞a transfer /trǽnsfəːr/ .▸ 転勤希望one's transfer request.転勤する動詞▸ 彼は神戸支店に転勤したHe (was [got]) transferred /trænsfə́ːrd/ to the Kobe branch (office). (!受身では他人の意志で転勤させられた感じを伴う) ▸ 松井一男さんをご紹介いたします. このたび名古屋支社から転勤してこられましたI'd like to introduce Mr. Kazuo Matsui. He's just joined us from the Nagoya office.
IoT
IoT ▶noun short for Internet of things: nearly every industry will be affected by the IoT | [ as modifier ] : IoT devices.
最初のサンプルではコード内で単語を指定していたので特に問題はありませんでしたが、標準入力から日本語を渡したい場合、それをまた標準出力に返したい場合にはデコード/エンコードの処理が必要なので注意が必要です。上の Gist のコードではこのあたりを考慮しています。 まとめて検索できるのはとても便利です。 参考: - Accessing Dictionaries - PyObjC — the Python ⟷ Objective-C bridge - jakwings/macdict: A commandline tool for Mac OS X to look up words in the builtin/user dictionaries.

2016/06/29

Python Tips:長い方のリストに合わせて zip したい

Python で複数のリストのループを回すときに、長い方のリストにあわせてループを回す方法についてご説明します。

Python では、組み込み関数 zip() を使うと、かんたんに複数のリストを同時に回すことができます。

names = ['Rao', 'Toki', 'Jagi', 'Ken']
numbers = [1, 5]

for name, number in zip(names, numbers):
    print([name, number])
# => ['Rao', 1]
# ['Toki', 5]

ただし、ループの回数は長さの短い方のリストに切り詰められます。この場合は names の長さが 4 、 numbers の長さが 2 なので、結果としてループの回数は 2 回となります。

これでよい場合は特に問題ないのですが、ループ回数を長い方にあわせてほしい場合はこれでは困ります。そんなときに便利なのが標準ライブラリ itertools の zip_longest() 関数です。名前がそのまま!ですね。

使い方は zip() とほぼ同じです。

from itertools import zip_longest

names = ['Rao', 'Toki', 'Jagi', 'Ken']
numbers = [1, 5]

for name, number in zip_longest(names, numbers):
    print([name, number])
# => ['Rao', 1]
# ['Toki', 5]
# ['Jagi', None]
# ['Ken', None]

短い方の足りない部分は None で代用されます。

以上です。

max(len(l1), len(l2)) で長い方のリストの長さは取得できるので、自分で実装してもさほど長い処理にはなりませんが、 zip_longest() を使うと Pythonic でわかりやすく書けるので、使えるところではなるべくこちらを使っていきたいところですね。

2016/06/20

Python の三項演算子

Python の三項演算子についてご説明します。

Python では次の形で三項演算を利用することができます。

result = a if cond else b

この場合、条件 cond が True の場合は a 、そうでない場合は b が返されます。

例を挙げてみます。たとえば、次の関数は引数が True と評価される場合だけその値を int() 関数に渡す関数です。

def to_int(num_raw):
    """引数を整数型に変換する
    """
    return int(num_raw) if num_raw else 0

次のように使います。

to_int('5')   # => 5
to_int('12')  # => 12
to_int('')    # => 0  else の値が返される
to_int(None)  # => 0  else の値が返される

int() に空文字列や None を渡すと例外があがるので、この例では引数が空文字列や None の場合の戻り値は 0 にフォールバックするようにしています。

もうひとつ例をあげてみます。次の例では debug フラグが True の場合は p は pprint となり、 False の場合は通常の print となります。

from pprint import pprint

debug = True

# debug フラグが True の場合は pprint を使う、そうでない場合は print を使う
p = pprint if debug else print

p({'first name': 'Taro', 'last name': 'Suzuki',
'country': 'Japan', 'age': 20, 'hobby': 'Tennis'})
# => {'age': 20,
# 'country': 'Japan',
# 'first name': 'Taro',
# 'hobby': 'Tennis',
# 'last name': 'Suzuki'}

以上です。

ちなみに三項演算の if else を重ねて入れ子にすることもできますが、見た目が複雑になり一見でぱっと処理が理解できない式になるので、原則三項演算は入れ子にはせず別の式に分けた方がよいかと思います。

以上です。

ちなみに、三項演算子は英語では「 ternary operator 」や「 ternary conditional operator 」というそうです。


- Does Python have a ternary conditional operator? - Stack Overflow

2016/06/08

Python Tips:Python 3 の nonlocal を使いたい

タイトルそのままですが、 Python 3 の nonlocal の利用方法について説明してみたいと思います。

Python の変数スコープは原則「関数」がスコープの切れ目となっており、関数の内部で定義された変数に関数の外部からアクセスすることはできません。

def myfunc():
    a = 10

print(a)  # => NameError: name 'a' is not defined

一方、関数の内側から関数の外側にある変数にはアクセスすることが可能です。

a = 10

def myfunc():
    print(a)

myfunc()  # => 10

ただし、関数の内側から外側の変数へのアクセスは基本的に「参照」のみが可能です。値を更新するには nonlocal 宣言をしなくてはなりません。

nonlocal を使った例はこちら。

# coding: utf8

def gen_counter():
        """呼び出すごとにカウントを上げるカウンタを生成する
        """

    # クロージャ _counter で利用する現在のカウント
    count = 0

    def _counter(reset=False):
          # 関数の外にある count を更新したいので nonlocal 宣言をする
        nonlocal count

        if reset:
            count = 0
        count += 1
        return count

    return _counter


# カウンタを使用する
c1 = gen_counter()

# nonlocal のおかげで更新した count の値が保持できることの確認
print(c1())  # => 1
print(c1())  # => 2
print(c1())  # => 3

print(c1(reset=True))  # => 1
print(c1())  # => 2

nonlocal 宣言をすることで、 _counter() 関数の外側にある count 変数を _counter() の内側で更新することができています。

この例では nonlocal 宣言をしないとむしろ、「 UnboundLocalError: local variable 'count' referenced before assignment 」というエラーが出てしまって実行する自体できません。

ただし、関数の内側から外側の変数の「代入」はできないということについては 1 点注意が必要です。ミュータブル( mutable )なデータ型の場合は「代入」はできなくてもその中身を変更することはできてしまいます。ミュータブルなデータ型の代表はリストです。

def gen_stack():
        """スタックを生成する
        """
    stack = []

    def _stack(value=False):
        if value:
            # 変数 stack は関数の外側にあるが変更が可能
            stack.append(value)
        else:
            # 変数 stack は関数の外側にあるが変更が可能
            return stack.pop()

    return _stack


q = gen_stack()

# スタックに値を入れる
q(3)  # stack: [3]
q(5)  # stack: [3, 5]
q(7)  # stack: [3, 5, 7]

# スタックから値を取り出す
print(q())  # => 7  stack: [3, 5]
print(q())  # => 5  stack: [3]
print(q())  # => 3  stack: []

以上です。

複雑になる場合はムリに関数と nonlocal を使わずクラスを使うのがよさそうなのでこのあたりでハマることはあまりないかとは思いますが、挙動を正しく理解していないと思わぬバグが生まれるので注意が必要ですね。

2016/06/02

Python Tips:Sublime Text で pep8 のオートフォーマッタを使いたい

エディタ「 Sublime Text 」で pep8 のオートフォーマッタを利用する方法についてです。対象バージョンは Sublime Text 2 と 3 の両方です。

「方法」といっても手順はかんたんで、「 Python PEP8 Autoformat 」という Sublime Text パッケージをインストールするだけです。

1. Sublime Text を開いてコマンドパレットを開く
2. 「 Package Control: Install Package 」を選択
3. 「 Python PEP8 Autoformat 」を選択
4. インストール完了

Python PEP8 Autoformat にはオートフォーマッタの本体である Python の pep8 パッケージが同梱されているので、パッケージを別途インストールする必要などはありません。 Python PEP8 Autoformat をインストールしたらすぐに使いはじめることができます。

.py ファイルを保存したときに自動で走らせたい場合は設定ファイルにその旨を書き込めば OK です。

1. ( Sublime Text 2 の場合) Preferences → Package Settings → Python PEP8 Autoformat → Settings User
2. パッケージディレクトリの「 User 」ディレクトリ内の「 pep8_autoformat.sublime-settings 」というファイルが開く
3. 以下の内容を記入して保存

{
    // autoformat code on save
    "autoformat_on_save": true
}

ファイルの保存後に .py ファイルを適当に保存すると、 pep8 でのオートフォーマット処理が走ることが確認できることと思います。

以上です。

(私は深掘りできていませんが)その他 pep8 パッケージが持つ各種オプションも利用できるようなので、カスタマイズして本格的に使用したい場合は公式のページをぜひ覗いてみてください。

こういうオートフォーマッタは強力で、一度使うととても便利なのでやめられなくなりますね。

Python PEP8 Autoformat - Packages - Package Control
Pythonにまつわるアイデア:PEP 8 - Life with Python


追記 2017/10:

Sublime Text の定番・おすすめパッケージのリストを作りました。 Sublime Text をお使いの方はよろしければご参考にどうぞ。

おすすめ Sublime Text パッケージ | gotohayato

2016/05/03

Python Tips:ウェブページの情報をプログラムでまとめて取得したい

今回はウェブページの情報を Python を使って取得する方法をご紹介します。いわゆるスクレイピングについてです。

一般にどんなものが使われているのか把握しきれていませんが、おそらく、 Python でスクレイピングをする場合は大きく次の 2 つのパターンに大別されるかと思います。

1. 軽くスクレイピング: requests + α を使う
2. 本格的にスクレイピング: Scrapy を使う

1. 軽くスクレイピング: requests + α を使う


軽くスクレイピングしたい場合、作業が一回こっきりの場合や手作業の手間を少しだけ省きたいだけの場合は requests を使う方法が多いでしょうか。 requests で HTML を取得し、また別のものを使って HTML 内のデータ抽出を行います。「別のもの」は大体は BeatifulSoup か PyQuery になるかと思います。

- Requests: HTTP for Humans
- Beautiful Soup Documentation
- pyquery: a jquery-like library for python

かつてこちらで紹介記事を書いていました。よろしければ参考にしてみてください。

- ライブラリ:Requests - Life with Python
- ライブラリ:BeautifulSoup - Life with Python
- ライブラリ:PyQuery - Life with Python

2. 本格的にスクレイピング: Scrapy を使う


本格的にスクレイピングする(クローリングなどもする)場合は、 requests などでは少し物足りなくなってきます。その場合に役立つのが Scrapy ライブラリです。

- Scrapy | A Fast and Powerful Scraping and Web Crawling Framework

「フレームワーク」とうたっているとおり、スクレイピング、クローリングのための全部入りツールとなっています。リンクを辿ったり CSV や DB に永続化したりすることを前提に、必要な部分だけを書いてあとはフレームワークに任せる形で利用するようなものです。

・・・

1 のパターンの例として、 WordPress のプラグインリストを取得するコードをご紹介します。

こちらを実行すると、 WordPress の日本語版サイトより人気プラグイン情報が一覧で取得されます。ご参考に。。。



2 のパターンの例はあいにくちょうどいいものが手元にないので、わかりやすく紹介されているページをご紹介しておきます。

- PythonとかScrapyとか使ってクローリングやスクレイピングするノウハウを公開してみる! - orangain flavor
- Python: Scrapy と BeautifulSoup4 を使った快適 Web スクレイピング | CUBE SUGAR STORAGE
- PythonによるWebスクレイピング入門

以上です。

一冊まるまる Python を使ったスクレイピングの方法に関する本が日本語に翻訳されたようですので、興味のある方は手にとってみられるとよいかもしれません。