Elizabeth — это библиотека для языка программирования Python, которая помогает генерировать фиктивные данные. Один из простейших примеров использования библиотеки — это заполнение баз данных для приложений на Flask или Django. На данный момент библиотека поддерживает 16 языковых стандартов и 18 классов-провайдеров, предоставляющих разного рода данные.
Возможность генерировать фиктивные, но в то же время валидные данные бывает очень полезна при разработке приложений, которые подразумевают работу с базой данных. Ручное заполнение базы данных представляется довольно-таки сложным и изнурительным процессом, но по-настоящему все усложняется в тот момент, когда требуется сгенерировать не 10-15 пользователей, а 100-150 тысяч. В этой статье я постараюсь обратить ваше внимание на инструмент, который в разы упрощает процесс начальной загрузки базы данных на этапе тестирования.
Общая информация
В представленной ниже таблице приведены классы-провайдеры.
№ | Provider | Описание |
---|---|---|
1 | Address | Адресные данные (названия улиц, номера домов, индексы и т.п.) |
2 | Business | Данные для бизнеса (компании, типы компаний, копирайты и т.п.) |
3 | Code | Коды (ISBN, EAN, IMEI и т.п.) |
4 | ClothingSizes | Размеры одежды (международные, европейский, американские и т.п.) |
5 | Datetime | Время (день недели, месяц, год, день рождения и т.п.) |
6 | Development | Разработка (версия, язык программирования, стек и т.п.) |
7 | File | Файлы (расширения, типы файлов и т.п.) |
8 | Food | Еда (овощи, фрукты, единицы измерения и т.п.) |
9 | Personal | Персональные данные (имя, фамилия, возраст, email и т.п.) |
10 | Text | Текстовые данные (предложение, заголовок, текст и т.п.) |
11 | Transport | Транспорт (модель грузового автомобиля и т.п.) |
12 | Network | Сеть (IPv4, IPv6, MAC address и т.п.) |
13 | Science | Научные данные (химический элемент, формул и т.п.) |
14 | Internet | Интернет (facebook, twitter, vk и т.п.) |
15 | Hardware | Железо (resolution, cpu, graphics etc.)* |
16 | Numbers | Числовые данные (floats, primes, digit etc.)* |
17 | Path | Пути (корневая директория, рабочая директория и т.п.) |
18 | Generic | Все в одном (когда используется только одна локаль) |
На момент написания этой статьи библиотека поддерживает 16 языковых стандартов, которые перечислены в таблице, что приведена ниже:
№ | ISO Код | Название | Самоназвание |
---|---|---|---|
1 | da |
Датский | Dansk |
2 | de |
Немецкий | Deutsch |
3 | en |
Английский | English |
4 | es |
Испанский | Espanol |
5 | fa |
Персидский | ????? |
6 | fi |
Финский | Suomi |
7 | fr |
Французский | Francais |
8 | is |
Исландский | Islenska |
9 | it |
Итальянский | Italiano |
10 | nl |
Нидерландский | Nederlands |
11 | no |
Норвежский | Norsk |
12 | pl |
Польский | Polski |
13 | pt |
Португальский | Portugues |
14 | pt-br |
Бразильский Португальский | Portugues Brasileiro |
15 | ru |
Русский | Русский |
16 | sv |
Шведский | Svenska |
Установка
Установка Elizabeth
производится как обычно, т.е посредством пакетного менеджера pip
:
? ~ pip install elizabeth
Генерация
Изначально я планировал показать генерацию данных на примере небольшого Flask-приложения (блога), но решил отказаться от этой идеи, ибо не все знакомы с Flask. По этой причине я буду показывать все на чистом Python. В случае, если вы захотите перенести все в свой проект, то вам нужно всего лишь определить статический метод в теле вашей модели и вызывать его в тот момент, когда нужно выполнить начальную загрузку БД, как показано ниже.
Модель будет выглядеть как-то так:
# ...
class Patient(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True)
phone_number = db.Column(db.String(25))
full_name = db.Column(db.String(100))
weight = db.Column(db.String(64))
height = db.Column(db.String(64))
blood_type = db.Column(db.String(64))
def __init__(self, **kwargs):
super(Patient, self).__init__(**kwargs)
@staticmethod
def _bootstrap(count=2000):
from elizabeth import Personal
person = Personal('en')
for _ in range(count):
patient = Patient(email=person.email(),
phone_number=person.telephone(),
full_name=person.full_name(gender='female'),
weight=person.weight(),
height=person.height(),
blood_type=person.blood_type()
)
db.session.add(patient)
try:
db.session.commit()
except Exception:
db.session.rollback()
А вот так будет выглядеть сам процесс генерации:
>>> patient = Patient() # Создать экземпляр класса-модели.
>>> patient._bootstrap(count=40000) # Сгенерировать 40к записей.
Примеры
Хотелось бы подчеркнуть, что я буду приводить в примерах только базовые возможности библиотеки и, в основном, обойдусь несколькими классами-провайдерами, ибо их там слишком много, чтобы рассказывать обо всех. Если вы заинтересуетесь, то можете найти ссылки на проект и на документацию в самом конце статьи и самостоятельно изучить все.
Библиотека устроена довольно просто и все, что вам необходимо для того, чтобы начать работать с данными — это создать экземпляр класса-провайдера, как показано в примере ниже.
>>> from elizabeth import Personal
>>> user = Personal('is')
>>> for _ in range(0, 9):
user.full_name(gender='male')
Karl Brynjulfsson
Rognvald Ei?sson
Vesteinn Rikhar?sson
Fri?leifur Granason
Fjarki Arngar?sson
Hafsteinn ?rymsson
Sivar Kakalason
Grimnir Unason
Gymir ?or?sson
Примеры
Код ниже создаст 50к email адресов.
>>> user = Personal('ru')
>>> emails = [user.email(gender='female') for _ in range(0, 50000)]
Вывод (сокращенный):
[
lvana6108@gmail.com', 'demetrice3816@live.com',
'alayna7278@yahoo.com', 'maida5494@outlook.com', ...
]
Сгенерировать имя, день рождения и группу крови:
>>> from elizabeth import Generic
>>> g = Generic('pl') # согласно стандарту ISO 639-1, pl - это код Польши.
>>> for _ in range(0, 5):
name = g.personal.full_name()
birthday = g.datetime.birthday(readable=True)
blood = g.personal.blood_type()
name + '-' + birthday + '/' + blood
Ibolya Smolik - Listopad 11, 1997 / B?
Lonislawa Podsiadlo - Styczen 20, 1989 / AB+
Loritta Boguski - Pazdziernik 11, 1990 / A?
Laurentyna Kowalczyk - Lipiec 16, 1993 / B?
Gelazyna Wawrzyniak - Pazdziernik 12, 2000 / B?
Можете генерировать и пользователей разного пола:
>>> from random import choice
>>> user = Personal('en')
>>> for _ in range(0, 20):
title = user.title(type_='academic')
name = user.full_name(choice(['female', 'male']))
'{0}, {1}'.format(name, title)
Sid Powell, M.Des
Vanita Christensen, M.D.
Vania Grant, DPhil
Chester Valenzuela, MSc
Georgeann Sanders, D.Ed.
Dong Clay, DPhil
Lala Vang, DPhil
Alden Nieves, M.A.
Deloise Erickson, B.A.
Willow Cox, M.D.
Belia Lowery, Prof.
Shirley Thompson, M.Des
Tracey Dickson, PhD
Alexis Newton, D.Ed.
Leland Gay, D.Ed.
Noel Joseph, MMath
Oda Ryan, B.A.
Alan Sharp, B.A.
Bernardina Solomon, Prof.
Paul Caldwell, M.D.
Сгенерировать фиктивных держателей карты Visa:
user = Personal('en')
def get_card(sex='female'):
owner = {
'owner': user.full_name(sex),
'exp_date': user.credit_card_expiration_date(maximum=21),
'number': user.credit_card_number(card_type='visa')
}
return owner
for _ in range(0, 10):
print(get_card())
{'exp_date': '02/20', 'owner': 'Laverna Morrison', 'card_number': '4920 3598 2121 3328'}
{'exp_date': '11/19', 'owner': 'Melany Martinez', 'card_number': '4980 9423 5464 1201'}
{'exp_date': '01/19', 'owner': 'Cleora Mcfarland', 'card_number': '4085 8037 5801 9703'}
{'exp_date': '06/21', 'owner': 'Masako Nielsen', 'card_number': '4987 3977 4806 4598'}
{'exp_date': '02/18', 'owner': 'Ji Reid', 'card_number': '4371 5151 1188 6594'}
{'exp_date': '12/19', 'owner': 'Kacie Huff', 'card_number': '4053 2310 5641 6849'}
{'exp_date': '05/19', 'owner': 'Violette Hurst', 'card_number': '4333 5939 9634 8805'}
{'exp_date': '07/16', 'owner': 'Shenika Moreno', 'card_number': '4509 3525 9097 4027'}
{'exp_date': '09/20', 'owner': 'Gianna Thompson', 'card_number': '4637 4384 3001 5237'}
{'exp_date': '05/19', 'owner': 'Pa Mccoy', 'card_number': '4290 9075 7914 2028'}
Сгенерировать модель грузового автомобиля (если сайт ориентирован на грузоперевозки или на иную деятельность, связанную с транспортом):
>>> trans = Transport()
>>> for _ in range(0, 5):
trans.truck()
Seddon-2537 IM
Karrier-7799 UN
Minerva-5567 YC
Hyundai-2808 XR
LIAZ-7174 RM
Ну или можно указать маску модели:
>>> for _ in range(0, 5):
trans.truck(model_mask="##@") # ## - числа, @ - буквы
Henschel-16G
Bean-44D
Unic-82S
Ford-05Q
Kalmar-58C
А вот так можно сгенерировать текст:
>>> text = Text('ru')
>>> print(text.text(quantity=5)) # quantity - показатель количества предложений.
'Язык включает в себя средства порождения параллельных легковесных процессов и их взаимодействия через обмен асинхронными сообщениями в соответствии с моделью акторов. Python поддерживает несколько парадигм программирования, в том числе структурное, объектно-ориентированное, функциональное, императивное и аспектно-ориентированное. Например, определение функции, которое использует сопоставление с образцом, для выбора одного из вариантов вычисления или извлечения элемента данных из составной структуры, напоминает уравнение. Сопоставление с образцом распространено даже на битовые строки, что упрощает реализацию телекоммуникационных протоколов. Haskell — стандартизированный чистый функциональный язык программирования общего назначения.'
Получить алфавит текущей локали можно так:
>>> text = Text('fa')
>>> text.alphabet()
['?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?']
Можно получить список слов:
>>> text = Text('pt-br')
>>> text.words(quantity=50)
['poder', 'de', 'maior', 'so', 'cima', 'Singularidades', 'mudou', 'ficara', 'nao', 'vossas', 'prata', 'chorar', 'c', 'Equilibrio', 'drogas', 'paul', 'simplesmente', 'querido', 'vendo', 'material', 'apartamento', 'postar', 'basico', 'com', 'papa', 'cor', 'simbolo', 'ocupado', 'armas', 'vidro', 'roda', 'droga', 'serve', 'menina', 'cor', 'semente', 'solucao', 'controle', 'desapareceu', 'livros', 'voltar', 'melhores', 'Menos', 'quantas', 'pensar', 'estas', 'jogar', 'personagem', 'personagem', 'cabelo']
Можно сгенерировать название улицы:
>>> address = Address('ru')
>>> street = address.address()
'ул. Хабаровская 651'
Или получить субъект РФ:
>>> subject = address.state()
>>> subject
'Кировская область'
Сгенерировать координаты:
>>> for _ in range(15):
address.coordinates()
{'latitude': -28.362892454682246, 'longitude': 11.512065821275826}
{'latitude': 88.6508382607752, 'longitude': 106.65769171105143}
{'latitude': 37.83559264558106, 'longitude': 152.45098344872065}
{'latitude': -13.876545618251413, 'longitude': -52.192814272082956}
{'latitude': 66.24928144869963, 'longitude': -128.7755343668871}
{'latitude': 38.294693362179856, 'longitude': -179.1208031527886}
{'latitude': -52.987692529476554, 'longitude': -59.705094559802475}
{'latitude': -33.69813204096576, 'longitude': 72.54315315052486}
{'latitude': 83.91496334322997, 'longitude': 105.58095303015153}
{'latitude': -83.45909366537177, 'longitude': -13.56788050796456}
{'latitude': 20.41176475353275, 'longitude': 151.7257568261699}
{'latitude': 63.52570687889795, 'longitude': 158.04375817974056}
{'latitude': -78.51081206017261, 'longitude': 47.50148838136573}
{'latitude': -81.94335735575271, 'longitude': -84.44100935967519}
{'latitude': -80.30372095039073, 'longitude': 21.819487429596222}
На самом деле возможностей очень много и потому кружится голова, когда пытаешься придумать что-то конкретное, но я почти уверен, что заинтересованные люди найдут много интересного для себя.
Github: lk-geimfari/elizabeth
Read the Docs: elizabeth
P.S Чуть позже я все-таки попытаюсь обновить статью и добавить пример Flask-приложения, о котором упоминалось ранее в статье.
Спасибо за внимание и удачных тестов!
Комментарии (33)
kmz161
21.12.2016 15:21Возникла ошибка при установке библиотеки
SyntaxError: Non-ASCII character '\xe2' in file elizabeth\core\interdata.py on line 720, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for detailslikid_ri
21.12.2016 15:23Скажите, пожалуйста, какая у вас ОС и какой Python использовали?
kmz161
21.12.2016 15:31Проблема идентичная как на windows 10, так и на Ubuntu 16.04. На обоих ОС python 2.7.12.
Извиняюсь, ошибся веткой
tytar
21.12.2016 15:34Скажите, а в чем профит вашей реализации от того же faker?
likid_ri
21.12.2016 15:56+1Какого-то глубокого сравнения я не производил, но могу сказать, что данных больше, провайдеров больше. Данные для русского языка достаточно точны и валидны. Скорость работы выше. Я, конечно, не производил сравнения скорости генерации в в боевых условиях (т.е с бд), но даже в обычной генерации данных `elizabeth` работает в разы быстрее, чем `faker`.
Небольшой пример:
Ниже приведен скрины работы кода, который генерирует 250к имен (Ф.И).
polarnik
21.12.2016 15:39Привет, спасибо. Как-то создал синтетический мир из нескольких тысяч организаций и сотрудников для тестирования сервиса электронного документооборота. Нужны были ИНН, КПП, ОГРН, СНИЛС, ФИО, наименования, города, улицы, индексы, ...
Подборку исходных данных по ФИО и наименованиям, частично, вот тут отразил:
- http://qapositive.blogspot.ru/2015/01/dictionaries.html
Взял из Википедии. Потом была реализация, где источником были базы данных переписи населения.
likid_ri
21.12.2016 15:58Пожалуйста! Мы по возможности стараемся добавлять только данные, которые годятся для всех языков. А ИНН и все такое можно генерировать другими классами-провайдерами, которые с легкостью с этим справляются.
- http://qapositive.blogspot.ru/2015/01/dictionaries.html
JeStoneDev
22.12.2016 02:07Win10
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:01:18) [MSC v.1900 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> from elizabeth import Personal >>> user = Personal('is') >>> for _ in range(0, 9): ... print(user.full_name(gender='male')) ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "C:\Users\mainj\AppData\Local\Programs\Python\Python35-32\lib\encodings\cp437.py", line 19, in encode return codecs.charmap_encode(input,self.errors,encoding_map)[0] UnicodeEncodeError: 'charmap' codec can't encode character '\xf0' in position 5: character maps to <undefined>
Нетекстовые данные (и текстовые на английском) нормально генерируются.likid_ri
22.12.2016 08:32Я подозревал, что на Windows проблемы могут возникнуть. Откройте, пожалуйста, issue, чтобы контрибьюторы, у которых Windows могли это исправить.
На Linux:
СкринPTM
22.12.2016 08:47+1Интересно…
как насчет того, чтобы в текст добавить универсальный генератор речей?
_https://dezinfo.net/images2/image/09.2009/ukot/1001.jpglikid_ri
22.12.2016 09:05Да, думали над этим. Пока будет только текст, но когда иностранных контрибьюторов наберется — откажемся от текста в файлах и напишем генераторы.
suharik
27.12.2016 12:39А как оно с падежами? Сгенерить массив из тысяч имен и фамилий это одно, а просклонять? Иными словами, как превратить ['Иван Петрович', 'Василий Байпассович', 'Феофан Илларионович'] в ['выставка картин Иван Петровича', 'концерт Василия Байпассовича', 'арест Феофана Илларионовича'] и есть ли вообще в Питоне средства для подобных трансформаций?
likid_ri
27.12.2016 13:34Библиотека не подразумевает, что имена и фамилии будут использоваться в одном контексте. Ф.И для одних задач, Текст — для других. Обеспечить такого рода тонкости для одного языка — это одно, а для 16 — другое. Каждый язык имеет свои тонкости. Потому проще генерировать текст из готовых Предложений.
suharik
27.12.2016 14:17+1Хорошо, а если абстрагироваться от текста и просто просклонять, можно ли получить на выходе ['Ивана Петровича'] вместо ['Иван Петрович']? Или не те задачи она решает?
likid_ri
27.12.2016 14:22Вы сможете сплитить строку и, в зависимости от окончания (метод endswith()), добавлять то чего, требуют правила русского языка. Другими словами, подобного рода вещи делегированы на пользователя. Задача же библиотеки — это дать вам «Ивана Петровича», а уж как его склонять — это уже дело ваше.
kivsiak
Полезная штука. Хочу такую же как плагин для sketch3. Ну или самому попереть справочники и портировать на праздниках