The ‘odict’ class, combining features from an OrderedDict and a list/array.

  • odict: flexible container representing the best-of-all-worlds across lists, dicts, and arrays

  • objdict: like an odict, but allows get/set via e.g. instead of foo['bar']



Convert any object for which you would normally do a['b'] to one where you can do a.b.



Lightweight class to create an object that can also act like a dictionary.


An odict that acts like an object -- allow keys to be set/retrieved by object notation.


Ordered dictionary with integer indexing


alias of defaultdict

class odict(*args, defaultdict=None, **kwargs)[source]

Ordered dictionary with integer indexing

An ordered dictionary, like the OrderedDict class, but supports list methods like integer indexing, key slicing, and item inserting. It can also replicate defaultdict behavior via the defaultdict argument.


# Simple example
mydict = sc.odict(foo=[1,2,3], bar=[4,5,6]) # Assignment is the same as ordinary dictionaries
mydict['foo'] == mydict[0] # Access by key or by index
mydict[:].sum() == 21 # Slices are returned as numpy arrays by default
for i,key,value in mydict.enumitems(): # Additional methods for iteration
    print(f'Item {i} is named {key} and has value {value}')

# Detailed example
foo = sc.odict({'ant':3,'bear':4, 'clam':6, 'donkey': 8}) # Create odict
bar = foo.sorted() # Sort the dict
assert bar['bear'] == 4 # Show get item by value
assert bar[1] == 4 # Show get item by index
assert (bar[0:2] == [3,4]).all() # Show get item by slice
assert (bar['clam':'donkey'] == [6,8]).all() # Show alternate slice notation
assert (bar[np.array([2,1])] == [6,4]).all() # Show get item by list
assert (bar[:] == [3,4,6,8]).all() # Show slice with everything
assert (bar[2:] == [6,8]).all() # Show slice without end
bar[3] = [3,4,5] # Show assignment by item
bar[0:2] = ['the', 'power'] # Show assignment by slice
bar[[0,3]] = ['hill', 'trip'] # Show assignment by list
bar.rename('clam','oyster') # Show rename
print(bar) # Print results

# Defaultdict examples
dd = sc.odict(a=[1,2,3], defaultdict=list)

nested = sc.odict(a=0, defaultdict='nested') # Create a infinitely nested dictionary (NB: may behave strangely on IPython)
nested['b']['c']['d'] = 2

Note: by default, integers are used as an alias to string keys, so cannot be used as keys directly. However, you can force regular-dict behavior using setitem(), and you can convert a dictionary with integer keys to an odict using sc.odict.makefrom(). If an odict has integer keys and the keys do not match the key positions, then the key itself will take precedence (e.g., od[3] is equivalent to dict(od)[3], not dict(od)[od.keys()[3]]). This usage is discouraged.

New in version 1.1.0: “defaultdict” argument
New in version 1.3.1: allow integer keys via makefrom(); removed to_OD; performance improvements
New in version 2.0.1: allow deletion by index

Combine the init properties of both OrderedDict and defaultdict

setitem(key, value)[source]

Use regular dictionary setitem, rather than odict’s

disp(maxlen=None, showmultilines=True, divider=False, dividerthresh=10, numindents=0, sigfigs=5, numformat=None, maxitems=20, **kwargs)[source]

Print out flexible representation, short by default.


z = sc.odict().make(keys=['a','b','c'], vals=[4.293487,3,6])

Export the odict in a form that is valid Python code

pop(key, *args, **kwargs)[source]

Allows pop to support strings, integers, slices, lists, or arrays

remove(key, *args, **kwargs)[source]

Remove an item by key and do not return it


Reset to an empty odict


Return the index of a given key


Return the index of a given value

findkeys(pattern=None, method=None, first=None)[source]

Find all keys that match a given pattern. By default uses regex, but other options are ‘find’, ‘startswith’, ‘endswith’. Can also specify whether or not to only return the first result (default false). If the key is a tuple instead of a string, it will search each element of the tuple.

findbykey(pattern=None, method=None, first=True)[source]

Same as findkeys, but returns values instead

findbyval(value, first=True, strict=False)[source]

Returns the key(s) that match a given value – reverse of findbykey, except here uses exact matches to the value or values provided.


z = odict({'dog':[2,3], 'cat':[4,6], 'mongoose':[4,6]})
z.findvals([4,6]) # returns 'cat'
z.findvals([4,6], first=False) # returns ['cat', 'mongoose']
filter(keys=None, pattern=None, method=None, exclude=False)[source]

Filter the odict keys and return a new odict which is a subset. If keys is a list, then uses that for matching. If the first argument is a string, then treats as a pattern for matching using findkeys(). If exclude=True, then will exclude rather than include matches.


Like filter, but filters by value rather than key

append(key=None, value=None)[source]

Support an append method, like a list

insert(pos=None, key=None, value=None)[source]

Function to do insert a key – note, computationally inefficient.


z = odict()
z['foo'] = 1492
z.insert(0, 'ganges', 1444)
z.insert(2, 'meikang', 1234)
copy(oldkey, newkey)[source]

Make a copy of an item

rename(oldkey, newkey)[source]

Change a key name (note: not optimized for speed)

sort(sortby=None, reverse=False, copy=False, verbose=True)[source]

Create a sorted version of the odict. Sorts by order of sortby, if provided, otherwise alphabetical. If copy is True, then returns a copy (like sorted()).

Note that you can also use this to do filtering.

sorted(sortby=None, reverse=False)[source]

Shortcut for making a copy of the sorted odict – see sort() for options


Reverse the order of an odict


Shortcut for making a copy of the sorted odict

make(keys=None, vals=None, keys2=None, keys3=None, coerce='full')[source]

An alternate way of making or adding to an odict.

  • keys (list/int) – the list of keys to use

  • vals (list/arr) – the list of values to use

  • keys2 (list/int) – for a second level of nesting

  • keys3 (list/int) – for a third level of nesting

  • coerce (str) – what types to coerce into being separate dict entries


a = sc.odict().make(5) # Make an odict of length 5, populated with Nones and default key names
b = sc.odict().make('foo',34) # Make an odict with a single key 'foo' of value 34
c = sc.odict().make(['a','b']) # Make an odict with keys 'a' and 'b'
d = sc.odict().make(['a','b'], 0) # Make an odict with keys 'a' and 'b', initialized to 0
e = sc.odict().make(keys=['a','b'], vals=[1,2]) # Make an odict with 'a':1 and 'b':2
f = sc.odict().make(keys=['a','b'], vals=np.array([1,2])) # As above, since arrays are coerced into lists
g = sc.odict({'a':34, 'b':58}).make(['c','d'],[99,45]) # Add extra keys to an exising odict
h = sc.odict().make(keys=['a','b','c'], keys2=['A','B','C'], keys3=['x','y','z'], vals=0) # Make a triply nested odict

New in version 1.2.2: “coerce” argument

static makefrom(source=None, include=None, keynames=None, force=True, *args, **kwargs)[source]

Create an odict from entries in another dictionary. If keys is None, then use all keys from the current dictionary.

  • source (dict/list/etc) – the item(s) to convert to an odict

  • include (list) – list of keys to include from the source dict in the odict (default: all)

  • keynames (list) – names of keys if source is not a dict

  • force (bool) – whether to force conversion to an odict even if e.g. the source has numeric keys


a = 'cat'
b = 'dog'
o = sc.odict.makefrom(source=locals(), include=['a','b']) # Make use of fact that variables are stored in a dictionary

d = {'a':'cat', 'b':'dog'}
o = sc.odict.makefrom(d) # Same as odict(d)
l = ['cat', 'monkey', 'dog']
o = sc.odict.makefrom(source=l, include=[0,2], keynames=['a','b'])

d = {12:'monkeys', 3:'musketeers'}
o = sc.odict.makefrom(d)

Apply a function to each element of the odict, returning a new odict with the same keys.


cat = odict({'a':[1,2], 'b':[3,4]})
def myfunc(mylist): return [i**2 for i in mylist]
dog = # Returns odict({'a':[1,4], 'b':[9,16]})
fromeach(ind=None, asdict=True)[source]

Take a “slice” across all the keys of an odict, applying the same operation to entry. The simplest usage is just to pick an index. However, you can also use it to apply a function to each key.


z = odict({'a':array([1,2,3,4]), 'b':array([5,6,7,8])})
z.fromeach(2) # Returns array([3,7])
z.fromeach(ind=[1,3], asdict=True) # Returns odict({'a':array([2,4]), 'b':array([6,8])})
toeach(ind=None, val=None)[source]

The inverse of fromeach: partially reset elements within each odict key.


z = odict({'a':[1,2,3,4], 'b':[5,6,7,8]})
z.toeach(2, [10,20])    # z is now odict({'a':[1,2,10,4], 'b':[5,6,20,8]})
z.toeach(ind=3,val=666) #  z is now odict({'a':[1,2,10,666], 'b':[5,6,20,666]})

Shortcut for enumerate(odict.keys()).

If transpose=True, return a tuple of lists rather than a list of tuples.


Shortcut for enumerate(odict.values())

If transpose=True, return a tuple of lists rather than a list of tuples.


Alias for enumvals(). New in version 1.2.0.


Returns tuple of 3 things: index, key, value.

If transpose=True, return a tuple of lists rather than a list of tuples.

static promote(obj=None)[source]

Like promotetolist, but for odicts.


od = sc.odict.promote(['There','are',4,'keys'])

Note, in most cases sc.odict(obj) or sc.odict().make(obj) can be used instead.


Return a list of keys (as in Python 2), not a dict_keys object.


Return a list of values (as in Python 2).


Return a list of items (as in Python 2).


Return an iterator (not a list) over keys (as in Python 2).


Return an iterator (not a list) over values (as in Python 2).


Return an iterator (not a list) over items (as in Python 2).


Alias to items()

makenested(*args, **kwargs)[source]

Alias to sc.makenested(odict); see sc.makenested() for full documentation. New in version 1.2.0.

getnested(*args, **kwargs)[source]

Alias to sc.getnested(odict); see sc.makenested() for full documentation. New in version 1.2.0.

setnested(*args, **kwargs)[source]

Alias to sc.setnested(odict); see sc.makenested() for full documentation. New in version 1.2.0.

iternested(*args, **kwargs)[source]

Alias to sc.iternested(odict); see sc.makenested() for full documentation. New in version 1.2.0.

class objdict(*args, **kwargs)[source]

An odict that acts like an object – allow keys to be set/retrieved by object notation.

For a lighter-weight example, see sc.dictobj().


>>> import sciris as sc
>>> od = sc.objdict({'height':1.65, 'mass':59})
>>> od.bmi = od.mass/od.height**2
>>> od['bmi'] = od['mass']/od['height']**2 # Vanilla syntax still works
>>> od.keys = 3 # This raises an exception (you can't overwrite the keys() method)

Nested logic based in part on addict:

For a lighter-weight equivalent (based on dict instead of odict), see sc.dictobj().


Get attribute if truly desired

setattribute(name, value, force=False)[source]

Set attribute if truly desired


Delete attribute if truly desired

class dictobj(*args, **kwargs)[source]

Lightweight class to create an object that can also act like a dictionary.


obj = sc.dictobj()
obj.a = 5
obj['b'] = 10

For a more powerful alternative, see sc.objdict().

New in version 1.3.0.
New in version 1.3.1: inherit from dict
New in version 2.0.0: allow positional arguments
asobj(obj, strict=True)[source]

Convert any object for which you would normally do a[‘b’] to one where you can do a.b.

Note: this may lead to unexpected behavior in some cases. Use at your own risk. At minimum, objects created using this function have an extremely odd type – namely “sciris.sc_odict.asobj.<locals>.objobj”.

  • obj (anything) – the object you want to convert

  • strict (bool) – whether to raise an exception if an attribute is being set (instead of a key)


d = dict(foo=1, bar=2)
d_obj = sc.asobj(d) = 10

New in version 1.0.0.