Подтвердить что ты не робот

Конструкторы классов и аргументы ключевых слов. Как 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 определяет, на кого жаловаться?

4b9b3361

Ответ 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.