In this section, some typical problems and their associated nob best practices are listed.

Don’t multiply smart (key) accesses

nob does a simple Python lookup when it is called with full paths, e.g. n['/kennel/dogs']. However, it performs a smart accesses whenever it is called with a simple key, e.g. (n.dogs) (read the Quickstart section if you haven’t or need a refresher). Smart accesses perform a full recursive search of the tree, since it does not know in advance where the keyword you’re looking for is. This can make them expensive on large trees.

In practice, say you have the following file:


If you want to access the name 'python2.7', you can take advantage of smart access:


instead of writing the full path:


The smart access will be a bit slower however, so in some performance-limiting cases you should keep that in mind. What you should never do is this:


Here, you’re performing a smart access to find n['root'], then a new one on n['root']['System'], etc… Each of these is expensive, and the result can be very slow.

Save sub-trees (NobView objects)

Any sub-tree can be efficiently saved as a Python object, and you should take advantage of this. In the example above, say you want to acces the executable values successively. You could write:


However, each key access on the root (n) is expensive. It would be more efficient to write:

nv = n['Versions']

Now, a single intial key access is performed on the root tree. Each final key access is performed on the much smaller subtree nv (a NobView object), and is much faster.

Filter by value and not by key

Often you’ll want to access some data (a sub-tree, or a value) that depends on another value. nob is well equipped for dealing with keys, but less so for values. Let’s look at an example. Say we want the sub-tree and the name of the alpha dog in this example:

city: Paris
 name: Dogtopia
 employees: 5
 - name: Rex
   age: 3
   alpha: False
 - name: Beethoven
   age: 8
   alpha: True

This can be achieved simply with the following:

n = Nob(open('file.yml').read())
alpha_tree = [nv for nv in n.dogs if nv.alpha[:]][0]
alpha_name = [nv for nv in n.dogs if nv.alpha[:]][0].name[:]

What if we want to know how old Rex is?

age = [nv for nv in n.dogs if[:] == 'Rex'][0].age[:]

Another syntax is to unpack the single value in a 1-uple (single-valued tuple).

rex, = [nv for nv in n.dogs if[:] == 'Rex']
age = rex.age[:]

Alternatively, an equivalent and more “functional” approach:

age = next(filter(lambda nv:[:] == 'Rex', n['dogs'])).age[:]

Error when dumping a Nob to yaml

If you try to dump the a Nob object directly, instead of extracting its data via [:], i.e. if you write:

yaml.dump(n, fh)

this will seem like it works, but in fact yaml will serialize the full Nob object here, making it unreadable as Nob object afterwards. When trying to re-read this file, you’ll get a cryptic error ending with:

yaml.constructor.ConstructorError: could not determine a constructor for the tag ',2002:python/object:nob.nob.Nob'

TL;DR: don’t forget the [:] here!

Comparing trees of paths

If you wish to compare two trees by their paths, since Path objects are hashable, you can convert build sets with them. E.g.:

n, m = Nob(...), Nob(...)

# The paths that are in both n and m
set(n.paths) & set(m.paths)

# The paths that are in n but not m
set(n.paths) - set(m.paths)