best way to distribute keyword arguments?

Viewed 104

What's the approved programming pattern for distributing keyword arguments among called functions?

Consider this contrived (and buggy) example:

def create_box(**kwargs):
    box = Box()
    set_size(box, **kwargs)
    set_appearance(box, **kwargs)

def set_size(box, width=1, height=1, length=1):
    ...

def set_appearance(box, material='cardboard', color='brown'):
    ...

Obviously the set_size() method will object to receiving material or color keyword arguments, just as set_appearance() will object to receiving width, height, or length arguments.

There's a valid argument that create_box() should make all of the keyword and defaults explicit, but the obvious implementation is rather unwieldy:

def create_box(width=1, height=1, length=1, material='cardboard', color='brown'):
    box = Box()
    set_size(box, width=width, height=height, length=length)
    set_appearance(box, material=material, color=color)

Is there a more Pythonic way to approach this?

3 Answers

You could add **kwargs as the last argument to the functions that would otherwise become annoyed.

def create_box(**kwargs):
    box = Box()
    set_size(box, **kwargs)
    set_appearance(box, **kwargs)

def set_size(box, width=1, height=1, length=1, **kwargs):
    ...

def set_appearance(box, material='cardboard', color='brown', **kwargs):
    ...

Assuming you can't alter set_size or set_appearance, you could filter kwargs depending on the expected keword arguments, I'm not sure if this is the most pythonic way though...

import inspect

def create_box(**kwargs):
    box = None
    set_size_args = inspect.getargspec(set_size).args
    set_size(box, {k:v for k,v in kwargs.items() if k in set_size_args})
    set_appearance_args = inspect.getargspec(set_appearance).args
    set_appearance(box, {k:v for k,v in kwargs.items() if k in set_appearance_args})


def set_size(box, width=1, height=1, length=1):
    pass

def set_appearance(box, material='cardboard', color='brown'):
    pass

create_box(width=1, height=1, length=1, material='', color='')

(use inspect.signature() for Python 3)

Hm, personally I would pass an object containing all of the params I needed. That way you're only passing one param all along.

Example:

class BoxDefinition:
    def __init__(self, width=1, height=1, length=1, material='cardboard', color='brown'):
        self.width = width 
        self.height = height
        # and so on for the rest of the params ...

Internally in the each method you can read from this object:

def create_box(box_definition):
    box = Box()
    set_size(box, box_definition)
    set_appearance(box, box_definition)

def set_size(box, box_definition):
    width = box_definition.width

def set_appearance(box, box_definition):
    color = box_definition.color

Then you can call it like this:

definition = BoxDefinition(width=2, height=3, length=4)
create_box(definition)

In fact, you can even make BoxDefinition and attribute of the Box class, and then the only thing you'd have to pass is Box.

Related