Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

function which allows only name arguments

I read a solution in the Python Cookbooks for creating a function that only allows name arguments. I wrote my own code to try it out:

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 it showing this?

like image 239
Bartłomiej Bartnicki Avatar asked Aug 11 '15 10:08

Bartłomiej Bartnicki


People also ask

What are named arguments in Python?

Keyword arguments (or named arguments) are values that, when passed into a function, are identifiable by specific parameter names. A keyword argument is preceded by a parameter and the assignment operator, = . Keyword arguments can be likened to dictionaries in that they map a value to a keyword.

What is keyword only argument?

Keyword-only arguments are another attribute of Python functions that have been available since Python 3.0. These arguments are specified using the '*' marker. They prompt the user to state the keyword used in the already defined function when making a call to the same function.

Which function has single arguments?

In mathematics, an argument of a function is a value provided to obtain the function's result. It is also called an independent variable. , is called a unary function. A function of two or more variables is considered to have a domain consisting of ordered pairs or tuples of argument values.

Can we keep any name instead of args?

It stores Java command-line arguments and is an array of type java. lang. String class. Here, the name of the String array is args but it is not fixed and the user can use any name in place of it.


4 Answers

The code you are using is valid syntax but for python3 so the book must be using python3 syntax, it allows keyword arguments only, pep-3102:

python 3 new syntax

You can also use a bare * in the parameter list to indicate that you don’t accept a variable-length argument list, but you do have keyword-only arguments.

Using your code and passing a non keyword in python 3 would error but for a different 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 a keyword it works fine:

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

Using a function with the same syntax would maybe 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 keywords args passed but only by name:

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

Then passing 3 positional args will 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
like image 189
Padraic Cunningham Avatar answered Oct 02 '22 09:10

Padraic Cunningham


I received a recent comment about this answer.
→ Please note it targets python2, since OP explicitly tagged his question with python2.7. For python3, see Padraic Cunningham's answer.

[original answer]

You may not use the * alone. In the function declaration, it means "unpack any other unnamed argument in this variable", so you have to give it a variable name.

You can achieve what you want by giving it a name, then checking it is empty, like this:

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

Then you'd want to add arguments you can allow, like this:

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

...except *args needs to be at the end. But if you put them at the end, you'll see that you can still pass the other arguments unnamed first.

You have to reverse 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 argments, they will be rejected.

like image 22
spectras Avatar answered Oct 02 '22 11:10

spectras


These are a few utility decorators I wrote on a tangent for a Python 2 project I was working on. The exceptions raised mirror, as closely as possible, the ones raised by functions in Python 3 that use the keyword-only arguments syntax.

They don't disallow positional arguments, but they can require/restrict keyword arguments. You could create another decorator that disallowed 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'
>>> 
like image 20
Cyphase Avatar answered Oct 02 '22 11:10

Cyphase


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

You may want to read Variable-Length Argument Tuples:

Functions can take a variable number of arguments. A parameter name that begins with * gathers arguments into a tuple. For example, printall takes any number of arguments and prints them:

def printall(*args):
    print args

The gather parameter can have any name you like, but args is conventional. Here’s how the function works:

>>> printall(1, 2.0, '3') (1, 2.0, '3')
like image 28
Reut Sharabani Avatar answered Oct 02 '22 10:10

Reut Sharabani