Skip to content

Subclassing Box

Chris Griffith edited this page Nov 4, 2026 · 1 revision

Box is built to be subclassed. Nested dictionaries become instances of your class, and so do copies, pickles and anything loaded with from_json, from_yaml and friends.

Changing the default options

New in Box 8.

Set _box_defaults to the options your class should use when they are not passed:

from box import Box

class Config(Box):
    _box_defaults = {"default_box": True, "box_dots": True}

config = Config()
config["server.host"] = "localhost"
print(config)
# {'server': {'host': 'localhost'}}

print(type(config.server))
# <class '__main__.Config'>

An option passed explicitly still wins, Config(default_box=False) turns it off again. Subclasses of Config inherit its defaults and only need to list what they change:

class FrozenConfig(Config):
    _box_defaults = {"frozen_box": True}

A misspelled option name raises BoxError when the class is created.

Overriding __init__

Box creates new instances of your class internally (for nested dictionaries, copies and pickling) and passes its options to them as keyword arguments. If you override __init__, accept *args and **kwargs and pass them on:

class Settings(Box):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # your setup here

An __init__ that only accepts its own arguments fails with TypeError: __init__() got an unexpected keyword argument 'default_box'. If all you need is different option defaults, use _box_defaults instead of an __init__.

Methods and properties

Methods and properties on your class take priority over keys with the same name for attribute access, the key is still there with item access:

class Printer(Box):
    def print(self):
        return "method"

p = Printer(print="key")
p.print()
# 'method'
p["print"]
# 'key'

To make Box reject keys that clash with your methods, add the names to _protected_keys:

class Printer(Box):
    _protected_keys = Box._protected_keys + ["print"]

    def print(self):
        return "method"

Printer().print = "key"
# box.exceptions.BoxKeyError: 'Key name "print" is protected'

Internal attributes

New in Box 8.

Attributes listed in _box_internal_attrs are stored on the instance, not in the data, so they never show up in to_dict(), iteration, comparisons or to_json():

class Document(Box):
    _box_internal_attrs = ("source_path",)

doc = Document(title="Notes")
doc.source_path = "/tmp/notes.json"
print(doc.to_dict())
# {'title': 'Notes'}
print(doc.source_path)
# /tmp/notes.json

copy() and deepcopy() carry them over. Nested boxes start without them, and they can be set even on a frozen box.

Copies keep your class

Changed in Box 8: copy(), copy.copy() and copy.deepcopy() return an instance of your class. Before Box 8 they returned a plain Box.

Clone this wiki locally