Let's have a look:
print([object, ...], *, sep=' ', end='\n', file=sys.stdout)
http://docs.python.org/py3k/library/functions.html?highlight=print#print
How can we interpret that '*'?
Usually an asterisk ('*') means numerous objects. But herein it is a mystery to me. Between two commas... I'm even afraid to think it may be a typo.
That's an error in the documentation, inserted by someone applying a new Python 3 feature to places where it shouldn't be used. It has since been fixed (see issue 15831).
The function signatures in the document used is given in a psuedo-formal-grammar form, but adding in the * marker only makes sense if you use actual python syntax. The [object, ...], * part of the signature should have been listed as *objects instead in that case.
The corrected version now reads:
print(*objects, sep=' ', end='\\n', file=sys.stdout, flush=False)
The online development version of the documentation has as of now not yet been updated, but the documentation source has been corrected; I'll see if we can request a regeneration of the docs.
To be clear: the * syntax is valid in Python 3 and means that the following arguments can only ever be used as keyword arguments, not positional arguments. This does however not apply to the print() function, as all positional arguments are to be printed anyway and could never be mistaken for the keyword arguments.
It means that the following arguments are keyword-only i.e., you can't supply them as positional arguments, you must use their names e.g.:
>>> def f(*, a): pass
...
>>> f(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 0 positional arguments (1 given)
>>> f(a=1)
>>> # ok
Another example:
>>> def g(*a, b): pass
...
>>> g(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: g() needs keyword-only argument b
>>> g(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: g() needs keyword-only argument b
>>> g(1, b=2)
>>> # ok
>>> g(1, 2, b=3)
>>> # ok
Related
I am new to making unit tests. I'm currently running pytest. I have this Program.py running but when I run pytest on my Program_test.py I've been failing the tests due to these TypeErrors from my where I had my assert line on the code below. I have the program ask the users for an input value or enter to exit the program. I have the 'import pytest' already included in my Program_test.py program.
Am I using lambda wrong? I'm not sure how to best approach this and get those user inputs to work. This is just testing the get_weight function from users.
***It was already fixed. I had a problem with lambda and underneath was very helpful
Here's an example to show where you are going wrong and for the purpose of explanation I am assigning the lambdas to variables:
zero_arg_lambda = lambda: "131" # Takes no args
one_arg_lambda = lambda x: "131" # Takes one arg
Call zero_arg_lambda with arg (same as your error):
zero_arg_lambda(1)
>>> Traceback (most recent call last):
>>> File "<input>", line 1, in <module>
>>> TypeError: <lambda>() takes no arguments (1 given)
Call one_arg_lambda :
one_arg_lambda(1)
>>> "131"
So in short your code is passing a parameter to the lambda even though you have specified that it does not take one.
The one_arg_lambda example takes a parameter and simply returns the value to the right of the colon.
I would recommend reading over the docs on lambda
Or if you don't look there the expected lambda format is:
lambda parameters: expression
Also note the docs on monkeypatch.context.setattr which have a good example of using a lambda expression.
To pin-point it the error in your code is coming from the context.setattr call inside your test.
def test_get_weight_returns_valid_input(monkeypatch):
with monkeypatch.context() as context:
# Old line causing error: context.setattr('builtins.input', lambda: "131")
context.setattr('builtins.input', lambda x: "131") # Fixed
According to help(getattr), two or three arguments are accepted:
getattr(...)
getattr(object, name[, default]) -> value
Doing some simple tests, we can confirm this:
>>> obj = {}
>>> getattr(obj, 'get')
<built-in method get of dict object at 0x7f6d4beaf168>
>>> getattr(obj, 'bad', 'with default')
'with default'
Too few/too many arguments also behave as expected:
>>> getattr()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: getattr expected at least 2 arguments, got 0
>>> getattr(obj, 'get', 'with default', 'extra')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: getattr expected at most 3 arguments, got 4
The argument names specified in the help text do not seem to be accepted as keyword arguments:
>>> getattr(object=obj, name='get')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: getattr() takes no keyword arguments
The inspect module is no help here:
>>> import inspect
>>> inspect.getargspec(getattr)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/inspect.py", line 816, in getargspec
raise TypeError('{!r} is not a Python function'.format(func))
TypeError: <built-in function getattr> is not a Python function
(messaging is a little different in python3, but the gist is the same)
Now, the question: Is there a straightforward way to write my own Python function with a signature that behaves exactly like getattr's signature? That is, keyword arguments are not allowed, and minumum/maximum number of arguments are enforced? The closest I've come is the following:
def myfunc(*args):
len_args = len(args)
if len_args < 2:
raise TypeError('expected at least 2 arguments, got %d' % len_args)
elif len_args > 3:
raise TypeError('expected at most 3 arguments, got %d' % len_args)
...
But now instead of meaningful argument names like object and name we get args[0] and args[1]. It's also a lot of boilerplate, and feels downright unpleasant. I know that, being a builtin, getattr must have vastly different implementation than typical Python code, and perhaps there's no way to perfectly emulate the way it behaves. But it's a curiosity I've had for a while.
This code ticks most of your requirements:
def anonymise_args(fn):
#functools.wraps(fn)
def wrap(*args):
return fn(*args)
return wrap
#anonymise_args
def myfunc(obj, name, default=None):
print obj, name, default
keyword arguments are not allowed
x.myfunc(obj=1, name=2)
TypeError: wrap() got an unexpected keyword argument 'obj'
A minumum/maximum number of arguments are enforced
x.myfunc(1,2,3,4)
TypeError: myfunc() takes at most 3 arguments (4 given)
meaningful argument names
not a lot of boilerplate
As of Python 3.8, there is now syntax-level support for this:
def f(a, b, c=None, /):
...
Note the slash. Any parameters before the slash are positional-only; they cannot be specified by keyword. This syntax has been picked out for quite a while - PEP 457 dates back to 2013 - but it was only made an actual language feature in Python 3.8.
Regardless of whether any parameters are made positional-only, default argument values still have the limitation that there is no way to distinguish the no-value-passed case from the case where the default is passed explicitly. To do that, you have to process *args manually.
Prior to Python 3.8, these kinds of function signatures are particular to functions written in C, using the C-level PyArg_Parse* family of functions and the Argument Clinic preprocessor. There's no built-in way to write that kind of signature in Python before 3.8. The closest you can get is what you already came up with, using *args.
Here is a function:
def add(a, b):
try:
return a + b
except TypeError:
print('Error: It takes exactly 2 arguments')
This runs perfectly:
print(add(5,2))
This does not print the message I expect:
print(add(5))
Why?
Your message doesn't get printed because the function is never called.
Here's a simpler example which doesn't try to catch any exceptions, to show what's going on:
# add_test.py
def add(a, b):
return a + b
If you save this as a module add_test.py and then import it from an interactive Python session, you can try a couple of things out:
>>> from add_test import add
>>> add(5, 2)
7
This works as expected.
>>> add(5, 'x')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/z/add_test.py", line 4, in add
return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
This raises TypeError inside the add() function (on line 4, as the traceback above tells you), because you can't add the integer 5 and the string 'x' together.
If you wanted to, you could catch this exception and print some kind of message:
# add_test_2.py
def add_exc(a, b):
try:
return a + b
except TypeError:
print("cannot add %r and %r." % (a, b))
… and it would behave as expected:
>>> from add_test_2 import add_exc
>>> add_exc(5, 'x')
cannot add 5 and 'x'.
However, when you miss out an argument completely, something different happens:
>>> add(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: add() missing 1 required positional argument: 'b'
This raises TypeError immediately, without even trying to execute the code in add(), because when you defined add() you specified that it takes exactly two arguments: a and b. Your function is saying "I can only be called with two arguments", so Python follows that rule.
Since you haven't given Python a value for b in the second example, the function simply wouldn't make any sense if it were called, and Python knows that, so it refuses to try to do something that doesn't make any sense.
One way of looking at this is that you're getting two different kinds of TypeError in the two examples above:
When you call add(5, 'x') the problem is that 5 and 'x' are types of things that can't be added together, and you get a TypeError at the exact moment you break that rule.
When you try to call add(5), the problem is that add() is a type of function that must be called with two arguments, and you get a TypeError at the exact moment when you break that rule, which is before the function can even start.
O'Reilly's Learn Python Powerful Object Oriented Programming by Mark Lutz teaches different ways to format strings.
This following code has me confused. I am interpreting 'ham' as filling the format place marker at index zero, and yet it still pops up at index one of the outputted string. Please help me understand what is actually going on.
Here is the code:
template = '{motto}, {0} and {food}'
template.format('ham', motto='spam', food='eggs')
And here is the output:
'spam, ham and eggs'
I expected:
'ham, spam and eggs'
The only thing you have to understand is that {0} refers to the first (zeroeth) unnamed argument sent to format(). We can see this to be the case by removing all unnamed references and trying to use a linear fill-in:
>>> "{motto}".format("boom")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'motto'
You would expect that 'boom' would fill in 'motto' if this is how it works. But, instead, format() looks for a parameter named 'motto'. The key hint here is the KeyError. Similarly, if it were just taking the sequence of parameters passed to format(), then this wouldn't error, either:
>>> "{0} {1}".format('ham', motto='eggs')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
Here, format() is looking for the second unnamed argument in the parameter list - but that doesn't exist so it gets a 'tuple index out of range' error. This is just the difference between the unnamed (which are positionally sensitive) and named arguments passed in Python.
See this post to understand the difference between these types arguments, known as 'args' and 'kwargs'.
>>> print(len.__doc__)
len(module, object)
Return the number of items of a sequence or mapping.
>>> len(os, 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: len() takes exactly one argument (2 given)
Notice the two parameters in the first line of the docstring.
When would you pass two arguments to len? Is the docstring incorrect? I'm using Python 3.4.0.
This was a bug submitted on 2014-04-18 here. It has since been fixed in 3.4.1.
Quoting Vedran Čačić, the original author of the bug report:
From recently, help(len) gives the wrong signature of len.
Help on built-in function len in module builtins:
len(...)
len(module, object)
^^^^^^^^
Return the number of items of a sequence or mapping.