I'm having trouble understanding why Python raises a TypeError
when you provide arguments that aren't part of a method signature.
Example:
>>> def funky():
... pass
...
>>> funky(500)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: funky() takes no arguments (1 given)
I thought, if this is because *args
is expected to be None
or []
within the scope of a no-arg function, that's a leaky abstraction, so I looked it up.
What I found
A page search for TypeError
on PEP-3102 found what appeared to be justification for one context in which TypeError
is raised, but I don't understand the justification. The PEP's example is essentially stating that the functionality is basically a shortcut for if args: raise TypeError()
. args
is a non-empty list in that case as opposed to an empty list... which are both of the same type. If I'm not mistaken and that is indeed the justification, perhaps a ValueError
would be more appropriate. However, that would still be sort-of a leaky abstraction since the example is written in Python, making it more of an implementation detail of a certain use case than a language feature. Something like ArgumentError
sounds much more appropriate here to me, which leads me to believe there is some obvious explanation that I missed as to why TypeError
makes sense.
The function takes n
arguments. You provided m
. n != m
. That's propably a bug in someone's code or a symptom of someone's misunderstanding of some API. Also, I don't think we could agree on meaningful, useful semantics what should happen in this case. Should superfluous arguments be ignored? That would let such bugs pass, and since "errors shall never pass silently", that's not acceptable. Similarily, supplying some default value for omitted arguments permits silent misbehaviour in the case of typos etc. and thus violates the same principle, not to mention that "explicit is better than implicit", i.e. if you want to pass some special value you should just write that out.
The PEP you refer to and it's if args: raise TypeError(...)
semantics apply only to functions with keyword-only arguments that don't accept varargs and use a plain *
in the parameter list to codify this. There are functions that accept a limited number of positional arguments and would benefit from keyword-only arguments but varargs wouldn't be meaningful for them. For these, plain *
exists to allow keyword-only arguments without requiring boilerplate code and still notify the programmer in case someone provides too many arguments (which is a good thing for reasons listed in the first part of the answer).
As for why TypeError
was chosen: "Wrong number of arguments" is a compiletime type error/type mismatch in static languages. We don't have compiletime checking for such things in Python, but it's still kind of a "type error". If the (in Python, informal) type signature says "I take two arguments" and you provide three, that's obviously a violation of that contract. TypeError
doesn't just mean not isinstance(x, expected)
, like much other typing-related topics (just think inheritance and "gets parent's stuff" vs. "is-a parent") it's a much broader concept.
Edit in response to your criticism of the static typing comparision: Considering an unary function different from a binary function isn't just an abritary limitation of static type systems. It's very useful even in languages like Python, since the two aren't similar enough to be used "as if they walked and quacked alike" (as in duck typing) - they can't be used in the same way. (The exception, metaprogramming which wraps functions of abritary arity, is handled by the *
and **
unpacking operators.) They happen to share the fact that they're callable, but they're incompatible even if Python's builtin object hierarchy doesn't create numerous identical classes that only differ in arity and thus name. type(f)
may say function
, but that's not always the end of the story.
You said:
def funky():
...
So the program "funky" should not take any 'arguments'. But when you used the program, you said funky(argument), and funky does not take arguments, so that caused the error. To fix this, use; def funky(n): pass .