Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【Watson/python】IBM Watson Visual Recognitionを使って、pythonスクリプト上で画像分類を行う

IBM Watson Visual Recognition

IBM Watson Visual Recognitionは、IBMが提供するDeep Learningを利用した画像認識サービスです。

www.ibm.com

IBM Cloud上で利用することができ、APISDK経由で画像データの分類を行うことができます。

今回はpython用Watson Visual Recognitionモジュールを用いて画像分類をやってみます。

Usage

IBM Cloudアカウントの取得

まずはIBM Cloudのアカウントを取得しましょう。
下記記事に取得方法を記載してあります。

rennnosukesann.hatenablog.com

Visual Recognition サービスを作成する

次にIBM Cloud上でVisual Recognitionのサービスを作成します。

IBM Cloudダッシュボード画面にて、画面右上の「リソースの作成」ボタンを押します。

f:id:rennnosukesann:20180701184946p:plain

すると利用できるリソース一覧が表示されるので、一番下の「Watson」の項目一覧にある「Visual Recognition」をクリック。

f:id:rennnosukesann:20180701185216p:plain

遷移先画面の右下にある「作成」ボタンをクリック。

f:id:rennnosukesann:20180701185401p:plain

するとVisual Recognitionサービス詳細画面に移動します。
ここで表示されるAPIキーは後ほどpythonスクリプト上で利用します。

f:id:rennnosukesann:20180701185641p:plain

Watson Visual Recognitinoモジュールをインストール

pipを使い、python用Visual Recognitionモジュールをインストールします。

$ pip install --upgrade "watson-developer-cloud>=1.4.0"

これで準備完了です。

Source

以下にVisual Recognitionを利用した画像分類のスクリプト例を示します。
このスクリプトは、一枚の画像をVisual Recognitionに送信し、
その分類結果をJSON形式で標準出力する簡単なものです。

{API_KEY}にはVisual Recognition サービス作成時に取得したAPIキーを入力してください。

from watson_developer_cloud import VisualRecognitionV3, WatsonApiException


def recognize():
    WVR_VERSION = '2016-05-20'
    WVR_API_KEY = '{API_KEY}'
    FILE_PATH = 'cat.jpg' # 分類対象画像ファイル

    try:
        visual_recognition = VisualRecognitionV3(WVR_VERSION, iam_api_key=WVR_API_KEY)
        with open( FILE_PATH , 'rb') as image:
            # クラス分類:今回はすでに用意された分類器を使用
            results = visual_recognition.classify(images_file=image, classifier_ids=['default'])
        print(results)
    except WatsonApiException as ex:
        print(ex.message)


if __name__ == '__main__':
    recognize()

Demo

試しに、以下の画像を分類してみます。

f:id:rennnosukesann:20180701190447j:plain:w200

すると、以下ようなJSON形式で結果が返されます。
今回は猫の画像を上げたので、「猫」クラスのスコアや「動物」クラスのスコアが高いのがわかります。

{'images': [{'classifiers': [{'classifier_id': 'default', 'name': 'default', 'classes': [{'class': 'cat', 'score': 0.893, 'type_hierarchy': '/animal/mammal/carnivore/feline/cat'}, {'class': 'feline', 'score': 0.905}, {'class': 'carnivore', 'score': 0.905}, {'class': 'mammal', 'score': 0.906}, {'class': 'animal', 'score': 0.953}, {'class': 'tabby cat', 'score': 0.548, 'type_hierarchy': '/animal/mammal/carnivore/feline/cat/domestic cat/tabby cat'}, {'class': 'domestic cat', 'score': 0.713}, {'class': 'kitten', 'score': 0.5, 'type_hierarchy': '/animal/young/kitten'}, {'class': 'young', 'score': 0.508}, {'class': 'ash grey color', 'score': 0.849}, {'class': 'gray color', 'score': 0.571}]}], 'image': '../res/cat.jpg'}], 'images_processed': 1, 'custom_classes': 0}

参考

www.ibm.com

Visual Recognition - API reference | IBM Watson

【python】pipでファイル内に記述した複数モジュールを一括インストールする

メモ。

pip install -rで、ファイルに記述したモジュールを一括してインストールできる。

# インストールしたいモジュールを記述したファイル
$ cat hoge.txt 
asn1crypto==0.23.0
bleach==1.5.0
certifi==2017.11.5
cffi==1.11.2
chardet==3.0.4
cryptography==2.1.3
cycler==0.10.0
...

# 一括インストール
$ pip install -r hoge.txt
Collecting asn1crypto==0.23.0 (from -r hoge.txt (line 1))
 ...

入れたいモジュールを一気に入れられるのは便利だし、スクリプトを共有するときに依存モジュールを外部にまとめて書いておいてスクリプト内で自動一括インストール...みたいなことができるのも便利。

【Watson】Watson Studio でMNIST手書き文字認識を行う②【モデル学習・推論】

前回までのあらすじ

rennnosukesann.hatenablog.com

前回の記事では、Watson Studioのセットアップを行いました。 今回の記事では、実際にMNISTデータセットを使って手書き文字の学習とクラス分類を行います。

MNISTとは

MNISTとは、パターン認識機械学習の研究で頻繁に用いられる手書き文字画像データです。

f:id:rennnosukesann:20180620081207p:plain

手書き文字といってもアルファベットやひらがながすべて網羅されている・・・というわけではなく、0から9までの10種類の手書き文字のみから構成されています。データセットとしてシンプルでありながら十分な数のデータ数があるため、少し前はアルゴリズム・手法の性能を図るためのスタンダードなデータセットとして、現在は機械学習のHello,World的データセットとして用いられています。

それでは、早速Watson StudioでMNISTを学習してみましょう!

Usage

データセットを取得する

まずはデータがなければ始まらないので、MNIST手書き文字データセットをダウンロードします。

今回はWatson Studioに読み込ませるMNISTがほしいので、こちらからダウンロードします。

遷移先のページ右上にダウンロードボタンがあるので、クリックしてMNISTのpklファイルをダウンロードしましょう。

f:id:rennnosukesann:20180620082507p:plain

MNIST-pkl.zipがダウンロードできるので、解凍して

  • mnist-keras-test.pkl
  • mnist-keras-train.pkl
  • mnist-keras-valid.pkl
  • mnist-keras-test-payload.json

があることを確認します。

Watson Studioプロジェクトを作成する

前回の記事で作成したWatson Studioサービスのページに遷移し、「Get Started」をクリックします。

f:id:rennnosukesann:20180630002352p:plain

するとWatson Studioのトップページに遷移するので「Get started with key tasks」メニューの「New project」ボタンをクリックしてください。

f:id:rennnosukesann:20180630002551p:plain

するとプロジェクトの種類を選択するダイアログが出現します。
今回は深層学習を行うので、「Deep Learning」を洗濯して「OK」を押してください。

f:id:rennnosukesann:20180630002751p:plain

画面遷移時にMachine Learning・Object Storageサービスの作成が行われます(未作成の場合)。インストールしたいスペースを聞かれるので、Watson Studioと同じスペースを選択してください。

次にプロジェクト詳細を定義します。
先程作成したMachine Learning・Object Storageサービスが右側に表示されているか確認してください。設定されていなければ「Reload」ボタンを押してみてください。

問題がなければ「create」を押します。

f:id:rennnosukesann:20180630003359p:plain

データセットをWatson Studioに読み込ませる

ストレージが用意できたので、ダウンロードしたデータセットをWatson Studioにアップロードします。

Watson Studio画面トップのメニューバーのプルダウン「Service」をクリックし、次いで「Data Services」をクリックします。

f:id:rennnosukesann:20180630003839p:plain

「Cloud Object Storage」メニュー中にある、先ほど作成したObject Storageサービスをクリック。

f:id:rennnosukesann:20180630004026p:plain

バケットの作成」をクリック。

f:id:rennnosukesann:20180630004130p:plain

バケット作成ダイアログが出現するので、名前を入力してください。
念の為、ロケーションをus-geoにします。その後「作成」をクリック。

f:id:rennnosukesann:20180630004334p:plain

するとデータをアップロードする画面が表示されます。
右上の「アップロード」ボタンをクリックしてファイルダイアログを開き、ダウンロードしたpklファイルをすべてアップロードしてください。ドラッグ&ドロップでもアップロード可能です。

f:id:rennnosukesann:20180630004728p:plain

f:id:rennnosukesann:20180630004840p:plain

f:id:rennnosukesann:20180630005033p:plain

f:id:rennnosukesann:20180630004859p:plain

このような感じでアップロードできたらOK。

f:id:rennnosukesann:20180630005122p:plain

その後、同じように学習結果出力用のバケットも作成します。
先ほどとは別名のバケットをもう一つ作成してください。
データセットのアップロードは不要です。

f:id:rennnosukesann:20180630012301p:plain

モデルの作成

次に学習・クラス分類のためのモデルを作成します。

Watson Studioトップページに戻り、「Get started with key tasks」メニューの「New Modeler Flow」をクリック。

f:id:rennnosukesann:20180630005416p:plain

遷移先の画面の「From example」タブをクリック。
今回はすでに用意されているDeep Learningのモデルを使います。

f:id:rennnosukesann:20180630005611p:plain

遷移したら、「Single Convolution layer on MNIST」のカードメニューをクリックし、「Project」が冒頭で作成したプロジェクト担っていることを確認して「Create」をくりっくしてください。

f:id:rennnosukesann:20180630005824p:plain

すると何やら数珠つなぎになった図が表示されます。
これはDeep Learningで用いられる「ニューラルネットワーク」と呼ばれる学習モデル(と最適化アルゴリズム等のオプション)の構成を表しており、これらの構成自体や、各パーツのパラメータを調整することでモデルの推論精度を高めたりすることができます。

一旦モデルの構造には変更を加えず、モデルの学習・テストに使うデータを設定します。

「Image Data」をダブルクリックすると下記サイドメニューが表示されるので、「Create a Connection」をクリックしてください。 Object Storageに接続します。

f:id:rennnosukesann:20180630010244p:plain

接続がうまく行った場合、「Data Connection」プルダウンが表示されるので、「Connect to project COS」を選択してください。すると続けて「Buckets」プルダウンが表示されるので、先程アップロードしたデータを含むバケットの名称を選択します。
その後「Train data file」「Test data file」「Validation data file」の所在を聞かれるのでそれぞれアップロード舌ファイルを選択し、「Close」をクリック。

f:id:rennnosukesann:20180630010835p:plain

最後にモデルを公開します。
画面右上のアップロードボタンをクリック。

f:id:rennnosukesann:20180630010954p:plain

ダイアログが出てくるので、名前が先ほど作成したモデル名であり、WML Instance の名前が冒頭で作成したサービスのものと一致するかチェックして「Publish」をクリック。

f:id:rennnosukesann:20180630011150p:plain

緑色の通知が出てきたら成功です!
さらにモデルの学習をすべく「or Train it in an experiment」をクリックしましょう。

f:id:rennnosukesann:20180630011339p:plain

モデルを学習する

先程のリンクをクリックすると、Experiment作成画面に遷移します。
Experimentはモデルの学習やテスト、精度推移の可視化などができる実験環境の単位を指します。

名前を入力し、作成したMachine Learning Serviceを選択してください。

f:id:rennnosukesann:20180630012858p:plain

その後下段の「Cloud Object Storage bucket for...」の「Select」ボタンをクリック。

f:id:rennnosukesann:20180630011832p:plain

「Cloud Object Storage connection」から「Connection to project COS」を選択し、 「Bucket containing training data」にはデータセットをアップロードしたバケットを、「Bucket for storing training results」には学習結果出力用のバケットを指定します。 指定したら「Select」をクリック。

f:id:rennnosukesann:20180630011955p:plain

新規experiment作成画面に戻り、「Add training definition」をクリックします。

f:id:rennnosukesann:20180630012910p:plain

先程作成したモデルを指定するため、「Existing training definition」タブ → 作成したモデル名を選択します。

f:id:rennnosukesann:20180630013223p:plain

すると「Training definition attributes」メニューが表示されます。 「Compute plan」はモデルの学習・テストに使う計算機リソースの構成のうち、どのGPUをどれだけ利用するかを選択します(台数や一台の性能が良いほどメモリや並列性が増し、学習やテストが高速になる)。「Hyperparameter optimization method」はモデルを学習する上でハイパパラメタ、つまり学習の際に予め設定しておくパラメータ初期値をどのように決定するかを指定します。今回は前者を「1 x NVIDIA® Tesla® K80 (2 GPU)」とし、校舎を「None」としました。選択したら「Select」をクリック。

f:id:rennnosukesann:20180630013722p:plain

「Create and Run」を押しましょう。

f:id:rennnosukesann:20180630013921p:plain


学習が開始されました!

遷移先の画面ではモデルの学習中の様子を見ることができます。 学習中のモデルの状態は「Queued」「In progress」「Completed」の順に遷移していき、「Completed」になると学習が終了したことを表します。

f:id:rennnosukesann:20180630014148p:plain

終了したら、詳細を見てみましょう。

f:id:rennnosukesann:20180630014516p:plain

学習が完了していることがわかります。

f:id:rennnosukesann:20180630014543p:plain

Logsには学習時のコンソールログも残されており、学習回数や、学習時点での学習データに対する分類精度等を見ることができます。

f:id:rennnosukesann:20180630014722p:plain

学習したモデルを保存する

学習したモデルは、保存して利用できるようになります。

先程の「Completed」状態にあるモデルの右端のメニュープルダウンから「Save model」を選択します。

f:id:rennnosukesann:20180630015326p:plain

保存用ページに遷移するので、名前を入力し、「Save」ボタンを押しましょう。

f:id:rennnosukesann:20180630015624p:plain

成功すると、緑色の通知が出ます。

f:id:rennnosukesann:20180630015712p:plain

「here」をクリックすることで、保存したモデルの詳細を見ることができます。 (またはメニューバー「Project」から作成したプロジェクトの「Assets」タブに遷移し、「Experiments」の項目で「Add Experiment」をクリックします)

保存したモデルをデプロイする

次に、保存したモデルをデプロイします。
デプロイすると、テストデータを使った検証や、API経由で分類器を提供することが可能になります。

今回はWeb Service としてデプロイしてみましょう。

先程のモデル詳細画面の「Deployments」タブをクリックします。

f:id:rennnosukesann:20180630020023p:plain

「Add Deployment」をクリック。

f:id:rennnosukesann:20180630020050p:plain

遷移先の画面

f:id:rennnosukesann:20180630020144p:plain

今回はテストデータセットを使った性能評価を行うので「Deployment type」の「Web Service」を選択します。 あとは名前を入力して「Save」をクリックします。

これだけでデプロイ完了です! 早速テストしてみましょう!

デプロイしたモデルをテストする

デプロイした結果作成されたDeployをクリックし、「Test」タブをクリックします。

f:id:rennnosukesann:20180630021900p:plain

f:id:rennnosukesann:20180630164518p:plain

冒頭でダウンロードしたmnist-keras-test-payload.jsonの拡張子を.htmlに変更します。 変更したあと、ブラウザでHTMLファイルを開きます。

f:id:rennnosukesann:20180630164744p:plain

開いたページのJSONファイルをコピーし、先程のDeploy画面のTestタブ内にあるフォームにペーストしてください。

f:id:rennnosukesann:20180630164850p:plain

「Predict」をクリックすると、クラス分類が開始されます。
元のJSONデータは数字の「7」を記した画像の濃淡値データを表しているので、帰ってきたクラス分類結果が「7」であれば分類成功です。

f:id:rennnosukesann:20180630165045p:plain

結果が帰ってきました!
レスポンスJSONvalues内配列の8番目の数値が1の場合、これは数字の7であるという分類結果になります。 上記画像では分類に成功していることがわかります。

まとめ

いかがだったでしょうか。

今回はMNIST分類の簡単な学習とテストを行いましたが、より複雑なモデルの構築、学習、テストももちろん可能です。
pythonスクリプトへのエクスポートなどもできるので、これからどんどん試していこうと思います。

参考

www.ibm.com

Neural network modeler - IBM Watson

qiita.com

【Java】Jacksonで任意形式のJSONをパースする

Jackson

Jacksonについては以下の記事を参照。

rennnosukesann.hatenablog.com

多様なJSONの構造

前回の記事で、JSONをMapオブジェクトにする記事を書きました。

rennnosukesann.hatenablog.com

JacksonではJSONファイルやStringオブジェクトとしてのJSONJavaクラスのオブジェクトに簡単にマッピングしてくれます。
この記事のように、JSONをMapオブジェクトに変換することも可能です。

しかし、各プロパティの値が階層的にオブジェクトになっている場合は、Map<String, Object>でごまかすか、 Map<String,Object>Map<String, Map<String, Object>>>のようにわかる範囲までジェネリクス型を明示してあげる必要があります。
更に後者の場合、各プロパティの階層レベルがすべて等しくなければパースに失敗します。

コンテナとなるJavaクラスを定義することでもパースが可能ですが、JSONのパターンの数だけJavaクラスを定義しなければならなくなり、面倒です (新しいパターンが増えたらまたクラスを定義する必要もあります)。

任意の構造を持つJSONを読み込む

Jacksonでは、ObjectMapperクラスのメソッドreadTree()を用いることでこの問題を解決できます。

String json = "{ ... }"; // JSON形式文字列
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);

readTree()JsonNodeオブジェクトを返します。
JsonNodeJSON木構造に見立てたときのノード(枝)として振る舞い、ノードに格納された値を参照したり、小ノードを参照することができます。

Source

JSON
{
  "hoge": {
    "fuga": 1,
    "piyo": 2
  },
  "foo": ["bar", "bow"]
}
Source
package com.company;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jdk.nashorn.internal.runtime.regexp.JoniRegExp;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.security.spec.ECParameterSpec;

public class Main {

    public static void main(String[] args) {

        String json = readJsonAsString("res/hoge.json");

        if (json != null) {
            try {
                readJsonNode(json);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *
     * readTree()でJsonNodeインスタンスを作成
     *
     */
    private static void readJsonNode(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        // JSON文字列を読み込み、JsonNodeオブジェクトに変換(Fileやbyte[]も引数に取れる)
        JsonNode root = mapper.readTree(json);

        // get()で指定したキーに対応するJsonNodeを取得できる
        System.out.println(root.get("hoge")); // {"fuga":1,"piyo":2}

        // 階層的にアクセス可能
        System.out.println(root.get("hoge").get("fuga")); // 1

        // 配列にアクセスするときは添字をわたす
        System.out.println(root.get("foo").get(0)); // "bar"

        // 値を特定の基本型に変換して取得可能
        System.out.println(root.get("hoge").get("piyo").asInt()); // 2
        System.out.println(root.get("hoge").get("piyo").asDouble()); // 2.0
        System.out.println(root.get("hoge").get("piyo").asBoolean()); // true

        // toString()でJSON全体を文字列として取得
        System.out.println(root.toString()); // {"hoge":{"fuga":1,"piyo":2},"foo":["bar","bow"]}

    }


    /**
     *
     * JSONファイルを一つの文字列オブジェクトとして読み込み
     *
     */
    private static String readJsonAsString(String filename) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            BufferedReader reader = new BufferedReader(new FileReader(new File(filename)));
            String line = null;
            StringBuilder builder = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                builder.append(line);
            }
            return builder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

参考

github.com

【Java】JacksonでJson文字列をMapオブジェクトに変換する

Jackson

Jacksonについてはこちら。

rennnosukesann.hatenablog.com

JacksonはJavaのライブラリで、JavaオブジェクトをJSON形式にしたり、その逆のこともできます。もちろんJSONファイルへの読み書きも可能です。

JSONをMapオブジェクトに変換する

Jacksonでは、JSON文字列をJavaオブジェクトに変換することができますが、 変換の方法の一つに、JSONのプロパティと同じ名称のプロパティを持つJavaクラスを用意するやり方があります。

public class JSONResponse {
    private String name;
    private String age;
}
// JSON文字列であるjsonをJSONResponse型オブジェクトに変換
JSONResponse response = mapper.readValue(json, JSONResponse.class);

JSONのプロパティがいつも同じであれば問題はないのですが、
例えば異なるAPIのレスポンスを処理する場合などに、レスポンスごとの個別のコンテナクラスを作成する必要があります。

そんなときは、JSONをMapオブジェクトに変換してしまいましょう。
JacksonのTypeReferenceオブジェクトを利用することでJSONからMap型オブジェクトを生成することができます。

TypeReference<HashMap<String, String>> reference = new TypeReference<HashMap<String, String>>() {};
Map<String, String> map = mapper.readValue(json, reference);

【python】コマンドラインパーサclick

clickとは

Welcome to the Click Documentation — Click Documentation (5.0)

clickpythonコマンドライン解析用モジュールです。
pythonスクリプトCLI上で実行したときに、コマンドライン引数のラベルを定義したり、ヘルプを表示するなどの機能を提供します。

rennnosukesann.hatenablog.com

前回の記事ではpython標準のコマンドラインパーサargparserを紹介しました。
今回はサードパーティ製のコマンドラインパーサclickの使用法を軽く紹介したいと思います。

Usage

pipclickをインストールします。

$ pip install click

これだけでOKです!

Source

基本形

clickでは、基本的に関数やメソッドにアノテーションを付加していきます。
ここがargparserとの大きな違いですね。

最初に、clickモジュールをインポートし、呼び出すトップレベルの関数に@click.command()アノテーションをつけてみましょう。

import click

@click.command()
def main():
    print()

if __name__ == '__main__':
    main()
$ python main.py --help
Usage: main.py [OPTIONS]

Options:
  --help  Show this message and exit.

これだけで、コマンドライン上でのヘルプメッセージがサポートされていることがわかります。

必須引数の設定

指定が必須な引数の設定は、@click.argument()アノテーションで行います。

import click

@click.command()
@click.argument("arg1") # 必須パラメータ定義
def main(arg1):
    print(arg1)

if __name__ == '__main__':
    main()

@click.argument()で指定した引数と関数の仮引数名(ここではarg1)は一致している必要があります。渡された引数はこの仮引数arg1に格納され、関数やメソッド内で利用することができます。

# ヘルプメッセージに引数情報が追加される
$ python main.py --help
Usage: main.py [OPTIONS] ARG1

Options:
  --help  Show this message and exit.

# NG(必須引数を指定していない)
$ python main.py       
Usage: main.py [OPTIONS] ARG1

Error: Missing argument "arg1".

# OK
$ python main.py test
test

引数を設定すると、ヘルプメッセージに引数の情報が追加されます。
必須引数はその名の通り必ず設定されている必要があります。
設定されていない場合、clickがエラーを送出します。

また指定した引数は指定順にコマンドライン引数値と対応付けされます。
例えば、上記の例ではarg1というコマンドライン引数がtestという値と対応付けされています。

オプション引数の指定

@click.option()で任意引数も定義することが可能です。

@click.command()
@click.option("--opt")
def main(option):
    print(option)

オプションの場合、オプション引数名が関数の引数名とハイフンを除いて一致している必要があります。

またオプション引数を指定した場合、実行時に引数の頭に明示的に名前を指定する必要があります。

# NG : 値だけ渡すのはだめ
$ python main.py option
Usage: main.py [OPTIONS]

Error: Got unexpected extra argument (option)

# OK
$ python main.py --opt option
option

# 指定しなくてもOK(指定しない場合、変数の値はNoneになる)
$ python main.py             
None

またオプション引数名は短縮形を登録することができます。

# OK
$ python main.py 
None

# OK
$ python main.py -o test
test

#OK
$ python main.py --opt test 
test

型指定

引数の型指定がtypeオプションで可能です。
引数が指定型で解釈できない文字列の場合、エラーとなります。

import click

@click.command()
@click.argument("arg1", type=float) # 型指定
def main(arg1):
    print(arg1)

if __name__ == '__main__':
    main()
# 引数がfloatで解釈できないので、エラー
$ python main.py test
Usage: main.py [OPTIONS] ARG1

Error: Invalid value for "arg1": test is not a valid floating point value

# OK
$ python main.py 3.14
3.14

複数値指定

一つの引数に複数の値をnargsオプションで設定できます。
nargsで設定した場合、引数はタプル値になります。

import click

@click.command()
@click.argument("arg1", type=float, nargs=3)
def main(arg1):
    print(arg1)

if __name__ == '__main__':
    main()
# NG 3つ引数が必要
$ python main.py 3.14
Error: argument arg1 takes 3 values

# OK
$ python main.py 3.14 1.23 4.44
(3.14, 1.23, 4.44)

またnargs=-1を指定すると、任意の数の引数をとることができるようになります。

@click.argument("arg1", type=float, nargs=-1)
$ python main.py 3.14 1.23 4.44 9.81 111. 
(3.14, 1.23, 4.44, 9.81, 111.0)

ただし、別の引数が後ろに追加された場合、末尾の引数が他の引数に割り当てられます。

@click.command()
@click.argument("arg1", type=float, nargs=-1)
@click.argument("arg2", type=float)
@click.argument("arg3", type=float)
def main(arg1, arg2, arg3):
    print(arg1)
    print(arg2)
    print(arg3)
# 9.81は`arg2`に、111.0は`arg3`に割り当てられる
$ python main.py 3.14 1.23 4.44 9.81 111.
(3.14, 1.23, 4.44)
9.81
111.0

フラグ指定

@click.optionに限り、true/falseを表すフラグを定義することができます。

import click

@click.command()
@click.option("--is_read/--is_unread", default=False)
def main(is_read):
    print(is_read)

if __name__ == '__main__':
    main()
$ python main.py           
False

$ python main.py --is_read  
True

$ python main.py --is_unread
False

まとめ

感想としては、コマンドライン引数の定義アノテーションで指定することで、

  • どこでコマンドライン引数が定義されているのか
  • どの関数が起点なのか(初期段階で呼ばれるのか)
  • どのような設定なのか

がわかりやすく、また設定自体もシンプルで使いやすい印象でした。

参考

Welcome to the Click Documentation — Click Documentation (5.0)

【python】argparserでコマンドライン引数を解析する

argparser

16.4. argparse — コマンドラインオプション、引数、サブコマンドのパーサー — Python 3.6.5 ドキュメント

argparserpython標準モジュールの一つで、pythonスクリプト実行時に受け取ったコマンドライン引数を解析するのに役立ちます。

例えば、lsコマンドのように、引数無しで実行することもできれば、引数やオプションを与えることで挙動が変更するようなスクリプトを記述することができます。またargparserを用いることで、デフォルトでヘルプメッセージを実装することができます。

Usage

基本形

argparserを利用する基本形は以下になります。

import argparse
parser = argparse.ArgumentParser("起動時メッセージ")
args = parser.parse_args()

argparseモジュールをインポートし、argparse.ArgumentParserクラスのインスタンスを生成するだけです。これでコマンドライン引数を解析する準備が整いました。

以降、解析したコマンドライン引数はparse_args()メソッドで取得することができます。 また、parse_args()メソッドを呼び出した時点でスクリプトのヘルプコマンドが使えるようになります。

$ python hoge.py --help
usage: 起動時メッセージ [-h]

optional arguments:
  -h, --help  show this help message and exit

必須コマンドライン引数の追加

引数の追加

引数の追加は、argparse.ArgumentParserインスタンスadd_argument()メソッドを用いて行います。

import argparse
parser = argparse.ArgumentParser("起動時メッセージ")
parser.add_argument("arg1")
args = parser.parse_args()

add_argument()で追加した文字列は、コマンドライン引数のラベルとして扱われます。ラベルは追加された順に割り当てられます。

$ python hoge.py --help
usage: 起動時メッセージ [-h] arg1

positional arguments:
  arg1

optional arguments:
  -h, --help  show this help message and exit

# 引数がないとエラー = 必須な引数
$ python hoge.py       
usage: 起動時メッセージ [-h] arg1
起動時メッセージ: error: the following arguments are required: arg1

# arg1が第一引数のラベルとして自動的に割り当てられる
$ python hoge.py a
Namespace(arg1='a')
引数の参照

引数への参照は、通常のオブジェクトのようにプロパティアクセスで可能です。

  parser = argparse.ArgumentParser("起動時メッセージ")
  parser.add_argument("arg1")
  args = parser.parse_args()
  print(args.arg1)
$ python hoge.py  100
100
ヘルプメッセージ

add_argument()の引数helpにはヘルプ表示時のメッセージを設定することができます。

parser.add_argument("arg1", help="arg1のヘルプメッセージ")
 $ python hoge.py --help
usage: 起動時メッセージ [-h] arg1

positional arguments:
  arg1        arg1のヘルプメッセージ

optional arguments:
  -h, --help  show this help message and exit
引数の型指定

引数には値の型も指定することができます。引数である文字列値を、指定された型にキャストしてくれます。

parser.add_argument("arg1", help="arg1のヘルプメッセージ", type=int)
# NG
 $ python hoge.py hoge  
usage: 起動時メッセージ [-h] arg1
起動時メッセージ: error: argument arg1: invalid int value: 'hoge'

# OK
$ python hoge.py 100 
Namespace(arg1=100)

Optional引数

上記の引数は必ず指定しなければならないものでしたが、
引数名の頭に--を付加することで任意引数とすることができます。

parser.add_argument("--arg1", help="arg1のヘルプメッセージ", type=int)

ただしoptional引数の場合、引数値の前に必ずラベルを明示する必要があります。

# optional引数は指定しなくても良い
$ python hoge.py       
Namespace(arg1=None)

# ただし、指定する際はラベルが必要
$ python hoge.py 100
usage: 起動時メッセージ [-h] [--arg1 ARG1]
起動時メッセージ: error: unrecognized arguments: 100
$ python hoge.py --arg1 100
Namespace(arg1=100)
optional引数の短縮形

多くのシェルコマンドと同様に、オプション指定の短縮形も設定することができます。

parser.add_argument("-a", "--arg1", help="arg1のヘルプメッセージ", type=int)
# OK
$ python hoge.py           
Namespace(arg1=None)

# OK
$ python hoge.py --arg1 100
Namespace(arg1=100)

# OK
$ python hoge.py -a 100    
Namespace(arg1=100)

argparserpythonコマンドラインパーサの基本形です。
慣れれば意外と使いやすいですし、何より標準なのでユーザに余計なパッケージを要求しないので一定の制約下では重宝します。

よりシンプルなコマンドラインパーサであるclickなどもおすすめなので、ぜひ使ってみることをおすすめします。

参考

16.4. argparse — コマンドラインオプション、引数、サブコマンドのパーサー — Python 3.6.5 ドキュメント

Argparse チュートリアル — Python 3.6.5 ドキュメント