What is the best style for a Python method that requires the keyword argument 'required_arg':
def test_method(required_arg, *args, **kwargs):
def test_method(*args, **kwargs):
required_arg = kwargs.pop('required_arg')
if kwargs:
raise ValueError('Unexpected keyword arguments: %s' % kwargs)
Or something else? I want to use this for all my methods in the future so I'm kind of looking for the best practices way to deal with required keyword arguments in Python methods.
The first method by far. Why duplicate something the language already provides for you?
Optional arguments in most cases should be known (only use *args and **kwargs when there is no possible way of knowing the arguments). Denote optional arguments by giving them their default value (def bar(foo = 0) or def bar(foo = None)). Watch out for the classic gotcha of def bar(foo = []) which doesn't do what you expect.
The first method offers you the opportunity to give your required argument a meaningful name; using *args doesn't. Using *args is great when you need it, but why give up the opportunity for clearer expression of your intent?
If you don't want arbitrary keyword arguments, leave out the ** parameter. For the love of all that is holy, if you have something that is required, just make it a normal argument.
Instead of this:
def test_method(*args, **kwargs):
required_arg = kwargs.pop('required_arg')
if kwargs:
raise ValueError('Unexpected keyword arguments: %s' % kwargs)
Do this:
def test_method(required_arg, *args):
pass
Related
Let's say I want to pass args and kwargs to a function without using *args and **kwargs.
In the event, what is the most general type hint to use?
Is it Iterable for args and Mapping for kwargs?
Sample Code
Here is some sample code made using Python 3.8.
from typing import Mapping, Iterable
def foo(*args, **kwargs) -> None:
pass
def foo_dispatcher(foo_args: Iterable, foo_kwargs: Mapping) -> None:
foo(*foo_args, **foo_kwargs)
foo_dispatcher([0], {"ham": 1})
If you know your actual arguments and kwargs then you can obviously use the specifics, including TypedDict for the the arguments.
In this most-general case you you can use Iterable and Mapping[str, object].
For known functions over-typing is quite possible here, thanks to named non-keywords, variable length positional arguments, etc. You may not want to disallow def foo(a, b, c=4) being called as foo(1, b=1, c=2)
In Python 3.x, I want to create a proxy function _proxy for a specific known function proxiedFunc and guarantee that all arguments passed are "forwarded" exactly as if they were passed directly to the proxiedFunc.
# Pseudo-Python code
def _proxy(???generic_parameters???):
return proxiedFunc(???arguments???)
What I mean by "pure pass through" -> The implementation of the _proxy method should not be affected by (non-)compatible changes to the proxiedMethod, assuming the name of the function doesn't change (SOLID principles). Obviously, callers of _proxy would need to be modified if proxiedMethod is changed incompatibly (i.e. I'm not intending for _proxy to be an adapter, but that would be a possibility).
The generic way of taking "anything" in a function definition is using *args, **kwargs.
The same syntax is used for passing those args when calling another function.
def _proxy(*args, **kwargs):
return proxiedFunc(*args, **kwargs)
The single * (e.g. *args) captures the positional arguments, and the double (e.g. **kwargs) captures the keyword arguments.
args and kwargs are the names you give to those argument-containers. By convention, the name of the "any-positional-args" argument is args or a (its type is tuple), and the name of the "any-keyword-args" argument is kwargs or kw (its type is dict).
I, too, wanted to find a way to do that, so I wrote a function for that. I posted it to github: https://github.com/make-itrain/function-proxy
Basically, what it does is:
def a(p1, p2, p3=3, *args, p4=228, p18=11, **kwargs):
# any function, doesn't matter
pass
def b(p4, p2, *args):
# some overlapping arguments
# it too doesn't matter, what's inside here
print(p4, p2, args)
args, kwargs = proxy_function(b, a, {"args": ("replaced args",)}, 322, "some_random_arg", 1337,
"arguments", ('anything',), 1, abc=338, cbd="here too?")
b(*args, **kwargs)
Prints 228 some_random_arg ('replaced args',). Cool, isn't it?
So, I have some function that takes a list of arguments with minimum one argument, but potentially an entire list of arguments. I know a couple ways of defining this method, but I'm trying to determine what the various alternatives are at my disposal and what the various advantages of each approach are.
I'm trying to figure out what possible prototypes I can use to define this argument. I've considered the following:
def func(arg_1, *args):
arg_list = (arg_1,) + args
# do stuff with arg_list
as well as
def func(*args):
if len(args) == 0: raise Exception("Not enough arguments specified")
# do stuff with args
Are there alternative ways to specify a function that takes an argument list with minimum one argument?
In Python 3.5 you can (it has been present since 3.0 I believe, when PEP 3102 came out) but with the restrictions that the extra arguments are defined without a default value and supplied in a keyword form when calling the defined function.
This is done by adding * as a separator and the required argument(s) after it:
def foo(*, must_specify, **kwargs): pass
Calling foo() without supplying the a keyword argument of the form must_specify = 'specified' will result in a TypeError hinting the requirement for that specific argument:
foo()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-45-624891b0d01a> in <module>()
----> 1 foo()
TypeError: foo() missing 1 required keyword-only argument: 'must_specify'
Positional arguments on their own can be supplied, but, the positional grouping parameter of the form *args cannot i.e:
def foo(pos_arg, *, must_specify, **kwargs): pass
is fine, but:
def foo(pos_arg, *args, *, must_specify, **kwargs): pass
is a SyntaxError. This is done because * is essentially the same as a 'throw-away' form of *args.
Either way, there is no other syntactic way (that I'm aware of) which can satisfy the restriction you wish. If working with kwargs is too much of a hassle for you or if you're working with Py2 and are adamant to switch, solutions as those suggested in the comments are your best bet.
This is something that's bugged me for awhile now:
def test (*args, **kwargs):
print target
test(foo='bar', target='baz')
I would presume that target='test' in the aFunc call at the bottom would end up in kwargs (and it does), and I would also presume that **would unpack kwargs in the function call, so target would exist as a keyword argument inside of aFunc. It doesn't. I know that it comes in as a dict, but I need to have that dict unpack in the argument list. Is this possible? In short, is there any way to have *args and **kwargs disappear and have the actual args and kwargs go into the call?
Edit: I threw together a case where unpacking of *args and **kwargs might help:
Let's say I have a function that prints a list:
def printList (inputList=None):
print inputList
I want to be able to pass no list and have a default list supplied:
def ensureList (listFunc):
def wrapper (inputList=None):
listFunc(inputList=inputList or ['a','default','list'])
return wrapper
#ensureList
def printList (inputList=None):
print inputList
Now I want to get a bit more complicated with a list repeater:
#ensureList
def repeatList (inputList=None):
print inputList*2
That works fine. But now I want variable repeating:
#ensureList
def repeatList (times, inputList=None):
print inputList*times
Now you would be able to say:
repeatList(5)
It would generate the default list and repeat it 5 times.
This fails, of course, because wrapper can't handle the times argument. I could of course do this:
#ensureList
def repeatList (inputList=None, times=1)
But then I always have to do this:
repeatList(times=5)
And maybe in some cases I want to enforce supplying a value, so a non-keyword arg makes sense.
When I first encountered problems like this last year, I thought a simple solution would be to remove the requirements on the wrapper:
def ensureList (listFunc):
"info here re: operating on/requiring an inputList keyword arg"
def wrapper (*args, **kwargs):
listFunc(inputList=inputList or ['a','default','list'])
return wrapper
That doesn't work, though. This is why I'd like to have args and kwargs actually expand, or I'd like to have a way to do the expansion. Then whatever args and kwargs I supply, they actually fill in the arguments, and not a list and a dict. The documentation in the wrapper would explain requirements. If you pass in inputList, it would actually go in, and inputList in the call back to repeatList from the wrapper would be valid. If you didn't pass in inputList, it would create it in the call back to repeatList with a default list. If your function didn't care, but used *kwargs, it would just gracefully accept it without issue.
Apologies if any of the above is wrong (beyond the general concept). I typed it out in here, untested, and it's very late.
The answer to "why doesn't ** unpack kwargs in function calls?" is: Because it's a bad idea, the person who develop a function does not want local variable to just appear depending on the call arguments.
So, this is not how it's working and you surely do not want python to behave like that.
To access the target variable in the function, you can either use:
def test(target='<default-value>', *args, **kwargs):
print target
or
def test(*args, **kwargs):
target = kwargs.get('target', '<default-value>')
print target
However, if you want a hack (educational usage only) to unpack **kwargs, you can try that:
def test(*args, **kwargs):
for i in kwargs:
exec('%s = %s' % (i, repr(kwargs[i])))
print target
The obvious way for this particular case is
def test(foo=None, target=None):
print target
test(foo='bar', target='baz')
If you want to access a parameter inside a function by name, name it explicitly in the argument list.
I have the following function:
def foo(**kwargs):
if not kwargs:
# No keyword arguments? It's all right. Set defaults here...
elif ('start_index' or 'end_index') in kwargs:
# Do something here...
else:
# Catch unexpected keyword arguments
raise TypeError("%r are invalid keyword arguments" % (kwargs.keys())
Question:
I want to make sure that the only valid keyword arguments are start_index or end_index. Anything else will raise an error, even if mixed with the valid ones. What's the cookbook recipe to make sure that only start_index or end_index are accepted? Yes, I'm looking for a cookbook recipe but I'm not sure how to search for it. I'm not sure if using an if-elif-else structure is the correct way to do it either.
Why do you need **kwargs here? Just
def foo(start_index=None, end_index=None):
and Python will perform all validation for you.
For the sake of completeness, Here's an alternative that still uses **kwargs.
def foo(**kwargs):
start_index = kwargs.pop('start_index', STARTINDEX_DEFAULT)
end_index = kwargs.pop('end_index', ENDINDEX_DEFAULT)
if kwargs:
# Catch unexpected keyword arguments
raise TypeError("%r are invalid keyword arguments" % (kwargs.keys())
# Do something here...
But, you shouldn't want to use this when you don't absolutely need it, use regular parameters with default values (as in Roman Bodnarchuk's answer).
Cases when you might need this is when you also want to use *args, and need a way to distinguish the keyword arguments from arbitrarily man positional arguments. using **kwargs this way forces the keyword arguments to be passed as keywords; A positional argument can never find its way into **kwargs.
Another reason is so that you can really distinguish between a default and an explicit parameter which happens to be the default. None is often used as a default value for arguments to indicate "the argument doesn't apply", but sometimes you actually need to interpret the None as something other than a default. Checking for the presence or absence of a key in the **kwargs dict can accurately distinguish between these cases. (An alternative is to create an instance of a subclass of object whos sole purpose is to be the default value of a specific argument to that specific function)
If you really want to use **kwargs, I'd write that like:
def foo(**kwargs):
# Define default values for all keys
args = {'start_index': 0, 'end_index': -1}
# Get the keys passed in that aren't in args
extraargs = set(kwargs) - set(args)
if extraargs:
raise TypeError("Invalid arguments: %s" % list(extraargs))
# Overwrite the default values with the passed-in values
args.update(kwargs)
# Now, do stuff with the values in args
But all of that is a complicated, slow way to duplicate built-in functionality. Don't do that unless you really need to.
In any case getting keys from a dict is as easy as using .get e.g.
kwargs.get('WIDTH',500)
this way if it doesn't find WIDTH as a key you get 500.