sc_odict

sc_odict

The “odict” class, combining features from an OrderedDict and a list/array.

Highlights

  • sc.odict(): flexible container representing the best-of-all-worlds across dicts, lists, and arrays
  • objdict: like an odict, but allows get/set via e.g. foo.bar instead of foo['bar']

Classes

Name Description
argparse Ultra-simple argument parser
counter Like collections.Counter, but with additional supported mathematical operations.
dictobj Lightweight class to create an object that can also act like a dictionary.
objdict An odict that acts like an object – allow keys to be set/retrieved by object
odict Ordered dictionary with integer indexing

argparse

sc_odict.argparse(parse=True, **kwargs)

Ultra-simple argument parser

Accepts positional or keyword arguments, and converts them to the correct type. While Python’s built in argparse has more features (such as help for each argument), this allows single-line parsing of arguments.

Parameters

Name Type Description Default
parse bool whether to parse the arguments immediately (default True) True
**kwargs dict keyword arguments to add to the parser {}

Returns

Name Type Description
args objdict a dictionary-like object with the arguments

Examples:

# Option 1: Supply arguments directly
args = sc.argparse(iterations=10, output_file='results.csv')

# Option 2: Add arguments one by one
args = sc.argparse()
args.add(iterations=10)
args.add(output_file='results.csv')
args.parse()

# Command-line usage
python argparse_example.py 100 'data.csv'
python argparse_example.py 100 output_file='data.csv'
python argparse_example.py iterations=100 --output_file='data.csv'

# Result
args.iterations == 10
args.output_file == 'data.csv'

New in version 3.2.6.

Methods

Name Description
add Add an argument
parse Parse the arguments into the dictionary
add
sc_odict.argparse.add(**kwargs)

Add an argument

parse
sc_odict.argparse.parse()

Parse the arguments into the dictionary

counter

sc_odict.counter()

Like collections.Counter, but with additional supported mathematical operations.

sc.counter has an “array” property, which converts the values to a NumPy array; methods not available to a Counter object are performed on the array instead.

Examples:

vals = [1,1,12,3,4,2,4,2,53,5,5,6,2,3,5]
counts = sc.counter(vals)
counts.max() # returns 3
  • New in version 3.2.3.

Attributes

Name Description
array NumPy array of values

dictobj

sc_odict.dictobj(*args, **kwargs)

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

Example:

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

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

Note: because dictobj is halfway between a dict and an object, it can’t be automatically converted to a JSON (but will fail silently). Use to_json() instead.

  • New in version 1.3.0.
  • New in version 1.3.1: inherit from dict
  • New in version 2.0.0: allow positional arguments
  • New in version 3.0.0: “fromkeys” now a class method; to_json() method
  • New in version 3.1.6: “copy” returns another dictobj

Methods

Name Description
copy Create a shallow copy
fromkeys Create a new dictobj from keys
to_json Export the dictobj to JSON (NB: regular json.dumps() does not work)
copy
sc_odict.dictobj.copy()

Create a shallow copy

fromkeys
sc_odict.dictobj.fromkeys(*args, **kwargs)

Create a new dictobj from keys

to_json
sc_odict.dictobj.to_json()

Export the dictobj to JSON (NB: regular json.dumps() does not work)

objdict

sc_odict.objdict(*args, **kwargs)

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

In general, operations that would normally act on attributes (e.g. obj.x = 3) instead act on dict keys (e.g. obj['x'] = 3). If you want to actually get/set an attribute, use obj.getattribute()/obj.setattribute().

For a lighter-weight example (an object that acts like a dict), see sc.dictobj().

Examples:

import sciris as sc

obj = sc.objdict(foo=3, bar=2)
obj.foo + obj.bar # Gives 5
for key in obj.keys(): # It's still a dict
    obj[key] = 10

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: https://github.com/mewwts/addict

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

Methods

Name Description
delattribute Delete attribute if truly desired
getattribute Get attribute if truly desired
setattribute Set attribute if truly desired
delattribute
sc_odict.objdict.delattribute(name)

Delete attribute if truly desired

getattribute
sc_odict.objdict.getattribute(name)

Get attribute if truly desired

setattribute
sc_odict.objdict.setattribute(name, value, force=False)

Set attribute if truly desired

odict

sc_odict.odict(*args, defaultdict=None, **kwargs)

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.

Parameters

Name Type Description Default
args dict convert an existing dict, e.g. sc.odict({'a':1}) ()
defaultdict class if provided, create as a defaultdict as well None
kwargs dict additional keyword arguments, e.g. sc.odict(a=1) {}

Examples:

# 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)
dd['c'].append(4)

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
  • New in version 3.0.0: allow numeric indices; inherit from dict rather than OrderedDict
  • New in version 3.1.1: allow steps in slices; copy() now behaves as a standard dict

Methods

Name Description
append Support an append method, like a list
copy Make a (shallow) copy of the dict.
dcp Shortcut to odict.copy(deep=True).
dict_items Return an iterator (not a list) over items (as in Python 2).
dict_keys Return an iterator (not a list) over keys (as in Python 2).
dict_values Return an iterator (not a list) over values (as in Python 2).
disp Print out flexible representation, short by default.
enumitems Returns tuple of 3 things: index, key, value.
enumkeys Shortcut for enumerate(odict.keys()).
enumvals Shortcut for enumerate(odict.values())
enumvalues Alias for enumvals(). New in version 1.2.0.
export Export the odict in a form that is valid Python code
filter Find matching keys in the odict, and return a new odict
filtervals Like filter, but filters by value rather than key
findbykey Same as findkeys, but returns values instead
findbyval Returns the key(s) that match a given value – reverse of findbykey, except here
findkeys Find all keys that match a given pattern. By default uses regex, but other options
fromeach Take a “slice” across all the keys of an odict, applying the same
getnested Alias to sc.getnested(odict); see sc.makenested() for full documentation. New in version 1.2.0.
index Return the index of a given key
insert Function to do insert a key – note, computationally inefficient.
items Return a list of items (as in Python 2).
iteritems Alias to items()
iternested Alias to sc.iternested(odict); see sc.makenested() for full documentation. New in version 1.2.0.
keys Return a list of keys (as in Python 2), not a dict_keys object.
make An alternate way of making or adding to an odict.
makefrom Create an odict from entries in another dictionary. If keys is None, then
makenested Alias to sc.makenested(odict); see sc.makenested() for full documentation. New in version 1.2.0.
map Apply a function to each element of the odict, returning
pop Allows pop to support strings, integers, slices, lists, or arrays
promote Like promotetolist, but for odicts.
remove Remove an item by key and do not return it
rename Change a key name (note: not optimized for speed)
reverse Reverse the order of an odict
reversed Shortcut for making a copy of the sorted odict
setitem Use regular dictionary setitem, rather than odict’s
setnested Alias to sc.setnested(odict); see sc.makenested() for full documentation. New in version 1.2.0.
sort Create a sorted version of the odict.
sorted Shortcut for making a copy of the sorted odict – see sort() for options
toeach The inverse of fromeach: partially reset elements within
update Update dict contents; set _stale so _cached_keys is refreshed on next use
valind Return the index of a given value
values Return a list of values (as in Python 2).
append
sc_odict.odict.append(key=None, value=None)

Support an append method, like a list

copy
sc_odict.odict.copy(deep=False)

Make a (shallow) copy of the dict.

Parameters
Name Type Description Default
deep bool if True, do a deep rather than shallow copy False

Examples:

d1 = sc.odict(a=[1,2,3], b='foo')
d2 = d1.copy()
d3 = d1.copy(deep=True)

d1.pop('b') # affects d1 but not d2 or d3
d1[0].append(4) # affects d1 and d2 but not d3
dcp
sc_odict.odict.dcp()

Shortcut to odict.copy(deep=True).

  • New in version 3.2.3.
dict_items
sc_odict.odict.dict_items()

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

dict_keys
sc_odict.odict.dict_keys()

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

dict_values
sc_odict.odict.dict_values()

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

disp
sc_odict.odict.disp(
    maxlen=None,
    showmultilines=True,
    divider=False,
    dividerthresh=10,
    numindents=0,
    sigfigs=5,
    numformat=None,
    maxitems=20,
    **kwargs,
)

Print out flexible representation, short by default.

Example:

z = sc.odict().make(keys=['a','b','c'], vals=[4.293487,3,6])
z.disp(sigfigs=3)
z.disp(numformat='%0.6f')
enumitems
sc_odict.odict.enumitems(transpose=False)

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

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

enumkeys
sc_odict.odict.enumkeys(transpose=False)

Shortcut for enumerate(odict.keys()).

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

enumvals
sc_odict.odict.enumvals(transpose=False)

Shortcut for enumerate(odict.values())

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

enumvalues
sc_odict.odict.enumvalues(transpose=False)

Alias for enumvals(). New in version 1.2.0.

export
sc_odict.odict.export(doprint=True, classname='odict')

Export the odict in a form that is valid Python code

filter
sc_odict.odict.filter(keys=None, pattern=None, method=None, exclude=False)

Find matching keys in the odict, and return a new odict

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() <odict.findkeys.

Parameters
Name Type Description Default
keys list the list of keys to keep (or exclude) None
pattern str the pattern by which to match keys; see findkeys() <odict.findkeys for details None
method str the method by which to match keys; see findkeys() <odict.findkeys for details None
exclude bool if exclude=True, then exclude rather than include matches False

See also sort(), which includes filtering by position.

filtervals
sc_odict.odict.filtervals(value)

Like filter, but filters by value rather than key

findbykey
sc_odict.odict.findbykey(pattern=None, method=None, first=True)

Same as findkeys, but returns values instead

findbyval
sc_odict.odict.findbyval(value, first=True, strict=False)

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

Example:

z = sc.odict({'dog':[2,3], 'cat':[4,6], 'mongoose':[4,6]})
z.findbyval([4,6]) # returns 'cat'
z.findbyval([4,6], first=False) # returns ['cat', 'mongoose']
findkeys
sc_odict.odict.findkeys(pattern=None, method=None, first=None)

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.

fromeach
sc_odict.odict.fromeach(ind=None, asdict=True)

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.

Example:

z = sc.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])})
getnested
sc_odict.odict.getnested(*args, **kwargs)

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

index
sc_odict.odict.index(value)

Return the index of a given key

insert
sc_odict.odict.insert(pos=None, key=None, value=None)

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

Example:

z = sc.odict()
z['foo'] = 1492
z.insert(1604)
z.insert(0, 'ganges', 1444)
z.insert(2, 'mekong', 1234)
items
sc_odict.odict.items(transpose=False)

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

iteritems
sc_odict.odict.iteritems(transpose=False)

Alias to items()

iternested
sc_odict.odict.iternested(*args, **kwargs)

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

keys
sc_odict.odict.keys()

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

make
sc_odict.odict.make(keys=None, vals=None, keys2=None, keys3=None, coerce='full')

An alternate way of making or adding to an odict.

Parameters
Name Type Description Default
keys list / int the list of keys to use None
vals list / arr the list of values to use None
keys2 list / int for a second level of nesting None
keys3 list / int for a third level of nesting None
coerce str what types to coerce into being separate dict entries 'full'

Examples:

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

makefrom
sc_odict.odict.makefrom(
    source=None,
    include=None,
    keynames=None,
    force=True,
    *args,
    **kwargs,
)

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

Parameters
Name Type Description Default
source dict / list / etc the item(s) to convert to an odict None
include list list of keys to include from the source dict in the odict (default: all) None
keynames list names of keys if source is not a dict None
force bool whether to force conversion to an odict even if e.g. the source has numeric keys True

Examples:

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 sc.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)
makenested
sc_odict.odict.makenested(*args, **kwargs)

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

map
sc_odict.odict.map(func=None)

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

Example:

cat = sc.odict({'a':[1,2], 'b':[3,4]})
def myfunc(mylist): return [i**2 for i in mylist]
dog = cat.map(myfunc) # Returns odict({'a':[1,4], 'b':[9,16]})
pop
sc_odict.odict.pop(key, *args, **kwargs)

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

promote
sc_odict.odict.promote(obj=None)

Like promotetolist, but for odicts.

Example:

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

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

remove
sc_odict.odict.remove(key, *args, **kwargs)

Remove an item by key and do not return it

rename
sc_odict.odict.rename(oldkey, newkey)

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

reverse
sc_odict.odict.reverse(copy=False)

Reverse the order of an odict

reversed
sc_odict.odict.reversed()

Shortcut for making a copy of the sorted odict

setitem
sc_odict.odict.setitem(key, value)

Use regular dictionary setitem, rather than odict’s

setnested
sc_odict.odict.setnested(*args, **kwargs)

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

sort
sc_odict.odict.sort(sortby=None, reverse=False, copy=False)

Create a sorted version of the odict.

By default, this method sorts alphabetically by key, but many other options are possible:

- 'keys' sorts alphabetically by key
- 'values' sorts in ascending order by value
- if a list of keys is provided, sort by that order (any keys not provided will be omitted from the sorted dict!)
- if a list of numbers is provided, treat these as indices and sort by that order
- if a list of boolean values is provided, then omit False entries
Parameters
Name Type Description Default
sortby str or list what to sort by; see above for options None
reverse bool whether to return results in reverse order False
copy bool whether to return a copy (same as sorted()) False

For filtering by string matching on keys, see filter().

  • New in version 3.0.0: removed “verbose” argument
sorted
sc_odict.odict.sorted(sortby=None, reverse=False)

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

toeach
sc_odict.odict.toeach(ind=None, val=None)

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

Example:

z = sc.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]})
update
sc_odict.odict.update(*args, **kwargs)

Update dict contents; set _stale so _cached_keys is refreshed on next use

valind
sc_odict.odict.valind(value)

Return the index of a given value

values
sc_odict.odict.values()

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

Functions

Name Description
asobj Convert any object for which you would normally do a['b'] to one where you

asobj

sc_odict.asobj(obj, strict=True)

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.

Parameters

Name Type Description Default
obj anything the object you want to convert required
strict bool whether to raise an exception if an attribute is being set (instead of a key) True

Example:

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

New in version 1.0.0.