How to get __init __ () to raise a more useful exception instead of TypeError with invalid # argument?

I have a class with init that takes a lot of arguments - some of them are required and some are not.

If one of the required arguments is not specified, you can get a TypeError with a useless message like "at least 10 arguments (14 data) are required."

I would like to have my own subclass of TypeError, which actually informs the user about which arguments are missing.

+5
source share
3 answers

If you want to use kwargs, you can set init to a list of required arguments, and then check for all the necessary kwarg. See the following example.

class Something(): def __init__(self, **kwargs): required_args = ['arg_1', 'arg_2'] for arg in required_args: if arg not in kwargs: raise TypeError("Argument %s is required" % arg) obj = Something(arg_1=77, arg_3=2) 
+5
source

In Python 3, you can define a function that accepts "required keyword-only arguments". This is most clearly described in PEP 3102 . The error message that you get when you omit the required arguments for a keyword only contains the argument names.

 $ python3 Python 3.5.2rc1 (default, Jun 13 2016, 09:33:26) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> class X: ... def __init__(self, *, foo, bar, baz): ... self.foo = foo ... self.bar = bar ... self.baz = baz ... >>> a = X(foo=1,bar=2,baz=3) # no error >>> b = X(foo=1,bar=2) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() missing 1 required keyword-only argument: 'baz' >>> b = X(foo=1) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() missing 2 required keyword-only arguments: 'bar' and 'baz' 

However, this is incompatible with code that expects to be able to call X() with positional arguments, and the error message you get is still what you don't like:

 >>> a = X(1,2,3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() takes 1 positional argument but 4 were given 

Additionally, this feature is not available in any version of Python 2:

 $ python Python 2.7.12rc1 (default, Jun 13 2016, 09:20:59) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> class X(object): ... def __init__(self, *, foo, bar, baz): File "<stdin>", line 2 def __init__(self, *, foo, bar, baz): ^ SyntaxError: invalid syntax 

Improving the diagnostics provided for positional arguments is likely to require hacking the interpreter. The Python development team may be fixable; I would consider how to do this on the python-ideas mailing list.

+1
source

I ended up using the Tomas Gonzalez answer option, rewriting it so that it supports the mixture of ordered arguments, args keywords, and default values โ€‹โ€‹that existed. This confirmed our requirement to write code that is always backward compatible with legacy code. For greenfield implementations, Thomas's answer is preferable (and everyone will always make calls with kwargs). For legacy situations like mine (or those that might require a combination of ordered required and optional arguments?), Consider the following:

 def __init__(*args, **kwargs): required_args = ['a', 'b'] # to support legacy use that may included ordered args from previous iteration of this function ordered_args = ['a', 'b', 'c', 'd', 'e'] default_args = { 'c': 0, 'd': 1, 'e': 2 } for index, value in enumerate(args): kwargs[ordered_args[index]] = value for arg in required_args: if arg not in kwargs: raise TypeError("Argument %s is required" % arg) for arg in default_args: if arg not in kwargs: kwargs[arg] = default_args[arg] 

Although this is an acceptable hack, I really hoped to subclass my own TypeError, so the original function would still look โ€œnormalโ€.

0
source

All Articles