Pull to refresh

Comments 14

От таких проблем защищает убирание дублирования строк в коде.

То есть вместо «arg_name» в десяти местах, в одном объявляем переменную ARG_NAME = «arg_name» и далее используем её везде, где надо. Идея расширяема до enum-ов.

Вообще, если какое-то значением используется больше одного раза, разумно вынести его в константу.
Согласен про константы, но тогда как это будет выглядеть со стороны пользователя?
kwargs = {MyEnum.Field1: value, MyEnum.Field2: value}
function(**kwargs)
Ухудшает читаемость :(
И еще нужно что-то параллельно решать с наследованием.
Через словарь, например. Криво будет выглядеть, зато надёжно :-)

А вариант с отказом от **kwargs рассматривался? Они нужны в основном либо для передачи параметров родительскому методу либо для хитрых манипуляций с именами. В первом случае досточно у базового метода не указать kwargs и любой неверный аргумент приведёт к исключению. Во втором случае, как я понял, Ваша реализация тоже не поможет.

Ну и третий вариант: почему бы просто декоратором не проверять kwargs по перечню разрешённых имён? Никакого дизасемблирования не понадобится.
Интересная работа. Но по прочтении у меня возникла парочка вопросов.
Перекраивание интерфейсов на специальные пользовательские структуры вроде namedtuple вместо **kwargs имело несколько проблем

Если это возможно, расскажите, пожалуйста, подробнее почему в Вашем случае нельзя было использовать замену параметров объектом (см. пример)?

И второй момент. Насколько я понимаю, то проверки происходят runtime. В связи с этим интересно было бы узнать насколько «проседает» быстродействие из-за использования этого инструмента?
Спасибо за вопросы! По поводу рантайма — конечно, проседает, но точных замеров я не делал. В моем случае используется метакласс и срабатывает он на 3-4 десятка классов при загрузке приложения.

Проблема в том, что объект параметров должен подстраиваться под каждый класс индивидуально. Т.е. есть у нас класс Foo и объект параметров для него FooKwArgs. Потом мы наследуем от него Bar и добавляем еще пару именованных аргументов в конструктор. Следовательно, делаем новый объект BarKwArgs. Наследовать его от FooKwArgs нельзя, если последний — namedtuple, так что надо делать нечто свое, с магией в __setattr__. А еще бывает diamond inheritance (в моем проекте часто получается), и там нужно быть внимательным.

В общем, вижу много трудозатрат на рефакторинг. Классов у нас около 300 (ветвистое дерево наследования). При описанном подходе я добавляю метакласс в корень дерева, грубо говоря трачу O(1) времени вместо O(N).
Спасибо за ответ.
По поводу быстродействия. А Вы не думали добавить какую-то опцию, которая бы позволяла включать проверку только в dev/test-окружениях?
Теперь думаю, спасибо за идею.
У меня возникло ощущение, что вы пытаетесь самостоятельно выполнить работу статического анализатора кода.
Ваш проект — это какая-то библиотека, рассчитанная на пользователей с начальными навыками программирования?
Или в Питоне все настолько печально в плане автоматизированного поиска ошибок?
Просветите, пожалуйста.
>>> Ваш проект — это какая-то библиотека, рассчитанная на пользователей с начальными навыками программирования?

10 из 10 :) Плюс начиная с критической массы кода даже у крутых программистов начинают сыпаться дурацкие опечатки.

Питон — динамический язык, и многие вещи нужно делать руками, например, проверять типы в сеттерах. Зато очень гибкий.
Я бы просто используемые аргументы извлекал через kwargs.pop,
а потом делал assert not kwargs.
Ну и ещё я не очень понимаю нужду в использовании в конструкторах kwargs — почему без него нельзя обойтись?
А чтобы ещё упростить код, можно сделать ещё вспомогательную функцию «pop_many», которая будет извлекать нужные для конструктора родительского объекта аргументы.
У питона зачастую бывает излишняя гибкость, которую нужно assert-ами или ещё как-то контролировать.
Особенно для больших программ.
kwargs.pop() будет давать коллизии при наследовании, когда и родитель и потомок обращаются к одному аргументу. kwargs в конструкторе используем, т.к. у нас есть набор обязательных аргументов и мы их требуем через zope.interface, а другие необязательные и имеют деволтные значения — их засунули в kwargs.
>kwargs.pop() будет давать коллизии при наследовании, когда и родитель и потомок обращаются к одному аргументу
Делайте сначала my_arg = kwargs.pop('my_arg'), потом преобразовывайте аргумент: my_arg=my_arg+1,
потом его применяйте к родителю: super(parent, MyClass).__init__(used_arg=arg)
Но вообще это скорее всего свидетельствует о плохом ОО дизайне, такого кода не должно быть много.
В этом как раз и есть один из смыслов наследования — что потомок наследует свойства от родителя.

>kwargs в конструкторе используем, т.к. у нас есть набор обязательных аргументов и мы их требуем через zope.interface, а другие необязательные и имеют деволтные значения — их засунули в kwargs.
Ну да, если у вас бардак с ООП творится, то действительно приходится придумывать странные вещи.
Если ваше утверждение состоит в том, что использовать **kwargs в конструкторе класса — бардак в ООП, то, пожалуй, на этом обсуждение можно закончить. Go ahead и копипастите списки из 15-20 именованных параметров с дефолтными зачениями из __init__-а в __init__ в 300 классах (они к тому же все понемногу или помногу разные).

Если нет, то в конце, когда вы на-pop()-ались, и оказался избыток, вам все равно нужно строить матрицу расстояний и сообщать пользователю о похожих или вообще «левых» kwarg-ах. Снова приходим к декораторам и метаклассам для автоматизации этой проверки. Получается, по сути, ваш способ просто делает явным проверку имен, без «магии». Однако, это накладывает дополнительные ограничения на разработчика и N+1-ую строку в JIRA о том, «как у нас принято вести разработку этого проекта». Хотя, в принципе, подход рабочий, можно добавить к существующему. Спасибо.
Да нет, дело в том, что такие глубокие деревья наследования в питоне (в отличие от Java) редко обоснованы. Я, конечно, не знаю специфики…
А вообще, да — явное лучше неявного (когда неявно вы всё равно будете делать ровно то же самое).
Более того, в случае «явного» решения IDE вам сможет помочь. И да, в других языках программирования обычно ровно таким способом и поступают, даже в крайне многословной Java.
Sign up to leave a comment.

Articles