limiting number of optional, positional arguments to a function/method
what is an appropriate solution to limit the number of optional, positional arguments for a function or method? E.g. I'd like to have a function that takes either two or three positional arguments (but not more). I cannot use an optional keyword argument (because the function needs to accept an unlimited number of arbitrarily named keyword arguments). What I've come up with so far is something like this:
def foo(x, y, *args, **kwargs):
if len(args) == 1:
# do some开发者_运维问答thing
elif len(args) > 1:
raise TypeError, "foo expected at most 3 arguments, got %d" % (len(args) + 2)
else
# do something else
Does this reasonable or is there a better way?
One way to find out what is consider "pythonic" is to search for examples in the python source code itself.
find '/usr/lib/python2.6' -name '*.py' -exec egrep 'len\(args\)' {} + | wc
156 867 12946
If you peruse the results of the above command (without the wc), you'll find plenty of examples using exactly the technique you propose.
this works:
>>> def foo(a, b, c=3, **kwargs):
print(a, b, c, kwargs)
>>> foo(3, 4, 2)
3 4 2 {}
>>> foo(3, 4)
3 4 3 {}
Your solution seems reasonable to me.
You can write a decorator:
class TooManyArgumentsException(Exception):
pass
def limit_args(n):
def limit_decorator(f):
def new_f(*args, **kwargs):
if len(args) > n:
raise TooManyArgumentsException("%d args accepted at most, %d args passed" % (n, len(args)))
return f(*args, **kwargs)
return new_f
return limit_decorator
And then use it like this:
>>> @limit_args(5)
... def f(a, b, *args):
... return a + b + sum(args)
...
>>> f(1, 2, 3)
6
>>> f(1, 2, 3, 4)
10
>>> f(1, 2, 3, 4, 5)
15
>>> f(1, 2, 3, 4, 5, 6)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "limit.py", line 8, in new_f
raise TooManyArgumentsException("%d args accepted at most, %d args passed" % (n, len(args)))
limit.TooManyArgumentsException: 5 args accepted at most, 6 args passed
>>>
It looks good to me.
If you want to abstract that logic out of the body of the function you can put it in a decorator:
def validate_num_args(num_args=None, num_kwargs=None):
def entangle(f):
def inner(*args, **kwargs):
if not num_args is None and len(args) > num_args:
raise ValueError("Too many arguments, got %s, wanted %s." % (len(args), num_args))
if not num_kwargs is None and len(kwargs) > num_kwargs:
raise ValueError("Too many keyword arguments, got %s, wanted %s." % (len(kwargs), num_kwargs))
return f(*args, **kwargs)
return inner
return entangle
@validate_num_args(num_args=3)
def foo(x, y, *args, **kwargs):
return "do something with these variables:", x, y, args, kwargs
print "Good:\n", foo(1,2,3)
print ""
print "Bad:\n", foo(1,2,3,4)
The output of this code is:
Good:
('do something with these variables:', 1, 2, (3,), {})
Bad:
Traceback (most recent call last):
File "c:\so.py", line 18, in <module>
print "Bad:\n", foo(1,2,3,4)
File "c:\so.py", line 5, in inner
raise ValueError("Too many arguments, got %s, wanted %s." % (num_args, len(args)))
ValueError: Too many arguments, got 4, wanted 3.
Am I late? I do it this way:
def (positional_1, positional_2, *, keyword_arguments...):
Check PEP 3102 -- Keyword-Only Arguments for further reference.
精彩评论