A function that allows only name arguments

I read the solution in Python cookbooks to create a function that only resolves name arguments. I wrote my own code to try:

class Reporter(object): def __init__(self, *, testline=None, sw_ver= None, directory=None): pass if __name__ == "__main__" r = Reporter() 

However, the interpreter shows this error:

 File "Reporter.py", line 6 def __init__(self, *, testline=None, sw_ver= None, directory=None): ^ SyntaxError: invalid syntax 

Why is this being shown?

+4
source share
6 answers

Strong syntax code but for python3, so the book should use the python3 syntax, it only allows the use of the keyword arguments, pep-3102 :

new python 3 syntax

You can also use bare * in the parameter list to indicate that you are not accepting a variable-length argument list, but you have arguments for the keyword only.

Using your code and passing a non-keyword in python 3 will fail, but for another reason:

 TypeError Traceback (most recent call last) <ipython-input-2-b4df44fa1e0c> in <module>() 1 if __name__ == "__main__": ----> 2 r = Reporter(4) 3 TypeError: __init__() takes 1 positional argument but 2 were given 

Once you use the keyword, it works fine:

 In [4]: if __name__ == "__main__": r = Reporter(testline=4) ...: 

Using a function with the same syntax may give a more obvious error:

 def f(*, foo=None, bar=None): return foo, bar In [6]: f(4) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-6-a122d87dbe99> in <module>() ----> 1 f(4) TypeError: f() takes 0 positional arguments but 1 was given 

Is is also useful if you want to allow some positional args and have optional args keywords, but only by name:

 def f(a,b, *, foo=None, bar=None): return a, b, foo, bar 

Then passing 3 positional arguments would be an error:

 In [8]: f(1,2,3) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-8-b61741968103> in <module>() ----> 1 f(1,2,3) TypeError: f() takes 2 positional arguments but 3 were given 
+6
source

You cannot use only * . In a function declaration, this means "unpack any other unnamed argument in this variable," so you must give it the name of the variable.

You can achieve what you want by giving it a name, and then verify that it is empty, for example:

 class Reporter(object): def __init__(self, *args): assert not args, "Reporter.__ini__ only accepts named arguments" 

Then you want to add arguments that you can resolve, for example:

 # won't work def __init__(self, *args, testline=None, sw_ver= None, directory=None): 

... except * args should be at the end. But if you put them at the end, you will see that you can still pass other arguments that are not named first.

You must cancel the logic and take only kwargs.

 class Reporter(object): def __init__(self, **kwargs): testline = kwargs.pop('testline', None) sw_ver = kwargs.pop('sw_ver', None) directory = kwargs.pop('directory', None) assert not kwargs, 'Unknown arguments: %r' % kwargs 

Now, if someone tries to give unnamed expressions, they will be rejected.

+4
source

The star operator ( * ) is used for unpacking . You cannot use it as an argument.

You might want to read variable length argument correlations :

Functions can take a variable number of arguments. The name of the parameter that begins with * collects the arguments into a tuple. For example, printall accepts any number of arguments and prints them:

 def printall(*args): print args 

The collection parameter can have any name that you like, but the arguments are conditional. Here's how this function works:

 >>> printall(1, 2.0, '3') (1, 2.0, '3') 
+1
source

These are several decorators that I wrote tangentially for the Python 2 project I was working on. Exceptions are the closest possible mirrors created by Python 3 functions that use keyword syntax for arguments only.

They do not prohibit positional arguments, but may require / restrict keyword arguments. You can create another decorator that would ban positional arguments.

 import functools def original_wrapped_function(f): try: while True: f = f.__wrapped__ except AttributeError: return f def restrict_kwargs(*allowed_keywords): def restrict_kwargs_decorator(func): @functools.wraps(original_wrapped_function(func)) def restrict_wrapper(*args, **kwargs): for keyword in kwargs: if keyword not in allowed_keywords: msg = "%s() got an unexpected keyword argument '%s'" raise TypeError(msg % (func.__name__, keyword)) return func(*args, **kwargs) restrict_wrapper.__wrapped__ = func return restrict_wrapper return restrict_kwargs_decorator def require_kwargs(*required_keywords): def require_kwargs_decorator(func): @functools.wraps(original_wrapped_function(func)) def require_wrapper(*args, **kwargs): missing_keywords = [] for keyword in required_keywords: if keyword not in kwargs: missing_keywords.append(keyword) if missing_keywords: func_name = func.__name__ count = len(missing_keywords) if count == 1: arg_word = 'argument' missing_keywords_str = "'%s'" % missing_keywords[0] else: arg_word = 'arguments' and_join_str = ' and ' if count == 2 else ', and ' missing_keywords_str = ', '.join( ("'%s'" % mk) for mk in missing_keywords[:-1]) missing_keywords_str = and_join_str.join(( missing_keywords_str, ("'%s'" % missing_keywords[-1]))) msg = "%s() missing %d required keyword-only %s: %s" raise TypeError(msg % (func_name, count, arg_word, missing_keywords_str)) return func(*args, **kwargs) require_wrapper.__wrapped__ = func return require_wrapper return require_kwargs_decorator def exact_kwargs(*exact_keywords): def exact_kwargs_decorator(func): @restrict_kwargs(*exact_keywords) @require_kwargs(*exact_keywords) @functools.wraps(original_wrapped_function(func)) def exact_wrapper(*args, **kwargs): return func(*args, **kwargs) exact_wrapper.__wrapped__ = func return exact_wrapper return exact_kwargs_decorator 

Some examples:

 >>> @restrict_kwargs('five', 'six') ... def test_restrict_kwargs(arg1, arg2, *moreargs, **kwargs): ... return (arg1, arg2, moreargs, kwargs) ... >>> >>> @require_kwargs('five', 'six') ... def test_require_kwargs(arg1, arg2, *moreargs, **kwargs): ... return (arg1, arg2, moreargs, kwargs) ... >>> >>> @exact_kwargs('five', 'six') ... def test_exact_kwargs(arg1, arg2, *moreargs, **kwargs): ... return (arg1, arg2, moreargs, kwargs) ... >>> >>> >>> >>> test_restrict_kwargs(1, 2, 3, 4, five=5) (1, 2, (3, 4), {'five': 5}) >>> >>> test_restrict_kwargs(1, 2, 3, 4, five=5, six=6) (1, 2, (3, 4), {'six': 6, 'five': 5}) >>> >>> test_restrict_kwargs(1, 2, 3, 4, five=5, six=6, seven=7) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "SO_31939890.py", line 19, in restrict_wrapper raise TypeError(msg % (func.__name__, keyword)) TypeError: test_restrict_kwargs() got an unexpected keyword argument 'seven' >>> >>> >>> >>> test_require_kwargs(1, 2, 3, 4, five=5) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "SO_31939890.py", line 49, in require_wrapper missing_keywords_str)) TypeError: test_require_kwargs() missing 1 required keyword-only argument: 'six' >>> >>> test_require_kwargs(1, 2, 3, 4, five=5, six=6) (1, 2, (3, 4), {'six': 6, 'five': 5}) >>> >>> test_require_kwargs(1, 2, 3, 4, five=5, six=6, seven=7) (1, 2, (3, 4), {'seven': 7, 'six': 6, 'five': 5}) >>> >>> >>> >>> test_exact_kwargs(1, 2, 3, 4, five=5) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "SO_31939890.py", line 20, in restrict_wrapper return func(*args, **kwargs) File "SO_31939890.py", line 49, in require_wrapper missing_keywords_str)) TypeError: test_exact_kwargs() missing 1 required keyword-only argument: 'six' >>> >>> test_exact_kwargs(1, 2, 3, 4, five=5, six=6) (1, 2, (3, 4), {'six': 6, 'five': 5}) >>> >>> test_exact_kwargs(1, 2, 3, 4, five=5, six=6, seven=7) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "SO_31939890.py", line 19, in restrict_wrapper raise TypeError(msg % (func.__name__, keyword)) TypeError: test_exact_kwargs() got an unexpected keyword argument 'seven' >>> 
+1
source

A star ( * ) is not a valid Python 2.7 identifier; it is an operator. I think you made a mistake when copying the code from the cookbook.

However, this is valid code in Python 3, as Padraic Cunningham replied.

0
source

In python 2. *:

Parameter names, defined as variable name names in any programming language. but in the python 2. * STAR sign, which was used before the parameter names to define a special situation and one STAR sign as an error in raising the parameter name.

But in python 3. *:

A parameter equal to STAR cannot assign any value to this, and the following position parameters cannot give a value by position and can only be evaluated by the parameter name.

0
source

All Articles