24
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonでテストデータを生成するライブラリfakerのコードリーディング

Last updated at Posted at 2015-11-16

##はじめに
このあいだ@TakesxiSximadaさんがPythonでパスワードの強度をしらべるライブラリSafeのコードリーディングという記事をあげていて面白そうだったので、自分もやってみました。

そこで以前からちょっと使ってみようと気になっていたfakerというライブラリを取り上げてみました。

fakerはダミーなテストデータをいい感じに生成してくれるライブラリです。他の言語でもよく見かけるやつのpython版です。

https://pypi.python.org/pypi/fake-factory/0.5.3
https://github.com/joke2k/faker

##Install
pipでインストールできます。

$ pip install fake-factory

##READMEのsampleコードを試す
※ 実行環境のpythonバージョンは3.5.0です。
とりあえずimport。

>>> from faker import Factory

そしてテストデータを作ってくれるgeneratorを生成すればOK。

>>> fake = Factory.create()

あとはこんな感じでテストデータが返ってきます。

>>> fake.name()
'Anfernee Reichel'
>>> fake.address()
'084 Tiney Fork Suite 757\nPort Earl, MI 20240-1776'
>>> fake.text()
'Facilis non eligendi qui deleniti ullam est. Ab minus est non et occaecati laborum sequi. Vero consectetur repellendus dicta velit. Quisquam omnis alias error sed totam.'

多言語化にも対応していて、さっきのFactory.create()localeを引数に渡してあげることで実現できます。

>>> fake = Factory.create('ja_JP')
>>> fake.name()
'津田 裕美子'
>>> fake.address()
'群馬県中央区芝公園32丁目22番3号 上広谷ハイツ400'
>>> fake.text()
'Non ut in unde ipsa fugiat excepturi voluptate. Enim molestias voluptatem aperiam. Est fuga distinctio sit officia qui velit numquam sint.'

textには日本語データが用意されてなかったので、デフォルトのen_USのデータが返ってきてます。

ちなみに'fakefaker.generator.Generator()`のインスタンスです。

>>> type(fake)
<class 'faker.generator.Generator'>

##コードリーディング
それではコードを読んでいきたいのですが、
その前にfakerではProviderが何者なのかを理解できた方が話が早いので、まずProviderに関して説明していきます。

###Provider - テストデータを提供してくれる

各Providerは faker/faker/providers 配下に格納されてます。

├── providers
│   ├── __init__.py
│   ├── __pycache__
│   ├── address
│   ├── barcode
│   ├── color
│   ├── company
│   ├── credit_card
│   ├── currency
│   ├── date_time
│   ├── file
│   ├── internet
│   ├── job
│   ├── lorem
│   ├── misc
│   ├── person
│   ├── phone_number
│   ├── profile
│   ├── python
│   ├── ssn
│   └── user_agent

address barcodeなどのカテゴリ毎に各言語に対応したディレクトリと、各カテゴリのBaseとなるProviderを実装しています。

ここではpersonに注目して、ソースを追っていきます。
person配下はこんな感じになってます。

├── providers
│   ├── __init__.py
│   ├── person
│   │   ├── __init__.py
│   │   ├── bg_BG
│   │   ├── cs_CZ
│   │   ├── de_AT
│   │   ├── de_DE
│   │   ├── dk_DK
│   │   ├── el_GR
│   │   ├── en
│   │   ├── en_US
│   │   ├── es_ES
│   │   ├── es_MX
│   │   ├── fa_IR
│   │   ├── fi_FI
│   │   ├── fr_FR
│   │   ├── hi_IN
│   │   ├── hr_HR
│   │   ├── it_IT
│   │   ├── ja_JP
│   │   ├── ko_KR
│   │   ├── lt_LT
│   │   ├── lv_LV
│   │   ├── ne_NP
│   │   ├── nl_NL
│   │   ├── no_NO
│   │   ├── pl_PL
│   │   ├── pt_BR
│   │   ├── pt_PT
│   │   ├── ru_RU
│   │   ├── sl_SI
│   │   ├── sv_SE
│   │   ├── tr_TR
│   │   ├── uk_UA
│   │   ├── zh_CN
│   │   └── zh_TW

次に、/faker/providers/person直下の__init__.pyを見てみます。

from .. import BaseProvider


class Provider(BaseProvider):
    formats = ['{{first_name}} {{last_name}}', ]

    first_names = ['John', 'Jane']

    last_names = ['Doe', ]

    def name(self):
        """
        :example 'John Doe'
        """
        pattern = self.random_element(self.formats)
        return self.generator.parse(pattern)

    @classmethod
    def first_name(cls):
        return cls.random_element(cls.first_names)

    @classmethod
    def last_name(cls):
        return cls.random_element(cls.last_names)
        
    # 以下省略

こんな感じで各言語のPersonProviderのベースとなるProviderが実装されてます。
random_element()などランダムにデータを抽出するclassmethod群を実装したBaseProviderを継承しているのがわかります。

そして、このProviderを継承してpropertyとmethodを新たに作ったり、オーバーライドしたりして各言語に対応したProviderを用意していきます。
日本語対応のPersonProviderは以下を参照ください。
https://github.com/joke2k/faker/blob/master/faker/providers/person/ja_JP/__init__.py

###Factory.create() - Generatorを生成する
https://github.com/joke2k/faker/blob/master/faker/factory.py#L14-L44

このメソッドでは<class 'faker.generator.Generator'>のインスタンスを生成してreturnしてます。

下記の処理で、Factory.create()の引数として渡したlocaleを元に、<class 'faker.generator.Generator'>のインスタンスであるfakerに各Providerをセットしています。
(指定したlocaleに対応したProviderがない場合は、DEFAULT_LOCALEであるen_USのものがセットされます)

for prov_name in as:
    if prov_name == 'faker.as':
        continue

    prov_cls, lang_found = cls._get_provider_class(prov_name, locale)
    provider = prov_cls(faker)
    provider.__provider__ = prov_name
    provider.__lang__ = lang_found
    faker.add_provider(provider)

それでは次に、上記の処理で出てきたadd_provider(provider)を見ていきましょう。

Generator.add_provider() - Generatorにformatを追加する

引数で渡されたProvider(ex. <faker.providers.person.ja_JP.Provider>)で定義されているpublicなmethodをGeneratorのformatに追加していってます。

Generator.set_formatter() - setattr()のラッパー関数

https://github.com/joke2k/faker/blob/master/faker/generator.py#L70-L75
Generator.add_provider()のところで急にformatという言葉が出てきましたが、ただGeneratorインスタンスに対してsetattr()を行っているだけです。

まとめ

ここまで見てきたようにFactory.create()を行うことで、
各言語のProvider群で定義されているpublicなmethodを全てattributesにセットされた<class 'faker.generator.Generator'>のインスタンスが取得できるようになってます。
このおかげで、以下のようにfake.method_name()と呼び出すだけで、各言語のProviderの中で実装されているmethod_name()が実行されてランダムなテストデータを取得することができてます。

>>> fake.name()
'Anfernee Reichel'

##さいごに
力尽きてFactory.create()の部分しか追えていませんが、Generatorの生成方法が理解できればこのライブラリの他の使い方もわかると思います。
こういった薄いライブラリを取り上げてのコードリーディングは、取っ付きやすく楽しかったのでオススメです!

##あとがき
この記事を書いている途中で、
ja_JPPersonProvidername()のformatを日本語で保持していたので、user_name()domain_word()がきちんと表示されない』
という問題に遭遇しました。
https://github.com/joke2k/faker/blob/master/faker/providers/internet/__init__.py#L27-L32
https://github.com/joke2k/faker/blob/master/faker/providers/internet/__init__.py#L90-L95

上記の問題に対処するためのPRを出して、無事mergeしてくれました。
https://github.com/joke2k/faker/pull/300

24
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?