Если цель состоит в том, чтобы иметь такой же эффект в вашем коде, как у #ifdef WINDOWS / #endif ... вот способ сделать это (я, кстати, на Mac).
Простой случай, без цепочки
>>> def _ifdef_decorator_impl(plat, func, frame):
... if platform.system() == plat:
... return func
... elif func.__name__ in frame.f_locals:
... return frame.f_locals[func.__name__]
... else:
... def _not_implemented(*args, **kwargs):
... raise NotImplementedError(
... f"Function {func.__name__} is not defined "
... f"for platform {platform.system()}.")
... return _not_implemented
...
...
>>> def windows(func):
... return _ifdef_decorator_impl('Windows', func, sys._getframe().f_back)
...
>>> def macos(func):
... return _ifdef_decorator_impl('Darwin', func, sys._getframe().f_back)
Таким образом, с этой реализацией вы получите тот же синтаксис, который используется в вашем вопросе.
>>> @macos
... def zulu():
... print("world")
...
>>> @windows
... def zulu():
... print("hello")
...
>>> zulu()
world
>>>
То, что делает код выше, по сути, назначает зулу зулу, если платформа соответствует. Если платформа не совпадает, она вернет зулу, если она была определена ранее. Если он не был определен, он возвращает функцию-заполнитель, которая вызывает исключение.
Декораторы концептуально легко понять, если учесть, что
@mydecorator
def foo():
pass
аналогично:
foo = mydecorator(foo)
Вот реализация с использованием параметризованного декоратора:
>>> def ifdef(plat):
... frame = sys._getframe().f_back
... def _ifdef(func):
... return _ifdef_decorator_impl(plat, func, frame)
... return _ifdef
...
>>> @ifdef('Darwin')
... def ice9():
... print("nonsense")
Параметризованные декораторы аналогичны foo = mydecorator(param)(foo)
.
Я немного обновил ответ. В ответ на комментарии я расширил его первоначальную область, включив в него применение к методам класса и охватывая функции, определенные в других модулях. В этом последнем обновлении я смог значительно снизить сложность определения того, была ли функция уже определена.
[Небольшое обновление здесь ... Я просто не мог оторваться от этого - это было забавное упражнение] Я провел еще несколько тестов этого и обнаружил, что он работает в основном на вызываемых объектах, а не только на обычных функциях; Вы также можете украсить объявления классов, независимо от того, вызывается ли это. И он поддерживает внутренние функции функций, поэтому такие вещи возможны (хотя, вероятно, не в хорошем стиле - это всего лишь тестовый код):
>>> @macos
... class CallableClass:
...
... @macos
... def __call__(self):
... print("CallableClass.__call__() invoked.")
...
... @macos
... def func_with_inner(self):
... print("Defining inner function.")
...
... @macos
... def inner():
... print("Inner function defined for Darwin called.")
...
... @windows
... def inner():
... print("Inner function for Windows called.")
...
... inner()
...
... @macos
... class InnerClass:
...
... @macos
... def inner_class_function(self):
... print("Called inner_class_function() Mac.")
...
... @windows
... def inner_class_function(self):
... print("Called inner_class_function() for windows.")
Выше демонстрируется основной механизм декораторов, как получить доступ к области действия вызывающего, и как упростить несколько декораторов, которые имеют похожее поведение, с помощью определения внутренней функции, содержащей общий алгоритм.
Поддержка цепочек
Для поддержки объединения этих декораторов, указывающих, применима ли функция к более чем одной платформе, декоратор может быть реализован следующим образом:
>>> class IfDefDecoratorPlaceholder:
... def __init__(self, func):
... self.__name__ = func.__name__
... self._func = func
...
... def __call__(self, *args, **kwargs):
... raise NotImplementedError(
... f"Function {self._func.__name__} is not defined for "
... f"platform {platform.system()}.")
...
>>> def _ifdef_decorator_impl(plat, func, frame):
... if platform.system() == plat:
... if type(func) == IfDefDecoratorPlaceholder:
... func = func._func
... frame.f_locals[func.__name__] = func
... return func
... elif func.__name__ in frame.f_locals:
... return frame.f_locals[func.__name__]
... elif type(func) == IfDefDecoratorPlaceholder:
... return func
... else:
... return IfDefDecoratorPlaceholder(func)
...
>>> def linux(func):
... return _ifdef_decorator_impl('Linux', func, sys._getframe().f_back)
Таким образом, вы поддерживаете цепочку:
>>> @macos
... @linux
... def foo():
... print("works!")
...
>>> foo()
works!
my_callback = windows(<actual function definition>)
- поэтому имяmy_callback
будет перезаписано независимо от того, что может делать декоратор. Единственный способ, которым версия функции Linux может оказаться в этой переменной, - этоwindows()
вернуть ее, но функция не может знать о версии Linux. Я думаю, что более типичный способ сделать это - иметь определения функций для конкретной ОС в отдельных файлах, и условноimport
только в одном из них.