I'm planning to use PyYAML for a configuration file. Some of the items in that configuration file are Python tuples of tuples. So, I need a convenient way to represent them. One can represent Python tuples of tuples as follows using PyYAML
print yaml.load("!!python/tuple [ !!python/tuple [1, 2], !!python/tuple [3, 4]]")
However, this is not convenient notation for a long sequence of items. I think it should be possible to define a custom tag, like python/tuple_of_tuples. I.e. something like
yaml.load("!!python/tuple_of_tuples [[1,2], [3,4]]")
See my first attempt to define this below, by mimicking how
python/tuple is defined, and trying to do similar subclassing. It
fails, but gives an idea what I am after, I think. I have a second
attempt that works, but is a cheat, since it just calls eval
.
If I can't find anything better I'll just use that. However, YAML is
intended as a replacement for ConfigObj, which uses INI files, and is
considerably less powerful than YAML, and I used the same approach
(namely eval
) for tuples of tuples. So in that respect it will be no
worse.
A proper solution would be most welcome.
I have a couple of comments on my first solution.
I'd have thought that the constructor
construct_python_tuple_of_tuples
would return the completed structure, but in fact it seems to return an empty structure as follows([], [])
I traced the calls, and there seems to be a lot of complicated stuff happening after
construct_python_tuple_of_tuples
is called.The value that is returned is a tuple of lists of integers, so quite close to the desired result. So, the structure must be completed later.
The line with
tuple([tuple(t) for t in x])
was my attempt to coerce the list of tuples to a tuple of tuples, but if I return that from
construct_python_tuple_of_tuples
, then the resulting call toyaml.load("!!python/tuple_of_tuples [[1,2], [3,4]]")
is just((),())
Not sure what is with the
yaml.org,2002
Why 2002?
First attempt
import yaml
from yaml.constructor import Constructor
def construct_python_tuple_of_tuples(self, node):
# Complete content of construct_python_tuple
# is
# return tuple(self.construct_sequence(node))
print "node", node
x = tuple(self.construct_sequence(node))
print "x", x
foo = tuple([tuple(t) for t in x])
print "foo", foo
return x
Constructor.construct_python_tuple_of_tuples =
construct_python_tuple_of_tuples
Constructor.add_constructor(
u'tag:yaml.org,2002:python/tuple_of_tuples',
Constructor.construct_python_tuple_of_tuples)
y = yaml.load("!!python/tuple_of_tuples [[1,2], [3,4]]")
print "y", y, type(y)
print y[0], type(y[0])
print y[0][0], type(y[0][0])
The results are
node SequenceNode(tag=u'tag:yaml.org,2002:python/tuple_of_tuples',
value=[SequenceNode(tag=u'tag:yaml.org,2002:seq',
value=[ScalarNode(tag=u'tag:yaml.org,2002:int', value=u'1'),
ScalarNode(tag=u'tag:yaml.org,2002:int', value=u'2')]),
SequenceNode(tag=u'tag:yaml.org,2002:seq',
value=[ScalarNode(tag=u'tag:yaml.org,2002:int', value=u'3'),
ScalarNode(tag=u'tag:yaml.org,2002:int', value=u'4')])])
x ([], [])
foo ((), ())
y ([1, 2], [3, 4]) <type 'tuple'>
y[0] [1, 2] <type 'list'>
y[0][0] 1 <type 'int'>
Second attempt
import yaml
from yaml import YAMLObject, Loader, Dumper
class TupleOfTuples(YAMLObject):
yaml_loader = Loader
yaml_dumper = Dumper
yaml_tag = u'!TupleOfTuples'
#yaml_flow_style = ...
@classmethod
def from_yaml(cls, loader, node):
import ast
print "node", node
print "node.value", node.value, type(node.value)
return ast.literal_eval(node.value)
@classmethod
def to_yaml(cls, dumper, data):
return node
t = yaml.load("!TupleOfTuples ((1, 2), (3, 4))")
print "t", t, type(t)
The results are:
node ScalarNode(tag=u'!TupleOfTuples', value=u'((1, 2), (3, 4))')
node.value ((1, 2), (3, 4)) <type 'unicode'>
t ((1, 2), (3, 4)) <type 'tuple'>