Безопасно ли использовать функцию, принимающую аргументы ключевого слова kwargs, которые не являются идентификаторами?

Безопасно ли в Python передавать аргументы ключевых слов, которые не являются идентификаторами Python для функции? Вот пример:

>>> '{x-y}'.format(**{'x-y': 3})  # The keyword argument is *not* a valid Python identifier
'3'
>>> '{x-y}'.format(x-y=3)
  File "<ipython-input-12-722afdf7cfa3>", line 1
SyntaxError: keyword can't be an expression

Я спрашиваю об этом, потому что мне удобнее форматировать имена, содержащие тире (поскольку значения соответствуют аргументу командной строки с тире в их имени). Но надежно ли это поведение (т. е. может ли оно варьироваться в зависимости от версии Python)?

Я не уверен, что использование неидентификаторов в качестве аргументов ключевого слова официально поддерживается: на самом деле документация гласит:

Если синтаксис **expression появляется в вызове функции, выражение должно оцениваться как сопоставление, содержимое которого обрабатывается как дополнительные аргументы ключевого слова.

… где «аргументы ключевого слова» определяются как имеющие имя, которое является идентификатором:

ключевое_аргументы ::= ключевое_элемент ("," ключевое_элемент)*

ключевое_элемент ::= идентификатор "=" выражение

где идентификаторы ограничены в том, какие символы они могут использовать ( - например запрещено):

идентификатор ::= (буква|"_") (буква | цифра | "_")*

Итак, документация указывает, что сопоставление, данное ** в вызове функции, должно содержать только допустимые идентификаторы в качестве ключей, но CPython 2.7 принимает более общие ключи (для format() и функций с аргументом **, которые не помещают значения в переменные). Это надежная функция?


person Eric O Lebigot    schedule 06.06.2013    source источник
comment
Соглашение состоит в том, чтобы заменить - на _ в именах, чтобы сделать их действительными идентификаторами. Соглашение о вызовах **{...} с именами, не являющимися идентификаторами, работает только в том случае, если вызываемая функция имеет аргумент **kw для их получения, поскольку она также не может определять явные аргументы ключевых слов, которые не являются допустимыми идентификаторами.   -  person Martijn Pieters    schedule 06.06.2013
comment
Действительно, но можно ли на это полагаться? Если использование неидентификаторов для аргументов ** гарантированно сработает, то в моем случае это более удобно (ключи словаря являются непосредственно именами параметров командной строки).   -  person Eric O Lebigot    schedule 06.06.2013
comment
Я думаю, что его безопасно использовать, потому что я сильно сомневаюсь, что функции kwargs и ** когда-либо будут изменены, чтобы принимать какой-то особый тип отображения, который ограничивает его ключи допустимыми идентификаторами Python.   -  person martineau    schedule 06.06.2013
comment
Я бы сказал, что грамматика применима только к анализатору исходного кода и не должна рассматриваться как функциональное ограничение содержимого результата **expression. Функциональное описание ниже не ограничивает ключи словаря явно, равно как и документация по определению функции. Было бы огромным кошмаром обратной совместимости, чтобы когда-либо явно ограничивать это, чтобы вы были в безопасности.   -  person Martijn Pieters    schedule 06.06.2013
comment
Да, я бы тоже так подумал, но мне интересно, гарантировано ли это (документация, насколько я понимаю, обычно ограничивает ключи) или использование общих ключей, скорее всего, безопасно.   -  person Eric O Lebigot    schedule 06.06.2013
comment
@MartijnPieters: да, я согласен, можно прочитать документацию как имеющую два разных значения для аргументов ключевого слова (одно для синтаксического анализатора с ограниченными идентификаторами и другое для аргументов, которые могут входить в аргумент **). Спасибо! Не стесняйтесь вкладывать свои мысли в ответ на этот вопрос. :)   -  person Eric O Lebigot    schedule 06.06.2013
comment
Вместо использования нотации **kwargs вы можете просто передать словарь с произвольными ключами. Таким образом, не будет никакой неопределенности по этому поводу.   -  person Ber    schedule 06.06.2013
comment
@Ber: я не уверен, что понимаю: str.format() не не принимает словарь с произвольными ключами (эквивалентный string.Formatter.vformat() принимает, но это не имеет значения).   -  person Eric O Lebigot    schedule 06.06.2013
comment
@EOL: вот что я говорю; формальная грамматика не ограничивает словарь ключевых слов; формальная грамматика ограничивает только то, как должны выглядеть аргументы ключевого слова, определяющие источник. Грамматика для словарей не ограничивает ключи (но спецификация ограничивает ключи неизменяемыми).   -  person Martijn Pieters    schedule 06.06.2013
comment
@MartijnPieters: Теперь понятно, спасибо!   -  person Eric O Lebigot    schedule 07.06.2013


Ответы (1)


Прежде всего: соглашение о вызовах **{...} с именами, не являющимися идентификаторами, работает только в том случае, если вызываемая функция имеет аргумент **kw для их получения, поскольку она также не может определять явные аргументы ключевых слов, которые не являются допустимыми идентификаторами.

Я бы сказал, что грамматика keyword_arguments применяется только к синтаксическому анализатору исходного кода и никогда не может рассматриваться как функциональное ограничение содержимого результата **expression. Приведенное ниже функциональное описание не ограничивает явно ни ключи словаря, ни документация по определению функций.

Вместо этого, поскольку грамматика допускает expression, а в функциональной спецификации указано, что это должно разрешаться в сопоставление, содержимое которого обрабатывается как дополнительные аргументы ключевого слова, ясно (для меня), что не существует ограничения на ключи вообще, помимо обычных, применимых к словарям Python (ключи должны быть неизменяемыми). Вы можете передать кортеж или числовые ключи для всех забот Python. Функциональная спецификация указывает, как обрабатывается содержимое, а не то, что содержимое должно соответствовать определенному формату.

Так что, на мой взгляд, функциональная спецификация должна явно ограничивать ключи в словаре **expression, чтобы запретить то, что вы делаете, потому что грамматика определенно этого не делает. Изменение этого было бы огромным изменением, несовместимым с предыдущими версиями, и вряд ли когда-либо будет добавлено.

Обратите внимание, что хотя в спецификации не упоминаются какие-либо ограничения на ключи словаря, CPython делает это:

>>> def f(*args, **kw): print args, kw
... 
>>> f(**{1: 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() keywords must be strings

Это ограничение, накладываемое интерпретатором Python при вызове объектов кода (пользовательских функций). Причина объясняется в исходном коде сразу после часть, которая вызывает указанное выше исключение:

/* Speed hack: do raw pointer compares. As names are
   normally interned this should almost always hit. */

Ограничивая ключевые слова строками, возможна оптимизация скорости.

person Martijn Pieters    schedule 06.06.2013
comment
Ключи kw dict do уже должны быть строками: попробуйте def foo(**kw): pass; foo(**{1: 5}). Это дает TypeError в Python 2.7 и 3.3. - person lvc; 06.06.2013
comment
@lvc: Действительно; интересно, поэтому CPython применяет ограничения в коде, которые не упоминаются в спецификации. - person Martijn Pieters; 06.06.2013
comment
@lvc, ключи kw dict должны быть строками, за исключением того, что в методе форматирования они не '{x-y}'.format(**{'x-y': 3, 4:5}) не вызывают ожидаемое исключение. - person Duncan; 06.06.2013
comment
@MartijnPieters Я думаю, что недавно обсуждалось это в списке рассылки разработчиков. По памяти был сделан вывод, что CPython не может отклонять нестроки без замедления вызовов, но реализации не обязаны принимать недопустимые идентификаторы. Хотя, возможно, я неправильно запомнил: я еще не нашел соответствующих сообщений. - person Duncan; 06.06.2013
comment
@Duncan: я думаю, ты прав; PyEval_EvalCodeEx явно использует необработанные сравнения указателей, которые будут работать только со строковыми ключами. - person Martijn Pieters; 06.06.2013
comment
@Duncan dict также является особым случаем в 2.7, но не в 3.3. Если я думаю о том же потоке, что и вы, я думаю, что в нем говорилось, что этот особый регистр не был преднамеренным, что Гвидо не любил его и что некоторые другие реализации уже не использовали его - и поэтому когда-то он был удален после выпуска версии 3.0. Возможно, что происходит, так это то, что логика уровня C для проверки этого находится в том же коде, который проверяет, были ли переданы нераспознанные kwargs - какие функции, такие как dict и str.format, которые явно принимают произвольные kwargs, обычно не беспокоят с. - person lvc; 06.06.2013
comment
@MartijnPieters, но обратите внимание, что сразу после этого блока находится запасной вариант с использованием PyObject_RichCompareBool. Я предполагаю, что «скоростной хак» — это пережиток предыдущих расширенных сравнений, потому что файл документация говорит, что PyObject_RichCompareBool уже выполняет проверку личности, но в документации 2.x нет такого примечания для PyObject_Cmp или PyObject_Compare. - person lvc; 06.06.2013
comment
Да, если у меня будет время сегодня, я покопаюсь в истории коммитов и посмотрю, что смогу откопать. - person Martijn Pieters; 06.06.2013