Human beings are flawed
The positional arguments are fixed arguments. Some are mandatory others are optional (and are being set at function definition).
Named arguments is a way to give arguments which are known by their name not their positions.
If we humans were not limited by our short term memory then positional arguments would be enough. Alas we are limited to 7 items in memory +- 2. I normally strongly advise to remember that even if you can go up to 9 because you are genius, when you are woken up at 3 am after a party for a critical bug your short term memory might drop to 5+-2 items. So be prepared for the worse and follow my advice and try to stick to 3 mandatory positional arguments the more you can.
Then, you have the case against the named arguments.
Named arguments are great for writing readable calls to function especially when there are a lot of optional arguments or when calls can be augmented on the fly thanks to duck typing and all funky stuffs that makes programming fun.
However, the documentation of the function might seems complex because you have to do it by hand as you can see here :
https://github.com/kennethreitz/requests/blob/master/requests/api.py
However, the documentation of the function might seems complex because you have to do it by hand as you can see here :
https://github.com/kennethreitz/requests/blob/master/requests/api.py
Plus the signature of the function is quite ugly.
get(url, **kwargs) Sends a GET request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param **kwargs: Optional arguments that ``request`` takes.
So, even if named arguments are great they are painful to document (thus a little less maintainable), and gives function a cryptic signature when used in the form **kwargs.
Having explicit named arguments with default values are therefore «more pythonic» since :
Explicit is better than implicit.
Decorators for easing validation documentation
Since I am an advocate of optional named arguments and I find them cool, I thought why not write code ...
>>> @set_default_kw_value(port=1026,nawak=123) ... @must_have_key("name") ... @min_positional(2) ... @validate(name = naming_convention(), port = in_range(1024,1030 )) ... def toto(*a,**kw): ... """useless fonction""" ... return 1
... that would magically return a documentation looking like this:
toto(*a, **kw) useless fonction
keywords must validate the following rules:
- key: <port> must belong to [ 1024, 1030 [,
- key: <name> must begin with underscore
keyword_must_contain_key :name
default_keyword_value :
- params: port is 1026,
- params: nawak is 123
The idea was just to make a class making decorator with reasonable defaults that would enhance the decorated function documentation based on functools.wrap code.
class Sentinel(object): pass SENTINEL=Sentinel() def default_doc_maker(a_func, *pos, **opt): doc = "\n\n%s:%s" % (a_func, a_func.__doc__) posd= "%s\n" % ",".join(map(str, pos)) if len(pos) else "" named = "\n%s" % ",\n".join([ "* params: %s is %r"%(k,v) for k,v in opt.items() ] ) if len(opt) else "" return """ **%s** :%s %s""" % ( a_func.__name__, posd, named ) def valid_and_doc( pre_validate = SENTINEL, post_validate = SENTINEL, doc_maker = default_doc_maker ): def wraps(*pos, **named): additionnal_doc="" if pre_validate is not SENTINEL: additionnal_doc += doc_maker(pre_validate, *pos, **named) if post_validate is not SENTINEL: additionnal_doc += doc_maker(post_validate, *pos, **named) def wrap(func): def rewrapped(*a,**kw): if pre_validate is not SENTINEL: pre_validate(*pos,**named)(*a,**kw) res = func(*a,**kw) if post_validate is not SENTINEL: post_validate(*pos,**named)(*a,**kw) return res rewrapped.__module__ = func.__module__ rewrapped.__doc__=func.__doc__ + additionnal_doc rewrapped.__name__ = func.__name__ return rewrapped return wrap return wraps
That can be used this way :
def keyword_must_contain_key(*key): def keyword_must_contain_key(*a,**kw): if set(key) & set(kw) != set(key): raise Exception("missing key %s in %s" % ( set(key)^( set(kw)& set(key)),kw) ) return keyword_must_contain_key def at_least_n_positional(ceil): def at_least_n_positional(*a, **kw): if a is not None and len(a) < ceil: raise Exception("Expected at least %s argument got %s" % (ceil,len(a))) return at_least_n_positional min_positional= valid_and_doc(at_least_n_positional) must_have_key = valid_and_doc(keyword_must_contain_key)
Okay, my code might not get an award for its beauty, but you can test it here https://github.com/jul/check_arg
And at least sphinx.automodule accepts the modified docs, and interactive help is working too.
Of course, it relies on people correctly naming their functions and having sensibles parameters names :P
However, though it sound ridicule, I do think that most of our experience comes from knowing the importance of naming variables, modules, classes and functions correctly.
Conclusion
Since I am not satisfied by the complexity/beauty of the code I strictly have no idea if I will package it, or even work on it. But at least, I hope you got the point that what makes optional optional named arguments difficult to document is only some lack of imagination. :)
No comments:
Post a Comment