Everyone loves Python’s dictionaries; they’re fast, easy to create and quite handy for a range of reasons. However, there are times that ["typing"]["out"]["all"]["those"]
extra quotes and brackets seems excessive. Wouldn’t it be nicer to access.them.like.class.methods?
Say hello to box.
from box import Box movie_data = { "movies": { "Spaceballs": { "imdb_stars": 7.1, "rating": "PG", "length": 96, "Director": "Mel Brooks", "Stars": [{"name": "Mel Brooks", "imdb": "nm0000316", "role": "President Skroob"}, {"name": "John Candy","imdb": "nm0001006", "role": "Barf"}, {"name": "Rick Moranis", "imdb": "nm0001548", "role": "Dark Helmet"} ] }, "Robin Hood: Men in Tights": { "imdb_stars": 6.7, "rating": "PG-13", "length": 104, "Director": "Mel Brooks", "Stars": [ {"name": "Cary Elwes", "imdb": "nm0000144", "role": "Robin Hood"}, {"name": "Richard Lewis", "imdb": "nm0507659", "role": "Prince John"}, {"name": "Roger Rees", "imdb": "nm0715953", "role": "Sheriff of Rottingham"}, {"name": "Amy Yasbeck", "imdb": "nm0001865", "role": "Marian"} ] } } } my_box = Box(movie_data) my_box.movies.Spaceballs.rating 'PG' my_box.movies.Spaceballs.Stars[0].name 'Mel Brooks' my_box.movies.Spaceballs.Stars[0] # <Box: {'name': 'Mel Brooks', 'imdb': 'nm0000316', 'role': 'President Skroob'}>
Box is a creation I made over three years ago, originally in the reusables code base named Namespace
, inspired by JavaScript Object access methods.
Install is super simple:
pip install python-box
Or just grab the file box.py
directly from the github project.
Every Box is usable as a drop in replacement to dictionaries in 99%* of cases. And every time you add a dictionary or list to a Box object, they become Box
(subclass of dict) or BoxList
(subclass of list) objects as well.
type(my_box) # box.Box assert isinstance(my_box, dict) type(my_box.movies.Spaceballs.Stars) # box.BoxList assert isinstance(my_box.movies.Spaceballs.Stars, list) my_box.movies.Spaceballs.Stars[0].additional_info = {'Birth name': 'Melvin Kaminsky', 'Birthday': "05/28/1926"} my_box.movies.Spaceballs.Stars[0].additional_info # <Box: {'Birth name': 'Melvin Kaminsky', 'Birthday': '05/28/1926'}>
At any level you can change a Box object back into a standard dictionary.
my_box.movies.Spaceballs.to_dict() {'Director': 'Mel Brooks', 'Stars': [ {'additional_info': {'Birth name': 'Melvin Kaminsky', 'Birthday': '05/28/1926'}, 'imdb': 'nm0000316', 'name': 'Mel Brooks', 'role': 'President Skroob'}, {'imdb': 'nm0001006', 'name': 'John Candy', 'role': 'Barf'}, {'imdb': 'nm0001548', 'name': 'Rick Moranis', 'role': 'Dark Helmet'}, {'imdb': 'nm0000597', 'name': 'Bill Pullman', 'role': 'Lone Starr'}], 'imdb_stars': 7.1, 'length': 96, 'rating': 'PG'}
You can also run to_list()
on lists in the Box to return them to a standard list, with all inner Box
and BoxList
objects transformed back to normal.
Box also has built in functions for dealing with json
and yaml**
.
my_box.movies.Spaceballs.to_json() # { # "imdb_stars": 7.1, # "rating": "PG", # "length": 96, # "Director": "Mel Brooks", # "Stars": [ # ... my_box.movies.Spaceballs.to_yaml() # Director: Mel Brooks # imdb_stars: 7.1 # length: 96 # rating: PG # Stars: # - imdb: nm0000316 # name: Mel Brooks # role: President Skroob # ...
Calling a Box object will return it’s keys. It’s also possible to access the attributes the standard dictionary method, which is required for keys that are numeric or have spaces.
my_box.movies() # ('Spaceballs', 'Robin Hood: Men in Tights') my_box.movies['Robin Hood: Men in Tights'] # <Box: {'imdb_stars': 6.7, 'rating': 'PG-13', 'length': 104, ...
Unlike addict
it does not act as a default dictionary, so you will get built-in errors if you try to access something that isn’t there.
my_box.tv_shows # Traceback (most recent call last): # ... # AttributeError: tv_shows
Another power previously mentioned is that you can add dictionaries into lists and they will automatically be converted into Box objects.
my_box.movies.Spaceballs.Stars.append( {"name": "Bill Pullman", "imdb": "nm0000597", "role": "Lone Starr"}) my_box.moves.Spaceballs.Stars[-1].name 'Bill Pullman'
It also protects itself from having its functions overwritten accidentally.
my_box.to_dict = '3' # AttributeError: Key name 'to_dict' is protected
Box is also a substitute for the Namespace used by argparse,
making it super easy to convert incoming arguments to a dict if wanted. This allows incoming arguments to be easily passed to function arguments.
import argparse from box import Box parser = argparse.ArgumentParser() parser.add_argument('floats', metavar='N', type=float, nargs='+') parser.add_argument("-v", "--verbosity", action="count", default=0) args = parser.parse_args(['1', '2', '3', '-vv'], namespace=Box()) print(args.to_dict()) {'floats': [1.0, 2.0, 3.0], 'verbosity': 2} def example_func(floats, verbosity): print(verbosity) example_func(**args) 2
If you have any questions, suggestions or feedback, please open a github issue and let me know!
Hope you enjoy!
Caveats
* Based off nothing but pure guess and personal experience. Only time drop in replacement doesn’t work is when converting or dumping. So make sure do use first for those cases.
** If you don’t have PyYAML installed, the to_yaml
function will not be available.