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?
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.
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:
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With