使用 Faker 类

在版本 2.0.4 及以下版本中,Faker 对象只是类方法 Factory.create 的快捷方式,该方法会创建一个 Generator 对象,该对象可以访问各种提供者方法。由于一切的设置方式,如果不通过 FactoryGenerator 内部结构,就很难完成某些操作,而且可能会破坏很多东西,这会给用户升级时带来困难。

解决方案是引入一个新的 Faker 代理类,它在大多数情况下表现得就像旧的 Faker 快捷方式一样,但支持多种语言环境,同时提供了子类化的选项以及在旧代码受影响时非常简单的升级路径。为了本文档的目的,“新 Faker”和“旧 Faker”将分别用于指代新的代理类和 Factory.create 快捷方式。

破坏性变更

任何使用 Faker.seed() 方法的代码库都将受到影响,因为虽然新旧 Faker.seed() 都指向 Generator.seed(),但在新 Faker 中,从 Faker 对象实例调用该方法已被禁用,尝试这样做将引发 TypeError,如下所示。

TypeError: Calling `.seed()` on instances is deprecated. Use the class method `Faker.seed()` instead.

理由可以在 相关 PR 中找到,但目标是处理涉及共享 random.Random 实例的非显式遗留行为,我们认为在添加新 Faker 后,这种情况只会变得更加混乱。

升级指南

假设受影响的代码看起来像这样

from faker import Faker
fake = Faker()
fake.seed(0)  # This will raise a TypeError

只需将所有来自实例的 seed() 方法调用替换为 Faker.seed(),如下所示。这是开始使用新 Faker 类及其功能所需的全部操作,即使将附加参数传递给 Faker,因为新 Faker 和旧 Faker 预期的参数是相同的。

from faker import Faker
fake = Faker()
Faker.seed(0)

一种保守的方法是将 Faker 重新定义为如下所示的旧快捷方式。这将跳过使用新的代理类,但代码仍能继续使用任何新的提供者方法,同时不受新 bug 的影响。当然,这也意味着将没有多语言环境支持,也没有子类化的选项。

from faker.factory import Factory
Faker = Factory.create
fake = Faker()
fake.seed(0)

代理类实现细节

一个新的 Faker 实例只是一个代理对象,它引用了 Generator 对象,每个在实例化时指定的唯一语言环境对应一个。这些 Generator 对象只是旧 Faker 的“实例”。如果只有一个内部 Generator 对象,则新的 Faker 实例处于单语言环境模式。如果有多个,则处于多语言环境模式。

在单语言环境模式下,新的 Faker 实例可以很容易地被强制表现得像使用旧 Faker 创建的实例,因为可以在新的 Faker 实例上暴露一个类似的接口,然后以 1:1 的方式将方法、属性和特性调用代理到唯一的 Generator 对象。事实上,这就是它的实现方式,也是如何保持向后兼容性(除了 Faker.seed)。

然而,在多语言环境模式下,这种 1:1 映射不再存在,调用如何被代理取决于该属性是提供者方法还是 Generator 对象中存在的某些属性。可以提供合理的默认实现,像我们对 seed_instance 所做的那样进行整齐的映射,但其余的,如 add_provider 以及 random 的 getter 和 setter,则更依赖于特定的用例,或者具有潜在危险。

在这些情况下,最好由用户创建自己的子类并实现,或者直接从内部 Generator 对象本身调用这些方法。多语言环境模式将在其专用部分中详细讨论。

代理类属性名称解析

代理类具有一个相当复杂的属性名称解析行为,按此顺序运行

  1. 如果属性名称是 seed,则引发 TypeError。这会阻止从实例调用类方法 seed

  2. 如果 #1 不适用,检查属性名称是否与代理类实例中存在的属性匹配。如果匹配,则返回匹配的属性。

  3. 如果 #2 失败,检查实例是否处于单语言环境模式。如果是,将调用代理到唯一的内部 Generator 对象,并尝试返回匹配的属性。

  4. 如果 #3 不适用,则该实例从此被称为处于多语言环境模式。继续检查属性名称是否与 Generator 属性匹配。如果匹配,则引发 NotImplementedError。

  5. 如果 #4 不适用,检查属性名称是否与缓存模式正则表达式匹配。如果不匹配,则引发 AttributeError,因为它应该在 #2 中已经被处理(如果存在的话)。

  6. 如果所有其他都失败或不适用,则假设属性名称可能指的是提供者方法,并执行工厂/生成器选择,然后将调用代理到选定的 Generator 对象。

工厂/生成器选择将在多语言环境模式的专用部分中详细讨论。

语言环境规范化

根据传入的 locale 值,新的 Faker 实例将以单语言环境模式或多语言环境模式运行。locale 的值可以是以下之一

  1. 任何空值,如 None(自动默认为 en_US

  2. 有效的语言环境字符串,带下划线或连字符

  3. 包含有效语言环境字符串(带下划线或连字符)的列表、元组或集合

  4. 带有有效语言环境字符串(带下划线或连字符)和权重的键值对的 OrderedDict

前两个选项是旧 Faker 已经预期的选项,因此新 Faker 也基本相同。使用这两个选项中的任何一个都将始终导致处于单语言环境模式的新 Faker 实例。在这种模式下,由于前面讨论的 1:1 代理行为,实际上没有必要检索对内部 Generator 对象的引用。

潜在的陷阱在于多语言环境模式以及需要单独访问内部 Generator 对象时。由于语言环境字符串可以写成带下划线(en_US)或连字符(en-US),这可能会导致混淆和错误,因此必须对语言环境字符串进行规范化以提供一致的结果且不重复。

在实例化期间,新 Faker 会将语言环境字符串规范化为下划线格式,并以此方式存储它们。换句话说,语言环境字符串 en_US 将被视为与 en-US 相同,当两者都被指定时,最后一个被处理的将被视为重复并被丢弃。通过键索引访问内部 Generator 对象时也会执行相同的规范化。

例如,下面的代码将创建一个处于单语言环境模式的新 Faker 实例,即使指定了四种语言环境。

from faker import Faker
fake = Faker(['en-US', 'en_US', 'en_US', 'en-US'])

# Will return ['en_US']
fake.locales

# Get reference to en_US generator
us1 = fake['en_US']

# Get reference to en-US generator
us2 = fake['en-US']

# Will return True
us1 == us2

多语言环境模式

要启用多语言环境模式,locale 参数的值必须是包含多个有效语言环境(规范化后)的列表、元组、集合或 OrderedDict。例如

from collections import OrderedDict
from faker import Faker

locale_list = ['en-US', 'ja-JP', 'en_US']
fake1 = Faker(locale_list)

# Will return ['en_US', 'ja_JP']
fake1.locales

locale_odict = OrderedDict([
    ('en-US', 1),
    ('ja-JP', 2),
    ('en_US', 2),
])
fake2 = Faker(locale_odict)

# Will return ['en_US', 'ja_JP']
fake2.locales

在这种模式下,从新的 Faker 实例调用潜在的提供者方法将按此顺序运行工厂/选择逻辑

  1. 检查提供者方法是否已存在缓存映射。如果是,使用该映射,并跳到 #3。

  2. 如果 #1 不适用,检查哪些 Generator 对象支持该提供者方法。缓存映射结果,以及如果在实例化期间提供了权重,则缓存相应的权重。

  3. 如果没有生成器支持该提供者方法,将引发 AttributeError,就像使用旧 Faker 时会引发一样。

  4. 如果只有一个生成器支持该提供者方法,则返回这唯一的生成器。

  5. 如果有多个适用的生成器,且未提供权重,则使用均匀分布随机选择一个生成器,即 random.choice

  6. 如果有多个适用的生成器,且提供了权重,则使用由提供的权重定义的分布随机选择一个生成器。

除了能够根据语言环境自定义概率并最大限度地减少性能损失外,工厂选择逻辑还保证只要至少有一个内部 Generator 对象支持它,调用提供者方法就不会失败。

示例

有时展示比用文字解释容易得多,这里有一份新 Faker 在多语言环境模式下的备忘单。

from collections import OrderedDict
from faker import Faker
locales = OrderedDict([
    ('en-US', 1),
    ('en-PH', 2),
    ('ja_JP', 3),
])
fake = Faker(locales)

# Get the list of locales specified during instantiation
fake.locales

# Get the list of internal generators of this `Faker` instance
fake.factories

# Get the internal generator for 'en_US' locale
fake['en_US']

# Get the internal generator for 'en_PH' locale
fake['en_PH']

# Get the internal generator for 'ja_JP' locale
fake['ja_JP']

# Will raise a KeyError as 'en_GB' was not included
fake['en_GB']

# Set the seed value of the shared `random.Random` object
# across all internal generators that will ever be created
Faker.seed(0)

# Creates and seeds a unique `random.Random` object for
# each internal generator of this `Faker` instance
fake.seed_instance(0)

# Creates and seeds a unique `random.Random` object for
# the en_US internal generator of this `Faker` instance
fake.seed_locale('en_US', 0)

# Generate a name based on the provided weights
# en_US - 16.67% of the time (1 / (1 + 2 + 3))
# en_PH - 33.33% of the time (2 / (1 + 2 + 3))
# ja_JP - 50.00% of the time (3 / (1 + 2 + 3))
fake.name()

# Generate a name under the en_US locale
fake['en-US'].name()

# Generate a zipcode based on the provided weights
# Note: en_PH does not support the zipcode provider method
# en_US - 25% of the time (1 / (1 + 3))
# ja_JP - 75% of the time (3 / (1 + 3))
fake.zipcode()

# Generate a zipcode under the ja_JP locale
fake['ja_JP'].zipcode()

# Will raise an AttributeError
fake['en_PH'].zipcode()

# Generate a Luzon province name
# Note: only en_PH out of the three supports this provider method
fake.luzon_province()

# Generate a Luzon province name
fake['en_PH'].luzon_province()

# Will raise an AttributeError
fake['ja_JP'].luzon_province()

唯一值

版本 v4.2.0 中的新功能是 Faker 代理上的 .unique 属性。

通过此属性访问提供者方法可确保返回的值在 Faker 实例的生命周期内是唯一的。

import faker

fake = faker.Faker()

numbers = set(fake.unique.random_int() for i in range(1000))
assert len(numbers) == 1000

对于具有多个 locale 的 Faker 实例,您可以使用下标表示法指定用于唯一值的 locale

from faker import Faker
fake = Faker(['en_US', 'fr_FR'])
names = [fake.unique["en_US"].first_name() for i in range(500)]
assert len(set(names)) == 500

要清除已见过的值,只需调用 fake.unique.clear(),这将允许再次返回以前生成的值。

提供者方法的不同参数签名不共享唯一性池。

import faker

fake = faker.Faker()

numbers = set(fake.unique.random_int(min=1, max=10) for i in range(10))
other_numbers = set(fake.unique.random_int(min=1, max=5) for i in range(5))

assert other_numbers.issubset(numbers)

如果提供者函数生成的值范围很小,并且使用了 .unique 属性,则可能在一定次数迭代后找不到合适的唯一值。

为避免无限循环,此时将引发 UniquenessException

import faker

fake = faker.Faker()

for i in range(3):
     fake.unique.boolean()  # UniquenessException!

最后的警告是,只有可哈希的参数和返回值才能与 .unique 属性一起使用,因为它内部由一个集合支持,用于快速成员测试。

import faker

fake = faker.Faker()

fake.unique.profile()  # TypeError: unhashable type: 'dict'