Конструкторы классов и аргументы ключевых слов. Как 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 at 0x8acbeac>
>>> MyClass(x=1, y=2)
<__main__.MyClass object at 0x8acbf8c>
>>> MyClass(y=2, x=1)
<__main__.MyClass object at 0x8acbeac>
Это просто отлично и денди.
Теперь мы попробуем с недопустимым аргументом ключевого слова и посмотрим, что произойдет:
>>> 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 правильно вызывает ошибку типа и жалуется на unexpected keyword argument 'j'
.
Теперь мы можем попробовать с двумя недопустимыми аргументами ключевого слова:
>>> MyClass(i=1,j=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'i'
Обратите внимание, что два аргумента ключевого слова были недопустимыми, но Python только жалуется на один из них, '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'
Это интересно. Я изменил порядок неверных аргументов ключевого слова, но Python по-прежнему решает жаловаться на 'i'
, а не 'j'
. Таким образом, Python явно не просто выбирает первый недопустимый ключ, чтобы жаловаться.
Давайте попробуем еще немного:
>>> 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'
В алфавитном порядке я пробовал письмо до 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'
Ага! Я получил жалобу на 'a'
вместо 'i'
.
Мой вопрос в том, когда недопустимые аргументы ключевого слова присваиваются конструктору класса, как Python определяет, на кого жаловаться?
Ответы
Ответ 1
Аргументы ключевого слова хранятся в словаре, и применяется порядок словаря (например, произвольный, на основе алгоритма хэширования, хеш-коллизий и истории вставки).
Для вашего первого образца словарь с двумя клавишами 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; Например, Python 3.3 представил случайное хэш-семя, чтобы предотвратить серьезный отказ в обслуживании вектора, что означает, что порядок словаря будет радикально отличаться даже между процессами Python.