Say I define the following class:
class MyClass(object):
def __init__(self, x, y):
self.x = x
self.y = y
Normally, one would instantiate this class in one of the following ways:
>>> MyClass(1,2)
<__main__.MyClass object at 0x8acbf8c>
>>> MyClass(1, y=2)
<__main__.MyClass object at 0x8acbeac>
>>> MyClass(x=1, y=2)
<__main__.MyClass object at 0x8acbf8c>
>>> MyClass(y=2, x=1)
<__main__.MyClass object at 0x8acbeac>
Which is just fine and dandy.
Now, we try with an invalid keyword argument and see what happens:
>>> MyClass(x=1, j=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'j'
Python correctly raises a type error and complains about the unexpected keyword argument 'j'
.
Now, we can try with two invalid keyword arguments:
>>> MyClass(i=1,j=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'i'
Notice, that two of the keyword arguments were invalid, but Python is only complaining about one of them, 'i'
in this case.
Lets reverse the order of the invalid keyword arguments:
>>> MyClass(j=2, i=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'i'
That is interesting. I changed the order of the invalid keyword arguments, but Python still decides to complain about 'i'
and not 'j'
. So Python obviously doesn't simply pick the first invalid key to complain about.
Lets try some more:
>>> MyClass(c=2, i=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'i'
>>> MyClass(q=2, i=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'i'
Alphabetically, I tried a letter before i
and one after i
, so Python is not complaining alphabetically.
Here are some more, this time with i
in the first position:
>>> MyClass(i=1, j=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'i'
>>> MyClass(i=1, b=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'i'
>>> MyClass(i=1, a=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'a'
Aha! I got it to complain about 'a'
instead of 'i'
.
My question is, when invalid keyword arguments are given to a class constructor, how does Python determine which one to complain about?
Keyword arguments are stored in a dictionary, and dictionary order (e.g. arbitrary, based on the hashing algorithm, hash collisions and insertion history) applies.
For your first sample, a dictionary with both i
and j
keys results in i
being listed first:
>>> dict(j=2, i=1)
{'i': 1, 'j': 2}
Note that the {...}
literal dict notation inserts keys from right-to-left, while keyword parsing inserts keywords from left-to-right (this is a CPython implementation detail); hence the use of the dict()
constructor in the above example.
This matters when two keys hash to the same slot, like i
and a
:
>>> dict(i=1, a=2)
{'a': 2, 'i': 1}
>>> {'i': 1, 'a': 2}
{'i': 1, 'a': 2}
Dictionary output order is highly dependent on the insertion and deletion history and the specific Python implementation; Python 3.3 introduced a random hash seed to prevent a serious denial of service vector, for example, which means that the dictionary order will be radically different even between Python processes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With