Я хотел бы знать, возможно ли управлять определением функции Python на основе глобальных настроек (например, ОС). Пример:
@linux
def my_callback(*args, **kwargs):
print("Doing something @ Linux")
return
@windows
def my_callback(*args, **kwargs):
print("Doing something @ Windows")
return
Затем, если кто-то использует Linux, my_callback
будет использовано первое определение , а второе будет игнорироваться.
Дело не в определении ОС, а в определении функций / декораторах.
my_callback = windows(<actual function definition>)
- поэтому имяmy_callback
будет перезаписано независимо от того, что может делать декоратор. Единственный способ, которым версия функции Linux может оказаться в этой переменной, - этоwindows()
вернуть ее, но функция не может знать о версии Linux. Я думаю, что более типичный способ сделать это - иметь определения функций для конкретной ОС в отдельных файлах, и условноimport
только в одном из них.functools.singledispatch
, который делает что-то похожее на то, что вы хотите. Тамregister
декоратор знает о диспетчере (потому что это атрибут функции диспетчеризации и специфичен для этого конкретного диспетчера), поэтому он может вернуть диспетчер и избежать проблем с вашим подходом.uuid.getnode()
,. (Тем не менее, ответ Тодда здесь довольно хороший.)Ответы:
Если цель состоит в том, чтобы иметь такой же эффект в вашем коде, как у #ifdef WINDOWS / #endif ... вот способ сделать это (я, кстати, на Mac).
Простой случай, без цепочки
Таким образом, с этой реализацией вы получите тот же синтаксис, который используется в вашем вопросе.
То, что делает код выше, по сути, назначает зулу зулу, если платформа соответствует. Если платформа не совпадает, она вернет зулу, если она была определена ранее. Если он не был определен, он возвращает функцию-заполнитель, которая вызывает исключение.
Декораторы концептуально легко понять, если учесть, что
аналогично:
Вот реализация с использованием параметризованного декоратора:
Параметризованные декораторы аналогичны
foo = mydecorator(param)(foo)
.Я немного обновил ответ. В ответ на комментарии я расширил его первоначальную область, включив в него применение к методам класса и охватывая функции, определенные в других модулях. В этом последнем обновлении я смог значительно снизить сложность определения того, была ли функция уже определена.
[Небольшое обновление здесь ... Я просто не мог оторваться от этого - это было забавное упражнение] Я провел еще несколько тестов этого и обнаружил, что он работает в основном на вызываемых объектах, а не только на обычных функциях; Вы также можете украсить объявления классов, независимо от того, вызывается ли это. И он поддерживает внутренние функции функций, поэтому такие вещи возможны (хотя, вероятно, не в хорошем стиле - это всего лишь тестовый код):
Выше демонстрируется основной механизм декораторов, как получить доступ к области действия вызывающего, и как упростить несколько декораторов, которые имеют похожее поведение, с помощью определения внутренней функции, содержащей общий алгоритм.
Поддержка цепочек
Для поддержки объединения этих декораторов, указывающих, применима ли функция к более чем одной платформе, декоратор может быть реализован следующим образом:
Таким образом, вы поддерживаете цепочку:
источник
macos
иwindows
определены в том же модуле, что иzulu
. Я полагаю, что это также приведет к тому, что функция будет оставлена так, какNone
если бы она не была определена для текущей платформы, что привело бы к очень запутанным ошибкам во время выполнения.Хотя
@decorator
синтаксис выглядит хорошо, вы получаете то же самое поведение, что и с простымif
.При необходимости это также позволяет легко обеспечить соответствие того или иного случая.
источник
def callback_windows(...)
иdef callback_linux(...)
, затемif windows: callback = callback_windows
, и т. Д. Но в любом случае это легче читать, отлаживать и поддерживать.elif
, так как он никогда не станет ожидать , так , что более чем один изlinux
/windows
/macOS
будет истинным. На самом деле, я бы просто определил одну переменнуюp = platform.system()
, затем использовалif p == "Linux"
и т. Д., А не несколько логических флагов. Переменные, которые не существуют, не могут быть синхронизированы.elif
безусловно , имеет свои преимущества - в частности, замыкающиеelse
+ ,raise
чтобы гарантировать , что по крайней мере один случай сделал матч. Что касается оценки предиката, я предпочитаю предварительно оценивать их - это позволяет избежать дублирования и разъединяет определение и использование. Даже если результат не сохраняется в переменных, теперь существуют жестко закодированные значения, которые могут точно так же не синхронизироваться. Я никогда не могу вспомнить различные магические строки для разных средств, например,platform.system() == "Windows"
противsys.platform == "win32"
...Enum
или просто набором констант.Ниже приведена одна из возможных реализаций этого механизма. Как отмечено в комментариях, может быть предпочтительнее реализовать интерфейс «главного диспетчера», такой как показанный в
functools.singledispatch
, для отслеживания состояния, связанного с множественными перегруженными определениями. Я надеюсь, что эта реализация, по крайней мере, даст некоторое представление о проблемах, с которыми вам, возможно, придется столкнуться при разработке этой функциональности для большей кодовой базы.Я только что проверил, что приведенная ниже реализация работает так, как указано в системах Linux, поэтому я не могу гарантировать, что это решение адекватно позволяет создавать специализированные для платформы функции. Пожалуйста, не используйте этот код в рабочей среде, не проверив его сначала самостоятельно.
Чтобы использовать этот декоратор, мы должны пройти через два уровня косвенности. Во-первых, мы должны указать, на какую платформу мы хотим, чтобы декоратор отвечал. Это достигнуто линией
implement_linux = implement_for_os('Linux')
и аналогом ее Окна выше. Далее нам нужно передать существующее определение перегружаемой функции. Этот шаг должен быть выполнен на сайте определения, как показано ниже.Чтобы определить платформо-специализированную функцию, вы можете написать следующее:
Вызовы
some_function()
будут соответствующим образом отправлены в предоставленное определение для конкретной платформы.Лично я бы не советовал использовать эту технику в рабочем коде. На мой взгляд, лучше четко указывать платформо-зависимое поведение в каждом месте, где возникают эти различия.
источник
implement_for_os
не возвращает сам декоратор, а скорее возвращает функцию, которая создаст декоратор, как только будет предоставлено предыдущее определение рассматриваемой функции.Я написал свой код, прежде чем читать другие ответы. После того, как я закончил свой код, я нашел, что код @ Тодда - лучший ответ. В любом случае я публикую свой ответ, потому что мне было весело, когда я решал эту проблему. Благодаря этому хорошему вопросу я узнал что-то новое. Недостаток моего кода заключается в том, что при каждом вызове функций возникают словари для извлечения словарей.
источник
Чистым решением было бы создать специальный реестр функций, который отправлял бы
sys.platform
. Это очень похоже наfunctools.singledispatch
. Исходный код этой функции обеспечивает хорошую отправную точку для реализации пользовательской версии:Теперь его можно использовать аналогично
singledispatch
:Регистрация также работает непосредственно с именами функций:
источник