Attribute accessible dicts and collections thereof
pip install items==0.6.5
Attribute-accessible dictonaries are the most convenient way to access
dictionaries and other mappings in many algorithms.
item.name
is more readable and concise than item['name']
.
Having attribute access often is the difference between being able to easily
de-reference a component of `item
directly and deciding to store that
attribute in a completely separate variable for clarity (item_name = item['name']
).
In traversing data structures from XML, JSON, and other typically-nested data sources, concise direct access can clean up code considerably.
items
therefore provides Item
, a convenient attribute-accessible dict
subclass,
plus helper functions to make working with Item
s.
itemize
, for example, helps iterate of a list of dictionaries, as often found
in JSON processing: Each record is handed back as an Item
rather than a Python
dict
.
A typical progression would be from:
for item in data:
item_name = item['name']
# ...
print(item_name)
to
from items import itemize
for item in itemize(data):
# ...
print(item.name)
To process a list wholesale:
from items import itemize_all
itemize_all(data)
Item
objects are subclasses of collections.OrderedDict
, so that keys are ordered the
same as when yoor program first encountered them.
The performance or ordered mappings is minimal in most development contexts, especially in
exploratory and data-cleanup tasks. Whatever overhead there is is more than made up for by
the programming and debugging clarity of not having keys occur in random locations.
Item``s are also permissive, in a way that ``dict
and its variants often are not:
If you access item.arbitary_attribute
where the attribute does not exist, you do
not raise a KeyError
as you might expect from normal Python dictionaries.
Instead you get back Empty
, a designated, false-y value
similar to, but distinct from, None
. This is convenient for processing data which is not
irregular and not uniformly filled-in, because you do not need the constant
"gaurd conditions"--either if
statements or try
/except KeyError
blocks--to
protect against cases where this data value or that is missing. Using Empty
instead of
None
preserves your ability to use None
in cases where it's semanticailly important.
For example, in parsing JSON, None
is returned from JSON's null
value.
Empty
objects are infinitely dereferenceable. No matter how many levels of indirection,
they always just hand back themselves--the same gentle "nothing here, but no exceptions
raised" behavior.
e = Empty
assert e[1].method().there[33][0].no.attributes[99].here is Empty
For more on the background of Empty
, see the [nulltype](https://pypi.org/project/nulltype/)
module. A typical use would be:
for item in itemize(data):
if item.name:
# if there is a name attribute, it's processed here
# if not, no problem; processing just continues here
The more nested, complex, and irregular your data structures, the more valueable this becomes.
Be careful importing data from files. Popular Python modules for reading JSON, YAML, and other formats do not believe mappings are ordered. Historically and officially, they're not, no matter how ordered they look, no matter that other languages such as JavaScript take a different approach, and no matter how many Stack Overflow questions demonstrate that ordered import is stronly and broadly desired. Therefore stock input/output modules can cause dislocation as data is parsed. Take steps to return ordered mappings from them.
# YAML module that will load into OrderedDict instances, which can then
# be easily converted to Item instances; based on default PyYAML
import oyaml as yaml
items = itemize_all(yaml.load(data))
# modified call to json.load or json.loads to preserve order by instantiating
# Item instances rather than dict
items = json.loads(data, object_pairs_hook=Item.from_tuples)
Not currently organized for handling recursive data structures. THose do not appear in processing JSON, XML, and other common data formats, but still might be a nice future extension.
To install or upgrade to the latest version:
pip install -U items
Sometimes Python installations have different names for pip
(e.g. pip
,
pip2
, and pip3
),
and on systems with multiple versions of Python, which pip
goes with which Python
interpreter can become confusing. In those cases, try running pip
as a module of the
Python version you want to install under. This can reduce conflects and confusion:
python3.6 -m pip install -U items
(On Unix, Linux, and macOS you may need to prefix these with sudo
to authorize
installation. In environments without super-user privileges, you may want to
use pip
's --user
option, to install only for a single user, rather
than system-wide.)
If you wish to run the module tests locally, you'll need to install
pytest
and tox
. For full testing, you will also need pytest-cov
and coverage
. Then run one of these commands:
tox # normal run - speed optimized tox -e py27 # run for a specific version only (e.g. py27, py34) tox -c toxcov.ini # run full coverage tests