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.