Mimesisでモックデータを手軽に生成する

2018.01.27

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

ソフトウェアの開発中に、動作デモやテスト用途にモックデータが欲しくなることがあります。

少量のデータで事足りる場合は手作業で作成することもできますが、パフォーマンスの計測などで 大量のデータが必要な場合はそうもいきません。

そこで、Pythonのモックデータ生成用ライブラリ Mimesis を試してみましょう。

検証環境

  • Python 3.6.4
  • Pipenv 8.2.6
  • Mimesis 1.0.4

Mimesisとは

Pythonのモックデータ生成用ライブラリです。簡単に使えて動作が高速なのが特徴です。 データプロバイダという仕組みが用意されており、人名や住所といった人物に関する情報、 書籍のISBN・携帯のIMEIといったコード値など、様々なデータを手軽に生成することができます。 データプロバイダの一覧は、公式サイトの Data Providers を参照してください。

また、データプロバイダを実装する仕組みが提供されているので、 用途に適したデータプロバイダが無い場合は、独自の実装を追加することもできます。

今回は組み込みのデータプロバイダを組み合わせてモックデータを生成します。

インストール

まず、Mimesis をインストールします。 以降は Python3.6.4と Pippenv がインストールされている前提での操作です。

# 作業用の環境に移動
cd /path/to/mimesis-demo

# Pipenv環境を構築
pipenv install --python 3.6.4
pipenv shell

# Mimesisをインストール
pipenv install mimesis

以上でインストール完了です。

前準備

今回は著者と書籍を表すデータを作ってみます。単純化のため1冊の本は1人の著者によって執筆されたものとします。

クラス

著者と書籍のクラスを定義します。

# -*- coding: utf-8 -*-

import datetime


class Author(object):
    """著者"""

    def __init__(self, author_id: int):
        self.id = author_id         # type: int
        self.first_name = None      # type: str
        self.last_name = None       # type: str


class Book(object):
    """書籍"""

    def __init__(self, book_id: int):
        self.id = book_id           # type: int
        self.title = None           # type: str
        self.author = None          # type: Author
        self.isbn = None            # type: str
        self.publish_date = None    # type: datetime.date

モックデータ生成

著者と書籍のモックデータを生成します。 今回は以下のデータプロバイダを使用します。

プロバイダ名 概要
Personal 人物関連。姓・名・年齢・メールアドレスなど
Code コード値関連。 ISBN・EAN・IMEIなど
Datetime 日時関連。日付・時間など
Text テキスト関連。タイトル・文章など

著者

著者IDはシーケンス値、姓・名はデータプロバイダから取得します。

# ... (前略)

import typing
import mimesis


def next_author(data_locale: str) -> typing.Iterator[Author]:
    personal = mimesis.Personal(data_locale)
    author_id = 0

    while True:
        author_id += 1

        author = Author(author_id)
        author.first_name = personal.name()
        author.last_name = personal.surname()

        yield author

引数の data_locale は生成するデータの言語指定です。 日本語データの場合は 'ja' を、英語データの場合は 'en' を指定します。 対応しているロケールの一覧は、公式サイトの Locales を参照してください。

書籍

書籍IDはシーケンス値、著者はリストの中からランダムに選択します。 その他の項目はデータプロバイダから取得します。

# ... (前略)

import typing
import mimesis
import random


def next_book(authors: typing.List[Author], data_locale: str) -> typing.Iterator[Book]:
    code = mimesis.Code(data_locale)
    date = mimesis.Datetime(data_locale)
    text = mimesis.Text(data_locale)

    book_id = 0

    while True:
        book_id += 1

        book = Book(book_id)
        book.title = text.title()
        book.author = random.choice(authors)
        book.isbn = code.isbn()
        book.publish_date = date.date(start=1800, end=2018)

        yield book

引数の authors が著者リストです。 random.choice で著者をランダムに選択します。 data_locale は著者と同様で言語指定です。

データを生成

著者10人・書籍50冊のデータを生成してJSONでダンプします。言語は日本語を試してみます。

# ... (前略)

import json


class SimpleJsonEncoder(json.JSONEncoder):
    """JSONエンコーダ、オブジェクトの __dict__ の内容でエンコードする"""

    def default(self, o):
        return o.__dict__


def generate_books(data_locale: str, n_authors: int, n_books: int) -> typing.Iterator[Book]:
    # 著者 n_authors人
    author_generator = next_author(data_locale)
    authors = [next(author_generator) for _ in range(n_authors)]

    # 書籍 n_books冊
    book_generator = next_book(authors, data_locale)
    return (next(book_generator) for _ in range(n_books))


def main():
    data_locale = 'ja'
    n_authors = 10
    n_books = 50
    books = generate_books(data_locale, n_authors=n_authors, n_books=n_books)
    encoder = SimpleJsonEncoder()

    with open(f'books_{data_locale}.json', 'w') as wf:
        for b in books:
            print(encoder.encode(b), file=wf)


if __name__ == '__main__':
    main()

このスクリプトを実行すると、 books_ja.json ファイルが生成されます。 日本語文字列はUnicodeエスケープされているので、 jq コマンドで確認してみます。

Mimesisは擬似乱数を利用してデータを生成するので、下記と異なる値が表示されると思います。

# 長くなるので先頭の2行だけ確認
$ head -n 2 books_ja.json  | jq .
{
  "id": 1,
  "title": "江戸時代においては江城(こうじょう)という呼び名が一般的だったと言われ、また千代田城(ちよだじょう)とも呼ばれる。",
  "author": {
    "id": 91,
    "first_name": "広明",
    "last_name": "塀内"
  },
  "isbn": "836-4-60472-364-9",
  "publish_date": "2015/06/07"
}
{
  "id": 2,
  "title": "権力の集中を避けるため主要な役職は複数名が配置され、一か月交代で政務を担当する月番制を導入し、重要な決定は合議を原則とした。",
  "author": {
    "id": 65,
    "first_name": "裕次郎",
    "last_name": "津島"
  },
  "isbn": "4-79506-017-6",
  "publish_date": "1990/01/17"
}

言語を変更してデータ生成

上述の main 関数の data_locale'en' に変更して英語のデータを生成してみます。 ファイル名は books_en.json になります。

def main():
    data_locale = 'ja'
    n_authors = 10
    n_books = 50
    books = generate_books(data_locale, n_authors=n_authors, n_books=n_books)
    # ... 以下略

ファイルの中身を確認します。

$ head -n 2 books_en.json  | jq .
{
  "id": 1,
  "title": "The arguments can be primitive data types or compound data types.",
  "author": {
    "id": 20,
    "first_name": "Ruben",
    "last_name": "Soto"
  },
  "isbn": "1-40752-717-0",
  "publish_date": "07/14/1900"
}
{
  "id": 2,
  "title": "I don't even care.",
  "author": {
    "id": 10,
    "first_name": "Boyd",
    "last_name": "Hubbard"
  },
  "isbn": "082-1-05353-450-1",
  "publish_date": "10/16/1889"
}

タイトルや著者名が英語に変わって、出版日(publish_date)の表記もアメリカ風になりましたね。

パフォーマンス測定

Rust製のパフォーマンス計測ツール hyperfine で測定してみました。

  • 言語: 英語
  • 著者: 1万人
  • 書籍: 100万冊

上記の条件でおよそ50秒程度でデータを生成できました。

# 計測コマンド、 main.py は上記のスクリプトファイル
$ hyperfine 'python main.py'

項目の数が増えるにつれて時間は伸びていくと思いますが、マルチコア前提の作りに変更するといった対応で処理時間を短縮できそうです。

おわりに

Mimesisを使ってモックデータを生成することができました。 今回は基本的な使い方を紹介しましたが、 pytest と連動させたり スキーマ定義にもとづいて データを生成することもできるようです。 業務でPythonを使う機会があればこれらの機能も試してみたいと思います!