In [1]:
import axon
from pprint import pprint
from collections import namedtuple
import io
Данная статья является продолжением Что такое AXON. Если первая статья была посвящена знакомству с нотацией AXON
, то это посвящена вариантам использования на основе библиотеки pyaxon.
Сообщение в нотации AXON
это поток объектов или поток пар ключ
:объект
в текстовом формате. Это позволяет не загружать предварительно в память весь поток, но загружать объекты из потока по очереди.
Для этого имеются две функции iload для итеративной загрузки из файла и iloads для итеративной загрузки из текста.
Две другие функции load и load – для полной загрузки объектов из файла и из текста, соответственно. В результате получается список закруженных объектов (list
) или словарь (ordered dict
).
In [2]:
text = """
RU: {capital:"Москва"}
US: {capital:"Вашингтон"}
GB: {capital:"Берлин"}
"""
for val in axon.iloads(text):
print(val)
In [3]:
text = """
{FR:"Paris" RU:"Moscow"}
{CN:"Beigin" JP:"Tokio"}
{US:"Washington" CA:"Ottava"}
"""
for val in axon.iloads(text):
print(val)
In [4]:
text = """
# последовательность словарей
# с сохранением порядка ключей
[RU:"Москва" FR:"Париж"]
[CN:"Пекин" JP:"Токио"]
[US:"Вашингтон" CA:"Оттава"]
"""
for val in axon.iloads(text):
print(val)
In [5]:
text = """
# кортежи экономят память в python,
# если мутируемость не нужна
("RU" "Moscow")
("US" "Washington")
("GB" "London")
"""
for val in axon.iloads(text):
print(val)
In [6]:
text = """
["RU" "Moscow"]
["US" "Washington"]
["GB" "London"]
"""
for val in axon.iloads(text):
print(val)
In [7]:
text = """
# это AXON без форматирования
country{id:"RU" capital:"Moscow"}
country{id:"USA" capital:"Washington"}
country{id:"GB" capital:"London"}
"""
for val in axon.iloads(text):
print(val)
Если вместо элементов нужны объекты определенного типа, то нужно зарегистрировать фабричные функции:
In [8]:
Country = namedtuple("Country", "id capital")
@axon.factory("country")
def factory_country(attrs, vals):
return Country(**attrs)
text = """
# это AXON без форматирования
country{id:"RU" capital:"Moscow"}
country{id:"USA" capital:"Washington"}
country{id:"GB" capital:"London"}
"""
for val in axon.iloads(text, mode='strict'):
print(val)
Для того чтобы обратно преобразовать в AXON
необходимо зарегистрировать функции для редуцирования:
In [9]:
@axon.reduce(Country)
def reduce_Country(ob):
return axon.node("country", {'id':ob.id, 'capital':ob.capital})
vals = axon.loads(text, mode='strict')
print(axon.dumps(vals))
Проблема копирования объектов возникает при загрузке объектов с зависимостями. Для примера рассмотрим пример с обычным графом.
In [10]:
text = """
graph {
nodes: [
&n1 node {id:1 val:4}
&n2 node {id:2 val:7}
&n3 node {id:3 val:2}
&n4 node {id:4 val:5}
]
edges: [
edge {val:12 *n1 *n2}
edge {val:8 *n1 *n4}
edge {val:-2 *n2 *n3}
edge {val:5 *n3 *n4}
]
}
"""
Метки объектов имеют префикс &
, а ссылки префикс *
.
In [11]:
vals = axon.loads(text)
Убеждаемся, что копирования не происходит:
In [12]:
graph = vals[0]
nodes, edges = graph.nodes, graph.edges
print(id(nodes[0]) == id(edges[0][0]) == id(edges[1][0]))
print(id(nodes[1]) == id(edges[0][1]) == id(edges[2][0]))
print(id(nodes[2]) == id(edges[2][1]) == id(edges[3][0]))
print(id(nodes[3]) == id(edges[1][1]) == id(edges[3][1]))
In [13]:
print("Дамп в компактной форме:")
print("=======================")
print(axon.dumps(vals))
Пусть нужно загрузить сторонние данные из XML
документа и вставить в AXON
. Для работы возьмем библиотеку xml.etree из стандартной библиотеки python
.
In [14]:
from xml.etree import ElementTree
Зарегистрируем функции для редуцирования при преобразования в AXON формат.
In [15]:
@axon.reduce(ElementTree.Element)
def element_reduce(elem):
children = elem.getchildren()
if elem.attrib:
if children:
return axon.node(elem.tag, elem.attrib, children)
else:
return axon.node(elem.tag, elem.attrib, None)
elif children:
return axon.node(elem.tag, None, children)
else:
return axon.node(elem.tag)
@axon.reduce(ElementTree.ElementTree)
def etree_reduce(element):
return element_reduce(element.getroot())
Возьмем XML документ (без пространств имен).
In [16]:
xml_doc = """
<persons>
<person firstname="Елена" lastname="Иванова" />
<person firstname="Анна" lastname="Сидорова" />
</persons>
"""
xml = ElementTree.fromstring(xml_doc)
In [17]:
axon_text = """
person {firstname:"Иван" lastname:"Иванов"}
person {firstname:"Николай" lastname:"Сидоров"}
"""
Загружаем и объединяем:
In [18]:
vals = axon.loads(axon_text)
vals.extend(xml.getchildren())
axon_text2 = axon.dumps(vals)
print(axon_text2)
Можно загрузить из AXON
в XML
. Для этого зарегистрируем построитель объектов. Изначально в библиотеке имеются три построителя:
safe
(безопасный в том смысле, что для представления используются только встроенные типы), strict
(используются только зарегистрированные пользователем фабричные функции, иначе генерируется исключение) иmixed
(как в strict
, только если нет зарегистрированной фабричной функции, то используются встроенные объекты).
In [19]:
def update_attribs(d):
for key, val in d.items():
d[key] = str(val)
class ElementTreeBuilder(axon.Builder):
def node(self, name, attrs, vals):
if attrs:
attrs = dict(attrs)
update_attribs(attrs)
else:
attrs = {}
e = ElementTree.Element(name, attrs)
if vals:
e.extend(vals)
return e
axon.register_builder('etree', ElementTreeBuilder())
Теперь можно загрузить и получить дерево XML
документа.
In [20]:
vals = axon.loads(axon_text2, mode='etree')
root = ElementTree.Element("persons")
root.extend(vals)
ElementTree.dump(root)
Скорость загрузки/дампа для AXON
ожидаемо ниже чем для AXON
, так как структура объектной нотации AXON
сложнее JSON
.
Для теста используется файл из 69933 записей (list
of dict
s). Запись имеет вид:
{"auc":233524008,"item":98799,"owner":"???","ownerRealm":"???","bid":8500000,"buyout":8500000,"quantity":1,"timeLeft":"VERY_LONG","rand":0,"seed":1639860480}
In [21]:
import json
In [22]:
# fp = io.open('data/auctions.json', 'rt')
# vals = json.load(fp)
# fp.close()
# axon.dump('data/auctions.axon', vals)
In [23]:
def load_and_iterate_json(path):
fp = io.open(path, 'rt')
vals = json.load(fp)
print(len(vals))
fp.close()
for val in vals:
pass
def iload_and_iterate_axon(path):
for val in axon.iload(path):
pass
def load_and_iterate_axon(path):
vals = axon.load(path)
for val in vals:
pass
In [24]:
%time load_and_iterate_json('data/auctions.json')
%time load_and_iterate_axon('data/auctions.axon')
%time iload_and_iterate_axon('data/auctions.axon')
In [25]:
import random
test_vals_1 = []
for i in range(100000):
test_vals_1.append([random.randint(0, 1000000), random.random(), random.randint(0, 1000000), random.random()])
fp = io.open('data/test_1.json', 'wt')
json.dump(test_vals_1, fp)
fp.close()
axon.dump('data/test_1.axon', test_vals_1)
In [26]:
%time load_and_iterate_json('data/test_1.json')
%time load_and_iterate_axon('data/test_1.axon')
%time iload_and_iterate_axon('data/test_1.axon')
In [27]:
import random
test_vals_2 = []
for i in range(100000):
test_vals_2.append({'a':random.randint(0, 1000000), 'b':random.random(), 'c':random.randint(0, 1000000), 'd':random.random()})
fp = io.open('data/test_2.json', 'wt')
json.dump(test_vals_2, fp)
fp.close()
axon.dump('data/test_2.axon', test_vals_2)
In [28]:
%time load_and_iterate_json('data/test_2.json')
%time load_and_iterate_axon('data/test_2.axon')
%time iload_and_iterate_axon('data/test_2.axon')
In [ ]: