Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

best way to distribute keyword arguments?

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?

like image 380
fearless_fool Avatar asked Nov 12 '18 14:11

fearless_fool


People also ask

What are keyword-only arguments?

It’s called keyword-only arguments. This type of argument has a similar behavior as that vanilla keyword argument, but it forces users to pass keyword arguments rather than positional or direct arguments. Let’s use the above function as an example.

How do I call a function with an arbitrary keyword argument?

Just as you can define functions that take arbitrary keyword arguments, you can also call functions with arbitrary keyword arguments. By this I mean that you can pass keyword arguments into a function based on items in a dictionary. Here we’re manually taking every key/value pair from a dictionary and passing them in as keyword arguments:

Why does Python 3’s sorted function require all arguments as keyword arguments?

But Python 3’s sorted function requires all arguments after the provided iterable to be specified as keyword arguments: Keyword arguments come up quite a bit in Python’s built-in functions as well as in the standard library and third party libraries.

How to send keyword arguments in Python?

You can also send arguments with the key = value syntax. This way the order of the arguments does not matter. The phrase Keyword Arguments are often shortened to kwargs in Python documentations.


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):
    ...
like image 194
Rob Bricheno Avatar answered Oct 17 '22 04:10

Rob Bricheno


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)

like image 31
Chris_Rands Avatar answered Oct 17 '22 04:10

Chris_Rands


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.

like image 1
LeKhan9 Avatar answered Oct 17 '22 04:10

LeKhan9