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?
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
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.
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')
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'
>>>
A star (*) is not a valid Python 2.7 identifier, it's an operator. I think you made a mistake while copying a code from the cookbook.
However, it's a valid code in Python 3, as Padraic Cunningham answered.
In python 2.*:
Parameters names derived as variables name laws in any programming languages. but in python 2.* STAR sign used before parameters names for determining special situation and single STAR sign as a parameter name raising error.
But in python 3.*:
A parameter that equal to STAR can't assign any value to this and next position parameters can't give value by position, and only can value by parameter name.
Related
This question already has answers here:
How do I use a string as a keyword argument?
(3 answers)
Closed 10 months ago.
I have a function with multiple arguments. I would like to pass data to the argument, depending on value in my variable. How to do this?
def Task(firstArg = None, lastArg = None):
if firstArg is not None: do_something()
if lastArg is not None: do_something_else()
arg = 'lastArg'
Task( arg = "Susanta") # Expecting do_something_else()
arg = 'firstArg'
Task( arg = "Susanta") # Expecting do_something()
Traceback (most recent call last):
File "test.py", line 6, in <module>
Task( arg = "Susanta") # Expecting do_something_else()
TypeError: Task() got an unexpected keyword argument 'arg'
You can pass it in as a dictionary that you unpack
Task(**{arg: "Susanta"})
def divide(num1,num2):
try:
return num1/num2
except TypeError:
return "Please provide two integers or floats"
except ZeroDivisionError:
return "Please do not divide by zero"
If you don't supply all of the required arguments, the function is never entered, so there's no way to catch that TypeError from inside the function.
To illustrate, consider a function that immediately errors out:
>>> def func(a, b, c):
... raise Exception("inside the function")
...
Now let's call it with the required arguments:
>>> func(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in func
Exception: inside the function
Here you can see from the traceback (in func) that the function was entered and the error thrown from there. However, if we call it again without the arguments:
>>> func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 3 arguments (0 given)
Note that the traceback doesn't include in func, the error happens before entering the function body. You can only catch it outside the function:
>>> try:
... func()
... except TypeError:
... print('oh no!')
...
oh no!
For positional parameters, you have to pass the same number of arguments. You can use the default argument concept, like :
def divide(num1=1,num2=1):
try:
return num1/num2
except TypeError:
return "Please provide two integers or floats"
except ZeroDivisionError:
return "Please do not divide by zero"
You can call this function using 0,1 or 2 arguments.
Because your try/except statement is catching that exception and returning the designated message.
Try something like this:
try:
if num1 and num2:
return num1 / num2
else:
return "Please provide two numbers"
except ZeroDivisionError:
return "Please do not divide by zero"
except TypeError:
return "Please provide two integers or floats"
Is there any way to check the existence of argument of a function by assert statement?
def fractional(x) :
assert x==None, "argument missing" <---- is it possible here to check?
assert type(x) == int, 'x must be integer'
assert x > 0 , ' x must be positive '
output = 1
for i in range ( 1 , int(x)+1) :
output = output*i
assert output > 0 , 'output must be positive'
return output
y=3
fractional() <----- argument missing
you shouldn't have to assert the existence of the argument explicitly. if the argument isn't given when you call the function, you'll get a TypeError like:
>>> def foo(x):
... pass
...
>>> foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 argument (0 given)
>>>
if you wanted to ensure other properties of the argument (you mentioned only existence), you could test those properties and raise exceptions if they weren't met:
>>> def foo(x):
... if not isinstance(x, str):
... raise ValueError("argument must be a string!")
...
>>> foo(42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in foo
ValueError: argument must be a string!
>>>
I am new to decorators and trying to write one that allows me to get the named argument, if it exists, otherwise Exception or something.
To explain:
# my decorator!
def test_mem(key, modifier):
def deco(func):
#wraps(func)
def wrapper(*args, **kwargs):
# something here, s.t.
# print(args + modifier) <------
return func(*args, **kwargs)
return wrapper
return deco
my function
#test_mem('username', modifier = '_allowed')
def myfunc(arg1, username = None, stuff = None):
# logic, this code is always run!
return 'Done'
myfunc(1, 3)
>>>> '3_allowed'
myfunc(1, username = 3)
>>>> '3_allowed'
myfunc(1, stuff = [])
>>>> Exception
When I coded it, my example 1 and example 2 were mutually exclusive, when example 1 worked example 2 broke and vice versa. I am trying to use this to create some automatic keys.
You may also want to consider inspect.getcallargs(). Inside your decorator, you can use:
dictionary = inspect.getcallargs(func, *args, **kwargs)
dictionary['username'] # Gets you the username, default or modifed
To copy from the linked Python docs:
>>> from inspect import getcallargs
>>> def f(a, b=1, *pos, **named):
... pass
>>> getcallargs(f, 1, 2, 3)
{'a': 1, 'named': {}, 'b': 2, 'pos': (3,)}
>>> getcallargs(f, a=2, x=4)
{'a': 2, 'named': {'x': 4}, 'b': 1, 'pos': ()}
>>> getcallargs(f)
Traceback (most recent call last):
...
TypeError: f() takes at least 1 argument (0 given)
Working through "Learning Python" came across factory function. This textbook example works:
def maker(N):
def action(X):
return X ** N
return action
>>> maker(2)
<function action at 0x7f9087f008c0>
>>> o = maker(2)
>>> o(3)
8
>>> maker(2)
<function action at 0x7f9087f00230>
>>> maker(2)(3)
8
However when going deeper another level I have no idea how to call it:
>>> def superfunc(X):
... def func(Y):
... def subfunc(Z):
... return X + Y + Z
... return func
...
>>> superfunc()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: superfunc() takes exactly 1 argument (0 given)
>>> superfunc(1)
<function func at 0x7f9087f09500>
>>> superfunc(1)(2)
>>> superfunc(1)(2)(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
>>> superfunc(1)(2)
>>>
Why doesn't superfunc(1)(2)(3) work while maker(2)(3) does?
While this kind of nesting certainly doesn't look like a good, usable code to me, Python still accepts it as valid, so I'm curious as to how this can be called.
You get a TypeError because function func doesn't return anything (thus its return is NoneType). It should return subfunc:
>>> def superfunc(X):
... def func(Y):
... def subfunc(Z):
... return X + Y + Z
... return subfunc
... return func
...
A return is missing somewhere in your superfunc: you have a return for subfunc, but none for func.
superfunc corrected, with a calling example
def superfunc(X):
def func(Y):
def subfunc(Z):
return X + Y + Z
return subfunc
return func
print superfunc(1)(2)(3)
You forgot the return of the second function.
Here is the fixed function
def superfunct(x):
def func(y):
def subfunc(z):
return x + y + z
return subfunc
return func
print superfunct(1)(2)(3)