类构造函数和关键字参数-Python如何确定哪一个是意外的?

类构造函数和关键字参数-Python如何确定哪一个是意外的?,python,Python,假设我定义了以下类: class MyClass(object): def __init__(self, x, y): self.x = x self.y = y 通常,可以通过以下方式之一实例化此类: >>> MyClass(1,2) <__main__.MyClass object at 0x8acbf8c> >>> MyClass(1, y=2) <__main__.MyClass object

假设我定义了以下类:

class MyClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
通常,可以通过以下方式之一实例化此类:

>>> 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>
请注意,其中两个关键字参数是无效的,但Python只抱怨其中一个,在本例中是
'i'

让我们反转无效关键字参数的顺序:

>>> 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(j=2, i=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'i'
按字母顺序,我在
I
之前试过一个字母,在
I
之后试过一个字母,所以Python不会按字母顺序抱怨

下面是更多的,这一次在第一个位置有
i

>>> 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'
MyClass(i=1,j=2) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 TypeError:\uuuu init\uuuuuuuuu()获得意外的关键字参数“i” >>>MyClass(i=1,b=2) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 TypeError:\uuuu init\uuuuuuuuu()获得意外的关键字参数“i” >>>MyClass(i=1,a=2) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 TypeError:\uuuu init\uuuuuuuuu()获取了意外的关键字参数“a” 啊哈!我让它抱怨
'a'
而不是
'I'


我的问题是,当给类构造函数提供无效的关键字参数时,Python如何确定要抱怨哪一个?

关键字参数存储在字典中,字典顺序(例如,任意,基于哈希算法、哈希冲突和插入历史)适用

对于第一个示例,同时具有
i
j
键的词典将导致
i
列在第一位:

>>> dict(j=2, i=1)
{'i': 1, 'j': 2}
注意,
{…}
literal dict表示法从右向左插入关键字,而关键字解析从左向右插入关键字(这是一个CPython实现细节);因此,在上述示例中使用了
dict()
构造函数

当两个键散列到同一个插槽时,这很重要,例如
i
a

>>> dict(i=1, a=2)
{'a': 2, 'i': 1}
>>> {'i': 1, 'a': 2}
{'i': 1, 'a': 2}

字典输出顺序高度依赖于插入和删除历史以及特定的Python实现;例如,Python3.3引入了一个随机散列种子来防止严重的错误,这意味着即使在Python进程之间,字典顺序也会完全不同。

我也这么认为,但是比较
MyClass(**{'I':1,'a':2})
MyClass(I=1,a=2)
。第一个报告
i
,而第二个报告
a
。这不应该是同一件事吗?字典的顺序没有保证。没有结果可以推广…@Claudiu:dict的迭代顺序是任意的。所以,不,你永远无法预测它将是同一件事。如果你想知道为什么它在某些特定情况下是不同的,不仅仅是“它被允许是不同的,而且经常是不同的”,你必须深入到解释器和dict对象的较低层次。@abarnert:我想这就是我好奇的地方。如果您在同一解释器中以相同的方式定义相同的dict,那么顺序将是相同的-那里没有随机化。我猜答案是关键字dict在两种不同的方法中构造不同。如果您想查看
CALL\u函数
和相关操作码的相关CPython源代码,请在
ceval.c
中查找(上面的链接指向3.3分支)和
ext\u do\u CALL
。基本上,调用方首先将名称和值推送到堆栈上,然后
CALL\u FUNCTION
将它们弹出。作为旁注,类构造函数调用的工作方式与调用函数(或方法)的工作方式完全相同,因此您可以稍微简化测试。@abarnert是否这样做?我得到:
def my_func(x,y):pass
,然后
my_func(I=2,a=1)
抱怨
'I'
,而
MyClass(I=2,a=1)
抱怨
'a'
。如@MartijnPieters的回答所述,行为是未定义的。因此,实践中的测试结果不一定有用或适用。是的,它们确实有用。使用
dis
模块查看字节码,您将看到两者都使用
CALL\u FUNCTION
(还要注意,由于python不是静态类型,解释器在开始执行调用之前无法区分类/可调用/函数)。还请注意,
\uuuuu init\uuuu
方法有一个自参数。如果您使用
def\uuu init\uuu(self,x):pass定义一个类,那么它会再次抱怨
i
。它似乎是一个函数物质的参数数量。
>>> dict(j=2, i=1)
{'i': 1, 'j': 2}
>>> dict(i=1, a=2)
{'a': 2, 'i': 1}
>>> {'i': 1, 'a': 2}
{'i': 1, 'a': 2}