How to make the `yaml.dump({1}) ` output "? 1" instead of "1: null" in Python?

86 Views Asked by At

This code in Python:

import yaml

data = {1}

print(yaml.dump(data))

prints:

!!set
1: null

Is it possible to make this output:

!!set
? 1

I think we should be able to do it, because this code:

import yaml

print(yaml.safe_load("""
!!set 
? 1"""))

Prints:

{1}
2

There are 2 best solutions below

0
blhsing On BEST ANSWER

Since sets are simply mappings with null values in YAML, you can customize the output of a set object by overriding yaml.emitter.Emitter.expect_block_mapping, the block-style emitter of a mapping event, to divert the processing to a custom handler when the event tag is of a set, where the processing logics would be similar to how a regular mapping event is processed by expect_block_mapping_key method, but would force a non-simple key output with a ? indicator, and then force the subsequent value state handler to directly move on to the key state handler to avoid the output of a null value:

import yaml

class SetDumper(yaml.Dumper):
    def expect_block_mapping(self):
        super().expect_block_mapping()
        if self.event.tag == 'tag:yaml.org,2002:set':
            self.state = self.expect_first_block_set_key

    def expect_first_block_set_key(self):
        return self.expect_block_set_key(first=True)

    def expect_block_set_key(self, first=False):
        if not first and isinstance(self.event, yaml.MappingEndEvent):
            self.indent = self.indents.pop()
            self.state = self.states.pop()
        else:
            self.write_indent()
            self.write_indicator('?', True, indention=True)
            self.states.append(self.expect_block_set_value)
            self.expect_node(mapping=True)

    def expect_block_set_value(self):
        self.state = self.expect_block_set_key

so that:

import sys
yaml.dump({1}, sys.stdout, Dumper=SetDumper)

outputs:

!!set
? 1

and that:

yaml.dump({1: {1, 2}, 2: {3, 4}}, sys.stdout, Dumper=SetDumper)

outputs:

1: !!set
  ? 1
  ? 2
2: !!set
  ? 3
  ? 4

Demo: https://replit.com/@blhsing1/PrettyGoldRelationaldatabase

0
Anthon On

If you can upgrade to ruamel.yaml, which gives you YAML 1.2 support as well as preservation of comments when loading-dumping, you can easily achieve your output.

By default as Python set is dumped as a tagged mapping with as null values the empty string:

import sys
import ruamel.yaml

   
yaml = ruamel.yaml.YAML()
data = {1}
yaml.dump(data, sys.stdout)

this gives:

!!set
1:

If you round-trip your expected output, you see that ruamel.yaml preserves the format that you want:

yaml_str = """\
!!set
? 1   # only one, lonely, member
"""
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)

this dumps the same as the input:

!!set
? 1   # only one, lonely, member

Inspecting the type(data) will reveal that this is a CommentedSet() instance, and you can use that knowledge to create the output from scratch:

data = ruamel.yaml.comments.CommentedSet({42})
yaml.dump(data, sys.stdout)

giving:

!!set
? 42