Reputation: 2217
I took a look at this question but it doesn't exactly answer my question. As an example, I've taken a simple method to print my name.
def call_me_by_name(first_name):
print("Your name is {}".format(first_name))
Later on, I realized that optionally, I would also like to be able to print the middle name and last name. I made the following changes to accommodate that using **kwargs fearing that in the future, I might be made to add more fields for the name itself (such as a 3rd, 4th, 5th name etc.)
I decided to use **kwargs
def call_me_by_name(first_name,**kwargs):
middle_name = kwargs['middle_name'] if kwargs.get('middle_name') else ""
last_name = kwargs['last_name'] if kwargs.get('last_name') else ""
print("Your name is {} {} {}".format(first_name,middle_name,last_name))
My only concern here is that as I continue to implement support for more names, I end up writing one line of code for every single keyword argument that may or may not come my way. I'd like to find a solution that is as pythonic as possible. Is there a better way to achieve this ?
EDIT 1
I want to use keyword arguments since this is just an example program. The actual use case is to parse through a file. The keyword arguments as of now would support parsing a file from
1) A particular byte in the file.
2) A particular line number in the file.
Only one of these two conditions can be set at any given point in time (since it's not possible to read from a particular byte offset in the file and from a line number at the same time.) but there could be more such conditions in the future such as parse a file from the first occurrence of a character etc. There could be 10-20 different such conditions my method should support BUT only one of those conditions would ever be set at any time by the caller. I don't want to have 20-30 different IF conditions unless there's no other option.
Upvotes: 1
Views: 2038
Reputation: 1593
You have two separate questions with two separate pythonic ways of answering those questions.
1- Your first concern was that you don't want to keep adding new lines the more arguments you start supporting when formatting a string. The way to work around that is using a defaultdict
so you're able to return an empty string when you don't provide a specific keyword argument and str.format_map
that accepts a dict as a way to input keyword arguments to format. This way, you only have to update your string and what keyword arguments you want to print:
from collections import defaultdict
def call_me_by_name(**kwargs):
default_kwargs = defaultdict(str, kwargs)
print("Your name is {first_name} {second_name} {third_name}".format_map(default_kwargs))
2- If, on the other hand and answering your second question, you want to provide different behavior depending on the keyword arguments, like changing the way a string looks or providing different file lookup functionalities, without using if statements, you have to add different functions/methods and call them from this common function/method. Here are two ways of doing that:
OOP:
class FileLookup:
def parse(self, **kwargs):
return getattr(self, next(iter(kwargs)))(**kwargs)
def line_number(self, line_number):
print('parsing with a line number: {}'.format(line_number))
def byte_position(self, byte_position):
print('parsing with a byte position: {}'.format(byte_position))
fl = FileLookup()
fl.parse(byte_position=10)
fl.parse(line_number=10)
Module:
def line_number(line_number):
print('parsing with a line number: {}'.format(line_number))
def byte_position(byte_position):
print('parsing with a byte position: {}'.format(byte_position))
def parse(**kwargs):
return globals()[next(iter(kwargs))](**kwargs)
parse(byte_position=29)
parse(line_number=29)
Upvotes: 2
Reputation: 218
I think that your call_me_by_name is no good example for **kwargs. But if you want to avoid omitting some exotic, unconsidered name fields, call_me_by_name could look like:
def call_me_by_name(first_name, last_name, middle_name='', **kwargs):
s = "Your name is {} {} {}".format(first_name,middle_name,last_name)
if kwargs:
s += " (" + ", ".join(["{}: {}".format(k,v) for k,v in kwargs.items()]) + ")"
print(s)
Test:
name = {'first_name': 'Henry', 'last_name': 'Ford', 'ordinal': 'II', 'nickname': 'Hank the Deuce'}
call_me_by_name(**name)
>>> Your name is Henry Ford (ordinal: II, nickname: Hank the Deuce)
Upvotes: 0
Reputation: 2154
I will post it as a answer then :
You can instantly unpack the kwargs values to the format function like this :
"Your name is {} {} {}".format(first_name , *kwargs)
But as a User @PM 2Ring mentioned You must be aware that doesn't guarantee that the names will be in the correct order.
Upvotes: 0
Reputation: 31
You can simplify it by:
middle_name = kwargs.get('middle_name', '')
Upvotes: 1
Reputation:
If your function care so specifically about keyword arguments, this is probably not the right tool. In this case, you can get the same effect with default arguments:
def call_me_by_name(first_name, middle_name="", last_name=""):
print("Your name is {} {} {}".format(first_name,middle_name,last_name))
It's better for when you want a sort of "grab-bag" of options. E.g.
def configure(**kwargs):
if 'color' in kwargs:
set_color(kwargs['color'])
etc.
Upvotes: 1
Reputation: 2362
Seem to me your better off not using kwargs and not even a function, you can simply do something like this:
print("Your name is", " ".join([first_name, middle_name, last_name]))
Or if you do want a function:
def call_me_by_name(*args):
print("Your name is", " ".join(args))
Upvotes: 1