源碼背景
Faker是一個Python第三方庫,GITHUB開源項目,主要用於創建偽數據創建的數據包含地理信息類、基礎信息類、個人賬戶信息類、網絡基礎信息類、瀏覽器信息類、文件信息類、數字類 文本加密類、時間信息類、其他類別等。
源碼的地址:https://github.com/joke2k/faker
收集的函數速查:https://blog.csdn.net/qq_41545431/article/details/105006681
源碼解讀
主源碼解讀
通過直接點擊初始化的類進入類初始化模塊
fake = Faker(locale='zh_CN')
核心的源碼搬運如下:
proxy.py文件
from __future__ import absolute_import, unicode_literals
from collections import OrderedDict
import random
import re
import six
from faker.config import DEFAULT_LOCALE
from faker.factory import Factory
from faker.generator import Generator
from faker.utils.distribution import choices_distribution
class Faker(object):
"""Proxy class capable of supporting multiple locales"""
cache_pattern = re.compile(r'^_cached_\w*_mapping$')
generator_attrs = [
attr for attr in dir(Generator)
if not attr.startswith('__')
and attr not in ['seed', 'seed_instance', 'random']
]
def __init__(self, locale=None, providers=None,
generator=None, includes=None, **config):
self._factory_map = OrderedDict()
self._weights = None
if isinstance(locale, six.string_types):
locales = [locale.replace('-', '_')]
# This guarantees a FIFO ordering of elements in `locales` based on the final
# locale string while discarding duplicates after processing
elif isinstance(locale, (list, tuple, set)):
assert all(isinstance(l, six.string_types) for l in locale)
locales = []
for l in locale:
final_locale = l.replace('-', '_')
if final_locale not in locales:
locales.append(final_locale)
elif isinstance(locale, OrderedDict):
assert all(isinstance(v, (int, float)) for v in locale.values())
odict = OrderedDict()
for k, v in locale.items():
key = k.replace('-', '_')
odict[key] = v
locales = list(odict.keys())
self._weights = list(odict.values())
else:
locales = [DEFAULT_LOCALE]
for locale in locales:
self._factory_map[locale] = Factory.create(locale, providers, generator, includes, **config)
self._locales = locales
self.AQ_factories = list(self._factory_map.values())
文件主要引入了內部類以及collections.OrderedDict,random,six等外部包。
以下對相應的關鍵外部包做個說明:
1、collections.OrderedDict實現了對字典對象中元素的排序,由於python的字典是按照hash值進行存儲的所以,導致字典是無序狀態,OrderedDict實現了對字典對象中元素的排序
2、six這個名字來源於 6 = 2 x 3,其產生主要是為了解決Python2 和 Python3 代碼兼容性
初始化的開頭按一定規則將Generator類下的屬性保存在generator_attrs中,以備后續方法調用。在init的初始化中規定了類的入參有哪些?同時定義了兩個名義上的私有變量,來防止外部調用類方法,說他是名義上的私有變量是因為python中沒有真正的私有化,不管是方法還是屬性,為了編程的需要,約定加了下划線 _的屬性和方法不屬於API,不應該在類的外面訪問,也不會被from M import * 導入。但是注意你想調用也可以調用。self._factory_map中保存的是OrderedDict()的實例化對象;self._weights為了保證其在類中被調用,賦予初始化的None值。接下來的是對入參locale的條件判斷,示意圖基本如下:
proxy.py文件
if isinstance(locale, six.string_types):
如果入參的locale是字符串,則替換-線為_線,保存在locales中
elif isinstance(locale, (list, tuple, set)):
如果入參的locale是列表,元祖,集合,則遍歷入參判斷元素為字符串后將元素替換-線為_線保存在locales中
elif isinstance(locale, OrderedDict):
如果入參的locale是有序字典,則遍歷入參判斷鍵為字符串后將鍵替換-線為_線保存在locales中,將鍵的值保存在之前定義的self._weights中
locales = list(odict.keys())
self._weights = list(odict.values())
else:
以上條件都不滿足時,將配置文件中自定義的locale保存到列表中賦值給locales
為什么在locale這個入參要做那么多的校驗呢,是因為在初始化是locale做了一件很重要的事,而這件事對locale的要求很高,具體來看源碼:
proxy.py文件
for locale in locales:
self._factory_map[locale] = Factory.create(locale, providers, generator, includes, **config)
源碼在這里主要做了對每種語言創建了一個map字典,里面涉及到了Factory工廠模式下的創建方法,入參基本為當前類的入參。那么Faker除了對locale入參進行了校驗外,有沒有做其他的校驗呢?答案是肯定的在對關鍵屬性self._weights、self._factories、self._factory_map.items(),通過object下的@property裝飾器進行了只讀的校驗,外部修改。
魔法方法解讀
對類的實例化后需要使用實例里面的屬性,那么為了增加其擴展性加了getitem的魔法方法使的我們可以對Fake()['pujen']操作,那么在Faker中Fake()['pujen']會返回啥呢,源碼中運算結果為KeyError,當然了因為Faker中沒有pujen這個語言包。
proxy.py文件
def __getitem__(self, locale):
return self._factory_map[locale.replace('-', '_')]
fake = Faker(locale='zh_CN')
print(fake['zh_CN'])
>>> <faker.generator.Generator object at 0x0000021AEE18FDD8>
接下來看一個實例來更好的去理解什么是getitem魔法方法
class Fake(object):
def __init__(self):
self.name = 'jack'
def __getitem__(self,item):
if item in self.__dict__: # item = key,判斷該key是否存在對象的 __dict__ 里,
return self.__dict__[item] # 返回該對象 __dict__ 里key對應的value
def __setitem__(self, key, value):
self.__dict__[key] = value # 在對象 __dict__ 為指定的key設置value
def __delitem__(self, key):
del self.__dict__[key] # 在對象 __dict__ 里刪除指定的key
f1 = Fake()
print(f1['name']) # jack
f1['age'] =10
print(f1['age']) # 10
del f1['name']
print(f1.__dict__) # {'age': 10}
接下來看一下getattribute__方法,這個方法出現在這個類中主要是因為防止seed()方法的直接調用而是要形如Faker.seed()這樣的調用,在Faker的源碼中seed()實際是Generator.seed()一種隨機種子函數。假設調用類的方法中不是seed()而是其他非此類方法,那么會執行__getattr方法,這個方法在Faker里面主要是干了什么呢:
proxy.py文件
def __getattr__(self, attr):
"""
Handles cache access and proxying behavior
:param attr: attribute name
:return: the appropriate attribute
"""
條件語句判斷異常情況,最后走如下代碼
factory = self._select_factory(attr)
return getattr(factory, attr)
工廠模式
在初始化中我們會發現核心的內容最后都是由工廠模式的Factory.create()創建接下來看一下此工廠函數。在Factory中create()是以靜態類方法來體現
factory.py文件
@classmethod
def create(
cls,
locale=None,
providers=None,
generator=None,
includes=None,
**config):
if includes is None:
includes = []
# fix locale to package name
locale = locale.replace('-', '_') if locale else DEFAULT_LOCALE
locale = pylocale.normalize(locale).split('.')[0]#返回規范化的語言環境代碼
if locale not in AVAILABLE_LOCALES:
msg = 'Invalid configuration for faker locale `{0}`'.format(locale)
raise AttributeError(msg)
config['locale'] = locale
providers = providers or PROVIDERS#排序的集合
providers += includes
faker = generator or Generator(**config)
for prov_name in providers:
if prov_name == 'faker.providers':
continue
prov_cls, lang_found = cls._get_provider_class(prov_name, locale)#prov_cls=faker.providers,lang_found語言包名稱
provider = prov_cls(faker)#繼承在Generator類中
provider.__provider__ = prov_name
provider.__lang__ = lang_found
faker.add_provider(provider)#增加類的方法和屬性
return faker
從上面的源碼可以梳理出來,基本就是給類增加方法和規范一下語言包。我們對里面的一些細節代碼梳理一下:
factory.py文件
1、
providers += includes
providers是一個空列表
includes是一個集合數據
那么假設providers=[],includes={1,2,3,4}
則providers += includes運行結果,會使的providers=[1,2,3,4],實際這段代碼就是將集合的數據放到空列表中。
2、
faker = generator or Generator(**config)
provider = prov_cls(faker)
這里faker是generator類,prov_cls實際上是一個類,那么prov_cls(faker)實際就是繼承了Generator類
3、
provider.__provider__ = prov_name
provider.__lang__ = lang_found
faker.add_provider(provider)#增加類的方法和屬性
給這些類賦予方法名和語言包,同時通過魔法方法增加類的方法和屬性,這里面涉及到Generator.add_provider()方法
Faker隱藏主方法類
以上工廠模式中create()主函數方法基本也介紹完了,類內部的其他方法暫時不過多的研究。接下來看一下在create()中涉及到的Generator.add_provider()方法,方法的源碼如下:
generator.py文件
def add_provider(self, provider):
if isinstance(provider, type):
provider = provider(self)
self.providers.insert(0, provider)#將provider插入到0索引位置
for method_name in dir(provider):
# skip 'private' method
if method_name.startswith('_'):
continue
faker_function = getattr(provider, method_name)#動態運行函數
if callable(faker_function):#函數用於檢查一個對象是否是可調用的
# add all faker method to generator
self.set_formatter(method_name, faker_function)
針對如下的這個用法做一下基本的說明,后續我們寫代碼的時候可以作為借鑒
if isinstance(provider, type):
說明:如果對象參數是classinfo參數的實例,或者是它的一個(直接、間接或虛擬)子類的實例,則返回True。如果對象不是給定類型的對象,則該函數始終返回False。如果classinfo是類型對象的元組(或者遞歸地,其他類似的元組),如果對象是任何類型的實例,則返回True。如果classinfo不是類型的類型或元組,而這些元組又不是類型的元組,則會引發類型錯誤異常。
for method_name in dir(provider):
dir的用法說明,如果provider類或者模塊沒有定義dir方法則返回類或者模塊的方法屬性
接下來看一下這兩個方法,主要是用於動態調用函數返回運行對象
faker_function = getattr(provider, method_name)#動態運行函數
if callable(faker_function):#函數用於檢查一個對象是否是可調用的
至此,Generator類中的核心方法介紹完成!
Faker里方法運行內部邏輯
當我們在pycharm里面寫好方法打算去看一下類函數時,Ctrl+鼠標左擊。奇怪的事情發生了,並沒有進入到對應的方法里面去,同時pycharm智能提示我們:
fake = Faker(locale='zh_CN')
fake.random_digit_not_null()
通過上面的源碼解析也可以很清晰的發現,Faker的方法和屬性不像我們往常寫的類一樣在類的下面,全文解析基本沒看到創建偽數據的直接方法和屬性。那么下面來看一下方法的基本運行內部邏輯實現方式。
如圖,在內部運行邏輯中實際上調用的是generator.py文件內容下的Generator.add_provider方法中有一個需要特別注意就是法,上面我們也提到了,在add_provider方法中有一個需要特別注意就是
for method_name in dir(provider):
通過這個基本的循環將所有的方法和屬性加載到對應的語言包中,也就說Faker的屬性和方法實際是在另外一個地方存放着,在使用的時候在拿過來,這樣做使的Faker的本身類看起來簡潔。那么外部是以什么形式來存放的呢?
可以看出在外部有一個provider包,包里面對應很多個方法歸類包,在往內部層級就是對應每個語言包下的方法。來看一下具體方法的內部表現形式是如何的
可以發現基本是以元祖的方式存放的原始數據,我們方法運行后最終的結果都是來自於此,那么函數最后是如何運行方法的呢?其實最上面的源碼解析已經提及到了,就是使用了init()下的
return getattr(factory, attr)
具體到每個方法或者函數上的實現方式由於太多了就不一一解讀了,大范圍的是使用random這個基本庫來實現的。
文章原創首發於微信公眾號 軟件測試微課堂