Ответ 1
Это выглядит как ошибка. Реализация заключается в преобразовании dict_keys
в set
, а затем вызовите .difference_update(arg)
на нем.
Похоже, что они неправильно использовали _PyObject_CallMethodId
(оптимизированный вариант PyObject_CallMethod
), передав строку формата только "O"
. Вещь, PyObject_CallMethod
и друзья документированы, чтобы потребовать строку формата Py_BuildValue
, которая должна "создать tuple
" . С более чем одним кодом формата он автоматически переносит значения в tuple
, но только с одним кодом формата, он не tuple
, он просто создает значение (в этом случае, поскольку оно уже PyObject*
, все, что он делает, - это увеличение счетчика ссылок).
Пока я не отслеживал, где это возможно, я подозреваю, что где-то во внутренних документах он идентифицирует вызовы CallMethod
, которые не производят tuple
, и обертывают их, чтобы создать один элемент tuple
, поэтому вызываемая функция может фактически принимать аргументы в ожидаемом формате. При вычитании a tuple
он уже a tuple
, и этот код исправления никогда не активируется; при прохождении a list
он делает, становясь одним элементом tuple
, содержащим list
.
difference_update
принимает varargs (как если бы он был объявлен def difference_update(self, *args)
). Поэтому, когда он получает развернутый tuple
, он считает, что он должен вычитать элементы из каждой записи в tuple
, а не обрабатывать упомянутые записи как значения для вычитания самих себя. Чтобы проиллюстрировать, когда вы выполните:
mydict.keys() - (1, 2)
ошибка вызывает его (грубо):
result = set(mydict)
# We've got a tuple to pass, so all well...
result.difference_update(*(1, 2)) # Unpack behaves like difference_update(1, 2)
# OH NO!
В то время как:
mydict.keys() - [1, 2]
делает:
result = set(mydict)
# [1, 2] isn't a tuple, so wrap
result.difference_update(*([1, 2],)) # Behaves like difference_update([1, 2])
# All well
Итак, почему tuple
из str
работает (неправильно), - ('abc', '123')
выполняет вызов, эквивалентный:
result.difference_update(*('abc', '123'))
# or without unpacking:
result.difference_update('abc', '123')
и поскольку str
являются итерабельными их символами, он просто blithely удаляет записи для 'a'
, 'b'
, 'c'
и т.д. вместо 'abc'
и '123'
, как вы ожидали.
В принципе, это ошибка, и (когда я получаю шанс), я отправлю ее против людей CPython.
Правильное поведение, вероятно, должно было быть вызвано (предполагается, что этот вариант Id
существует для этого API):
_PyObject_CallMethodObjArgsId(result, &PyId_difference_update, other, NULL);
который вообще не имел бы проблем с упаковкой, и будет работать быстрее для загрузки; наименьшее изменение заключалось бы в изменении строки формата на "(O)"
, чтобы принудительное создание tuple
даже для одного элемента, но поскольку строка формата ничего не получает, _PyObject_CallMethodObjArgsId
лучше.