cdxcore.pretty#
The main feature of this module is the simple cdxcore.pretty.PrettyObject
class which mimics directory access to its members.
Overview#
The purpose is a functional-programming style pattern for generating complex objects:
from cdxbasics.prettydict import PrettyObject
pdct = PrettyObject(z=1)
pdct.num_samples = 1000
pdct.num_batches = 100
pdct.method = "signature"
The object allows accessing members via []:
print( pdct[‘num_samples’] ) # -> 1000 print( pdct[‘num_batches’] ) # -> 100
Features#
cdxcore.pretty.PrettyObject implements all relevant dictionary protocols, so objects of type cdxcore.pretty.PrettyObject can
(nearly always) be passed where dictionaries are expected:
A
cdxcore.pretty.PrettyObjectobject supports standard dictionary semantics in addition to member attribute access. That means you can usepdct['num_samples']as well aspdc.num_samples. You can mix standard dictionary notation with member attribute notation:print(pdct["num_samples"]) # -> prints "1000" pdct["test"] = 1 # sets pdct.test to 1
Iterations work just like for dictionaries; for example:
for k,v in pdct.items(): print( k, v)
Applying
strandreprto objects of typecdxcore.pretty.PrettyObjectwill return dictionary-type results, so for exampleprint(pdct)of the above will return{'z': 1, 'num_samples': 1000, 'num_batches': 100, 'method': 'signature'}.
Vectorized access#
Several member functions of cdxcore.pretty.PrettyObject support member access for example:
from cdxbasics.prettydict import PrettyObject
r = PrettyObject()
# assign
r['a','b'] = 1,2
# read, with defaults
a,b,c = r.get(['a','b','c'],[11,22,33]) # -> 1,2,33
# dictionary notation
a,b,c = r.get(a=11,b=22,c=33) # -> 1,2,33
# works with pop, too:
a,b,c = r.pop(a=11,b=22,c=33) # -> 1,2,33
r = PrettyObject(a=1,b=2)
# reading with defaults
a, b, c = r.setdefault(a=11,b=22,c=33) # -> 1,2,33
print(r.c) # -> 33
Access by Position#
The cdxcore.pretty.PrettyObject.at_pos attribute allows accessing elements of the ordered dictionary
by positon:
cdxcore.pretty.PrettyObject.at_pos[i]returns the i th element.cdxcore.pretty.PrettyObject.at_pos.keys[i]returns the i th key.cdxcore.pretty.PrettyObject.at_pos.items[i]returns the i th item.
For example:
print(pdct.at_pos[3]) # -> prints "signature"
print(pdct.at_pos.keys[3]) # -> prints "method"
You can also assign member functions to a cdxcore.pretty.PrettyObject.
The following works as expected:
pdct.f = lambda self, y: return self.y*x
(to assign a static function which does not refer to self, use pdct['g'] = lambda z : return z).
Dataclasses#
dataclasses rely on default values of any member being “frozen” objects, which most user-defined objects and
cdxcore.pretty.PrettyObject objects are not.
This limitation applies as well to flax modules.
To use non-frozen default values, use the
cdxcore.pretty.PrettyObject.as_field() function:
from cdxbasics.prettydict import PrettyObject
from dataclasses import dataclass
@dataclass
class Data:
data : PrettyObject = PrettyObject(x=2).as_field()
def f(self):
return self.data.x
d = Data() # default constructor used.
d.f() # -> returns 2
Hierachies#
This module also provides cdxcore.pretty.PrettyHierarchy derived from cdxcore.pretty.PrettyObject
which allows, in addition, automatic generation of hierarchies e.g.:
from cdxbasics.prettydict import PrettyHierarchy
r = PrettyHierarchy(a=1,b=2)
r.x.c = 3
This is a short cut for:
from cdxbasics.prettydict import PrettyHierarchy
r = PrettyHierarchy(a=1,b=2)
r.x = PrettyHierarchy()
r.x.c = 3
However, runtime semantics can be confusing as cdxcore.pretty.PrettyHierarchy creates objects
on the fly if an attrbute is not known. Hence typos can generate confusing error messages:
assume we have some code that creates a cdxcore.pretty.PrettyHierarchy:
data = PrettyHierarchy()
data = ...
data.center = compute_centre()
Somewhere else we then access data.centre instead of data.center (a typo) using e.g.:
np.sum( data.centre )
This raises TypeError: 'PrettyHierarchy' object is not callable instead of an AttributeError.
Import#
from cdxcore.pretty import PrettyObject as pdct
Documentation#
Classes
|
A |
|
Class mimicing an ordered dictionary. |
|
A |
- class cdxcore.pretty.PrettyHierarchy(copy=None, **kwargs)[source]#
Bases:
PrettyObjectA
cdxcore.pretty.PrettyObjectwhich can easily create hierarchies ofcdxcore.pretty.PrettyObject’s.This works:
from cdxcore.pretty import PrettyHierarchy r = PrettyHierarchy() r.a = 1 r.x.b = 2 assert r.a == 1 assert isinstance(r.x,PrettyHierarchy) assert r.x.b == 2
Some oddities:
from cdxcore.pretty import PrettyHierarchy r = PrettyHierarchy() r.A = 1 print( r.a ) # -> prints an empty PrettyHierarchy _ = r.b # generates an empty PrettyHierarchy assert set(r) == {'A','a','b'} # all above created entries.
Runtime semantics can be confusing as
cdxcore.pretty.PrettyHierarchycreates objects on the fly if an attrbute is not known. Hence typos can generate confusing error messages: assume we have some code that creates acdxcore.pretty.PrettyHierarchy:data = PrettyHierarchy() data = ... data.center = compute_centre()
Somewhere else we then access
data.centreinnstead ofdata.center, a typo:np.sum( data.centre )
This raises
TypeError: 'PrettyHierarchy' object is not callableinstead of anAttributeError.Note that
["x"]keeps working as expected, i.e. it will fail if ‘x’ does not exist.
- class cdxcore.pretty.PrettyObject(copy=None, **kwargs)[source]#
Bases:
MutableMappingClass mimicing an ordered dictionary.
Example:
from cdxcore.pretty import PrettyObject pdct = PrettyObject() pdct.x = 1 pdct['y'] = 2 print( pdct['x'], pdct.y ) # -> prints 1 2
The object mimics a dictionary:
print(pdct) # -> '{'x': 1, 'y': 2}' u = dict( pdct ) print(u) # -> {'x': 1, 'y': 2} u = { k: 2*v for k,v in pdct.items() } print(u) # -> {'x': 2, 'y': 4} l = list( pdct ) print(l) # -> ['x', 'y']
Important: attributes starting with ‘__’ cannot be accessed with item
[]notation. In other words:pdct = PrettyObject() pdct.__x = 1 # fine _ = pdct['__x'] # <- throws an exception
Access by Index Position
cdxcore.pretty.PrettyObjectretains order of construction. To access its members by index position, use thecdxcore.pretty.PrettyObject.at_posattribute:print(pdct.at_pos[1]) # -> prints "2" print(pdct.at_pos.keys[1]) # -> prints "y" print(list(pdct.at_pos.items[2])) # -> prints "[('x', 1), ('y', 2)]"
Vectorized access
Several functions support accessing member elements using vectors, for example:
Setting elements:
from cdxcore.pretty import PrettyObject r = PrettyObject() r['a','b'] = 1,2 print(r.a,r.b) # -> 1,2 r['a','b'] = (1,2) print(r.a,r.b) # -> 1,2
Reading elements:
r = PrettyObject(a=1,b=2) a, b = r['a','b'] print(a,b) # -> 1,2
Reading elements with defaults, classic method:
r = PrettyObject(a=1,b=2) a, b, c = r.get(['a','b','c'],[1,2,33]) print(a,b,c) # -> 1,2,33
Reading elements with defaults, keyword method:
r = PrettyObject(a=1,b=2) a, b, c = r.get(a=11,b=22,c=33) print(a,b,c) # -> 1,2,33
Popping elements with defaults, keyword method:
r = PrettyObject(a=1,b=2) a, b, c = r.pop(a=11,b=22,c=33) print(a,b,c) # -> 1,2,33 assert len(r)==0
Same for
cdxcore.pretty.PrettyObject.setdefault():r = PrettyObject(a=1,b=2) a, b, c = r.setdefault(a=1,b=2,c=33) print(a,b,c) # -> 1,2,33 print(r.c) # -> 33
Assigning Member Functions
PrettyObjectobjects also allow assigning bona fide member functions by a simple semantic of the form:pdct = PrettyObject(b=2) pdct.mult_b = lambda self, x: self.b*x pdct.mult_b(3) # -> 6
Calling
pdct.mult_b(3)with abovepdctwill return 6 as expected. To assign static member functions, use the[]operator. The reason for this is as follows: consider:def mult( a, b ): return a*b pdct = PrettyObject() pdct.mult = mult pdct.mult(3,4) --> produces am error as three arguments must be passed: self, 3, and 4
In this case, use:
pdct = PrettyObject() pdct['mult'] = mult pdct.mult(3,4) --> 12
You can also pass member functions to the constructor:
p = PrettyObject( f=lambda self, x: self.y*x, y=2) p.f(3) # -> 6
Operators
Objects of type
cdxcore.pretty.PrettyObjectsupport the following operators:Comparison operator
==and!=test for equality of keys and values. Unlike for dictionaries comparisons are performed in in order. That meansPrettyObject(x=1,y=2)andPrettyObject(y=2,x=1)are not equal.Super/subset operators
>=and<=test for a super/sup set relationship, respectively.a | breturns the union of twocdxcore.pretty.PrettyObject. Elements of theboverwrite any elements ofa, if they are present in both. The order of the new dictionary is determined by the order of appearance of keys in firstaand thenb, that means in all but trivial casesa|b != b|a.The
|=operator is a short-cut forcdxcore.pretty.PrettyObject.update().
- Parameters:
- copyMapping, optional
If present, assign elements of
copytoself. This is a shallow copy.- ** kwargs:
Key/value pairs to be added to
self.
- as_field()[source]#
This function provides support for
dataclasses.dataclassfields withPrettyObjectdefault values.When adding a field with a non-frozen default value to a
@dataclassclass, adefault_factoryhas to be provided. The functionas_fieldreturns the correspondingdataclasses.Fieldelement by returning simply:def factory(): return self return dataclasses.field( default_factory=factory )
Usage is as follows:
from dataclasses import dataclass @dataclass class A: data : PrettyDict = PrettyDict(x=2).as_field() a = A() print(a.data.x) # -> "2" a = A(data=PrettyDict(x=3)) print(a.data.x) # -> "3"
- property at_pos#
Elementary access to the data contained in
selfby ordinal position. The ordinal position of an element is determined by the order of addition to the dictionary.at_pos[position]returns an element or elements at an ordinal position:It returns a single element if
positionrefers to only one field.If
positionis a slice then the respecitve list of fields is returned.
at_pos.keys[position]returns the key or keys atposition.at_pos.items[position]returns the tuple(key, element)or a list thereof forposition.
You can also write data using the attribute notation:
at_pos[position] = itemassigns an item to an ordinal position:If
positionrefers to a single element,itemmust be the value to be assigned to this element.If
positionis a slice then ‘itemmust resolve to a list (or generator) of the required size.
- get(__key__=None, default=<cdxcore.pretty.PrettyObject._No_Default_dummy object>, **keys)[source]#
Get element
keywith optional defaultdefault. Equivalent todict.get(). Alternatively, this function takes a list of keys and their default values in dictionary notation in which case the read values are returned in order. You cannot mix usingkeyandkeys.Standard usage
This function supports
keybeing a sequence in which case this function returns a tuple of the same length with the respective results. Thedefaultvalue will be interpreted accordingly.Hence, the following works:
from cdxcore.pretty import PrettyObject r = PrettyObject(a=1, b=2) a,b,c = r.get( ['a', 'b','c'],[11,22,33] ) print(a,b,c) # -> 1,2,33
Keyword usage
Provide keys with default values, i.e.:
from cdxcore.pretty import PrettyObject r = PrettyObject(a=1, b=2) a,b,c = r.get( a=11, b=22, c=33 ) print(a,b,c) # -> 1,2,33
- items()[source]#
Equivalent to
dict.items()
- keys()[source]#
Equivalent to
dict.keys()
- no_default = <cdxcore.pretty.PrettyObject._No_Default_dummy object>#
- pop(__key__=None, default=<cdxcore.pretty.PrettyObject._No_Default_dummy object>, **keys)[source]#
Get and remove element
keywith optional defaultdefault. Equivalent todict.pop(). Alternatively, this function takes a list of keys and their default values in dictionary notation in which case the read values are returned in order. You cannot mix usingkeyandkeys.Standard usage
This function supports
keybeing a sequence in which case this function returns a tuple of the same length with the respective results. Thedefaultvalue will be interpreted accordingly.Hence, the following works:
from cdxcore.pretty import PrettyObject r = PrettyObject(a=1, b=2) a,b,c = r.pop( ['a', 'b','c'],[11,22,33] ) print(a,b,c) # -> 1,2,33
Keyword usage
Provide keys with default values, i.e.:
from cdxcore.pretty import PrettyObject r = PrettyObject(a=1, b=2) a,b,c = r.pop( a=11, b=22, c=33 ) print(a,b,c) # -> 1,2,33
- setdefault(__key__=None, default=None, **keys)[source]#
Returns the value for
keyordefaultif not found. In the latter case it addsdefaultas value forkeyto the dictionary. Equivalent todict.setdefault().Alternatively, this function takes a list of keys and their default values in dictionary notation in which case the read values are returned in order. You cannot mix using
keyandkeys.Standard usage
This function supports
keybeing a sequence in which case this function returns a tuple of the same length with the respective results. Thedefaultvalue will be interpreted accordingly.Hence, the following works:
from cdxcore.pretty import PrettyObject r = PrettyObject(a=1, b=2) a,b,c = r.setdefault( ['a', 'b','c'],[11,22,33] ) print(a,b,c) # -> 1,2,33 print(r.c) # -> 33
Keyword usage
Provide keys with default values, i.e.:
from cdxcore.pretty import PrettyObject r = PrettyObject(a=1, b=2) a,b,c = r.setdefault( a=11, b=22, c=33 ) print(a,b,c) # -> 1,2,33 print(r.c) # -> 33
- update(other=None, **kwargs)[source]#
Equivalent to
dict.update().Note that functon assignments are handled in normal dictionary fashion - in particular, bound functions will not become magically unbound.
- values()[source]#
Equivalent to
dict.values()
- class cdxcore.pretty.PrettyValueObject(copy=None, **kwargs)[source]#
Bases:
PrettyObjectA
cdxcore.pretty.PrettyObjectwhose default iteration is over its values, not keys as is the case of dictionaries andcdxcore.pretty.PrettyObject.Therefore those work:
from cdxcore.pretty import PrettyValueObject r = PrettyValueObject(a=1,b=2,c=3) a,b,c = r assert (a,b,c) == (1,2,3) for i, v in enumerate(r): assert v == i+1